libDaisy Developer Guide & Architecture Reference¶
This skill provides a comprehensive operational guide and architectural reference for developing embedded audio firmware using libDaisy, the official C++ hardware abstraction library and board support package (BSP) for the Electro-Smith Daisy platform.
1. System & Hardware Architecture¶
libDaisy targets the STM32H750IBK6 high-performance microcontroller and standard Daisy hardware peripherals:
| Component | Hardware Specification | libDaisy Support |
|---|---|---|
| MCU Core | ARM Cortex-M7 @ 480 MHz with FPU (double/single precision) & DSP extensions | CMSIS-DSP, CMSIS Core, LL / HAL drivers |
| Internal Flash | 128 KB User Flash (0x08000000) |
Base bootloader location (APP_TYPE = BOOT_NONE) |
| Internal RAM | 1 MB total: AXI SRAM (512 KB), DTCM (128 KB), SRAM1–3 (288 KB), SRAM4 (64 KB) | Fast DSP state (DTCM), DMA buffers (SRAM1) |
| External SDRAM | 64 MB 32-bit SDRAM (AS4C32M16SB) via FMC bus @ ~100–120 MHz | Large audio buffers / delay lines (DSY_SDRAM_BSS) |
| External Flash | 8 MB QSPI NOR Flash (IS25LP064A) via Quad-SPI | Daisy Bootloader app storage (APP_TYPE = BOOT_SRAM) & presets |
| Audio Codec | 24-bit stereo up to 96 kHz (AK4556, WM8731, PCM3060, TAC5242) | Interrupt double-buffered DMA via SAI1 |
| USB | USB 2.0 Full-Speed (12 Mbps) or High-Speed with external ULPI | USB CDC (Serial), USB MIDI, USB Audio, USB Host |
2. Codebase Organization & Prefix Taxonomy¶
The libDaisy source tree (libDaisy/src/) uses standardized module prefixes:
| Prefix | Subsystem Domain | Description & Key Classes |
|---|---|---|
sys/ |
System Infrastructure | Clocks, MPU, D-cache invalidation, DMA routing, FatFs. • System, DmaHandler, FatFSInterface |
per/ |
Internal Peripherals | High-performance C++ drivers for STM32 on-chip peripherals. • AdcHandle, DacHandle, GPIO, I2CHandle, SpiHandle, SaiHandle, UartHandler, QSPIHandle, TimerHandle, Pwm, Random |
dev/ |
External Devices | External ICs mounted on boards. • Codecs: Ak4556, Wm8731, Pcm3060• Displays: SSD130x, SH1106, SSD1327, SSD1351• Memory & Expanders: SdramHandle, IS25LP064A, MCP23x17, TCA9534, ShiftRegister595, ShiftRegister4021• Sensors & LEDs: Mpr121, APDS9960, NeoPixel, DotStar |
hid/ |
Human Interface | Real-time audio engine, smoothed inputs, debounce, encoders, MIDI, USB, and display drawing. • AudioHandle, AnalogControl, Parameter, Switch, Switch3, Encoder, GateIn, Led, RgbLed, MidiHandler, UsbHandle, UsbHostHandle, Logger, Display, OledDisplay |
ui/ |
User Interface | Screen menu hierarchies, event queues, and input monitoring. • UI, UiEventQueue, AbstractMenu, FullScreenItemMenu, ButtonMonitor |
util/ |
Utilities | Lock-free FIFOs, string formatting without heap allocation, WAV streaming, preset saving, and load profiling. • FIFO, FixedCapStr, WavPlayer, WavWriter, WaveTableLoader, PersistentStorage, CpuLoadMeter, VoctCalibration |
3. Core Audio Engine (AudioHandle)¶
The audio engine processes audio blocks using double-buffered circular DMA via SAI1.
3.1 Audio Callback Formats¶
#include "daisy_seed.h"
using namespace daisy;
DaisySeed hw;
// Non-interleaved (Preferred): out[channel][sample]
void AudioCallback(AudioHandle::InputBuffer in,
AudioHandle::OutputBuffer out,
size_t size)
{
for (size_t i = 0; i < size; i++)
{
out[0][i] = in[0][i]; // Left
out[1][i] = in[1][i]; // Right
}
}
int main(void)
{
hw.Init();
hw.SetAudioBlockSize(48); // 1 ms @ 48 kHz
hw.SetAudioSampleRate(SaiHandle::Config::SampleRate::SAI_48KHZ);
hw.StartAudio(AudioCallback);
while (1) {}
}
- Sample format: 32-bit floating point (
float) normalized in the range[-1.0f, +1.0f]. - Block sizes: 1 to 256 samples (powers of 2 or multiples of 48).
- Sample rates: 8 kHz, 16 kHz, 32 kHz, 44.1 kHz, 48 kHz, 96 kHz.
4. Memory Layout & Real-Time Constraints¶
4.1 STM32H7 Memory Sections¶
| Memory Region | Address | Size | Attribute / Macro | Usage & Caveats |
|---|---|---|---|---|
| ITCM RAM | 0x00000000 |
64 KB | .itcmram |
Zero-wait-state instruction RAM. |
| DTCM RAM | 0x20000000 |
128 KB | DTCM_MEM_SECTION |
480 MHz zero-wait data RAM for DSP state. No DMA access. |
| AXI SRAM | 0x24000000 |
512 KB | Standard RAM (.bss, .data) |
Primary application RAM (BOOT_SRAM). Cached. |
| SRAM1 (D2) | 0x30000000 |
128 KB | DMA_BUFFER_MEM_SECTION |
Non-cached. Mandatory for all ADC, SAI, SPI, I2C DMA buffers. |
| SDRAM | 0xC0000000 |
64 MB | DSY_SDRAM_BSS |
External delay buffers. No C++ static constructors before hw.Init(). |
| QSPI Flash | 0x90000000 |
8 MB | BOOT_QSPI / Memory-mapped |
Non-volatile code storage & presets (PersistentStorage). |
4.2 Critical Real-Time Rules¶
- DMA Cache Incoherency Hazard: The Cortex-M7 data cache does not snoop peripheral DMA buses. Any buffer targeted by DMA must use
DMA_BUFFER_MEM_SECTION. - Never Block Inside
AudioCallback: The audio callback runs inside an ISR. Never execute I2C/SPI display refreshes (oled.Update()), flash operations, SD card reads, or delays (System::Delay()) inside the audio callback. - SDRAM Constructor Hazard: Global objects in SDRAM cannot have dynamic C++ constructors, because static constructors run prior to
main()before the SDRAM controller is initialized.
5. Bootloader & Deployment Workflows¶
Configure your project's Makefile with the appropriate APP_TYPE:
TARGET = my_synth
APP_TYPE = BOOT_SRAM
CPP_SOURCES = main.cpp
LIBDAISY_DIR = ../../libDaisy
DAISYSP_DIR = ../../DaisySP
include $(LIBDAISY_DIR)/core/Makefile
APP_TYPE |
Target Memory | Max Size | Flashing Command |
|---|---|---|---|
BOOT_SRAM (Recommended) |
QSPI (0x90040000) -> AXI SRAM (0x24000000) |
~480 KB | make program-dfu |
BOOT_QSPI |
Direct QSPI Flash execution | ~8 MB | make program-dfu |
BOOT_NONE |
Internal Flash (0x08000000) |
128 KB | make program-dfu or OpenOCD |
5.1 Build Verification & Flashing¶
# 1. Compile firmware and verify memory region allocations:
make clean && make
# 2. Flash application to device via USB DFU:
make program-dfu
To install the Daisy Bootloader onto a fresh board:
6. Common Peripheral Patterns¶
6.1 Analog Potentiometer with Curve Mapping¶
#include "daisy_seed.h"
using namespace daisy;
DaisySeed hw;
AnalogControl knob;
Parameter cutoff_param;
int main(void)
{
hw.Init();
AdcChannelConfig adc_cfg;
adc_cfg.InitSingle(seed::A0);
hw.adc.Init(&adc_cfg, 1);
hw.adc.Start();
knob.Init(hw.adc.GetPtr(0), hw.AudioCallbackRate());
cutoff_param.Init(knob, 20.0f, 20000.0f, Parameter::Curve::EXPONENTIAL);
while (1)
{
System::Delay(1);
knob.Process();
float cutoff_hz = cutoff_param.Process();
}
}
6.2 Debounced Switch¶
Switch btn;
// Pin, update rate in Hz, momentary/latching, normal/inverted (active LOW)
btn.Init(seed::D0, 1000.0f, Switch::Type::TYPE_MOMENTARY, Switch::Polarity::POLARITY_INVERTED);
// Inside main loop (1 kHz):
btn.Debounce();
if (btn.RisingEdge())
{
// Button just pressed
}
6.3 USB CDC Serial Logging¶
[!CAUTION] libDaisy Logger Blocking Hazard:
Logger<LOGGER_EXTERNAL>starts in non-blocking mode, but automatically transitions to synchronous blocking mode (LOGGER_SYNC_IN) after successfully transmitting 2 packets. Once in sync mode,TransmitSync()runswhile(false == impl_.Transmit) {}with no timeout. If a host terminal or test script disconnects or stops reading, any subsequent log line in the main loop spins indefinitely in this loop. Because the main thread freezes while the audio DMA interrupt continues, the synth locks up with a note stuck playing continuously.[!IMPORTANT] Gamma: the USB-C jack is wired to the Seed's external USB pins (
D29/D30).hw.StartLog()/hw.PrintLine()target the internal port and never enumerate. Always useUsbHandle::FS_EXTERNALand a deadlock-immune non-blocking logger with a microsecond-level timeout (System::GetUs()) and failure bypass (seegamma-pinoutskill §8 andfirmware/phase5_audio/main.cpp). Never print from a USB receive callback (ISR deadlock).
hw.Init();
UsbLog::StartLog(false); // Custom non-blocking logger on FS_EXTERNAL
while (1)
{
System::Delay(500);
UsbLog::PrintLine("Daisy running, Tick: %d", System::GetTick());
}
6.4 Non-Volatile Preset Storage (PersistentStorage)¶
struct MyConfig
{
float volume;
int channel;
bool operator==(const MyConfig& o) const { return volume == o.volume && channel == o.channel; }
bool operator!=(const MyConfig& o) const { return !(*this == o); }
};
PersistentStorage<MyConfig> storage(hw.qspi);
MyConfig defaults = { 0.8f, 1 };
int main(void)
{
hw.Init();
storage.Init(defaults);
MyConfig& current = storage.GetSettings();
current.channel = 3;
storage.Save(); // Writes to QSPI flash wear-leveled sector
}
7. Troubleshooting & Diagnostics¶
| Symptom | Probable Cause | Corrective Action |
|---|---|---|
| Audio glitches / crackling | Callback overrun or blocking call in ISR | Use CpuLoadMeter to measure budget. Move all I2C, SPI, OLED, and flash operations to the main while(1) loop. |
| Synth freezes with note stuck playing | libDaisy's Logger entered blocking TransmitSync after USB host closed/disconnected |
Replace Logger<LOGGER_EXTERNAL> with a non-blocking timeout-guarded logger (System::GetUs() < 500 us timeout). See §6.3. |
| ADC / SPI data corrupted or stale | CPU D-cache reading stale memory | Decorate DMA buffers with DMA_BUFFER_MEM_SECTION (places them in non-cached SRAM1). |
| HardFault on startup | Accessing uninitialized SDRAM or DMA from DTCM | Ensure global SDRAM arrays use DSY_SDRAM_BSS without static constructors. Do not route DMA to DTCM_MEM_SECTION. |
| Firmware rejected by bootloader | Mismatched linker script / vector table | Verify APP_TYPE = BOOT_SRAM is set in the Makefile. |
| I2C OLED hangs on soft reset | SDA line held LOW by display from previous run | Force reset I2C peripheral (__HAL_RCC_I2C1_FORCE_RESET) and clock out dummy SCL pulses before initializing. |
8. Cross-Reference Documentation¶
- Detailed Architecture Guide:
docs/LIBDAISY_GUIDE.md - libDaisy Source Tree:
libDaisy/src/ - libDaisy Examples:
libDaisy/examples/