A idéia agora é usar um DAC real, placa sugerida: módulo DAC MPC 4725 que é de 12-bits. Isto implica que esta placa aceita valores variando na faixa ìnt de 0 até 4025 (). Esta placa trabalha com comunicação I2C. Ver página Trabalhar com o Módulo DAC, para ver maiores detalhes e saber como instalar o driver para esta placa na IDE do Arduino.
Lab 3) Gerador de Senóide usando DACObjetivoProcedimentoHardware necessarioMontagem sugeridaDetalhes de SoftwareDica No. 1Dica No. 2Teste de "Desempenho Puro"AnexosA. Instalação da Biblioteca Adafruit MCP4725B. Em caso de erro associado com "Adafruit_BusIO_Register.h"C. Geração dos NPOINTS da senoide
Tentar gerar uma onda senoidal o mais rápido possível usando placa Arduino Uno e ISR para garantir sua frequência.
Programar uma ISR para rodar com taxa de amostragem de 10 KHz.
Programar a rotina ISR para gerar 20 ou 10 pontos de uma seníde.
Comprovar usando osciloscópio:
Montar relatório final documentando:
Fixar o módulo DAC no Proto-board;
Realizar as devidas conexões ao DAC:
Pino no DAC | Pino no Arduíno |
---|---|
Vcc | Algum pino Vcc |
GND | GND |
SDA (Data) | A4 |
SCL (Clock) | A5 * |
VOUT | Saída analógica [0 .. 5 Volts ] |
* pino A5 do Arduino (aparentemente já contêm resistor pullup de 10K para 5 Volts no DAC, e esta saída já trabalha com um sinal de 440 KHz).
Diagrama elétrico:
Detalhe da ligação do IRF640 ao alto-falante:
Será necessário programar a ISR para ser chamada na taxa de 10 KHz, ou a cada: segundos ou à cada 0,1 ms. Lembre que o clock do Arduíno Uno normalmente é de 16 MHz e considere que seu uP gasta 4 ciclos de clock para executar uma instrução (típico de processadores RISC), o que significa que cada instrução mínima leva segundos para ser executada ou 0,375 ms. Ou seja, sim, estamos tentando colocar o Arduíno no limite de sua capacidade de processamento.
A fim de reduzir custos computacionais, se sugere que o próprio Arduíno no bloco void setup() {...}
calcule os NPOINTS necessários para gerar os pontos da senóide e os armazene num array (deverá ser uma variável global). Considerar que devem ser gerados valores int
na faixa de [0, 4095] que correspondem aos 12-bits do DAC sendo usado.
A rotina ISR deve bem "enxuta", isto é, exigir o menor esforço computacional possível e realizar apenas 3 coisas:
Realizar testes, documentando os resultados. O ideal seria apresentar um gráfico obtido com auxílio do osciloscópio que mostre no canal 1, a onda senoidal sendo gerada e no canal 2, o pino que muda de estado indicando a execução da ISR.
Para minimizar o esforço computacional dentro da ISR, é melhor evitar if
pesados e usar o operador módulo (%
) apenas se o compilador não transformar isso em instruções mais simples. Outra alternativa é um teste condicional simples (que tende a ser mais rápido em uC's sem divisão rápida).
Segue sugestão de código compacto em C:
xxxxxxxxxx
// Suponha que esta seja a interface do DAC
extern void dac_setVoltage(uint16_t valor);
// Vetor de até 20 pontos
volatile uint16_t vetor[MAX_POINTS]; // variável global
volatile uint8_t NPOINTS = 0; // quantidade de pontos válidos
// Índice atual
volatile uint8_t index = 0; // variável global
// --- Rotina de interrupção (ISR) ---
void ISR_timer(void) {
// 1. Envia valor atual para o DAC
dac_setVoltage(vetor[index]);
// 2. Incrementa índice
index++;
// 3. Se chegou no fim, volta para zero
if (index >= NPOINTS) {
index = 0;
}
}
Observações:
index
e NPOINTS
foram definidos como uint8_t
porque o vetor tem no máximo 20 pontos. Assim, evita-se variáveis maiores que o necessário.if (index >= NPOINTS)
) é muito leve e geralmente mais rápida que usar:
index = (index+1) % NPOINTS;
principalmente em uC's pequenos (como AVR do Arduino).volatile
foi usado porque tanto index
quanto NPOINTS
podem ser acessados dentro e fora da ISR.Existe uma forma ainda mais rápida de exceutar a ISR, mas envolve o uso de "máscara binária" e só funciona se NPOINTS for compatível com potência de 2. Por exemplo, suponha que NPOINT = 16
, então podemos fazer algo como:
xxxxxxxxxx
// potência de 2
volatile uint16_t vetor[NPOINTS];
volatile uint8_t index = 0;
void ISR_timer(void) {
dac_setVoltage(vetor[index]);
index = (index + 1) & MASK; // volta automaticamente para 0
}
Nesse caso não há if
nem divisão, só uma operação AND bit a bit. Mas para isto NPOINTS deve ser potência de 2.
Outro teste que poderia ser realizado, mas que não garante nenhum compromisso com a taxa de amostragem, seria usar a seção void loop() {..}
do Arduíno apenas para comandar o DAC e atualizar a variável índice do vetor. Eventualmente se poderia acrescentar neste código, instrução para alternar o nível de algum pino de "controle" do Arduino, assim, usando o osciloscópio, se poderia comprovar a máxima frequência da senóide possível de ser gerada (canal 1). E no canal 2 do osciliscópio, conectado no pino de controle seria observada uma onda quadrada cujos ciclos (em nível lógico baixo e em nível lógico alto) corresponderiam ao tempo gasto pela Arduíno para processar esta seção de código. Este teste resultaria algo como:
xxxxxxxxxx
// Fernando Passold, em 27.08.2025
// biblioteca I2C
// necessário para acessar o DAC
// Pino de controle (por ex. 8)
bool estado = 0;
// Vetor da senoide
// associado com os 12-bits do DAC: 2^12-1=4095
// qtdade de pontos usados para sintetizar ciclo completo senóide
int senoide[NPOINTS] = {
2047, 2682, 3274, 3784, 4094, 4094, 3784, 3274,
2682, 2047, 1412, 820, 310, 0, 0, 310,
820, 1412, 2047, 2682
}; // a inicialização deste vetor é opcional
// variáveis associadas com o DAC
Adafruit_MCP4725 dac; // para instanciar o objeto "dac" pertencente a classe "Adafruit_MCP4725"
int ender_DAC = 0x60; // endereço I2C do DAC
bool DAC_found = false; // indica problemas para acessar DAC
// Índice do vetor
int index = 0;
// função para identificar DAC
// Retorna true se módulo encontrado
// caso contrário retorna false
bool found_DAC(void) {
Serial.println("");
Serial.println("Buscando Módulo DAC MCP4725");
for (ender_DAC = 0x60; ender_DAC <= 0x65; ender_DAC++) {
Serial.print("Testando endereço: 0x");
Serial.print(ender_DAC, HEX);
DAC_found = dac.begin(ender_DAC);
if (DAC_found) {
Serial.println(" <-- Encontrado, Ok");
digitalWrite(PINO_CONTROLE, HIGH);
return true;
break; // sai do for
}
Serial.println("");
}
if (!DAC_found) {
Serial.println("Ops... Modulo DAC MCP4725 não encontrado");
return false;
}
}
// Função que calcula pontos da senóide
// Armazena no vetor senoide[] seus NPOINTS
void gera_senoide() {
Serial.println("Gerando pontos da Senoide:");
for (int i = 0; i < NPOINTS; i++) {
double ang = 2.0 * M_PI * i / NPOINTS;
senoide[i] = (int)(((sin(ang) + 1.0) * 0.5) * MAXVAL);
Serial.print(senoide[i]); // Mostra valor gerado
Serial.print(", "); // para próximo valor
if ((i + 1) % 8 == 0) {
Serial.println(); // quebra linha a cada 8 valores
}
}
Serial.print("\b\b"); // backspace para apagar ", "
Serial.println(); // Fim
}
void setup() {
Serial.begin(115200); // ativa porta serial
delay(100); // espera porta inicializar
Serial.println("");
Serial.println("Programa para gerar Senoide");
Serial.println("* Teste de Desempenho Puro *");
pinMode(PINO_CONTROLE, OUTPUT);
digitalWrite(PINO_CONTROLE, HIGH); //indica que estamos dentro da seção setup
DAC_found = found_DAC();
if (!DAC_found) { // Modulo DAC MCP4725 não encontrado
Serial.println("Programa sendo encerrado.. Sorry!");
return; // encerra setup() e não segue para loop()
}
gera_senoide(); // prepara vetor da senoide
index = 0; // garante que inicia no primeiro ponto
Serial.println("Iniciando senóide...");
digitalWrite(PINO_CONTROLE, LOW); // indica saída da seção setup
}
void loop() {
// O bloco abaixo poderia ter sido mantido
// mas queremos verificar a taxa máxima alcançada pelo Arduino Uno
// Alterna o pino de controle
// estado = !estado; // faz "toggle" no nível lógico do pino de controle
// digitalWrite(PINO_CONTROLE, estado);
DAC_found = dac.setVoltage(senoide[index], false);
// if (!DAC_found) { // Modulo DAC MCP4725 desconectado!
// digitalWrite(PINO_CONTROLE, LOW);
// Serial.println("Modulo DAC MCP4725 foi desconectado");
// Serial.println("Programa sendo encerrado.. Sorry!");
// exit(0); // finaliza programa, não garantido no Arduíno
// /*** ou você pode tentar usar:
// set_sleep_mode(SLEEP_MODE_PWR_DOWN);
// sleep_enable();
// sleep_cpu(); // entra em modo de baixo consumo e só volta com reset/interrupção
// }
// Bloco mínimo necessário para output dos pontos da senoide
// Incrementa índice
index++;
if (index >= NPOINTS) {
index = 0; // retorna ao início do vetor de pontos
}
}
A execução deste código gera a seguine saída no terminal (porta serial):
xxxxxxxxxx
Programa para gerar Senoide
* Teste de Desempenho Puro *
Buscando Módulo DAC MCP4725
Testando endereço: 0x60 <-- Encontrado, Ok
Gerando pontos da Senoide:
2047, 2680, 3250, 3703, 3994, 4095, 3994, 3703,
3250, 2680, 2047, 1414, 844, 391, 100, 0,
100, 391, 844, 1414, ••
Iniciando senóide...
Note que os valores calculados pelo Arduino são praticamente os mesmos calculados testando o código via MATLAB (Anexo C (final da página)):
Simulação Matlab: | Captura real (Osciloscópio) |
---|---|
![]() |
Pela medição realizada pelo osciloscópio se percebe que a frequência máxima atingida foi pouco além de 300 Hz. Apenas! (E sim, a resoluçao e UX com usuário do osciloscópio da Tektronix é "sofrível").
Detalhe: se esta mesma senóide for implementada via ISR sendo executada a taxa de 10 KHz (período de amostragem de 100 s), ao sintetizar um ciclo de senóide usando 20 pontos, pode-se alcançar frequência um pouco superior à 400 Hz. O que significa que a ISR é executada de forma mais rápida que o bloco void loop() { ... }. Mas a ISR vai ocupar pouco mais de 80% do período de tempo dos 10 KHz para repassar os valores do vetor da senóide para o DAC. Isto significa que aproximadamente 82 s para sua execução, sobrando menos de 20 s para o Arduíno recuperar "contexto" após execução da ISR. Na prática, o mesmo ficaria insensível talvez à leitura do simples nível lógico de um push-bottom conectado à alguma porta I/O inviabilizando até uma interface mínima com o usuário.
Fim.
Vá até a página https://github.com/adafruit/Adafruit_MCP4725, acesse o botão verde contendo " <> Code v " e com o botão direito do mouse sobre este botão, faça abrir um menu pull-down e selecione: , como mostra a figura abaixo:
Cliando em "Download ZIP" será baixado um arquivo compactado ZIP.
Basta ir para a IDE do Arduíno e buscar por: Menu >> Rascunho > Incluir Biblioteca > Adicionar biblioteca .ZIP..., como mostra a figura abaixo:
E então selecionar a pasta (provavelmente /Downloads) onde antes você baixou o arquivo zipado.
Pronto.
Em caso de erro do tipo:
xxxxxxxxxx
ResolveLibrary(Adafruit_MCP4725.h)
-> candidates: [Adafruit MCP4725@2.0.2]
fatal error: Adafruit_BusIO_Register.h: No such file or directory
^~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
Alternatives for Adafruit_BusIO_Register.h: []
ResolveLibrary(Adafruit_BusIO_Register.h)
-> candidates: []
exit status 1
Compilation error: exit status 1
Esta mensagem de erro:
xxxxxxxxxx
#include <Adafruit_BusIO_Register.h>
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilação encerrada.
Alternativas para Adafruit_BusIO_Register.h: []
ResolveLibrary(Adafruit_BusIO_Register.h)
indica que o IDE do Arduino não conseguiu localizar o arquivo de cabeçalho especificado, Adafruit_BusIO_Register.h
. Isso normalmente ocorre quando uma biblioteca necessária não está instalada ou não é reconhecida corretamente pela IDE.
Faltaria instalar a biblioteca faltante:
Os NPOINTS da senóide podem ser gerados de forma simples e antecipada, gerando valores dentro um vetor. Testando um código no MATLAB
xxxxxxxxxx
>> NPOINTS=20;
>> help linspace
linspace Linearly spaced vector.
linspace(X1, X2) generates a row vector of 100 linearly
equally spaced points between X1 and X2.
linspace(X1, X2, N) generates N points between X1 and X2.
For N = 1, linspace returns X2.
Class support for inputs X1,X2:
float: double, single
See also logspace, colon.
Documentation for linspace
Other functions named linspace
>> angle=linspace(0,2*pi,NPOINTS+1); % NPOINTS+1 para senoide recomeçar no mesmo valor em NPOINTS+1
>> size(angle)
ans =
1 21
>> % Ok, 21 pontos, só nos interessa os 20 primeiros pontos
>> MAXVAL=4095;
>> senoide=MAXVAL*0.5*(1+sin(angle)); % gera pontos entre [0..MAXVAL]
>> [angle' senoide' round(senoide')] % gera uma espécie de tabela na CLI do Matlab
ans =
0 2047.5 2048
0.31416 2680.2 2680
0.62832 3251 3251
0.94248 3704 3704
1.2566 3994.8 3995
1.5708 4095 4095
1.885 3994.8 3995
2.1991 3704 3704
2.5133 3251 3251
2.8274 2680.2 2680
3.1416 2047.5 2048
3.4558 1414.8 1415
3.7699 844.01 844
4.0841 391.04 391
4.3982 100.21 100
4.7124 0 0
5.0265 100.21 100
5.3407 391.04 391
5.6549 844.01 844
5.969 1414.8 1415
6.2832 2047.5 2047
>> % Note que o último ponto (No. 21), recomeça a senoide no mesmo ponto (praticamente).
>> % No momento de sintetizar um ciclo da senoide repassar apenas os 20 primeiros ponto
>> stairs (angle,senoide,'bo-') % ou use: plot(angle, senoide, 'bo-')
>> grid
Os pontos calculados para a senóide podem ser vistos no gráfico à seguir:
Se espera ver esta forma de onda na saída do DAC.
Fernando Passold, em 27/08/2025