Crie Visuais Personalizados no Osciloscópio com DAC R-2R
Componentes e Materiais
| Componente | Quantidade | Observações |
|---|---|---|
| Resistor 1k ohm | 50 | Qualquer valor de resistor é aceitável desde que você mantenha as proporções do DAC corretas. Não precisa de 50 se tiver resistores de 2k. Ou duas vezes o valor do resistor escolhido. |
| Arduino UNO | 1 | Ou STM32 "blue pill" ou "black pill". |
Aplicativos e Serviços Online
Arduino IDE
Sobre o Projeto
O DAC R-2R
Uma das implementações mais simples de um conversor digital-para-analógico pode ser alcançada projetando um DAC escada R-2R. Este tipo de DAC só precisa de resistores para funcionar, tornando-o muito amigável para iniciantes.
O princípio básico deste tipo de DAC é que você só precisa de dois valores para resistores: R e 2R. Então você pode configurá-los como mostrado no esquemático abaixo.
R pode ser qualquer valor desde que cada ocorrência de R no esquemático seja o mesmo valor.
Por exemplo, se escolhermos R = 1k, então 2R seria apenas duas vezes o valor. Neste caso 2R = 2k. Então você poderia usar apenas resistores de 1k e 2k.
Se você usasse R=3.3K então 2R = 6.6k e assim por diante. Se você quiser usar apenas o mesmo valor de resistores para tudo, então para obter 2R, você só precisaria colocar dois resistores em série e isso aumentaria sua contagem de componentes.
O número de bits é determinado por quantos ramos de 2R você tem saindo. Neste projeto usaremos um R-2R de 8 bits e um R-2R de 6 bits para o Arduino Uno ou Nano. Se você estiver usando outro microcontrolador que sabe que suporta manipulação de porta completa de pelo menos 8 bits, como o STM32 "blue pill" e "black pill", então você pode simplesmente usar dois DACs de 8 bits.
Nota lateral sobre DACs IC:
Este projeto foca no uso de um DAC R-2R, mas você pode se sentir à vontade para usar um DAC IC para alcançar algo similar. Se for usar um DAC IC, recomendo usar um que suporte um protocolo de comunicação rápido como SPI, já que I2C será muito lento para desenhar uma imagem detalhada. Não cobrirei como usar um DAC IC neste post, mas talvez em um futuro. O R-2R ainda é meu método preferido pois pode gerar imagens com mais detalhes.
Manipulação de Porta
Usaremos manipulação de porta paralela do nosso microcontrolador. A razão para fazer isso é porque permite uma velocidade de saída mais rápida e também mantém o código simples. Você pode ler mais detalhes sobre os registradores de porta para Arduino neste link.
O Arduino Uno e Nano têm o microcontrolador ATmega328p. Este microcontrolador compartilha o mesmo mapeamento de pinos com o Atmega168:
Os pinos e bits de porta são denotados por seu rótulo PXN onde X é a letra da porta e N é o número do bit.
Exemplo: PB1 é para PORT B bit 1. Similarmente PC5 é para PORT C bit 5.
Se inspecionarmos os pinos, vemos que o microcontrolador tem portas B, C e D. Voltando à referência do Arduino, podemos ver quais portas podem ser escritas ou lidas ou ambas. Em nossa aplicação, só nos importamos com escrita.
A documentação afirma que PORTD é leitura/escrita e é mapeada para pinos digitais 0 a 7. Isso nos dá 8 bits completos da porta para escrever.
Em seguida, temos PORTB que, como afirmado, são mapeados para pinos digitais 8-13. Uma declaração adicional é feita dizendo que bits 6 e 7, que completariam os 8 bits completos, não são acessíveis. Esta porta só pode ser usada para escrever 6 bits.
O mesmo vale para PORTC. É mapeada para pinos analógicos 0-5 mas dois pinos também são reservados, nos dando apenas 6 bits para escrever.
Devido a essas restrições, teremos que usar uma combinação de um DAC de 8 bits para PORTD e um DAC de 6 bits para PORTC ou PORTB.
Usaremos PORTD e PORTB e agora podemos começar a ligar os DACs aos pinos.
Se precisar de ajuda visual sobre como montar, assim montei ambos os R-2Rs na protoboard. Note que usei dois R-2Rs pois estava testando no meu STM32 "black pill". Coloquei dois fios jumpers saindo para conectar as pontas do osciloscópio e também um fio de ground na lateral. Usei todos resistores de 10k, então para meu 2R coloquei dois resistores de 10k em série.
Aqui está uma configuração de protoboard mais limpa usando Tinkercad. Marquei as saídas com X e Y, pois serão onde as pontas do osciloscópio conectarão. Se precisar de mais orientação sobre a configuração, fornecerei esquemáticos mostrando as escadas R-2R feitas com todos os mesmos valores de resistores e outra com dois valores.
Certifique-se de verificar seus grounds! Toda vez que monto essa configuração costumo esquecer. Também verifique se não deixou espaços sem conexões.
Código Arduino
Agora é hora do sketch do Arduino.
Primeiro precisamos configurar nossas portas como saídas. Da referência do Arduino sobre manipulação de porta, podemos ver como definir as portas:
Basicamente, um valor 1 no bit o define como saída e valor 0 como entrada. Para PORTD podemos escrever diretamente já que todos os bits são acessíveis. Para PORTB, lembre-se que bits 6 e 7 não são utilizáveis pois mapeiam para o cristal. É boa prática usar OR bitwise com valor 0 para evitar modificar bits que não serão definidos.
No nosso loop de setup, isso será o código:
O loop for abaixo das configurações simplesmente remapeia os valores para a porta de 6 bits. Esses valores virão de uma lista que terá valores de faixa de 8 bits. Para remapear para 6 bits, nosso valor máximo será 2^6-1 que é 63. Estamos mapeando de uma faixa de 0 a 2^8-1 que é 255. A razão pela qual remapeamos no código do Arduino é porque nossa ferramenta de plotagem manual gerará os pontos x e y em termos de 8 bits. Podemos mudar em qualquer código, mas é melhor mudar no microcontrolador usado.
Basicamente precisamos de algum delay dependendo do osciloscópio. Descobri que para o meu 1 está bom. Na verdade, omitir completamente o delay também está bom. Para pontos, será gerado automaticamente por nossa ferramenta de plotagem manual, então você pode apenas copiar e colar o número, mas basicamente é quantos pontos existem para plotar, então a variável de iteração pode percorrê-los. Enfim, aqui está o código completo:
Os arrays x_points e y_points serão diferentes para você cada vez que desenhar algo e serão gerados automaticamente com nossa ferramenta.
Nota lateral sobre PWM rápido:
Se você é um usuário avançado de microcontroladores, pode perceber que, se preocupado com velocidade, então talvez o mesmo possa ser alcançado por PWM rápido e modificando registradores de clock. Então alimentando as saídas PWM para um filtro passa-baixa para obter uma tensão analógica, o que reduziria a contagem de pinos. Tentei ambos os métodos, e este ainda resulta em melhor suporte para imagens com mais detalhes. Pelo menos no Arduino Uno.
Ferramenta de Desenho
Fiz esta ferramenta em Python 3 usando tkinter. Se não tiver tkinter instalado, instale com pip.
pip install tk
O código será apenas anexado, mas basicamente ele registra a coordenada na janela onde seu cursor foi clicado. Não tente redimensionar a janela pois mudará a resolução. Se quiser ser mais preciso, use a ferramenta lupa no Windows. A janela é baseada em 255x255, pois esses serão os valores máximos de nossas faixas de bits. Claro, uma das dimensões será mapeada para 6 bits se você estiver usando um Arduino UNO.
Para chamar a ferramenta, simplesmente use este formato:
python drawlog.py > arduino_list.txt
Isso criará um arquivo .txt chamado arduino_list.txt com a lista do Arduino para x_points, y_points e NUM_POINTS gerados para você, prontos para copiar e colar no seu código.
Como exemplo, assim é como o arquivo .txt aparece quando você o abre. Você só precisa substituir essas linhas no código.
Veja como fica uma vez aberto. A partir daí você pode desenhar qualquer coisa.
Desenhei a seguinte imagem para testar. O código é gerado ao fechar a janela.
E após carregar o código no Arduino, assim ficou no modo X-Y.
IMPORTANTE:
Se você encontrar um erro de compilação falando sobre memória, isso é devido à baixa memória disponível no Arduino UNO. Isso significa que você deve desenhar uma imagem menos detalhada. Acho que a quantidade máxima de pontos era cerca de 400, mas pode ser mais. Se você usar um STM32 "Blue pill" ou "black pill", este número é superior a 4.000.
Modo X-Y do Osciloscópio
O osciloscópio deve ser configurado no modo x-y. A saída do DAC R-2R de 8 bits (PORTD) será conectada ao Canal 1 e o DAC R-2R de 6 bits (PORTB) será conectado ao Canal 2. A partir daí você pode ajustar com os botões até ver uma imagem.
É isso! Se tiver dúvidas, sinta-se à vontade para perguntar. Além disso, se quiser testar no blue pill ou black pill, aqui está o link para minha página no GitHub com alguns códigos de exemplo.
Agora aqui estão algumas imagens que plotei.
Sinta-se à vontade para compartilhar as suas!!!
Edição:
Detalhes Importantes para Gerenciamento de Memória
Como Tim mencionou nos comentários:
- No seu código Arduino, seu iterador sobre os pontos, t, é do tipo "byte", então suporta apenas até 255 pontos. Por que não torná-lo um "int"?
- Seu código de plotagem gera "const unsigned long" para x_points e y_points - isso não funcionará pois você modifica y_points depois. Além disso, por que não usar apenas "byte" para economizar memória?
Esteja ciente das limitações de hardware que está usando. Para o Arduino Uno é melhor usar byte, porém se estiver usando STM32 ou até ESP32, terá mais pontos disponíveis para plotar. Obrigado a todos pelo feedback.
Timster:
Encontrei uma maneira de aumentar massivamente o número de pontos suportados em um UNO - movê-los para o espaço de armazenamento do programa. Você pode fazer isso com o modificador PROGMEM. Dessa forma, você pode armazenar cerca de 15.000 pontos!
Então declare os arrays assim:
const int NUM_POINTS = ...
const byte x_points[NUM_POINTS] PROGMEM = {...
const byte y_points[NUM_POINTS] PROGMEM = {...
Não faça a modificação nos y_points na função setup (ou seja, remova o loop for lá). Em vez disso, faça um shift de bit na função loop.
Você também precisa de um comando especial para ler dados da memória do programa (pgm_read_byte_near). Então o loop for no void loop() fica assim:
for (int t=0; t < NUM_POINTS; t++) // percorre pontos
{
PORTD = pgm_read_byte_near(x_points + t);
PORTB = pgm_read_byte_near(y_points + t) >> 2;
delayMicroseconds(FIGURE_DELAY);
}
Então é possível armazenar uma imagem de alta resolução ou várias imagens menores e alternar entre elas 😃
Código
Código Arduino
Sketch Arduino para usar em uma placa Arduino Uno ou Nano
const byte FIGURE_DELAY = 1; // delay de traço em µs. ajuste se necessário
const int NUM_POINTS = 87; // número de pontos XY na figura
// coordenadas x e y para plotar
byte x_points[NUM_POINTS] = {106,106,105,104,103,102,101,100,99,98,97,96,95,94,93,92,91,90,90,89,88,87,87,87,86,86,86,86,87,89,90,91,93,95,97,99,101,102,102,104,104,105,105,106,106,106,106,106,106,108,109,110,112,113,115,117,119,121,122,123,123,124,124,124,124,124,123,122,121,120,119,118,117,116,115,114,113,112,111,110,110,109,109,109,108,107,107};
byte y_points[NUM_POINTS] = {78,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,98,99,100,101,103,105,106,108,110,112,113,113,114,115,115,115,115,115,115,114,112,112,110,109,107,106,106,108,110,112,114,114,115,116,116,117,117,117,117,117,116,115,113,112,110,108,106,104,103,102,100,99,98,97,96,95,94,93,92,91,90,89,87,86,84,82,81,80,78};
void setup(){
// inicializa porta D e B para escrita
DDRD = B11111111;
DDRB = B00111111;
byte t;
for (t=0; t < NUM_POINTS; t++) {
y_points[t] = map(y_points[t],0,255,0,63); // remapeia pontos Y para 6 bits já que porta B é limitada a 6 bits no Arduino
}
}
void loop(){
for (byte t=0; t < NUM_POINTS; t++) // percorre pontos
{
PORTD = x_points[t];
PORTB = y_points[t];
delayMicroseconds(FIGURE_DELAY);
}
}
Ferramenta de Plotagem
Ferramenta de desenho para exportar pontos para plotar
import tkinter as tk
X = []
Y = []
lastx, lasty = 0, 0
# xy e addLine são apenas para propósitos gráficos
# on_move_press é o que registra na lista e corrige Y
def xy(event):
# registra coordenadas quando mouse é clicado
global lastx, lasty
lastx, lasty = event.x, event.y
def addLine(event):
# desenha linha do ponto antigo para novo
global lastx, lasty
canvas.create_line((lastx, lasty, event.x, event.y))
# isso torna o novo ponto inicial do desenho
lastx, lasty = event.x, event.y
# registra coordenada clicada na lista
def on_move_press(event):
curX, curY = (event.x, event.y)
curY=255-curY # já que tkinter usa coordenadas diferentes
X.append(str(curX))
Y.append(str(curY))
# configuração necessária para janela
root = tk.Tk()
root.geometry("255x255")
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
canvas = tk.Canvas(root)
canvas.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S))
# vincula clique esquerdo e arrasto a funções e inicia loop
canvas.bind("", xy)
canvas.bind("", addLine)
root.bind("", on_move_press)
root.mainloop()
# remove cada 2ª entrada para reduzir pontos e aumentar taxa de atualização do traço
for i in range(1, int(len(X)/2)):
X.pop(i)
Y.pop(i)
print("const int NUM_POINTS = %s;" % str(len(X)))
print("const unsigned long x_points[NUM_POINTS] = {%s};" % ','.join(X))
print("const unsigned long y_points[NUM_POINTS] = {%s};" % ','.join(Y))
# chame python drawlog.py > arduino_list.txt
Esquemáticos
Processo de manufatura
- Resistores: Fundamentos, Tipos e Aplicações Práticas
- Construa uma Rede de Sensores Sem Fio Acessível para Sua Casa
- Monitore a Temperatura da Sua Casa com Dashboard no Raspberry Pi
- Principais Acessórios para Desbloquear Todo o Potencial do Seu Raspberry Pi
- Construa seu Próprio Google Voice HAT para Raspberry Pi
- Teste de Tempo de Reação Preciso com Arduino UNO
- Entendendo a Frenagem Dinâmica em Trens Pesados
- Guia Definitivo para Escolher a Serra de Fita Certa
- Guia Passo a Passo para Reparar a Maçaneta da Porta do Carro
- Guia Rápido para Reparar Ferrugem no Carro: Proteja Seu Veículo