All checks were successful
ESP32 Build & Release / build (push) Successful in 1m18s
272 lines
7.4 KiB
C
272 lines
7.4 KiB
C
/*
|
|
* 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 "nvs_flash.h"
|
|
#include "nvs.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 audio_profile_t s_profile = AUDIO_PROFILE_CHIPTUNE;
|
|
|
|
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)
|
|
{
|
|
/* Load saved profile from NVS */
|
|
nvs_handle_t h;
|
|
if (nvs_open("mtg_cfg", NVS_READONLY, &h) == ESP_OK) {
|
|
uint8_t prof = 0;
|
|
if (nvs_get_u8(h, "audio_prof", &prof) == ESP_OK && prof <= AUDIO_PROFILE_SILENT) {
|
|
s_profile = (audio_profile_t)prof;
|
|
}
|
|
nvs_close(h);
|
|
}
|
|
|
|
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 (profile: %s)", HW_BUZZER_GPIO, buzzer_get_profile_name(s_profile));
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t buzzer_start(uint32_t freq_hz, uint32_t duty_percent)
|
|
{
|
|
if (s_profile == AUDIO_PROFILE_SILENT) {
|
|
return ESP_OK;
|
|
}
|
|
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_profile == AUDIO_PROFILE_SILENT) {
|
|
return ESP_OK;
|
|
}
|
|
if (s_melody_playing) {
|
|
return ESP_OK; /* Don't disrupt ongoing background chiptune melody */
|
|
}
|
|
uint32_t duty = (s_profile == AUDIO_PROFILE_TACTILE) ? 10 : 30;
|
|
if (s_profile == AUDIO_PROFILE_TACTILE && duration_ms > 20) {
|
|
duration_ms = 20;
|
|
}
|
|
esp_err_t ret = buzzer_start(freq_hz, duty);
|
|
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_profile != AUDIO_PROFILE_CHIPTUNE) {
|
|
return ESP_OK;
|
|
}
|
|
if (s_sound_queue != NULL) {
|
|
xQueueSend(s_sound_queue, &sound, 0);
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
void buzzer_set_profile(audio_profile_t profile)
|
|
{
|
|
s_profile = profile;
|
|
nvs_handle_t h;
|
|
if (nvs_open("mtg_cfg", NVS_READWRITE, &h) == ESP_OK) {
|
|
nvs_set_u8(h, "audio_prof", (uint8_t)profile);
|
|
nvs_commit(h);
|
|
nvs_close(h);
|
|
}
|
|
ESP_LOGI(TAG, "Audio profile set to: %s", buzzer_get_profile_name(profile));
|
|
}
|
|
|
|
audio_profile_t buzzer_get_profile(void)
|
|
{
|
|
return s_profile;
|
|
}
|
|
|
|
const char *buzzer_get_profile_name(audio_profile_t profile)
|
|
{
|
|
switch (profile) {
|
|
case AUDIO_PROFILE_CHIPTUNE: return "CHIPTUNE";
|
|
case AUDIO_PROFILE_TACTILE: return "TACTILE";
|
|
case AUDIO_PROFILE_SILENT: return "SILENT";
|
|
default: return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
void buzzer_sound_life_gain(void)
|
|
{
|
|
if (s_profile == AUDIO_PROFILE_SILENT) return;
|
|
if (s_profile == AUDIO_PROFILE_TACTILE) {
|
|
buzzer_chirp(NOTE_C6, 10);
|
|
return;
|
|
}
|
|
/* Chiptune: ascending two-tone */
|
|
buzzer_start(NOTE_G5, 25);
|
|
vTaskDelay(pdMS_TO_TICKS(22));
|
|
buzzer_start(NOTE_C6, 25);
|
|
vTaskDelay(pdMS_TO_TICKS(35));
|
|
buzzer_stop();
|
|
}
|
|
|
|
void buzzer_sound_life_loss(void)
|
|
{
|
|
if (s_profile == AUDIO_PROFILE_SILENT) return;
|
|
if (s_profile == AUDIO_PROFILE_TACTILE) {
|
|
buzzer_chirp(NOTE_C4, 12);
|
|
return;
|
|
}
|
|
/* Chiptune: descending two-tone */
|
|
buzzer_start(NOTE_E4, 25);
|
|
vTaskDelay(pdMS_TO_TICKS(25));
|
|
buzzer_start(NOTE_C4, 25);
|
|
vTaskDelay(pdMS_TO_TICKS(40));
|
|
buzzer_stop();
|
|
}
|
|
|
|
void buzzer_sound_rfid_tap(void)
|
|
{
|
|
if (s_profile == AUDIO_PROFILE_SILENT) return;
|
|
if (s_profile == AUDIO_PROFILE_TACTILE) {
|
|
buzzer_chirp(NOTE_A5, 20);
|
|
return;
|
|
}
|
|
/* Chiptune: bright recognition chime */
|
|
buzzer_start(NOTE_A5, 25);
|
|
vTaskDelay(pdMS_TO_TICKS(28));
|
|
buzzer_start(NOTE_E6, 25);
|
|
vTaskDelay(pdMS_TO_TICKS(45));
|
|
buzzer_stop();
|
|
}
|
|
|
|
void buzzer_sound_tick(void)
|
|
{
|
|
if (s_profile == AUDIO_PROFILE_SILENT) return;
|
|
if (s_profile == AUDIO_PROFILE_TACTILE) {
|
|
buzzer_start(NOTE_D6, 8);
|
|
vTaskDelay(pdMS_TO_TICKS(6));
|
|
buzzer_stop();
|
|
return;
|
|
}
|
|
buzzer_chirp(NOTE_C5, 12);
|
|
} |