Bluetooth A2DP and BLE GATT bridge for the ChaoticVolt 42dB platform, relaying audio over I2S and control over UART to the DSP engine.
This repository contains the bridge layer that sits between the user-facing control side of the 42dB platform and the downstream DSP engine. It handles Bluetooth audio ingress, BLE-facing control, transport-side coordination, and control forwarding toward ChaoticVolt-42dB-DSP-Engine.
- Bluetooth A2DP Sink β Receive high-quality audio from phones, tablets, and computers
- I2S Audio Output β Streams received audio to I2S DAC or STM32 DSP engine
- BLE GATT Control β Receives DSP commands wirelessly from the 42 Decibels iOS/Watch app
- UART DSP Relay β Forwards BLE commands to external STM32 DSP engine via UART (GPIO4/GPIO5 @ 115200)
- GalacticStatus β Extended status reporting with periodic BLE notifications (2Γ/sec)
- OTA Firmware Updates β Over-the-air updates via BLE provisioning + WiFi download
- Persistent Settings β Settings survive power cycles via NVS flash storage
- iOS Compatible β Secure Simple Pairing (SSP) for seamless iOS pairing
- BT RSSI Monitoring β Reads Bluetooth signal strength every 10s when A2DP is active
- ESP32-WROOM-32 or compatible module
- I2S DAC (e.g., MAX98357A, PCM5102A)
- Speaker/amplifier
This repository is the transport-facing and control-bridging component of the 42dB platform.
Its role is to:
- receive Bluetooth audio
- forward audio over I2S toward the DSP engine
- expose the BLE GATT control interface
- relay control updates over UART toward the DSP engine
- reflect device state back toward the companion app
- manage settings persistence and update-related coordination where needed
| Signal | GPIO | Description |
|---|---|---|
| TX | GPIO4 | To STM32 USART2 RX (PD6) |
| RX | GPIO5 | From STM32 USART2 TX (PD5) |
GATT commands received via BLE are echoed as GATT:CTRL:<hex>\r\n on GPIO4 @ 115200 baud.
This repository is not the main DSP implementation and is not the companion app.
The 42dB platform is split into a few clear layers:
- ChaoticVolt-42dB-Companion-App β user-facing control on iPhone, iPad, and Apple Watch
- ChaoticVolt-BLE-I2S-Bridge β BLE control bridge and audio transport-facing component
- ChaoticVolt-42dB-DSP-Engine β real-time DSP processing layer
- downstream DAC / output stage β conversion and playback
This repository exists to keep the transport and protocol-facing responsibilities separate from the DSP engine itself.
- Role: bridge layer for Bluetooth audio ingress and BLE control
- Audio path: Bluetooth audio -> bridge -> I2S -> DSP engine
- Control path: companion app -> BLE GATT -> bridge -> UART -> DSP engine
- Downstream integration:
ChaoticVolt-42dB-DSP-Engine - Status: active platform component
The bridge receives Bluetooth audio and forwards the digital audio stream toward the DSP engine over I2S.
- Power on the ESP32
- Search for "42 Decibels" in your device's Bluetooth settings
- Pair and connect
- Play audio β it streams to the I2S DAC
The bridge exposes the BLE GATT interface used by the companion app for settings, control, and state synchronization.
Use any BLE app (nRF Connect, LightBlue) or the 42 Decibels iOS app:
- Scan for BLE devices
- Connect to "42 Decibels"
- Find the DSP Control Service (UUID:
00000001-1234-5678-9ABC-DEF012345678) - Write commands to the Control characteristic (UUID ends in
...0002)
DSP Control Service: 00000001-1234-5678-9ABC-DEF012345678
| Characteristic | UUID | Properties | Size |
|---|---|---|---|
| Control Write | 00000002-1234-5678-9ABC-DEF012345678 |
Write, Write Without Response | 2 bytes |
| Status Notify | 00000003-1234-5678-9ABC-DEF012345678 |
Read, Notify | 4 bytes |
| GalacticStatus | 00000004-1234-5678-9ABC-DEF012345678 |
Read, Notify | 7 bytes |
| OTA Credentials | 00000005-1234-5678-9ABC-DEF012345678 |
Write | 98 bytes |
| OTA URL | 00000006-1234-5678-9ABC-DEF012345678 |
Write | 258 bytes |
| OTA Control | 00000007-1234-5678-9ABC-DEF012345678 |
Write | 2 bytes |
| OTA Status | 00000008-1234-5678-9ABC-DEF012345678 |
Read, Notify | 8 bytes |
Commands are 2 bytes: [CMD] [VALUE]
| CMD | Name | Value | Description |
|---|---|---|---|
0x01 |
SET_PRESET | 0x00-0x03 |
OFFICE / FULL / NIGHT / SPEECH |
0x02 |
SET_LOUDNESS | 0x00/0x01 |
Loudness OFF/ON |
0x03 |
GET_STATUS | 0x00 |
Request status notification |
0x04 |
SET_MUTE | 0x00/0x01 |
Unmute/Mute audio |
0x05 |
SET_AUDIO_DUCK | 0x00/0x01 |
Audio Duck OFF/ON (-12dB panic) |
0x06 |
SET_NORMALIZER | 0x00/0x01 |
Normalizer (DRC) OFF/ON |
0x07 |
SET_VOLUME | 0x00-0x64 |
Volume trim 0β100 (0=mute, 100=full) |
0x08 |
SET_BYPASS | 0x00/0x01 |
DSP Bypass OFF/ON (skip EQ, keep safety) |
0x09 |
SET_BASS_BOOST | 0x00/0x01 |
Bass Boost OFF/ON (+8dB @ 100Hz) |
0x0A |
SET_SINE_TEST | 0x00/0x01 |
Sine Test OFF/ON (1kHz internal tone) |
| Value | Preset | Description |
|---|---|---|
0x00 |
OFFICE | Mild EQ for background/office listening |
0x01 |
FULL | Rich bass and treble enhancement |
0x02 |
NIGHT | Balanced for low volume (volume capped at 60%) |
0x03 |
SPEECH | Voice clarity for podcasts and calls |
Format: [VERSION] [PRESET] [LOUDNESS] [FLAGS]
| Byte | Description |
|---|---|
| 0 | VERSION β Protocol version (0x01) |
| 1 | PRESET β Current preset (0β3) |
| 2 | LOUDNESS β Loudness state (0/1) |
| 3 | FLAGS β Status bitfield (see below) |
FLAGS bitfield (byte 3):
| Bit | Mask | Field | Description |
|---|---|---|---|
| 0 | 0x01 | Limiter | Always 1 (limiter is always active) |
| 1 | 0x02 | Clipping | Clipping detected |
| 2 | 0x04 | Thermal | Thermal warning |
| 3 | 0x08 | Muted | Audio is muted |
| 4 | 0x10 | Duck | Audio Duck active |
| 5 | 0x20 | Normalizer | DRC active |
| 6 | 0x40 | Bypass | DSP Bypass active |
Note: FLAGS byte 3 has its own bit layout β it is not a copy of the internal
s_dsp_flagsvariable. Correctly remapped since v2.4.3.
Extended status with periodic notifications (every 500ms when subscribed).
Format: [VER] [PRESET] [FLAGS] [ENERGY] [VOLUME] [BATTERY] [LAST_CONTACT]
| Byte | Field | Description |
|---|---|---|
| 0 | VER | Protocol version (always 0x42) |
| 1 | PRESET | Current preset (0β3) |
| 2 | FLAGS | Shield status bitfield |
| 3 | ENERGY | Reserved (0β100) |
| 4 | VOLUME | Effective volume level (0β100) |
| 5 | BATTERY | Reserved (0β100) |
| 6 | LAST_CONTACT | Seconds since last BLE communication (0β255) |
Shield Status FLAGS (byte 2):
| Bit | Mask | Field | Description |
|---|---|---|---|
| 0 | 0x01 | Muted | Audio is muted |
| 1 | 0x02 | Audio Duck | Volume reduced (panic mode) |
| 2 | 0x04 | Loudness | Loudness compensation enabled |
| 3 | 0x08 | Normalizer | DRC enabled |
| 4 | 0x10 | Bypass | DSP Bypass active (corrected v2.4.3) |
| 5 | 0x20 | Bass Boost | Bass Boost enabled (corrected v2.4.3) |
0100 - Set preset to OFFICE
0101 - Set preset to FULL
0102 - Set preset to NIGHT (volume capped at 60%)
0103 - Set preset to SPEECH
0200 - Disable loudness
0201 - Enable loudness
0300 - Request status
0400 - Unmute
0401 - Mute
0500 - Audio Duck OFF (normal volume)
0501 - Audio Duck ON (panic mode, -12dB)
0600 - Normalizer OFF
0601 - Normalizer ON
0764 - Set volume to 100%
073C - Set volume to 60%
0700 - Set volume to 0%
0800 - DSP Bypass OFF (full DSP processing)
0801 - DSP Bypass ON (skip EQ, keep safety)
0900 - Bass Boost OFF
0901 - Bass Boost ON (+8dB @ 100Hz)
0A00 - Sine Test OFF
0A01 - Sine Test ON (1kHz internal tone)
| CMD | Name | Description |
|---|---|---|
0x10 |
START | Start OTA download process |
0x11 |
CANCEL | Cancel active OTA |
0x12 |
REBOOT | Reboot to new firmware |
0x13 |
GET_VERSION | Get current firmware version |
0x14 |
ROLLBACK | Rollback to previous firmware |
0x15 |
VALIDATE | Mark new firmware as valid |
Format: [STATE] [ERROR] [PROGRESS] [DL_KB_L] [DL_KB_H] [TOTAL_KB_L] [TOTAL_KB_H] [RSSI]
| Byte | Field | Description |
|---|---|---|
| 0 | STATE | OTA state (0x00=Idle, 0x05=Downloading, 0x07=Success, 0xFF=Error) |
| 1 | ERROR | Error code (0=none) |
| 2 | PROGRESS | Download progress 0β100% |
| 3-4 | DOWNLOADED_KB | Bytes downloaded (little-endian KB) |
| 5-6 | TOTAL_KB | Total firmware size (little-endian KB) |
| 7 | RSSI | WiFi signal strength (signed dBm) |
Up to and including v2.3.1, this firmware contained a full DSP processing chain running on the ESP32 itself (dsp_processor.c/h). This is useful as a reference or for standalone use without the STM32 DSP engine.
Last tag with ESP32-side DSP: v2.3.1
The v2.3.1 signal chain was:
Input β Pre-gain (-3dB) β HPF (95Hz) β Preset EQ β Loudness β Bass Boost β Normalizer β Limiter β Volume β Duck β Mute β Output
DSP components in v2.3.1:
| Component | Description |
|---|---|
| Pre-gain | -3 dB headroom (always active) |
| High-pass Filter | 2nd-order Butterworth @ 95 Hz |
| Preset EQ | 4-band parametric EQ (OFFICE / FULL / NIGHT / SPEECH) |
| Loudness | Bass +12dB @ 80Hz, treble +6dB @ 12kHz |
| Bass Boost | Low-shelf +8dB @ 100Hz |
| Normalizer | Block DRC β threshold -20dB, ratio 4:1, +6dB makeup |
| Limiter | Peak limiter @ -1 dBFS (3ms attack, 120ms release) |
| Volume | Device-side trim 0β100 |
| Audio Duck | -12dB panic reduction |
| Mute | Final output gate |
The DSP module was removed in v2.4.0 ("Release v2.4.0: V4 architecture β A2DP sink + BLE GATT dual mode") when DSP processing was migrated to the external STM32 DSP engine.
βββ CMakeLists.txt
βββ README.md
βββ Protocol.md # Full BLE protocol specification
βββ FSD-DSP-001_ESP32_BLE_GATT.md # Functional Specification Document
βββ partitions_ota.csv # OTA partition table
βββ sdkconfig.defaults
βββ main/
βββ CMakeLists.txt
βββ main.c # Application entry point + A2DP/I2S handling
βββ ble_gatt_dsp.h/.c # BLE GATT service + UART relay to STM32
βββ nvs_settings.h/.c # Persistent storage (NVS)
βββ ota_manager.h/.c # OTA state machine and download logic
βββ wifi_manager.h/.c # WiFi STA mode for OTA downloads
Key configuration options:
# Dual-mode Bluetooth (Classic + BLE)
CONFIG_BTDM_CTRL_MODE_BTDM=y
CONFIG_BT_CLASSIC_ENABLED=y
CONFIG_BT_A2DP_ENABLE=y
CONFIG_BT_BLE_ENABLED=y
CONFIG_BT_GATTS_ENABLE=y
# Performance optimization
CONFIG_COMPILER_OPTIMIZATION_PERF=y
- Device Name: Change
BT_DEVICE_NAMEinmain.c - I2S Pins: Modify
I2S_BCK_PIN,I2S_WS_PIN,I2S_DATA_PINinmain.c - UART DSP Relay: GPIO4/GPIO5 @ 115200 baud β always enabled when STM32 DSP engine is wired
- Fixed: Bypass and Bass Boost bits were swapped in GalacticStatus Shield byte (bit 4 was Bass Boost, bit 5 was Bypass β reversed from Protocol.md). Now bit 4 (0x10) = Bypass, bit 5 (0x20) = Bass Boost.
- Fixed: Status Notify FLAGS byte (characteristic 0x0003, byte 3) was a raw copy of
s_dsp_flags. Now correctly remapped to Protocol.md layout: bit 0 = limiter always 1, bit 3 = mute, bit 4 = duck, bit 5 = normalizer, bit 6 = bypass.
- Fixed: Transient DSP state (duck, mute) was not reflected immediately in BLE status notifications.
- New: Sine Test mode (command
0x0A) β generates a 1kHz internal tone for DAC/amp verification - Fixed: Audio path ring buffer with 50ms pre-buffering prevents DMA underrun at startup
- Fixed: I2S output stuttering on fast connect/disconnect cycles
- New: V4 architecture β dual Classic BT A2DP + BLE GATT in same firmware
- New: UART echo of BLE GATT commands to external STM32 DSP engine (GPIO4/GPIO5 @ 115200)
- New: BT RSSI monitoring and SBC codec quality logging
- Removed:
dsp_processormodule β DSP processing migrated to external STM32 DSP engine
- New: Bass Boost feature (+8dB @ 100Hz) β command
0x09 - New: DSP Bypass command (
0x08) β skips EQ, keeps safety processing - Fixed: Bypass mode now keeps pre-gain, limiter, volume (prevents hot tracks from clipping)
- Fixed: Audio Duck debug log was printing "Bypass" incorrectly
- Fixed: Sample rate detection for multi-bit SBC bitmasks (fallback to 44.1kHz)
- DSP optimizations with block-based normalizer
- Added initial DSP Bypass mode
- Pre-gain adjusted to -3dB
- OTA firmware update support (BLE + WiFi hybrid)
- GalacticStatus periodic notifications every 500ms
- Volume control with preset-based caps
- Normalizer/DRC feature
- Audio Duck panic button
- Initial release with DSP presets and loudness
This firmware is one of three projects that form the 42dB audio system:
| Project | Repository | Role |
|---|---|---|
| 42dB STM32 DSP Engine | ChaoticVolt-42dB_STM32_DSP_engine | Real-time audio DSP processor |
| 42dB iPhone & Apple Watch App | ChaoticVolt-42_Decibels-iPhone-and-WatchOS-app | BLE GATT control interface |
| ESP32 BLE GATT / A2DP firmware (this repo) | ChoticVolt-ESP32_I2S_Master_with_BLE_GATT | A2DP Bluetooth sink + GATT relay |
The three projects communicate over a shared UART/BLE protocol (command format GATT:CTRL:<hex>). When the protocol changes, all three must be updated together:
| Protocol | STM32 DSP Engine | ESP32 Firmware | iPhone/Watch App |
|---|---|---|---|
| v1 | v0.3.0 β v0.5.x | v2.4.1 β v2.4.3 | v1.0.x |
PolyForm Noncommercial 1.0.0 β See LICENSE. Commercial use requires explicit written permission.
Robin Kluit
- Audio EQ Cookbook β Robert Bristow-Johnson's biquad filter formulas
- ESP-IDF Programming Guide β Espressif Systems
This repo currently does not accept external pull requests. Please use Issues or Discussions for reports and suggestions.
Control updates destined for the DSP engine are translated and forwarded over UART or an equivalent low-level control path.
The bridge helps keep the user-facing control layer aligned with device reality by reflecting relevant status back toward BLE clients.
Where needed, the bridge may also participate in settings persistence, OTA-related coordination, and other platform glue responsibilities.
This repository represents an architectural transition away from older all-in-one implementations.
Earlier iterations may have concentrated more roles inside one ESP32-based codebase, including DSP-adjacent behavior. The current role of this project is narrower and cleaner:
- audio transport-facing integration
- BLE control handling
- bridge logic
- state synchronization
- downstream handoff to the DSP engine
The DSP engine now lives in its own dedicated repository:
- ChaoticVolt-42dB-DSP-Engine
That separation is intentional and should remain visible throughout the repo.
This repository makes the most sense when viewed together with:
- ChaoticVolt-42dB-Companion-App
- ChaoticVolt-42dB-DSP-Engine
Together, these repositories form the user-facing control path, the bridge layer, and the real-time DSP path of the 42dB platform.
See the docs/ directory for deeper technical documentation.
Suggested core documents:
docs/overview.mdβ repository summary and role in the platformdocs/architecture.mdβ system layout and bridge-layer responsibilitiesdocs/audio-path.mdβ Bluetooth audio ingress and I2S handoffdocs/control-path.mdβ BLE GATT, UART relay, and state synchronizationdocs/protocol.mdβ BLE-facing control protocol and message structuredocs/development-notes.mdβ prototype history, transitions, and practical constraints
This repository is a bridge and integration component.
It should be presented as a stable architectural role within the 42dB platform, even if the exact hardware partitioning and transport-side implementation details continue to evolve.