Este projeto está relacionado com Gerar Senóide.
Versão usando Arduino-IDE…
Arqutetura usada:
ESP32│┌───────┴────────┐│ │GPTimer FreeRTOS│ │10 kHz ISR tarefa do botão│ │▼ ▼DAC GPIO25 GPIO4 / botão│▼senoide 440 Hz
A versão Arduino fica conceitualmente mais simples (em comparação com a versão usando ESP-IDF), porque o Arduino-ESP32 já fornece os blocos: setup()/loop() e abstrai boa parte da inicialização.
A lógica do programa vai ser algo como: gerar senoide de 440 Hz, amostrada à 10 kHz, usando DAC no GPIO25, botão no GPIO4 para ligar/desligar e usar um pino de GPIO de DEBUG para observar a duração do processamento no osciloscópio.
Segue cópia de:
~/Documents/Arduino/Senoide2/Senoide2.ino:
xxxxxxxxxx/** Gera Senoide - versão: Angle Accumulator** Geração direta de uma senoide usando acumulador de fase.** Frequência da senoide: 440 Hz* Frequência de amostragem: 10 kHz* DAC1: GPIO25 - 8 bits** O angulo de senóide é incrementado a cada interrupção do timer* angle = angle + angle_increment** e o valor instantâneo da senoide é calculado por:* sin(angle)** Versão para Arduino-IDE / Arduino-ESP32*/
/* ============================================================* Constantes* ============================================================ */
// DAC1 do ESP32 clássico// Deveria ser Botão "Boot" na DevKitC_v4// GPIO para medir duração da ISR
// Frequência de amostragem// Frequência da senoide// 1 / 10000 = 100 us
// Acrescentado LED para avisar on/off senóide
/* ============================================================* Variáveis globais* ============================================================ */
static volatile float angle = 0.0f;static float angle_increment;static volatile bool generateSine = false;static gptimer_handle_t timer_handler = NULL;
volatile unsigned int TickCount = 0; // variável global que muda dentro da ISRunsigned long LastTime = 0;unsigned long Now = 0;
// Variáveis para controle do estado do botãoint estadoBotaoAtual;int estadoBotaoAnterior = HIGH; // Começa em HIGH porque é ativo baixo// Variáveis para o Debounce sem travar o código (sem dealy())unsigned long ultimoTempoDebounce = 0;unsigned long tempoDebounce = 50;
/* ============================================================* Callback do GPTimer* ISR Executada a cada 100 us = 10 kHz.* ============================================================ */
static bool IRAM_ATTR onTimer( gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx) { (void)timer; (void)edata; (void)user_ctx;
// incrementa TickCount TickCount++;
// Identifica o início do processamento da ISR digitalWrite(DEBUG_PIN, HIGH);
if (generateSine) { // Calcula o valor instantâneo da senoide float sine_value = sinf(angle);
// Converte [-1,+1] para [0,255] uint8_t dac_value = (uint8_t)((sine_value + 1.0f) * 127.5f);
// Escreve no DAC dacWrite(DAC_PIN, dac_value);
// Avança a fase angle += angle_increment;
// Mantém a fase em [0,2*pi) if (angle >= 2.0f * M_PI) { angle -= 2.0f * M_PI; } } else { // Senoide desligada: // mantém o DAC no ponto médio dacWrite(DAC_PIN, 128); }
// Marca o final do processamento da ISR digitalWrite(DEBUG_PIN, LOW);
// Não solicita troca de contexto return false;}
/* ============================================================* Configuração do GPTimer* ============================================================ */
static void setupTimer(void) { // Resolução do timer: 1 MHz // 1 tick = 1 us gptimer_config_t timer_config = { .clk_src = GPTIMER_CLK_SRC_DEFAULT, .direction = GPTIMER_COUNT_UP, .resolution_hz = 1000000 };
// Cria o timer ESP_ERROR_CHECK( gptimer_new_timer( &timer_config, &timer_handler));
// Identifica/Registra o callback gptimer_event_callbacks_t callbacks = { .on_alarm = onTimer };
ESP_ERROR_CHECK( gptimer_register_event_callbacks( timer_handler, &callbacks, NULL));
// "Alarme" periódico de 100 us /* Antes gptimer_alarm_config_t alarm_config = { .reload_count = 0, .alarm_count = SAMPLE_PERIOD_US, .flags.auto_reload_on_alarm = true }; Erro de compilação associado com .flags.XXX */ gptimer_alarm_config_t alarm_config = { .alarm_count = SAMPLE_PERIOD_US, .reload_count = 0 }; alarm_config.flags.auto_reload_on_alarm = true;
ESP_ERROR_CHECK( gptimer_set_alarm_action( timer_handler, &alarm_config));
// Habilita o timer ESP_ERROR_CHECK( gptimer_enable(timer_handler));
// Inicia o timer ESP_ERROR_CHECK( gptimer_start(timer_handler));}
/* ============================================================* UpdateStatus()* Indica no LED_PIN e porta serial, estado atual de geração senoidal* ============================================================ */
void UpdateStatus(){ digitalWrite(LED_PIN, generateSine); Serial.print("Senoide: "); if (generateSine){ Serial.println("ON"); } else { Serial.println("Off"); }}
/* ============================================================* setup()* ============================================================ */
void setup() { // Calcula o incremento de fase: // Δφ = 2π f / fs // Para 440 Hz e 10 kHz: // Δφ ≈ 0.27646 rad angle_increment = 2.0f * M_PI * FREQUENCY_HZ / SAMPLE_RATE_HZ; angle = 0.0f; // valor inicial
Serial.begin(115200); // Inicializa a comunicação serial a 115200 bps Serial.println(" "); Serial.println("..... Geraddor Senoidal ....."); Serial.print("> angle_increment = "); Serial.println(angle_increment);
// Configura botão pinMode(BUTTON_PIN, INPUT_PULLUP);
// Configura GPIO de DEBUG pinMode(DEBUG_PIN, OUTPUT); digitalWrite(DEBUG_PIN, LOW);
// Configura DAC // Coloca inicialmente no ponto médio Serial.println("> DAC1 configurado para ponto central (128)"); dacWrite(DAC_PIN, 128);
pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, generateSine);
// Configura e inicia o GPTimer (a ISR) UpdateStatus(); delay(100); // apenas para dar um tempo entre msg e execução da ISR LastTime = millis(); setupTimer(); // daqui para frente a cada 100us está sendo chamada a ISR}
/* ============================================================* loop()** No Arduino, loop() substitui a necessidade de uma tarefa* FreeRTOS separada para o botão.* ============================================================ */
void loop() { int leitura = digitalRead(BUTTON_PIN); // A seção abaixo mostra a cada segundo no terminal serial // Quantas vezes a ISR foi chamada no ultimo segundo // Isto permite saber se a ISR está rodando bem ou não Now = millis(); if (Now - LastTime > 1000){ Serial.print("TickCount = "); Serial.println(TickCount); LastTime = Now; TickCount = 0; } // Le botão // Se houve mudança por ruído ou clique, ativa o temporizador if (leitura != estadoBotaoAnterior) { ultimoTempoDebounce = millis(); } // Se passou o tempo do debounce, valida a leitura if ((millis() - ultimoTempoDebounce) > tempoDebounce) { // Se o estado do botão realmente mudou if (leitura != estadoBotaoAtual) { estadoBotaoAtual = leitura; // Só entra aqui se o novo estado for LOW (foi pressionado neste instante) if (estadoBotaoAtual == LOW) { generateSine = !generateSine; // Alterna o estado de generateSine UpdateStatus(); } } } estadoBotaoAnterior = leitura;}Se tudo correu certo, a placa deve gerar as seguintes mensagens no terminal serial (depois que o código foi baixado e o botão de “EN” (ou “Reset”) foi pressionado:
xxxxxxxxxxrst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)configsip: 0, SPIWP:0xeeclk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00mode:DIO, clock div:1load:0x3fff0030,len:4876ho 0 tail 12 room 4load:0x40078000,len:16532load:0x40080400,len:3500entry 0x400805b4..... Geraddor Senoidal .....> angle_increment = 0.28> DAC1 configurado para ponto central (128)Senoide: OffTickCount = 10005TickCount = 10009TickCount = 10009TickCount = 10009TickCount = 10009TickCount = 10009TickCount = 10009TickCount = 10009TickCount = 10009TickCount = 10009TickCount = 10009TickCount = 10009TickCount = 10009TickCount = 10009Senoide: ONTickCount = 10009TickCount = 10009TickCount = 10009Senoide: OffTickCount = 10009TickCount = 10009Senoide: ONTickCount = 10009TickCount = 10009TickCount = 10009TickCount = 10009TickCount = 10009TickCount = 10009TickCount = 10009
Note que o botão “Boot” presente na placa do ESP32 perminte alternar entre gerar e não gerar a senóide e que o “status” da placa é mostrado cada vez que o botão pressionado. E note ainda que a cada 1 segundo, aproximadamente, é mostrado o número de vezes que a ISR foi executada. Isto permite avaliar (sem necessidade de um osciloscópio) se a rotina de ISR está sendo executada sem problemas.
Na versão ESP-IDF, foi criada explicitamente uma tarefa FreeRTOS:
xTaskCreate( buttonTask, ...);e essa tarefa executava:
while (1) { ... vTaskDelay(pdMS_TO_TICKS(10));}Na versão Arduino, isso vira simplesmente:
void loop(){ if (digitalRead(BUTTON_PIN) == LOW) { generateSine = true; } else { generateSine = false; }
delay(10);}Ou seja, loop() seria equivalente a uma tarefa de nível superior que fica fazendo esse trabalho. O GPTimer continua funcionando independentemente e interrompe o processador a cada 100 µs.
Queremos ter uma idéia real do tempo de processamento gasto com a rotina ISR que calcula/gera os pontos da senóide.
A parte:
digitalWrite(DEBUG_PIN, HIGH);no início da ISR/callback e:
digitalWrite(DEBUG_PIN, LOW);no final produz exatamente um sinal no pino de DEBUG similar à:
DEBUG
┌────────┐ ┌────────┐ │ │ │ │───────┘ └──────────────────┘ └──── <------> T_ISR
<------------- 100 µs -------------->Portanto, no osciloscópio você poderá medir diretamente o tempo de execução da callback e seu duty-cycle.
Uma observação importante: para essa primeira versão Arduino foi mantida sinf() e o DAC dentro da callback propositalmente. Assim, a comparação entre a versão ESP-IDF e a versão Arduino fica interessante: você estará usando essencialmente o mesmo mecanismo de GPTimer e o mesmo algoritmo, mas com as APIs de alto nível do Arduino (digitalWrite(), digitalRead(), dacWrite(), delay()).
Sugestão: podemos fazer uma segunda versão Arduino mais "crua", usando acesso direto aos registradores/GPIO e compará-la no osciloscópio. Isso permitiria mostrar uma coisa interessante: quanto do tempo medido é realmente sinf() e quanto é overhead das funções Arduino.
<< Voltar para tópico de origem: Gerar Senóide | Ver Versão usado ESP-IDF Puro.
Fernando Passold, em 14/09/2026.