sl-firmware fa75c442a7 feat: remove all STM32/Mamba/BlackPill references — ESP32-S3 only
Archive STM32 firmware to legacy/stm32/:
- src/, include/, lib/USB_CDC/, platformio.ini, test stubs, flash_firmware.py
- test/test_battery_adc.c, test_hw_button.c, test_pid_schedule.c, test_vesc_can.c, test_can_watchdog.c
- USB_CDC_BUG.md

Rename: stm32_protocol → esp32_protocol, mamba_protocol → balance_protocol,
  stm32_cmd_node → esp32_cmd_node, stm32_cmd_params → esp32_cmd_params,
  stm32_cmd.launch.py → esp32_cmd.launch.py,
  test_stm32_protocol → test_esp32_protocol, test_stm32_cmd_node → test_esp32_cmd_node

Content cleanup across all files:
- Mamba F722S → ESP32-S3 BALANCE
- BlackPill → ESP32-S3 IO
- STM32F722/F7xx → ESP32-S3
- stm32Mode/Version/Port → esp32Mode/Version/Port
- STM32 State/Mode labels → ESP32 State/Mode
- Jetson Nano → Jetson Orin Nano Super
- /dev/stm32 → /dev/esp32
- stm32_bridge → esp32_bridge
- STM32 HAL → ESP-IDF

docs/SALTYLAB.md:
- Update "Drone FC Details" to describe ESP32-S3 BALANCE board (Waveshare ESP32-S3 Touch LCD 1.28)
- Replace verbose "Self-Balancing Control" STM32 section with brief note pointing to SAUL-TEE-SYSTEM-REFERENCE.md

TEAM.md: Update Embedded Firmware Engineer role to ESP32-S3 / ESP-IDF

No new functionality — cleanup only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 09:00:38 -04:00

294 lines
9.1 KiB
C

#include "buzzer.h"
#include "stm32f7xx_hal.h"
#include "config.h"
#include <string.h>
/* ================================================================
* Buzzer Hardware Configuration
* ================================================================ */
#define BUZZER_PIN GPIO_PIN_8
#define BUZZER_PORT GPIOA
#define BUZZER_TIM TIM1
#define BUZZER_TIM_CHANNEL TIM_CHANNEL_1
#define BUZZER_BASE_FREQ_HZ 1000 /* Base PWM frequency (1kHz) */
/* ================================================================
* Predefined Melodies
* ================================================================ */
/* Startup jingle: C-E-G ascending pattern */
const MelodyNote melody_startup[] = {
{NOTE_C4, DURATION_QUARTER},
{NOTE_E4, DURATION_QUARTER},
{NOTE_G4, DURATION_QUARTER},
{NOTE_C5, DURATION_HALF},
{NOTE_REST, 0} /* Terminator */
};
/* Low battery warning: two descending beeps */
const MelodyNote melody_low_battery[] = {
{NOTE_A5, DURATION_EIGHTH},
{NOTE_REST, DURATION_EIGHTH},
{NOTE_A5, DURATION_EIGHTH},
{NOTE_REST, DURATION_EIGHTH},
{NOTE_F5, DURATION_EIGHTH},
{NOTE_REST, DURATION_EIGHTH},
{NOTE_F5, DURATION_EIGHTH},
{NOTE_REST, 0}
};
/* Error alert: rapid repeating tone */
const MelodyNote melody_error[] = {
{NOTE_E5, DURATION_SIXTEENTH},
{NOTE_REST, DURATION_SIXTEENTH},
{NOTE_E5, DURATION_SIXTEENTH},
{NOTE_REST, DURATION_SIXTEENTH},
{NOTE_E5, DURATION_SIXTEENTH},
{NOTE_REST, DURATION_SIXTEENTH},
{NOTE_REST, 0}
};
/* Docking complete: cheerful ascending chime */
const MelodyNote melody_docking_complete[] = {
{NOTE_C4, DURATION_EIGHTH},
{NOTE_E4, DURATION_EIGHTH},
{NOTE_G4, DURATION_EIGHTH},
{NOTE_C5, DURATION_QUARTER},
{NOTE_REST, DURATION_QUARTER},
{NOTE_G4, DURATION_EIGHTH},
{NOTE_C5, DURATION_HALF},
{NOTE_REST, 0}
};
/* ================================================================
* Melody Queue
* ================================================================ */
#define MELODY_QUEUE_SIZE 4
typedef struct {
const MelodyNote *notes; /* Melody sequence pointer */
uint16_t note_index; /* Current note in sequence */
uint32_t note_start_ms; /* When current note started */
uint32_t note_duration_ms; /* Duration of current note */
uint16_t current_frequency; /* Current tone frequency (Hz) */
bool is_custom; /* Is this a custom melody? */
} MelodyPlayback;
typedef struct {
MelodyPlayback queue[MELODY_QUEUE_SIZE];
uint8_t write_index;
uint8_t read_index;
uint8_t count;
} MelodyQueue;
static MelodyQueue s_queue = {0};
static MelodyPlayback s_current = {0};
static uint32_t s_last_tick_ms = 0;
/* ================================================================
* Hardware Initialization
* ================================================================ */
void buzzer_init(void)
{
/* Enable GPIO and timer clocks */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_TIM1_CLK_ENABLE();
/* Configure PA8 as TIM1_CH1 PWM output */
GPIO_InitTypeDef gpio_init = {0};
gpio_init.Pin = BUZZER_PIN;
gpio_init.Mode = GPIO_MODE_AF_PP;
gpio_init.Pull = GPIO_NOPULL;
gpio_init.Speed = GPIO_SPEED_HIGH;
gpio_init.Alternate = GPIO_AF1_TIM1;
HAL_GPIO_Init(BUZZER_PORT, &gpio_init);
/* Configure TIM1 for PWM:
* Clock: 216MHz / PSC = output frequency
* For 1kHz base frequency: PSC = 216, ARR = 1000
* Duty cycle = CCR / ARR (e.g., 500/1000 = 50%)
*/
TIM_HandleTypeDef htim1 = {0};
htim1.Instance = BUZZER_TIM;
htim1.Init.Prescaler = 216 - 1; /* 216MHz / 216 = 1MHz clock */
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = (1000000 / BUZZER_BASE_FREQ_HZ) - 1; /* 1kHz = 1000 counts */
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
HAL_TIM_PWM_Init(&htim1);
/* Configure PWM on CH1: 50% duty cycle initially (silence will be 0%) */
TIM_OC_InitTypeDef oc_init = {0};
oc_init.OCMode = TIM_OCMODE_PWM1;
oc_init.Pulse = 0; /* Start at 0% duty (silence) */
oc_init.OCPolarity = TIM_OCPOLARITY_HIGH;
oc_init.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim1, &oc_init, BUZZER_TIM_CHANNEL);
/* Start PWM generation */
HAL_TIM_PWM_Start(BUZZER_TIM, BUZZER_TIM_CHANNEL);
/* Initialize queue */
memset(&s_queue, 0, sizeof(s_queue));
memset(&s_current, 0, sizeof(s_current));
s_last_tick_ms = 0;
}
/* ================================================================
* Public API
* ================================================================ */
bool buzzer_play_melody(MelodyType melody_type)
{
const MelodyNote *notes = NULL;
switch (melody_type) {
case MELODY_STARTUP:
notes = melody_startup;
break;
case MELODY_LOW_BATTERY:
notes = melody_low_battery;
break;
case MELODY_ERROR:
notes = melody_error;
break;
case MELODY_DOCKING_COMPLETE:
notes = melody_docking_complete;
break;
default:
return false;
}
return buzzer_play_custom(notes);
}
bool buzzer_play_custom(const MelodyNote *notes)
{
if (!notes || s_queue.count >= MELODY_QUEUE_SIZE) {
return false;
}
MelodyPlayback *playback = &s_queue.queue[s_queue.write_index];
memset(playback, 0, sizeof(*playback));
playback->notes = notes;
playback->note_index = 0;
playback->is_custom = true;
s_queue.write_index = (s_queue.write_index + 1) % MELODY_QUEUE_SIZE;
s_queue.count++;
return true;
}
bool buzzer_play_tone(uint16_t frequency, uint16_t duration_ms)
{
if (s_queue.count >= MELODY_QUEUE_SIZE) {
return false;
}
/* Create a simple 2-note melody: tone + rest */
static MelodyNote temp_notes[3];
temp_notes[0].frequency = frequency;
temp_notes[0].duration_ms = duration_ms;
temp_notes[1].frequency = NOTE_REST;
temp_notes[1].duration_ms = 0;
MelodyPlayback *playback = &s_queue.queue[s_queue.write_index];
memset(playback, 0, sizeof(*playback));
playback->notes = temp_notes;
playback->note_index = 0;
playback->is_custom = true;
s_queue.write_index = (s_queue.write_index + 1) % MELODY_QUEUE_SIZE;
s_queue.count++;
return true;
}
void buzzer_stop(void)
{
/* Clear queue and current playback */
memset(&s_queue, 0, sizeof(s_queue));
memset(&s_current, 0, sizeof(s_current));
/* Silence buzzer (0% duty cycle) */
TIM1->CCR1 = 0;
}
bool buzzer_is_playing(void)
{
return (s_current.notes != NULL) || (s_queue.count > 0);
}
/* ================================================================
* Timer Update and PWM Frequency Control
* ================================================================ */
static void buzzer_set_frequency(uint16_t frequency)
{
if (frequency == 0) {
/* Silence: 0% duty cycle */
TIM1->CCR1 = 0;
return;
}
/* Set PWM frequency and 50% duty cycle
* TIM1 clock: 1MHz (after prescaler)
* ARR = 1MHz / frequency
* CCR1 = ARR / 2 (50% duty)
*/
uint32_t arr = (1000000 / frequency);
if (arr > 65535) arr = 65535; /* Clamp to 16-bit */
TIM1->ARR = arr - 1;
TIM1->CCR1 = arr / 2; /* 50% duty cycle for all tones */
}
void buzzer_tick(uint32_t now_ms)
{
/* Check if current note has finished */
if (s_current.notes != NULL) {
uint32_t elapsed = now_ms - s_current.note_start_ms;
if (elapsed >= s_current.note_duration_ms) {
/* Move to next note */
s_current.note_index++;
if (s_current.notes[s_current.note_index].duration_ms == 0) {
/* End of melody sequence */
s_current.notes = NULL;
buzzer_set_frequency(0);
/* Start next queued melody if available */
if (s_queue.count > 0) {
s_current = s_queue.queue[s_queue.read_index];
s_queue.read_index = (s_queue.read_index + 1) % MELODY_QUEUE_SIZE;
s_queue.count--;
s_current.note_start_ms = now_ms;
s_current.note_duration_ms = s_current.notes[0].duration_ms;
buzzer_set_frequency(s_current.notes[0].frequency);
}
} else {
/* Play next note */
s_current.note_start_ms = now_ms;
s_current.note_duration_ms = s_current.notes[s_current.note_index].duration_ms;
uint16_t frequency = s_current.notes[s_current.note_index].frequency;
buzzer_set_frequency(frequency);
}
}
} else if (s_queue.count > 0 && s_current.notes == NULL) {
/* Start first queued melody */
s_current = s_queue.queue[s_queue.read_index];
s_queue.read_index = (s_queue.read_index + 1) % MELODY_QUEUE_SIZE;
s_queue.count--;
s_current.note_start_ms = now_ms;
s_current.note_duration_ms = s_current.notes[0].duration_ms;
buzzer_set_frequency(s_current.notes[0].frequency);
}
s_last_tick_ms = now_ms;
}