/* * MTG RFID Companion - Piezo buzzer driver (LEDC PWM) */ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" #include "driver/ledc.h" #include "hw_pins.h" #include "buzzer.h" #include "freertos/queue.h" static const char *TAG = "buzzer"; #define BUZZER_SPEED_MODE LEDC_LOW_SPEED_MODE #define BUZZER_TIMER_NUM LEDC_TIMER_0 #define BUZZER_CHANNEL LEDC_CHANNEL_0 static QueueHandle_t s_sound_queue = NULL; static volatile bool s_melody_playing = false; static const buzzer_note_t MELODY_VICTORY[] = { {NOTE_C5, 90}, {NOTE_C5, 90}, {NOTE_C5, 90}, {NOTE_C5, 220}, {NOTE_GS4, 220}, {NOTE_AS4, 220}, {NOTE_C5, 140}, {NOTE_REST, 35}, {NOTE_AS4, 90}, {NOTE_C5, 400} }; static const buzzer_note_t MELODY_LEGENDARY[] = { {NOTE_G4, 65}, {NOTE_C5, 65}, {NOTE_E5, 65}, {NOTE_G5, 90}, {NOTE_C6, 280} }; static const buzzer_note_t MELODY_LOSS[] = { {NOTE_D4, 130}, {NOTE_CS4, 130}, {NOTE_C4, 130}, {NOTE_B3, 260}, {NOTE_REST, 35}, {NOTE_AS3, 400} }; esp_err_t buzzer_play_notes(const buzzer_note_t *notes, size_t count) { if (notes == NULL || count == 0) return ESP_OK; for (size_t i = 0; i < count; i++) { if (notes[i].freq_hz > 0) { buzzer_start(notes[i].freq_hz, 30); vTaskDelay(pdMS_TO_TICKS(notes[i].duration_ms)); buzzer_stop(); } else { vTaskDelay(pdMS_TO_TICKS(notes[i].duration_ms)); } /* 12ms articulation gap between notes for distinct 8-bit chiptune feel */ vTaskDelay(pdMS_TO_TICKS(12)); } return ESP_OK; } static void buzzer_task(void *arg) { (void)arg; buzzer_sound_t sound; while (1) { if (xQueueReceive(s_sound_queue, &sound, portMAX_DELAY) == pdTRUE) { s_melody_playing = true; switch (sound) { case BUZZER_SOUND_VICTORY: buzzer_play_notes(MELODY_VICTORY, sizeof(MELODY_VICTORY) / sizeof(MELODY_VICTORY[0])); break; case BUZZER_SOUND_LEGENDARY: buzzer_play_notes(MELODY_LEGENDARY, sizeof(MELODY_LEGENDARY) / sizeof(MELODY_LEGENDARY[0])); break; case BUZZER_SOUND_LOSS: buzzer_play_notes(MELODY_LOSS, sizeof(MELODY_LOSS) / sizeof(MELODY_LOSS[0])); break; default: break; } s_melody_playing = false; } } } esp_err_t buzzer_init(void) { ledc_timer_config_t timer_cfg = { .speed_mode = BUZZER_SPEED_MODE, .timer_num = BUZZER_TIMER_NUM, .duty_resolution = LEDC_TIMER_13_BIT, .freq_hz = 2000, .clk_cfg = LEDC_AUTO_CLK, }; esp_err_t ret = ledc_timer_config(&timer_cfg); if (ret != ESP_OK) { return ret; } ledc_channel_config_t chan_cfg = { .gpio_num = HW_BUZZER_GPIO, .speed_mode = BUZZER_SPEED_MODE, .channel = BUZZER_CHANNEL, .timer_sel = BUZZER_TIMER_NUM, .duty = 0, .hpoint = 0, }; ret = ledc_channel_config(&chan_cfg); if (ret != ESP_OK) { return ret; } if (s_sound_queue == NULL) { s_sound_queue = xQueueCreate(4, sizeof(buzzer_sound_t)); xTaskCreate(buzzer_task, "buzzer_task", 2048, NULL, 3, NULL); } ESP_LOGI(TAG, "buzzer ready on GPIO %d (chiptune synth active)", HW_BUZZER_GPIO); return ESP_OK; } esp_err_t buzzer_start(uint32_t freq_hz, uint32_t duty_percent) { if (freq_hz == 0) { return buzzer_stop(); } uint32_t duty = ((uint32_t)1 << 13) * duty_percent / 100u; if (duty_percent >= 100) { duty = ((uint32_t)1 << 13); } esp_err_t ret = ledc_set_freq(BUZZER_SPEED_MODE, BUZZER_TIMER_NUM, freq_hz); if (ret != ESP_OK) { return ret; } ret = ledc_set_duty(BUZZER_SPEED_MODE, BUZZER_CHANNEL, duty); if (ret != ESP_OK) { return ret; } return ledc_update_duty(BUZZER_SPEED_MODE, BUZZER_CHANNEL); } esp_err_t buzzer_stop(void) { ledc_set_duty(BUZZER_SPEED_MODE, BUZZER_CHANNEL, 0); return ledc_update_duty(BUZZER_SPEED_MODE, BUZZER_CHANNEL); } esp_err_t buzzer_chirp(uint32_t freq_hz, uint32_t duration_ms) { if (s_melody_playing) { return ESP_OK; /* Don't disrupt ongoing background chiptune melody */ } esp_err_t ret = buzzer_start(freq_hz, 30); if (ret != ESP_OK) { return ret; } vTaskDelay(pdMS_TO_TICKS(duration_ms)); return buzzer_stop(); } esp_err_t buzzer_play_sound(buzzer_sound_t sound) { if (s_sound_queue != NULL) { xQueueSend(s_sound_queue, &sound, 0); } return ESP_OK; }