249 lines
7.6 KiB
C++
249 lines
7.6 KiB
C++
#include "my_buzzer.h"
|
|
#include <FS.h>
|
|
#include <LittleFS.h>
|
|
|
|
#include <anyrtttl.h>
|
|
#include <binrtttl.h>
|
|
#include <pitches.h>
|
|
|
|
#include "JsonConstrain.h"
|
|
#include "global.h"
|
|
|
|
const char* DEFAULT_MELODY = "Ack:d=16,o=5,b=200:c,e,g";
|
|
|
|
// serial debugging enabled
|
|
//#define ANY_RTTTL_INFO
|
|
|
|
static const char* tag = "buzzer";
|
|
|
|
BUZZ_TUNE buzzTune[TUNE_MAX_COUNT];
|
|
int8_t buzzPin;
|
|
int8_t buzzerChannel = -1; // Store the LEDC channel used by the buzzer
|
|
|
|
// File-scope state for tune management (minimal overhead)
|
|
static volatile int prev_tune = -1;
|
|
static volatile bool buzzer_busy = false;
|
|
|
|
// Optimized tone functions - minimal overhead, no logging
|
|
void buzzerTone(uint8_t pin, unsigned int frequency, unsigned long duration) {
|
|
if (buzzerChannel >= 0 && frequency > 0) {
|
|
ledcWriteTone(buzzerChannel, frequency);
|
|
}
|
|
}
|
|
|
|
void buzzerNoTone(uint8_t pin) {
|
|
if (buzzerChannel >= 0) {
|
|
ledcWrite(buzzerChannel, 0);
|
|
}
|
|
}
|
|
|
|
void Init_Buzzer(int8_t pin, const char* configFile, int8_t channel)
|
|
{
|
|
buzzPin = pin;
|
|
if(buzzPin >= 0){
|
|
pinMode(buzzPin, OUTPUT);
|
|
|
|
// If channel is not provided, find an unused one
|
|
if (channel < 0) {
|
|
buzzerChannel = findUnusedLedcChannel();
|
|
if (buzzerChannel < 0) {
|
|
ESP_LOGE(tag, "No available LEDC channel for buzzer");
|
|
return;
|
|
}
|
|
} else {
|
|
// Use the provided channel and mark it as used
|
|
extern bool markLedcChannelUsed(int ch); // Function from global.cpp
|
|
if (markLedcChannelUsed(channel)) {
|
|
buzzerChannel = channel;
|
|
} else {
|
|
ESP_LOGE(tag, "Requested channel %d is already in use, finding alternative", channel);
|
|
buzzerChannel = findUnusedLedcChannel();
|
|
if (buzzerChannel < 0) {
|
|
ESP_LOGE(tag, "No available LEDC channel for buzzer");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set up the channel for the buzzer with proper audio frequency range
|
|
ledcSetup(buzzerChannel, 2000, 10); // 2000 Hz base, 10-bit resolution for better frequency range
|
|
ledcAttachPin(buzzPin, buzzerChannel);
|
|
|
|
// Test the channel is working
|
|
ESP_LOGI(tag, "Testing buzzer channel %d...", buzzerChannel);
|
|
ledcWriteTone(buzzerChannel, 1000); // Test tone
|
|
delay(100);
|
|
ledcWrite(buzzerChannel, 0); // Stop test tone
|
|
|
|
// Set custom tone functions for anyrtttl
|
|
anyrtttl::setToneFunction(buzzerTone);
|
|
anyrtttl::setNoToneFunction(buzzerNoTone);
|
|
|
|
ESP_LOGI(tag, "Buzzer hardware initialized on pin %d using LEDC channel %d", buzzPin, buzzerChannel);
|
|
}
|
|
|
|
Buzzer_Load_Tunes(configFile); // Load Tunes
|
|
ESP_LOGI(tag, "Buzzer initialized on pin %d, channel %d", buzzPin, buzzerChannel);
|
|
}
|
|
|
|
int8_t Buzzer_Get_Channel() {
|
|
return buzzerChannel;
|
|
}
|
|
|
|
void Buzzer_Play_Tune(TUNE_TYPE tune, bool async, bool hasPriority)
|
|
{
|
|
// Fast path checks - minimal overhead
|
|
if (buzzPin < 0 || buzzerChannel < 0) {
|
|
ESP_LOGW(tag, "Buzzer not initialized - pin:%d, channel:%d", buzzPin, buzzerChannel);
|
|
return;
|
|
}
|
|
if (tune < 0 || tune >= TUNE_MAX_COUNT) {
|
|
ESP_LOGW(tag, "Invalid tune index: %d (max: %d)", (int)tune, TUNE_MAX_COUNT);
|
|
return;
|
|
}
|
|
|
|
// Direct reference to avoid String copying
|
|
const String& melody = buzzTune[tune].melody;
|
|
if (melody.isEmpty()) {
|
|
ESP_LOGW(tag, "Empty melody for tune %d", (int)tune);
|
|
return;
|
|
}
|
|
|
|
ESP_LOGI(tag, "Playing tune %d: %s (async=%d, priority=%d)", (int)tune, melody.c_str(), async, hasPriority);
|
|
|
|
// Simple atomic check for thread safety without mutex overhead
|
|
if (buzzer_busy && !hasPriority) {
|
|
ESP_LOGD(tag, "Buzzer busy, skipping tune %d", (int)tune);
|
|
return;
|
|
}
|
|
|
|
// Async mode: minimal state management
|
|
if (async) {
|
|
bool playing = anyrtttl::nonblocking::isPlaying();
|
|
if (hasPriority && playing) {
|
|
ESP_LOGD(tag, "Stopping current tune for priority tune %d", (int)tune);
|
|
anyrtttl::nonblocking::stop();
|
|
playing = false;
|
|
}
|
|
if (!playing || prev_tune != tune) {
|
|
ESP_LOGI(tag, "Starting async tune %d", (int)tune);
|
|
anyrtttl::nonblocking::begin(buzzPin, melody.c_str());
|
|
prev_tune = tune;
|
|
}
|
|
anyrtttl::nonblocking::play();
|
|
return;
|
|
}
|
|
|
|
// Blocking mode: minimal cycles with yield for multitasking
|
|
ESP_LOGI(tag, "Playing blocking tune %d, cycles=%d", (int)tune, buzzTune[tune].cycles);
|
|
buzzer_busy = true;
|
|
const int cycles = buzzTune[tune].cycles;
|
|
const int pause_ms = buzzTune[tune].pause;
|
|
|
|
for (int c = 0; c < cycles; ++c) {
|
|
anyrtttl::blocking::play(buzzPin, melody.c_str());
|
|
if (pause_ms > 0 && c + 1 < cycles) {
|
|
delay(pause_ms);
|
|
}
|
|
yield(); // Allow other tasks to run
|
|
}
|
|
|
|
prev_tune = tune;
|
|
buzzer_busy = false;
|
|
ESP_LOGI(tag, "Finished playing tune %d", (int)tune);
|
|
}
|
|
|
|
// Optimized beep function - minimal overhead
|
|
void Buzzer_Beep(int mSecs, int freq)
|
|
{
|
|
if (buzzPin < 0 || buzzerChannel < 0) return;
|
|
|
|
ledcWriteTone(buzzerChannel, freq);
|
|
delay(mSecs);
|
|
ledcWrite(buzzerChannel, 0);
|
|
}
|
|
|
|
// Test function to verify buzzer functionality
|
|
void Buzzer_Test() {
|
|
if (buzzPin < 0 || buzzerChannel < 0) {
|
|
ESP_LOGE(tag, "Cannot test buzzer - not initialized");
|
|
return;
|
|
}
|
|
|
|
ESP_LOGI(tag, "Testing buzzer...");
|
|
|
|
// Test direct LEDC control
|
|
ESP_LOGI(tag, "Test 1: Direct LEDC tones");
|
|
for (int freq = 500; freq <= 2000; freq += 500) {
|
|
ESP_LOGI(tag, "Playing %d Hz", freq);
|
|
ledcWriteTone(buzzerChannel, freq);
|
|
delay(200);
|
|
ledcWrite(buzzerChannel, 0);
|
|
delay(100);
|
|
}
|
|
|
|
// Test custom tone functions
|
|
ESP_LOGI(tag, "Test 2: Custom tone functions");
|
|
buzzerTone(buzzPin, 1000, 500);
|
|
delay(500);
|
|
buzzerNoTone(buzzPin);
|
|
|
|
// Test anyrtttl with a simple melody
|
|
ESP_LOGI(tag, "Test 3: anyrtttl blocking play");
|
|
anyrtttl::blocking::play(buzzPin, DEFAULT_MELODY);
|
|
|
|
ESP_LOGI(tag, "Buzzer test complete");
|
|
}
|
|
|
|
// Optimized tune loading - minimal memory allocation
|
|
void Buzzer_Load_Tunes(const char* tunesPath){
|
|
ESP_LOGI(tag, "Loading tunes from: %s", tunesPath);
|
|
File file = LittleFS.open(tunesPath);
|
|
if (!file) {
|
|
ESP_LOGW(tag, "Could not open %s, using default tune", tunesPath);
|
|
// Set default tune only at index 0
|
|
buzzTune[0].cycles = 1;
|
|
buzzTune[0].pause = 0;
|
|
buzzTune[0].melody = DEFAULT_MELODY;
|
|
ESP_LOGI(tag, "Loaded default tune at index 0: %s", DEFAULT_MELODY);
|
|
return;
|
|
}
|
|
|
|
// Use smaller JSON document for memory efficiency
|
|
JsonDocument doc;
|
|
DeserializationError error = deserializeJson(doc, file);
|
|
file.close();
|
|
|
|
if(error){
|
|
ESP_LOGE(tag, "JSON parse error: %s", error.c_str());
|
|
// Set default tune on error
|
|
buzzTune[0].cycles = 1;
|
|
buzzTune[0].pause = 0;
|
|
buzzTune[0].melody = DEFAULT_MELODY;
|
|
ESP_LOGI(tag, "Loaded default tune due to JSON error: %s", DEFAULT_MELODY);
|
|
return;
|
|
}
|
|
|
|
JsonArray tuneJsonArray = doc["tunes"];
|
|
if(!tuneJsonArray.isNull()){
|
|
int tuneIndex = 0;
|
|
for(JsonObject obj : tuneJsonArray){
|
|
if(tuneIndex >= TUNE_MAX_COUNT) break;
|
|
buzzTune[tuneIndex].cycles = jsonConstrain<int>(tag, obj, "cycles", 1, 100, 1);
|
|
buzzTune[tuneIndex].pause = jsonConstrain<int>(tag, obj, "pause", 0, 100, 0);
|
|
buzzTune[tuneIndex].melody = jsonConstrainString(tag, obj, "tune", DEFAULT_MELODY);
|
|
ESP_LOGI(tag, "Loaded tune %d: cycles=%d, pause=%d, melody=%.40s...",
|
|
tuneIndex, buzzTune[tuneIndex].cycles, buzzTune[tuneIndex].pause,
|
|
buzzTune[tuneIndex].melody.c_str());
|
|
tuneIndex++;
|
|
}
|
|
ESP_LOGI(tag, "Successfully loaded %d tunes", tuneIndex);
|
|
} else {
|
|
ESP_LOGW(tag, "No 'tunes' array found in JSON");
|
|
// Set default tune if no tunes array found
|
|
buzzTune[0].cycles = 1;
|
|
buzzTune[0].pause = 0;
|
|
buzzTune[0].melody = DEFAULT_MELODY;
|
|
ESP_LOGI(tag, "Loaded default tune: %s", DEFAULT_MELODY);
|
|
}
|
|
} |