Gerador de funções através de um script python


Projeto e construção de um gerador de funções na faixa de áudio

Um gerador de funções na faixa de áudio (20 Hz, 20 Khz) , e que nos permita gerar diferentes formas de onda, é um instrumento de grande valia em um laboratório de desenvolvimento eletrônico. Até alguns anos atrás o projeto e construção de um dispositivo deste tipo era algo que demandava muito cálculo e principalmente muito cuidado na especificação dos componentes, pois a frequência e distorção do sinal são muito dependentes do comportamento destes componentes com a idade e temperatura. Com o advento dos microcomputadores, esta situação se alterou radicalmente. Ao levar para o domínio do software a responsabilidade pela geração das diferentes formas de onda, sistemas muito mais estáveis e baratos puderam ser reconhecidos.

Uma curiosidade interessante a respeito de geradores de sinal. A hoje poderosa empresa HP (Hewlet Packard) iniciou suas atividades em 1939, na garagem de um dos sócios, produzindo um gerador de sinais de áudio, utilizando a topologia, nova para a época, chamada de Ponte de Wein. O forno utilizado foi o forno da mãe de um deles. O site http://www.hp.com/hpinfo/abouthp/histnfacts/museum/earlyinstruments/0002/ conta um pouco desta estória inspiradora.

Nosso gerador será projetado para atender as seguintes especificações:

  • Amplitude de saída: \+- 5 volts, ajustada de 0 a 5 volts, com ajuste do nível DC.
  • Impedância de saída: 600 ohms
  • Frequência: 20 Hz a 15 Khz
  • Formas de onda: senoidal, quadrada, triangular

Utilizaremos a placa de som do computador para gerar os sinais. Embora esta solução não nos permita um controle total sobre o resultado, pois existem placas melhores do que outras, simplifica muito e ao mesmo tempo atende a grande maioria das aplicações. Para isolar a placa de som do dispositivo sob teste, utilizaremos um amplificador operacional e circuitos associados.

No artigo de hoje vamos cobrir a parte referente ao programa python que gera os diferentes sinais. No próximo artigo mostraremos como projetar e construir um isolador com amplificadores operacionais para separar o computador dos circuitos em teste (e protege-lo de eventuais curtos circuitos e sobretensões).

Implementando o gerador de sinais em Python

O script foi desenvolvido em ambiente Linux Mint18.1, Python 3.5. Preliminarmente é necessário instalar algumas bibliotecas. Lembre-se de que é sempre interessante utilizar um ambiente virtual para o desenvolvimento de programas Python.

  • sudo apt-get install python-all-dev
  • sudo apt-get install portaudio19-dev
  • pip install pyaudio

O programa completo pode ser obtido na página  https://cadernodelaboratorio.com.br/scripts-softwares-e-arquivos-de-configuracao/ , sob o nome pack171006.tar.gz. Iremos agora separar as partes principais para facilitar a compreensão do funcionamento. Para executar o programa, desempacote o arquivo pack171006.tar.gz emum diretório e digite o comando:

 python3 geradorFuncoes.py

Destacamos a seguir os trechos mais importantes do programa.

Programando a interface

Utilizaremos o Tkinter como interface gráfica. Caso você não conheça nada a respeito desta interface multiplataforma, visite a página  https://cadernodelaboratorio.com.br/2017/01/17/tkinter-interface-grafica-para-programas-em-python/ .

O painel do instrumento foi elaborado para permitir a seleção das três formas de onda disponíveis, senoidal, quadrada e retangular. Além disto precisamos de especificar a frequência e a duração do som. A tela do instrumento ficou a seguinte:

Tela do Gerador de Funções

O segmento do script que gera esta interface é apresentado a seguir. Todos estes elementos já foram revisados em artigos anteriores. Mas se você tiver alguma dúvida, utilize o campo de comentários.

 
 def create_canvas_area(self):
        #criação do label na parte superior da tela
        #usaremos o gerenciador de laytou packer, devido a simplicidade da interface
        lbl0= tk.Label(self.root, text="Gerador de Funções CADLAB 1710",
                 font=("Helvetica", 32,"bold"), fg="blue")   
        lbl0.pack()  
        
        #neste frame iremos inserir os três botões que selecionam a forma de onda       
        frame1= tk.Frame(self.root)  
                 
        #botão para seleção da forma "quadrada"                                            
        self.btn1= tk.Button(frame1,text="Quadrado",
            font=("Helvetica", 14,"bold"), 
            fg="green", command=self.OnButtonQuadrado)                              
        
        self.btn1.pack(side=tk.LEFT, padx="10", pady= "10") 
        
        #botão para seleção da forma "triangular"
        self.btn2= tk.Button(frame1,text="Triangular",
           font=("Helvetica", 14,"bold"), 
           fg="green", command=self.OnButtonTriangular)                              
        
        self.btn2.pack(side=tk.LEFT, padx="10", pady= "10")    
        
        #botão para seleção da forma "senoidal"
        self.btn3= tk.Button(frame1,text="Senoidal",
           font=("Helvetica", 14,"bold"), 
           fg="green", command=self.OnButtonSenoidal)  
                                       
        self.btn3.pack(side=tk.LEFT, padx="10", pady= "10")            
         
        frame1.pack()  
        
                                 
        # criamos um frame para a inserção do campo que receberá
        # a frequencia desejada
        
        frame2= tk.Frame(self.root)  
        lbl1= tk.Label(frame2, text="Frequência(Hz): ",
                 font=("Helvetica", 16,"bold"), fg="green") 
                   
        lbl1.pack(side=tk.LEFT, padx="10", pady="20") 
        
        self.entryFrequencia = tk.Entry(frame2,
            font=("Helvetica", 16,"bold"), 
            fg="green", width="5") 
        self.entryFrequencia.insert(0,"1000")    
        
        self.entryFrequencia.pack(side=tk.LEFT, padx="10", pady="20")
        
        frame2.pack()
        
        
               
        #criamos um frame que receberá a duração, em segundos, desejada
        frame3= tk.Frame(self.root) 
        
        lbl2= tk.Label(frame3, text="Duração(s): ",
                 font=("Helvetica", 16,"bold"), fg="black") 
                   
        lbl2.pack(side=tk.LEFT, padx="10", pady="20") 
        
        self.entryDuracao = tk.Entry(frame3,
            font=("Helvetica", 16,"bold"), 
            fg="black", width="4") 
        self.entryDuracao.insert(0,"10")    
        
        self.entryDuracao.pack(side=tk.LEFT, padx="10", pady="20")
        
        frame3.pack()
        
        
        #finalmente criamos um frame que contem os botoes INICIAR/PARAR 
        frame4= tk.Frame(self.root)
        
        self.btn4= tk.Button(frame4,text="INICIAR",
           font=("Helvetica", 14,"bold"), 
           fg="black", command=self.OnIniciaSinal)  
                                       
        self.btn4.pack(side=tk.LEFT, padx="10", pady= "10")
        
 
        self.btn5= tk.Button(frame4,text="PARAR",
           font=("Helvetica", 14,"bold"), 
           fg="black", command=self.OnParaSinal)  
                                       
        self.btn5.pack(side=tk.LEFT, padx="10", pady= "10") 
                
        frame4.pack()

Programando o gerador senoidal

def geraSenoide(frequencia= 1000, duracao=1, valorPico= 16384, taxaAmostragem=44100):
    """Gera um array numpy correspondente a uma senoide"""
    numeroAmostras = duracao * taxaAmostragem
    
    periodo = 1.0 / float(frequencia)
    omega = np.pi * 2.0 / periodo
    deltaX= 1.0 / float(taxaAmostragem)
    
    tempo = np.arange(start=0, stop= duracao, step= deltaX, dtype=np.float)
    valorSinal = valorPico * np.sin(tempo * omega)

    return valorSinal

Programando o gerador de sinal quadrado


def geraQuadrado(frequencia= 1000, duracao=1, valorPico=16384, taxaAmostragem=44100):
    """Gera um array numpy correspondente a uma forma de onda quadrada"""
    numeroAmostras = duracao * taxaAmostragem
    periodo = 1.0 / float(frequencia)
    omega = np.pi * 2.0 / periodo
    delta= 1.0 / float(taxaAmostragem)
    
    tempo = np.arange(start=0, stop= numeroAmostras*delta, step= delta, dtype=np.float)
    valorSinal = valorPico * np.sin(tempo * omega)
    
    
    for nLoop in range(0,len(valorSinal)):
        if valorSinal[nLoop] < 0.0:
            valorSinal[nLoop]= -valorPico
        else:
            valorSinal[nLoop]= valorPico
    
    return valorSinal  

Programando o gerador de sinal triangular


def geraTriangulo(frequencia= 1000, duracao=1, valorPico=16384, taxaAmostragem=44100):
    """Gera um array numpy correspondente a uma forma de onda triangular"""
    
    numeroAmostras = duracao * taxaAmostragem
    periodo = 1.0 / float(frequencia)
    deltaX= 1.0 / float(taxaAmostragem)
    
    numeroPontos= (periodo /deltaX )/4.0
    
    deltaY= valorPico/numeroPontos 
        
    
    tempo = np.arange(start=0, stop= numeroAmostras*deltaX, step= deltaX, dtype=np.float)
    valorSaida= np.arange(start=0, stop= numeroAmostras*deltaX, step= deltaX, dtype=np.float)
    valorSaida = np.zeros_like(valorSaida)
    
    
    for nLoop in range(1,len(valorSaida)):
        if abs(valorSaida[nLoop-1]) >= (valorPico - abs(deltaY)):
            deltaY = -deltaY
        valorSaida[nLoop] =  valorSaida[nLoop-1] + deltaY   
        
  
    return valorSaida       

Gerando um arquivo wav a partir de um vetor numpy

class SoundFile:
    """ grava os dados recebidos no formato wav"""
    def  __init__(self, sinal, nomeArquivo, taxaAmostragem=44100):
        self.arquivoWav = wave.open(nomeArquivo, 'wb')
        self.sinal = sinal.tostring()
        self.taxaAmostragem = taxaAmostragem
                
  
    def write(self):
            
        self.arquivoWav.setnchannels(1) # mono
        self.arquivoWav.setsampwidth(2) # dois bytes por amostra
        self.arquivoWav.setframerate(self.taxaAmostragem) 
        self.arquivoWav.writeframes(self.sinal)
        self.arquivoWav.close()

Reproduzindo um arquivo wav

   def playFile(self, strFileName):
        
          
        chunk = 1024  

        #abre o arquivo gerado anteriormente  
        f = wave.open(strFileName,"rb")  

        #instancia  PyAudio  
        p = pyaudio.PyAudio()  
  
        stream = p.open(format = p.get_format_from_width(f.getsampwidth()),  
            channels = f.getnchannels(),  
            rate = f.getframerate(),  
            output = True)  

        #le os dados  
        data = f.readframes(chunk)  

        #play stream  
        while data:  
            self.root.update()
                
            if self.enable == True:
                stream.write(data)  
                data = f.readframes(chunk)  
            else:
                data = f.readframes(chunk) 
               
        
          
        stream.stop_stream()  
        stream.close()  
        p.terminate()    

Se você estiver executando um programa com placa de som e sistema de áudio integrado, poderá testar selecionando uma forma de onda e disparar a execução pressionando INICIAR.

Por exemplo, selecione “Senoidal” e vá aumentando a frequência de 1000 em 1000 Hz. O som se tornará progressivamente mais agudo (correto) e de amplitude menor (mais baixo). Este último efeito é função de dois fatores principais. Um, a sua placa de som pode estar limitando. Isto é fácil de ser verificado com um osciloscópio. Dois, o seu ouvido pode estar perdendo a sensibilidade para sinais de frequência mais alta. Se no osciloscópio o sinal estiver estável, pode estar na hora de uma visita ao especialista!

Utilizando o conector de fone de ouvido, podemos enviar este sinal para o amplificador isolador, que será apresentado no próximo artigo. Até lá!

Deixe um comentário