Objetivos
Solução possível:
Para implementar uma task síncrona usando FreeRTOS para gerar uma onda quadrada na frequência de 10 KHz e outra task que roda a cada 100 ms mostrando o nível lógico da onda, vamos usar a IDE Eclipse e programar para a placa ESP32. Além disso, vamos usar o botão built-in para pausar e retomar o sinal.
Nota: é fácil gerar uma onda quadrada, simplesmente alternando o nível lógico de certo pino. Neste caso, supondo duty-cycle de 50%, metade do período de tempo estaremos em nível lógico alto ou biaox, o que significa que para gerar a onda quadrada com 10 KHz, metade do período corresponde à: segundos 0,1 mili-segundos 100 s. Metade deste valor corresponde à 50 micro-secundos, ou seja, devemos alternar o sinal na frequência de 20 KHz. Então necessitamos programar a task síncrona para ser interrompida na frequência de 20 KHz. Lembrar que o clock comum de uma placa ESP32 é de 80 MHz ou s s; se for considerado que o ESP32 consegue executar uma insfrução à cada ciclo de máquin ou 4 pulsos de clock, resulta no período de tempo de: 50 ns (ou frequência de 20 MHz).
Certifique-se de ter a ESP-IDF (Espressif IoT Development Framework) instalada e configurada com Eclipse IDE. Este guia assume que você já tem o ambiente configurado.
A seguir segue código completo para IDE Eclipse ou Terminal de comandos:
xxxxxxxxxx// Definindo o GPIO do LED e do botão// Pino para gerar a onda quadrada// Botão built-in// Divisor do timer (80 MHz / 16 = 5 MHz)// Frequência do timer// 50 microsegundos para 10kHzstatic xQueueHandle gpio_evt_queue = NULL;static volatile int is_running = 1; // Flag para controlar o estado do sinal// ISR do botãostatic void IRAM_ATTR gpio_isr_handler(void* arg) { uint32_t gpio_num = (uint32_t) arg; xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);}// Task para tratar a interrupção do botãostatic void button_task(void* arg) { uint32_t io_num; for(;;) { if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) { is_running = !is_running; // Alterna o estado do sinal gpio_set_level(GPIO_OUTPUT_IO, 0); // Garante que o sinal seja zero quando pausado } }}// Task para gerar a onda quadradastatic void square_wave_task(void* arg) { // Configuração do timer timer_config_t config = { .divider = TIMER_DIVIDER, .counter_dir = TIMER_COUNT_UP, .counter_en = TIMER_PAUSE, .alarm_en = TIMER_ALARM_EN, .auto_reload = TIMER_AUTORELOAD_EN, }; timer_init(TIMER_GROUP_0, TIMER_0, &config); timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0x00000000ULL); timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, TIMER_INTERVAL0_SEC * TIMER_SCALE); timer_enable_intr(TIMER_GROUP_0, TIMER_0); timer_start(TIMER_GROUP_0, TIMER_0); int level = 0; for(;;) { if (is_running) { timer_get_counter_value(TIMER_GROUP_0, TIMER_0, NULL); timer_pause(TIMER_GROUP_0, TIMER_0); timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0x00000000ULL); timer_start(TIMER_GROUP_0, TIMER_0); level = !level; gpio_set_level(GPIO_OUTPUT_IO, level); // Aguarda até o próximo alarme timer_wait_for_intr(TIMER_GROUP_0, TIMER_0); } else { vTaskDelay(pdMS_TO_TICKS(10)); // Aguarda um pouco quando pausado } }}// Task para monitorar o estado do sinalstatic void monitor_task(void* arg) { for(;;) { int level = gpio_get_level(GPIO_OUTPUT_IO); printf("Nível lógico da onda: %d\n", level); vTaskDelay(pdMS_TO_TICKS(100)); }}void app_main() { // Configura os pinos GPIO gpio_config_t io_conf; io_conf.intr_type = GPIO_INTR_NEGEDGE; // Interrupção na borda de descida io_conf.pin_bit_mask = (1ULL << GPIO_INPUT_IO); io_conf.mode = GPIO_MODE_INPUT; io_conf.pull_up_en = 1; gpio_config(&io_conf); io_conf.intr_type = GPIO_INTR_DISABLE; io_conf.pin_bit_mask = (1ULL << GPIO_OUTPUT_IO); io_conf.mode = GPIO_MODE_OUTPUT; io_conf.pull_down_en = 0; io_conf.pull_up_en = 0; gpio_config(&io_conf); // Cria a fila de eventos GPIO gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t)); gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT); gpio_isr_handler_add(GPIO_INPUT_IO, gpio_isr_handler, (void*) GPIO_INPUT_IO); // Cria as tasks xTaskCreate(button_task, "button_task", 2048, NULL, 10, NULL); xTaskCreate(square_wave_task, "square_wave_task", 2048, NULL, 10, NULL); xTaskCreate(monitor_task, "monitor_task", 2048, NULL, 10, NULL);}Configuração do GPIO:
GPIO_OUTPUT_IO é configurado como saída para gerar a onda quadrada.GPIO_INPUT_IO é configurado como entrada para o botão built-in com uma interrupção configurada na borda de descida.Task button_task:
is_running.Task square_wave_task:
Task monitor_task:
timer_wait_for_intr que espera a próxima interrupção do timer. Certifique-se de adaptar a função de acordo com a API de timers do ESP-IDF.printf no monitor serial.is_running controla este comportamento.monitor_task.Compile e carregue este código na sua ESP32 usando a IDE Eclipse configurada para ESP-IDF. A task button_task controlará o estado do sinal e a task monitor_task exibirá o nível lógico da onda.
Modificação para ativar o builtin led para indicar se onda quadrada está sendo gerada ou não:
Para modificar a task button_task para ativar o LED built-in da placa ESP32 quando a onda quadrada estiver sendo gerada, podemos fazer o seguinte:
button_task para alternar o estado do LED conforme o estado da flag is_running.A seguir segue código para Eclipse (ou terminal de comandos):
xxxxxxxxxx// Definindo o GPIO do LED, do LED built-in e do botão// Pino para gerar a onda quadrada// Botão built-in// LED built-in// Divisor do timer (80 MHz / 16 = 5 MHz)// Frequência do timer// 50 microsegundos para 10kHzstatic xQueueHandle gpio_evt_queue = NULL;static volatile int is_running = 1; // Flag para controlar o estado do sinal// ISR do botãostatic void IRAM_ATTR gpio_isr_handler(void* arg) { uint32_t gpio_num = (uint32_t) arg; xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);}// Task para tratar a interrupção do botãostatic void button_task(void* arg) { uint32_t io_num; for(;;) { if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) { is_running = !is_running; // Alterna o estado do sinal gpio_set_level(GPIO_OUTPUT_IO, 0); // Garante que o sinal seja zero quando pausado gpio_set_level(GPIO_BUILTIN_LED, is_running); // Define o estado do LED built-in } }}// Task para gerar a onda quadradastatic void square_wave_task(void* arg) { // Configuração do timer timer_config_t config = { .divider = TIMER_DIVIDER, .counter_dir = TIMER_COUNT_UP, .counter_en = TIMER_PAUSE, .alarm_en = TIMER_ALARM_EN, .auto_reload = TIMER_AUTORELOAD_EN, }; timer_init(TIMER_GROUP_0, TIMER_0, &config); timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0x00000000ULL); timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, TIMER_INTERVAL0_SEC * TIMER_SCALE); timer_enable_intr(TIMER_GROUP_0, TIMER_0); timer_start(TIMER_GROUP_0, TIMER_0); int level = 0; for(;;) { if (is_running) { timer_get_counter_value(TIMER_GROUP_0, TIMER_0, NULL); timer_pause(TIMER_GROUP_0, TIMER_0); timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0x00000000ULL); timer_start(TIMER_GROUP_0, TIMER_0); level = !level; gpio_set_level(GPIO_OUTPUT_IO, level); // Aguarda até o próximo alarme timer_wait_for_intr(TIMER_GROUP_0, TIMER_0); } else { vTaskDelay(pdMS_TO_TICKS(10)); // Aguarda um pouco quando pausado } }}// Task para monitorar o estado do sinalstatic void monitor_task(void* arg) { for(;;) { int level = gpio_get_level(GPIO_OUTPUT_IO); printf("Nível lógico da onda: %d\n", level); vTaskDelay(pdMS_TO_TICKS(100)); }}void app_main() { // Configura os pinos GPIO gpio_config_t io_conf; io_conf.intr_type = GPIO_INTR_NEGEDGE; // Interrupção na borda de descida io_conf.pin_bit_mask = (1ULL << GPIO_INPUT_IO); io_conf.mode = GPIO_MODE_INPUT; io_conf.pull_up_en = 1; gpio_config(&io_conf); io_conf.intr_type = GPIO_INTR_DISABLE; io_conf.pin_bit_mask = (1ULL << GPIO_OUTPUT_IO); io_conf.mode = GPIO_MODE_OUTPUT; io_conf.pull_down_en = 0; io_conf.pull_up_en = 0; gpio_config(&io_conf); // Configura o LED built-in como saída io_conf.pin_bit_mask = (1ULL << GPIO_BUILTIN_LED); io_conf.mode = GPIO_MODE_OUTPUT; io_conf.pull_down_en = 0; io_conf.pull_up_en = 0; gpio_config(&io_conf); // Cria a fila de eventos GPIO gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t)); gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT); gpio_isr_handler_add(GPIO_INPUT_IO, gpio_isr_handler, (void*) GPIO_INPUT_IO); // Cria as tasks xTaskCreate(button_task, "button_task", 2048, NULL, 10, NULL); xTaskCreate(square_wave_task, "square_wave_task", 2048, NULL, 10, NULL); xTaskCreate(monitor_task, "monitor_task", 2048, NULL, 10, NULL);}Definição do LED Built-in:
#define GPIO_BUILTIN_LED 2: Adicionada definição do pino do LED built-in.Configuração do LED Built-in:
GPIO_BUILTIN_LED como saída no app_main.Modificação na button_task:
gpio_set_level(GPIO_BUILTIN_LED, is_running) para definir o estado do LED built-in conforme o estado de is_running.Agora, quando a onda quadrada estiver sendo gerada (is_running for verdadeiro), o LED built-in estará aceso. Quando a geração da onda estiver pausada (is_running for falso), o LED built-in estará apagado.
Código anterior levemente alterado para interface IDE do Arduino:
Claro! A seguir, vou adaptar o código anterior para ser usado na interface IDE do Arduino com a placa ESP32. O código irá gerar uma onda quadrada com frequência de 10 kHz, monitorar o botão built-in para pausar ou retomar a geração da onda e ativar o LED built-in quando a onda estiver sendo gerada.
xxxxxxxxxx// Definindo o GPIO do LED, do LED built-in e do botão// Pino para gerar a onda quadrada (LED built-in)// Botão built-involatile int is_running = 1; // Flag para controlar o estado do sinalvolatile int level = 0; // Nível lógico da ondahw_timer_t *timer = NULL; // Ponteiro para o timer// ISR do botãovoid IRAM_ATTR button_isr() { is_running = !is_running; // Alterna o estado do sinal digitalWrite(GPIO_OUTPUT_IO, LOW); // Garante que o sinal seja zero quando pausado digitalWrite(LED_BUILTIN, is_running); // Define o estado do LED built-in}// ISR do timervoid IRAM_ATTR onTimer() { if (is_running) { level = !level; digitalWrite(GPIO_OUTPUT_IO, level); }}void setup() { // Configura os pinos GPIO pinMode(GPIO_OUTPUT_IO, OUTPUT); // Configura o pino de saída pinMode(GPIO_INPUT_IO, INPUT_PULLUP); // Configura o botão com pull-up interno pinMode(LED_BUILTIN, OUTPUT); // Configura o LED built-in como saída // Configura a interrupção do botão attachInterrupt(digitalPinToInterrupt(GPIO_INPUT_IO), button_isr, FALLING); // Configura o timer timer = timerBegin(0, 80, true); // Timer 0, prescaler de 80 para 1 MHz timerAttachInterrupt(timer, &onTimer, true); timerAlarmWrite(timer, 50, true); // 50 microsegundos para 10 kHz timerAlarmEnable(timer); // Ativa o alarme do timer // Inicializa o serial para monitorar o nível lógico da onda Serial.begin(115200);}void loop() { static unsigned long lastMillis = 0; // Monitoramento do estado do sinal a cada 100 ms if (millis() - lastMillis >= 100) { lastMillis = millis(); int current_level = digitalRead(GPIO_OUTPUT_IO); Serial.printf("Nível lógico da onda: %d\n", current_level); }}Configuração dos Pinos GPIO:
GPIO_OUTPUT_IO é configurado como saída para gerar a onda quadrada.GPIO_INPUT_IO é configurado como entrada para o botão built-in com pull-up interno.LED_BUILTIN (pino 2) é configurado como saída para indicar o estado da geração da onda.Interrupção do Botão:
button_isr alterna a flag is_running, define o sinal para baixo (LOW) e ajusta o estado do LED built-in.Interrupção do Timer:
onTimer alterna o nível lógico do pino de saída a cada interrupção do timer se is_running estiver verdadeiro.Setup:
Loop Principal:
Essa adaptação deve funcionar de maneira semelhante ao código original, agora dentro da interface do Arduino, tornando o desenvolvimento e a depuração mais acessíveis para quem está acostumado com esse ambiente.
Atenção: Falta testar estas rotinas. Parece que usam versões atrasadas das API's da Espressif.
🌊 Fernando Passold 📬 ,