In [1]:
import pylef # importar pylef
import visa # importar a bilioteca pyVISA para lidar com virtualização de instrumentos
import matplotlib.pyplot as plt # importar a bilioteca pyplot para fazer gráficos
import numpy as np # importar a biblioteca Numpy para lidar com matrizes
import time # importar a bilioteca para funções temporais
import pandas as pd # importa bilioteca pandas para lidar com processamento de dados
# próxima linha faz plotar o gráfico dentro do notebook
%matplotlib inline
In [2]:
?pylef # imprime o help para o pylef
In [ ]:
In [3]:
# imprime os recursos para ter certeza que os instrumentos estão conectados
visa.ResourceManager().list_resources()
Out[3]:
Os controles tanto do gerador quanto o osciloscópios são baseados no que se pode fazer fisicamente no instrumento e da maneira mais intuitiva que conseguimos.
Para o gerador de funções é possivel acessar os dois canais individualmente. Podemos ligar e desligar os canais, escolher a forma de onda e ajustar todos os seus parâmetros, como frequência, tensão pico-a-pico, offset DC, defasagem. Também é possível ligar e desligar a saída de sincronia (SYNC) para o trigger externo do osciloscópio.
Como é comum no Jupyter, as informações sobre determinada função ou módulo, pode ser encontradas executando a função com uma interrogação antes do nome, por exemplo "?pylef.BK4052" imprime informações sobre o osciloscópio. Se o help estiver errado, incompleto ou não estiver claro, nos avise!
Para o osciloscópio é possivel acessar cada canal individualmente também, assim como o trigger e o modo MATH. Podemos ajustar as escalas dos canais (inclusive de forma dinâmica), fazer diversas medidas e adquirir as formas de onda presentes na tela. Também podemos ajustar o trigger e salvar os dados diretamente em um arquivo texto.
In [4]:
func_gen = pylef.BK4052() # definição do gerador de funções
scope = pylef.TektronixTBS1062() # definição do osciloscópio
In [5]:
# imprime ajuda para o gerador de funções
?func_gen
In [6]:
# informações sobre o gerador de fuções
print(func_gen.ch1.wave_info()) # informações sobre a função no canal 1
print(func_gen.ch2.wave_info()) # informações sobre a função no canal 2
In [7]:
# checar se o canal está ligado
print(func_gen.ch1.state()) # checa se o canal está ligado ou desligado
func_gen.ch1.turn_on() # liga o canal 1
print(func_gen.ch1.state()) # checa se o canal está ligado ou desligado
In [20]:
# Duas formas de trabalhar: podemos criar uma variável para cada canal!!!
canal1 = func_gen.ch1 # cria uma variável para cada canal
print(canal1.state()) # checa se o canal está ligado ou desligado
canal1.turn_on() # desliga o canal 1
print(canal1.state()) # checa se o canal está ligado ou desligado
In [21]:
# liga o gerador de novo
canal1.turn_on()
In [22]:
### vamos colocar a onda que queremos
freq = 100.0 # frequência de 100 Hz
Vpp = 2.0 # voltagem pico-a-pico de 2 V
offset = 0.0 # offset zero
tipo = 'sine' # tipo da forma de onda (em inglês)
### enviar comando
func_gen.ch1.set_function(tipo)
func_gen.ch1.set_frequency(freq)
func_gen.ch1.set_Vpp(Vpp)
func_gen.ch1.set_offset(offset)
In [23]:
# imprime informações sobre a onda
func_gen.ch1.wave_info()
Out[23]:
In [24]:
# cria onda quadrada
func_gen.ch1.set_function('square')
func_gen.ch1.wave_info()
Out[24]:
In [25]:
# Quais tipos de onda podemos ter?
func_gen.ch1.functions
Out[25]:
In [26]:
# cria onda triangular?
func_gen.ch1.set_function('tamp')
func_gen.ch1.wave_info()
In [27]:
# Ops, deu errado! Leia a mensagem de erro!! Achou o erro?
func_gen.ch1.set_function('ramp')
func_gen.ch1.wave_info()
Out[27]:
In [28]:
# E se alguém mudar a lista de funções. Tenta aí! Boa sorte!!
func_gen.ch1.functions = ['pepe']
In [29]:
# imprime ajuda para o scope
?scope
In [32]:
# Osciloscópio está louco?" Configurando o trigger externo (lembre-se de fazer a ligação da saída de
# SYNC do gerador na entrada EXT do osciloscópio
func_gen.ch1.sync_on() # ligar o sync reference ao canal 1
scope.trigger.set_source('ext') # ajusta o trigger para externo no osciloscópio
In [33]:
## Mude a escala do osciloscópio para 2mV!!
# aquisição da função mostrada na tela do osciloscópio
t, V = scope.ch1.read_channel() # adquire dados de tensão por tempo
plt.plot(t, V) # plot V x t
plt.show() # mostra o gráfico
In [34]:
## Ops, mas não era para ser uma onda triangular?
## Ajuste a escala do osciloscópio ou use a função set_smart_scale
scope.ch1.set_smart_scale() # muda a escala dinamicamente para aparecer na tela
t, V = scope.ch1.read_channel() # adquire dados de tensão por tempo
plt.plot(t, V) # plot V x t
plt.show() # mostra o gráfico
In [35]:
# E as medidas? Use a subclasse measure
scope.ch1.measure.period(), scope.ch1.measure.frequency(), scope.ch1.measure.Vpp()
Out[35]:
Conecte um capacitor de $2.2\, \mu C$ e um resistor de $150\, \Omega$ na configuração de filtro passa-baixa. Ligue o canal 1 do gerador de função no canal 1 do osciloscópio e conecte o canal 2 do osciloscópio entre o resistor e o gerador de funções. Também conecte a saída SYNC do gerador de funções na entrada EXT do osciloscópio
In [37]:
## Ligue um capacitor de 2.2 uC e um resistor de 150 Ohm na configuração de filtro passa-baixa
R, C = 150, 2.2e-6 # definições de R e C
omega = 1./(R*C) # frequência de cotovelo ângular
freq = omega/(2*np.pi) # frequência de cotovelo
print('frequência de cotovelo = %4.1f Hz' % freq) # imprime frequência de cotovelo em Hz
In [38]:
##### Pré-aquisição
# mude a onda para uma senóide! O que acontece se usarmos a onda triângular?
func_gen.ch1.set_function('sine')
## ajuste o trigger externo
func_gen.ch1.sync_on() # ligar o sync reference ao canal 1
scope.trigger.set_source('ext') # ajusta o trigger para externo no osciloscópio
## ajusta média nos canais
scope.set_average_number(4) # ajusta o número de médias
scope.set_average() # turn average ON
#scope.set_sample() # turn average OFF
## parametros de varredura
PATH = 'exemplo-pylef/' # pasta onde salvar todos os arquivos
Vpp = 2.0 # Tensão pico-a-pico na saida do gerador
freq0, freq1, Nfreq = 10, 30e3, 20 # frequências inicial, final e número de pontos
## parâmetros inicias do gerador
func_gen.ch1.set_frequency(freq0)
func_gen.ch1.set_Vpp(Vpp)
func_gen.ch1.set_offset(0.0)
func_gen.ch1.set_phase(0.0)
## tenha certeza que o gerador está ligado e que o canal 1 está na escala certa
func_gen.ch1.turn_on() # liga o canal 1
scope.set_horizontal_scale((1/freq0)/4.) # escala horizontal = 1/4 período (2.5 oscilações em tela)
scope.ch1.set_smart_scale() # rescala o canal 1
scope.ch2.set_smart_scale() # rescala o canal 2
In [39]:
#### Aquisição de dados!! ####
freq = np.logspace(np.log10(freq0), np.log10(freq1), Nfreq, endpoint = True) # varredura logaritmica
Vpp1, Vpp2 = [], [] # listas para guardar as variáveis
phase1, phase2 = [], [] # listas para guardar as variáveis
### aquisição de dados no gerador com varredura de frequência
for m, freqP in enumerate(list(freq)): # loop de aquisição
### ajuste dos instrumentos
func_gen.ch1.set_frequency(freqP) # muda a frequência
periodP = 1./freqP # período da onda
num = np.floor(-np.log10(periodP)/3.) + 1 # referência para a escala do gráfico
scope.set_horizontal_scale(periodP/4.) # escala horizontal = 1/4 período (2.5 oscilações em tela)
time.sleep(0.05) # espere 50 microssegundos
scope.ch2.set_smart_scale() # rescala o canal 2
### aquisição de dados
Vpp1.append(scope.ch1.measure.Vpp()) # acumula a medida do Vpp no canal 1
phase1.append(scope.ch1.measure.phase()) # acumula a medida da fase no canal 1
time.sleep(0.05) # wait between the data acquisition
Vpp2.append(scope.ch2.measure.Vpp()) # acumula a medida do Vpp no canal 2
phase2.append(scope.ch2.measure.phase()) # acumula a medida da fase no canal 2
### leitura dos traços temporais
(t1, V1) = scope.ch1.read_channel() # mede o canal 1
(t2, V2) = scope.ch2.read_channel() # mede o canal 2
### produção das figuras
fig = plt.figure() # definição da figura
ax = fig.add_subplot(111) # definição do eixo
ax.plot(t1*10**(3*num), V1, label = 'Vin') # plota canal 1
ax.plot(t2*10**(3*num), V2, label = 'Vout') # plota canal 2
plt.axis([None, None, -1.1*Vpp/2, 1.1*Vpp/2]) # ajusta os máximos e mínimos dos gráficos
ax.set_ylabel('Tensões (V)')
ax.legend() # imprime as legendas no gráfico
if num == 0.0: ax.set_xlabel('Tempo (s)')
if num == 1.0: ax.set_xlabel('Tempo (ms)')
if num == 2.0: ax.set_xlabel('Tempo (us)')
### salva em arquivo os dados da aquisição
file_sweep_name = 'freq_sweep' + str(m + 1) # nome dos arquivos de sweep
scope.save_channels(file_sweep_name, PATH = PATH)
fig.savefig(PATH + file_sweep_name + '.png', bbox_inches = 'tight')
plt.show() # impressão na tela!
Vpp1 = np.array(Vpp1) # convete a lista em array
Vpp2 = np.array(Vpp2) # convete a lista em array
phase1 = np.array(phase1) # convete a lista em array
phase2 = np.array(phase2) # convete a lista em arra
In [27]:
## Análise de dados (não mude a variável PATH)
T = Vpp2/Vpp1 # cálculo da transmissão
T_dB = 20*np.log10(T) # transmissão em dB
dados = pd.DataFrame() # inicializa um dataframe do pandas
dados['Vpp1 (V)'], dados['Vpp2 (V)'] = Vpp1, Vpp2
dados['fase1 (rad)'], dados['fase2 (rad)'] = Vpp1, Vpp2
dados['frequencia (Hz)'], dados['T'] , dados['T_dB'] = freq, T, T_dB
# plota o diagrama de bode para a transmissão e exporta em png
fig = plt.figure() # define uma figura
ax = fig.add_subplot(111) # define um eixo
ax.plot(freq, T_dB, 'ko') # plota a transmissão
ax.set_xscale('log') # seta a escala de x para logaritmica
# Por que não usamos escala log no eixo y também?
ax.set_xlabel('frequência (Hz)') # seta escala do eixo x
ax.set_ylabel('Transmissão (dB)') # seta escala do eixo y
fig.savefig(PATH + 'bode_diag.png', bbox_inches = 'tight') # salva figura na pasta de trabalho
dados.to_csv(PATH + 'dados_sweep.csv')
plt.show()
In [ ]:
Agora conecte um diodo diretamente polarizado na saída do gerador de funções. Vamos fazer quatro exemplos e para cada um deles, você terá que fazer um procedimento específico:
In [78]:
#### Não mude a variável PATH ####
Vpp = 4.0 # tensão pico-a-pico
func_gen.ch1.turn_on() # liga o canal 1
freq0 = 100. # ajusta a frequência para 100 Hz
# parâmetros iniciis do gerador
func_gen.ch1.set_frequency(freq0)
func_gen.ch1.set_Vpp(Vpp)
func_gen.ch1.set_offset(0.0)
func_gen.ch1.set_phase(0.0)
# ajuste a escala
scope.set_horizontal_scale((1/freq0)/4.) # escala horizontal = 1/4 período (2.5 oscilações em tela)
scope.ch1.set_smart_scale() # rescala o canal 1
scope.ch2.set_smart_scale() # rescala o canal 2
In [43]:
## aquisição de dados
t1, V1 = scope.ch1.read_channel() # leitura do canal 1
t2, V2 = scope.ch2.read_channel() # leitura do canal 2
## impressão das figuras
fig = plt.figure() # definição da figura
ax = fig.add_subplot(111) # definição do eixo
ax.plot(t1*1e3, V1, label = 'Vin') # plota canal 1
ax.plot(t2*1e3, V2, label = 'Vout') # plota canal 2
plt.axis([None, None, -1.1*Vpp/2, 1.1*Vpp/2]) # ajusta os máximos e mínimos dos gráficos
ax.set_ylabel('Tensões (V)')
ax.set_xlabel('tempo (ms)')
ax.legend(loc = 'lower right')
### salva em arquivo os dados da aquisição
file_name = 'diodo1' # nome dos arquivos de sweep
scope.save_channels(file_name, PATH = PATH)
fig.savefig(PATH + file_name + '.png', bbox_inches = 'tight')
plt.show() # impressão na tela!
In [69]:
#### varredura de tensão
Va, Vb, Nv = 0.2, 19, 10 # frequências inicial, final e número de pontos
#### Aquisição de dados!! ####
V = np.logspace(np.log10(Va), np.log10(Vb), Nv, endpoint = True) # varredura logaritmica
Vmax1, Vmax2 = [], [] # listas para guardar as variáveis
### aquisição de dados no gerador com varredura de tensão
for m, Vpp in enumerate(list(V)): # loop de aquisição
### ajuste dos instrumentos
func_gen.ch1.set_Vpp(Vpp) # muda a frequência
time.sleep(0.05) # espere 50 microssegundos
scope.ch1.set_smart_scale() # rescala o canal 1
scope.ch2.set_smart_scale() # rescala o canal 2
### aquisição de dados
Vmax1.append(scope.ch1.measure.maximum()) # acumula a medida do Vmax no canal 1
time.sleep(0.05) # wait between the data acquisition
Vmax2.append(scope.ch2.measure.maximum()) # acumula a medida do Vmax no canal 2
### leitura dos traços temporais
(t1, V1) = scope.ch1.read_channel() # mede o canal 1
(t2, V2) = scope.ch2.read_channel() # mede o canal 2
### produção das figuras
fig = plt.figure() # definição da figura
ax = fig.add_subplot(111) # definição do eixo
ax.plot(t1*1e3, V1, label = 'Vin') # plota canal 1
ax.plot(t2*1e3, V2, label = 'Vout') # plota canal 2
plt.axis([None, None, -1.1*Vpp/2, 1.1*Vpp/2]) # ajusta os máximos e mínimos dos gráficos
ax.set_ylabel('Tensões (V)')
ax.legend(loc = 'lower right') # imprime as legendas no gráfico
ax.set_xlabel('Tempo (ms)')
### salva em arquivo os dados da aquisição
file_sweep_name = 'diodo_sweep_voltage' + str(m + 1) # nome dos arquivos de sweep
scope.save_channels(file_sweep_name, PATH = PATH)
fig.savefig(PATH + file_sweep_name + '.png', bbox_inches = 'tight')
plt.show() # impressão na tela!
Vmax1 = np.array(Vmax1) # convete a lista em array
Vmax2 = np.array(Vmax2) # convete a lista em array
In [70]:
## Análise exemplo 1 (não mude a variável PATH)
T = Vmax2/Vmax1 # cálculo da transmissão
T_dB = 20*np.log10(T) # transmissão em dBdados = pd.DataFrame()
dados = pd.DataFrame()
dados['T'] , dados['T_dB'] = T, T_dB
dados['Vpp (V)'], dados['Vmax1 (V)'], dados['Vmax2 (V)'] = V, Vmax1, Vmax2
# plota o diagrama de bode para a transmissão e exporta em png
fig = plt.figure() # define uma figura
ax = fig.add_subplot(111) # define um eixo
ax.plot(Vmax1, T_dB, 'ko') # plota a transmissão
ax.set_xscale('log')
# Por que não usamos escala log no eixo y também?
ax.set_xlabel('Amplitude de entrada (V)') # seta escala do eixo x
ax.set_ylabel('Transmissão (dB)') # seta escala do eixo y
fig.savefig(PATH + 'trans_diodo.png', bbox_inches = 'tight') # salva figura na pasta de trabalho
dados.to_csv(PATH + 'dados_diodo2.csv')
plt.show()
In [85]:
Va, Vb, Nv = 10., 3., 4 # frequências inicial, final e número de pontos
#### Aquisição de dados!! ####
V = np.linspace(Va, Vb, Nv, endpoint = True) # varredura logaritmica
func_gen.ch1.set_Vpp(Va)
scope.ch1.set_smart_scale() # rescala o canal 1
scope.ch2.set_scale(scope.ch1.scale())
scope.ch2.set_position(scope.ch1.position())
for m, Vpp in enumerate(list(V)):
func_gen.ch1.set_Vpp(Vpp)
time.sleep(5.0) # oberve o osciloscópio e veja o atraso entre a mudança das duas curvas
In [88]:
Va, Vb, Nv = 10., 3., 4 # frequências inicial, final e número de pontos
#### Aquisição de dados!! ####
V = np.linspace(Va, Vb, Nv, endpoint = True) # varredura logaritmica
func_gen.ch1.set_Vpp(Va)
scope.ch1.set_smart_scale() # rescala o canal 1
scope.ch2.set_scale(scope.ch1.scale())
scope.ch2.set_position(scope.ch1.position())
for m, Vpp in enumerate(list(V)):
func_gen.ch1.set_Vpp(Vpp)
time.sleep(2.0) # oberve o osciloscópio e veja o atraso entre a mudança das duas curvas
# Viu alguma diferença? O tempo de reação mudou?
In [89]:
#### Não mude a variável PATH ####
Vpp = 4.0 # tensão pico-a-pico
func_gen.ch1.turn_on() # liga o canal 1
freq0 = 100. # ajusta a frequência para 100 Hz
# parâmetros iniciis do gerador
func_gen.ch1.set_frequency(freq0)
func_gen.ch1.set_Vpp(Vpp)
func_gen.ch1.set_offset(0.0)
func_gen.ch1.set_phase(0.0)
scope.ch1.set_smart_scale() # rescala o canal 1
scope.ch2.set_smart_scale() # rescala o canal 2
In [90]:
freq0, freq1, Nfreq = 100, 500e3, 10 # frequências inicial, final e número de pontos
#### Aquisição de dados!! ####
freq = np.logspace(np.log10(freq0), np.log10(freq1), Nfreq, endpoint = True) # varredura logaritmica
Vmax1, Vmax2 = [], [] # listas para guardar as variáveis
### aquisição de dados no gerador com varredura de frequência
for m, freqP in enumerate(list(freq)): # loop de aquisição
### ajuste dos instrumentos
func_gen.ch1.set_frequency(freqP) # muda a frequência
periodP = 1./freqP # período da onda
num = np.floor(-np.log10(periodP)/3.) + 1 # referência para a escala do gráfico
scope.set_horizontal_scale(periodP/4.) # escala horizontal = 1/4 período (2.5 oscilações em tela)
time.sleep(0.05) # espere 50 microssegundos
scope.ch2.set_smart_scale() # rescala o canal 2
### aquisição de dados
Vmax1.append(scope.ch1.measure.maximum()) # acumula a medida do Vmax no canal 1
time.sleep(0.05) # wait between the data acquisition
Vmax2.append(scope.ch2.measure.maximum()) # acumula a medida do Vmax no canal 2
### leitura dos traços temporais
(t1, V1) = scope.ch1.read_channel() # mede o canal 1
(t2, V2) = scope.ch2.read_channel() # mede o canal 2
### produção das figuras
fig = plt.figure() # definição da figura
ax = fig.add_subplot(111) # definição do eixo
ax.plot(t1*10**(3*num), V1, label = 'Vin') # plota canal 1
ax.plot(t2*10**(3*num), V2, label = 'Vout') # plota canal 2
plt.axis([None, None, -1.1*Vpp/2, 1.1*Vpp/2]) # ajusta os máximos e mínimos dos gráficos
ax.set_ylabel('Tensões (V)')
ax.legend(loc = 'lower right') # imprime as legendas no gráfico
if num == 0.0: ax.set_xlabel('Tempo (s)')
if num == 1.0: ax.set_xlabel('Tempo (ms)')
if num == 2.0: ax.set_xlabel('Tempo (us)')
### salva em arquivo os dados da aquisição
file_sweep_name = 'diode_freq_sweep' + str(m + 1) # nome dos arquivos de sweep
scope.save_channels(file_sweep_name, PATH = PATH)
fig.savefig(PATH + file_sweep_name + '.png', bbox_inches = 'tight')
plt.show() # impressão na tela!
Vmax1 = np.array(Vmax1) # convete a lista em array
Vmax2 = np.array(Vmax2) # convete a lista em array
In [ ]: