
A idéia é criar uma pequena rotina que:
Gera senóide na frequência de: 440 Hz
Usa frequência/faxa de amostragem:

Existem diferentes formas de solucionar este problema. Aqui vamos usar a abordagem chamada “phase accumulator”. Ao final é comentada a técnica DDS fixed-point + LUT.
A idéia é implementar uma interrupção por software gerada por oversflow de times, ou simplesmente “ISR”.
A cada instante de amostragem (execução da ISR), vamos:
Determinar o novo ângulo da senóide:
Calculamos antes de iniciar a ISR, o valor de
Isto significa que calculamos
00.276460.552920.829381.10584
E depois calculamos:
sin(angle)a cada 100
Detalhes extras:

Observações:
É interessante colocar um filtro passa-baixa analógico na saída do DAC para cortar os harmônicas de ordem elevada associados com o processo de amostragem.
Como estamos trabalhando com
Vamos usar um simples circuito RC, um filtro passa-baixa de 1a-ordem cuja função transferência é dada por:
onde:
Note que para frequências de entrada abaixo de
O valor de
E finalmente podemos relacionar
onde:
Supondo que
O valor comercial mais próximo é 3,9 ou 2,7 nF.
Assim, recomenda-se usar um resistor de:

Duas abordagem de codificação são apresentadas:
Quanto tempo de processamento é dedicado dentro da ISR (uma tarefa crítica) para calcular o sinf() !?
A idéia é justamente medir o duty-cycle do pino de DEBUG para medir o valor real de tempo (custo) de processamento gasto nesta forma de abordagem.
São esperadas formas de onde como as mostradas na figura abaixo:

A figura anterior mostra uma captura da tela do osciloscópio. Na parte superior aparece uma senóide de 438 Hz sendo gerada, com amplitude variando entre [0, 3.14] Volts (a placa ESP32 funciona internamente a 3,3 Volts) e na parte inferior aparece o resultado obtudo no pino DEBUG, confirmando que a ISR está sendo executada na taxa de 10 KHz, e ocupando apenas 5,8% do ciclo de 10 KHz (
Espera-se um Relatório, contendo:
Cópia do código implementado com comentários relevantes (apenas os mais importantes);
Cópia da tela do Osciloscópio mostrando a onda senoida sendo gerada, preferencialmente acompanhada do valor da frequência medida pelo equipamento.
Cópia da tela do Osciloscópio mostrando::
Determinação do período de tempo ocupado exclusivamente com o processamento da ISR.
sin() ou sinf() é uma operação relativamente “cara” para um minicontrolador.
Não estamos falando apenas de uma multiplicação:
x = a * b;
O cálculo do seno envolve uma série de operações matemáticas para obter uma aproximação da função.
E estamos pedindo isso
Uma solução seria aprimorar um pouco mais esta abordagem e usar acumular inteiro ou por fixed-point.
Esta outra abordagem chamada de DDS (Direct Digital Synthesis) fixed Point + LUT table, usa variáveis inteiras de ponto fixo + tabelas (Lookup Table) armazenando valores pré-calculados externamente do seno (dentro de uma faixa equivalente à 0 à
Representamos a fase como um número inteiro de 32-bits:
uint32_t angle;Um período inteiro da senóide seria representado pela faixa:
E continuamos fazendo algo como:
angle += angle_increment;Antes desta abordagem, tínhamos que controlar a faixa do ângulo (em radianos):
if (angle >= 2*pi) angle -= 2*pi;Agoram usando aritmética de ponto inteiro de 32-bits, caso o angulo ultrapasse o maior valor (overflow), ocorre naturalmente a “volta ao zero”. Então, nesta nova abordagem simplesmente se faz:
angle += angle_increment;deixando ocorrer o overflow sem problemas, sem necessidade de um bloco if.
A frequência desejada continua sendo
Então o incremento de fase no formato de 32 bits fica:
Para:
aproximadamente:
Então o ISR fica essencialmente:
angle += angleIncrement;
uma soma inteira.
Essa é a grande vantagem do ponto de vista computacional.
O acumulador inteiro não elimina automaticamente o cálculo de seno.
Precisamos transformar:
xxxxxxxxxxangle
em:
xxxxxxxxxxsin(angle)
Há várias possibilidades.
A mais clássica é usar uma LUT (Lock Up Table):
xxxxxxxxxxangle (32 bits)││ separa-se os bits mais significativos│▼índice│▼LUT seno│▼DAC
Por exemplo, uma LUT com 256 pontos (8-bits):
xxxxxxxxxxuint8_t sineTable[256];
e usamos os 8 bits mais significativos fazendo um shit right:
xxxxxxxxxxindex = angle >> 24;
Agora o ISR pode ser aproximadamente:
xangle += angleIncrement;index = angle >> 24;dac_oneshot_output_voltage(dac_handle,sineTable[index]);
Veja:
Antes, versão angle accumulator (float):
xxxxxxxxxx10 kHz│├── soma float│└── sinf()↓DAC
Agora, DDS inteiro + LUT:
xxxxxxxxxx10 kHz│├── soma inteira│├── shift│└── acesso à LUT↓DAC
A segunda abordagem é bem menos custosa computacionalmente.
Se ao invés de montar (externamente) uma LUT de 8-bits, usarmos 10-bits (1024 valores) o erro de quantizaçao angular diminui.
Cada ponto corresponderia à passos de:
O erro máximo de quantização de fase, assumindo o ponto mais próximo, seria aproximadamente metade disso, então:
A LUT de 1024 seria endereçada na forma:
xxxxxxxxxxindex = angle >> 22;porque:
Portanto:
xxxxxxxxxx32-bit phase │ │ >> 22 ▼10-bit index │ ▼LUT[0 ... 1023]Mas…. a LUT de 1024 valores de 8-bits vai ocupar 1024 bytes, enquanto a LUT de 256 valores iria ocupar apenas 256 bytes, uint32_t angle e uint32_t angleIncrement, continuariam ocupando 8 bytes.
A parte do código para sintetizar a senóide ficaria:
xxxxxxxxxxuint32_t angle;uint32_t angleIncrement;mas algo como:
xxxxxxxxxxuint8_t sineTable[1024];Assim temos:
xxxxxxxxxx ┌─────────────────────────────┐ │ 32-bit angle │ │ │ │ 10 bits 22 bits │ │ índice "fração" │ └──────┬──────────────────────┘ │ >> 22 │ ▼ ┌──────────────┐ │ LUT 1024 × 8 │ └──────┬───────┘ │ ▼ DACE a ISR ficaria praticamente reduzido a:
xxxxxxxxxxangle += angleIncrement;
uint16_t index = angle >> 22;
dac_oneshot_output_voltage( dac_handle, sineTable[index]);Isso é muito mais eficiente que:
xxxxxxxxxxangle += angle_increment;
float value = sinf(angle);
...Nesta nova técnica, se quisermos variar a frequência da senóide, simplesmente re-calculamos o valor do incremento.
Não temos que recalcular a tabela do seno.
E podemos inclusive alterar a frequência em tempo de execução:
xxxxxxxxxxphaseIncrement↑│└── variável de controle
Isso é a essência de um NCO — Numerically Controlled Oscillator.
Fernando Passold, em 14/09/2026.