Buzzer Module (sound system)
Section titled “Buzzer Module (sound system)”The Buzzer module provides audible feedback for the ESP3D-X pendant firmware. Since a14 it is the sound module (main/modules/buzzer/, global esp3d_sound): the rest of the firmware only names what happened (ESP3DSoundEvent), never how it sounds. The module resolves each event to a note list (compiled defaults, ui_resources blob, or a user /esp3dsound.ini), sequences it with a single esp_timer, and drives the hardware through a backend interface. Today’s only backend is a PWM tone on the LEDC buzzer driver; an audio decoder backend can be added later without touching callers.
Authoritative design reference: sound_system.md (API rules, engine, priorities, limits, ini format, blob layout). Design rationale and remaining work: sound_customization_design.md.
Built only when BUZZER_SERVICE is ON (ESP3D_BUZZER_FEATURE); today only pibot_pendant_v1_0 enables it.
Table of Contents
Section titled “Table of Contents”- Architecture Overview
- Layer 1 — Hardware Driver
- Layer 2 — Sound Module
- Definitions and Customization
- UI Integration
- Board-Level Configuration
- Factory App & Bootloader Buzzer
- Build Configuration
- Related Modules
Architecture Overview
Section titled “Architecture Overview”Layer 1 — Hardware Driver
Section titled “Layer 1 — Hardware Driver”Location: hardware/common/drivers/buzzer/ (buzzer.c, buzzer.h, buzzer_config.h)
The driver only starts and stops a PWM tone. Sequencing and timing belong to the sound module. The former blocking buzzer_bip(), the malloc + task based buzzer_play() and buzzer_tone.h were removed.
| Function | Description |
|---|---|
esp_err_t buzzer_configure(const buzzer_config_t *config) | Configures the LEDC timer and channel for the buzzer GPIO |
esp_err_t buzzer_tone_start(uint16_t freq_hz) | Starts a continuous tone; non-blocking, no log on success, safe from an esp_timer callback. 0 silences the buzzer |
esp_err_t buzzer_tone_stop(void) | Silences the buzzer; non-blocking, no log on success |
buzzer_config_t:
| Field | Meaning |
|---|---|
output_invert | Invert the GPIO (active-low buzzer) |
gpio_num | Buzzer GPIO |
timer_idx / channel_idx | LEDC timer / channel |
freq_hz | Default PWM frequency |
resolution_bits | PWM resolution |
duty_percent | Tone duty cycle 1–50 % = volume (50 = loudest, lower = quieter and less supply current); 0 (field omitted) = 50 %, the previous behavior |
Layer 2 — Sound Module
Section titled “Layer 2 — Sound Module”Location: main/modules/buzzer/
| File | Role |
|---|---|
esp3d_sound.h | Public API: ESP3DSoundEvent, ESP3DSound, esp3d_sound |
esp3d_sound.cpp | Engine: definitions, limits, priorities, esp_timer sequencer, mute stack, /esp3dsound.ini update |
esp3d_sound_defaults.h | Generated (gitignored) from resources/sounds/sounds_default.ini: compiled-in defaults + blob ID |
esp3d_sound_backend.h | Backend interface (internal) |
esp3d_sound_backend_tone.cpp | Tone backend (LEDC buzzer driver) |
Events
Section titled “Events”selection, forward, backward, active_item, back, response_no, error, lock, wakeup, success, startup, unlock (ESP3DSoundEvent, in storage order — new events are always appended).
ESP3DSound API
Section titled “ESP3DSound API”| Method | Description |
|---|---|
begin() / end() | Start (idempotent, also reloads the enabled state from settings) / stop |
reloadDefinitions() | Point each event to its slot in the mapped ui_resources blob, or keep the compiled default |
enabled(fromSettings) / enable(on, toSettings) | Global on / off (esp3d_buzzer_on setting) |
play(event) | Non-blocking, any task, best effort: dropped when disabled or when a higher-priority sound is playing |
playTone(freq_hz, duration_ms) | Raw tone for [ESP250]: 100–5000 Hz, 1–3000 ms; ESP_ERR_INVALID_STATE when disabled or busy, ESP_ERR_INVALID_ARG when out of range |
stop() | Stop the current sound |
muteAndSave() / restoreMute() / resetMute() | Counted mute stack (tool change, probe…); does not touch the setting |
Engine
Section titled “Engine”One static one-shot esp_timer sequences the notes: no task, no queue, no heap allocation at play time. play() stores the event as pending under a spinlock and fires the timer; the timer callback (esp_timer task, Core 0) starts / stops notes through the backend outside the spinlock, with a 10 ms gap between notes. The hardware is only touched from the callback, so there is no concurrent LEDC access. The callback shares the esp_timer task with the LVGL tick timer: no LVGL call, no blocking call, no log on the normal path.
Priority — a new sound preempts the current one if its priority is ≥ the current one, otherwise it is dropped: 0 = forward / backward; 1 = selection, back, active_item, response_no; 2 = error, lock, unlock, wakeup, success, startup, raw tone. An error is never cut by an encoder click, and fast encoder turns restart the click instead of piling up.
Limits (applied at play time) — max notes / total duration per event class, from 1 note / 40 ms (forward, backward) to 16 notes / 3000 ms (startup); frequencies outside 100–5000 Hz play as rests. Full table: sound_system.md “Limits”.
Definitions and Customization
Section titled “Definitions and Customization”Single source of the default sounds: resources/sounds/sounds_default.ini. generate_resources.py (with tools/build_scripts/sound_defs.py) derives from it both the sounds blob of the ui_resources partition and the compiled-in fallback header.
- Early boot:
esp3d_sound.begin()uses the compiled defaults (partition not mapped yet). UIManager::initialize()callsreloadDefinitions()right afteresp3d_resources_init(): events then read the mapped blob (no copy); an absent, invalid or older blob (fewer events) keeps the compiled defaults.
Customization paths (same ini format, partial files allowed):
| Path | How |
|---|---|
SD card /esp3dsound.ini | Applied at boot by the update service (ESP3DUpdateType::sounds): merged into the current definitions, blob rewritten, file renamed .ok / .bad, reboot. A syntax error rejects the whole file |
| Board overlay | boards/<board>/resources/sounds/esp3dsound.ini, merged at build time |
| Rebuild | Edit sounds_default.ini and rebuild ui_resources |
SD kit (ui_resources_kit) | An esp3dsound.ini in the overrides folder is baked into the rebuilt ui_resources.bin |
Format (one section per event, notes = freq:duration_ms, …, note names C3..B8 with optional #, 0 = rest, off mutes the event):
[error]notes = 100:200[forward]notes = offUser guide: docs/user documentation/ui_resources_customization.md; example: docs/ui_resources/esp3dsound.ini.example.
UI Integration
Section titled “UI Integration”main/display/esp3d_ui.h maps the UI macros to events (empty when the feature is off): ESP3D_SELECTION_BEEP → selection, ESP3D_MOVEMENT_FORWARD_BEEP / _BACKWARD_BEEP → forward / backward, ESP3D_ACTIVE_ITEM_BEEP, ESP3D_BACK_BEEP, ESP3D_RESPONSE_NO_BEEP, ESP3D_ERROR_BEEP, ESP3D_LOCK_BEEP, ESP3D_UNLOCK_BEEP, ESP3D_WAKEUP_SOUND, ESP3D_SUCCESS_SOUND, ESP3D_STARTING_SOUND; ESP3D_SAVE_SOUND_STATE_AND_DISABLE / ESP3D_RESTORE_SOUND_STATE → muteAndSave() / restoreMute(). See ui_core.md §3.7.
Board-Level Configuration
Section titled “Board-Level Configuration”File: boards/pibot_pendant_v1_0/components/bsp/buzzer_def.h
buzzer_config_t buzzer_cfg = { .output_invert = !BUZZER_ACTIVE_HIGH_FLAG, // Invert if not active-high .gpio_num = BUZZER_PIN, .timer_idx = BUZZER_PWM_TIMER_IDX, .channel_idx = BUZZER_PWM_CHANNEL_IDX, .freq_hz = BUZZER_PWM_FREQ_HZ, .resolution_bits = BUZZER_PWM_RESOLUTION_BITS, .duty_percent = BUZZER_PWM_DUTY_PERCENT,};board_init() passes it to buzzer_configure() when ESP3D_BUZZER_FEATURE is set. PiBot: BUZZER_PWM_DUTY_PERCENT = 15 (lowered because SC8002B current peaks on fast repeated key beeps are suspected of causing BROWNOUT resets, 2026-11-01, not yet confirmed on hardware). The volume is fixed at build time: there is no user volume setting.
The output_invert field handles both active-high and active-low buzzers without changing driver logic: !BUZZER_ACTIVE_HIGH_FLAG inverts the GPIO signal for active-low hardware, keeping the drive waveform correct in either polarity.
Boards without a physical buzzer either leave ESP3D_BUZZER_FEATURE OFF in their CMakeLists, or define BUZZER_PIN as GPIO_NUM_NC. In the latter case the driver’s GPIO_IS_VALID_OUTPUT_GPIO check returns ESP_ERR_INVALID_ARG at configure time.
Factory App & Bootloader Buzzer
Section titled “Factory App & Bootloader Buzzer”The factory test application and the custom bootloader use entirely separate, minimalist buzzer implementations. These have no dependency on the main firmware buzzer stack.
Factory App (Bit-banged GPIO)
Section titled “Factory App (Bit-banged GPIO)”Location: boards/*/Factory/main/buzzer.c and buzzer.h
Implemented as a direct GPIO-toggle loop with esp_rom_delay_us. No LEDC, no FreeRTOS tasks.
Boards with no buzzer hardware define BUZZER_PIN as GPIO_NUM_NC; the guard GPIO_IS_VALID_GPIO(BUZZER_PIN) makes both functions safe no-ops on those boards.
Custom Bootloader (GPIO-LL)
Section titled “Custom Bootloader (GPIO-LL)”Location: boards/pibot_pendant_v1_0/Factory/custom_bootloader/hooks.c
Before the RTOS scheduler starts, the bootloader hooks use gpio_ll_set_level() directly, bypassing the driver layer entirely:
| Function | Tone produced |
|---|---|
beep_short() | 2700 Hz, 100 ms |
beep_confirm() | 2700 Hz 150 ms → 100 ms silence → 3200 Hz 150 ms |
These functions provide auditory confirmation of button-triggered factory-reset events during the bootloader phase, where no heap, no RTOS, and no LEDC peripheral setup is available.
Summary: Three Buzzer Contexts
Section titled “Summary: Three Buzzer Contexts”The Factory apps keep their own buzzer.c copies, independent of the main firmware driver.
Build Configuration
Section titled “Build Configuration”# CMakeLists.txtOPTION(BUZZER_SERVICE "Buzzer service" OFF)cmake/features.cmake turns it into -DESP3D_BUZZER_FEATURE=1. Without it, the sound module is not built, the UI macros are empty, board_init() skips buzzer_configure() and no LEDC timer or channel is allocated.
Related Modules
Section titled “Related Modules”| Module | Relationship |
|---|---|
| bsp_board_initialization | board_init() calls buzzer_configure() with the board-specific buzzer_cfg |
| bsp_buzzer | BSP view of the driver |
| ui_core | UI sound macros, reloadDefinitions() call in UIManager::initialize() |
| esp3d_commands | [ESP250] raw tone, [ESP910] enable / disable |
| update_service | /esp3dsound.ini SD update (ESP3DUpdateType::sounds) |
| tools_resource_generation | sounds blob generation |
| factory_app | Standalone buzzer implementations of the factory apps and the custom bootloader |