neil-thomas-1euFcqLsPWA

Gerar Senóide

Problema

A idéia é criar uma pequena rotina que:

 

Solução usando Phase Accumulator

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:

  1. Determinar o novo ângulo da senóide: ϕ[n+1]=ϕ[n]+Δϕ

  2. Calculamos antes de iniciar a ISR, o valor de Δϕ. Como a freq. de amostragem é: fs=10000 Hz, Freq. a ser sintetizada é: f=440 Hz, temos então:

    Δϕ=2π44010000=0,27646 (rad) ou ≈15,84o.

  3. Isto significa que calculamos ϕ[n] sendo algo como:

  4. E depois calculamos:

    a cada 100 μs.

Detalhes extras:

  1. Calculamos a senoide usando: x[n]=sin⁡(ϕ[n]) que gera uma saída no intervalo: [−1,1]
  2. Temos que escalonar a saída para a faixa: [−1,+1]→[0,255] para alimentar o DAC de 8-bits.

 

Pinagem

ESP32-DEV-KIT-DevKitC-v4-pinout-mischianti.jpg

Observações:

  1. A placa ESP32 DevKitC v4 variação 32UE do chip não apresenta o “apêndice” superior associado com a antena Bluettoh/WiFi. Na realidade, ela apresenta um conector na parte superior direito para conexão de antena externa.
  2. Falta acrescentar filtro passa-baixas na saída do DAC.

 

Filtro RC na saída do DAC

É 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 fs=10 KHz, devemos localizar a frequência de corte abaixo da frequência de Nyquist, oiu seja, em fc=10K/2=5 KHz.

Vamos usar um simples circuito RC, um filtro passa-baixa de 1a-ordem cuja função transferência é dada por:

H(s)=K⋅ωc(s+ωc)

onde:

Note que para frequências de entrada abaixo de ωc, o ganho do filtro é dado por:

G=K⋅ωcωc=K.

O valor de ωc em Hz por ser calculado via:

ωc=2π⋅fc

E finalmente podemos relacionar fc com o valores de um circuito RC usados para sintetizar este filtro, através da equação:

fc=12πRC

onde: R se refere ao valor do resitor e C se refere ao valor do capacitor.

Supondo que R= 10 KΩ, que representaria uma carga muito leve para o DAC da placa ESP32, decobrimos que o valor do capacitor deve ser de:

C=12πRfc=12π⋅10K⋅5K=3,1831 nF

O valor comercial mais próximo é 3,9 ou 2,7 nF.

Assim, recomenda-se usar um resistor de:

 

Setup físico final

setup_ESP32_Senoide

 

Codificação

Duas abordagem de codificação são apresentadas:

 

Observações

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.

Resultados

São esperadas formas de onde como as mostradas na figura abaixo:

senoide_scope_5

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 (≈ 6 μs para ser executada). O que indica que poderíamos trabalhar com taxas de amostragem maiores e ainda assim conseguir sintetizar esta mesma senóide usando esta abordagem que calcula o sin⁡(angle) (ponto flutuante por software à cada 100 μs).

 

Espera-se um Relatório, contendo:

  1. Cópia do código implementado com comentários relevantes (apenas os mais importantes);

  2. Cópia da tela do Osciloscópio mostrando a onda senoida sendo gerada, preferencialmente acompanhada do valor da frequência medida pelo equipamento.

  3. Cópia da tela do Osciloscópio mostrando::

    1. Canal 1: onda quadrada de 10 KHz correspondendo à exceução da ISR;
    2. Informações sobre frequência e duty-cycle desta onda.
  4. Determinação do período de tempo ocupado exclusivamente com o processamento da ISR.

 

Detalhes

sin() ou sinf() é uma operação relativamente “cara” para um minicontrolador.

Não estamos falando apenas de uma multiplicação:

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 10.000× por segundo.

Uma solução seria aprimorar um pouco mais esta abordagem e usar acumular inteiro ou por fixed-point.

 

Técnica DDS inteiro + LUT

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 à 2π). Esta abordagem tentar buscar uma solução mais eficiente computacionalmente (ou menos custosa).

Representamos a fase como um número inteiro de 32-bits:

Um período inteiro da senóide seria representado pela faixa:

0≤angle<232

E continuamos fazendo algo como:

Antes desta abordagem, tínhamos que controlar a faixa do ângulo (em radianos):

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:

deixando ocorrer o overflow sem problemas, sem necessidade de um bloco if.

Mas como determinamos o incremento?

A frequência desejada continua sendo f0 e a taxa de amostragem: fs.

Então o incremento de fase no formato de 32 bits fica:

angleIncrement=f0fs⋅232

Para: f0=440 e fs=10000, temos:

angleIncrement=(44010000)⋅232

aproximadamente:

angleIncrement≈188.978.562

Então o ISR fica essencialmente:

uma soma inteira.

Essa é a grande vantagem do ponto de vista computacional.

Mas ainda precisamos calcular o seno

O acumulador inteiro não elimina automaticamente o cálculo de seno.

Precisamos transformar:

em:

Há várias possibilidades.

A mais clássica é usar uma LUT (Lock Up Table):

Por exemplo, uma LUT com 256 pontos (8-bits):

e usamos os 8 bits mais significativos fazendo um shit right:

Agora o ISR pode ser aproximadamente:

Veja:

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.

2π→1024

Cada ponto corresponderia à passos de:

360o1024=0,3515625o

O erro máximo de quantização de fase, assumindo o ponto mais próximo, seria aproximadamente metade disso, então:

±0,17578125o

A LUT de 1024 seria endereçada na forma:

porque:

32−10=22

Portanto:

Mas…. a LUT de 1024 valores de 8-bits vai ocupar 1024 bytes, enquanto a LUT de 256 valores iria ocupar apenas 256 bytes, 4× menos memória, Já as variáveies uint32_t angle e uint32_t angleIncrement, continuariam ocupando 8 bytes.

Resumo Final

A parte do código para sintetizar a senóide ficaria:

mas algo como:

Assim temos:

E a ISR ficaria praticamente reduzido a:

Isso é muito mais eficiente que:

Vantagens Extras

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:

Isso é a essência de um NCO — Numerically Controlled Oscillator.


Fernando Passold, em 14/09/2026.