Gamma USB Connectivity Reference¶
This skill documents how to interface with the this.is.NOISE Gamma Mini Synth over USB using its embedded Electro-Smith Daisy Seed 2 DFM (ARM Cortex-M7 @ 480 MHz, STM32H750IBK6).
It includes:
- Hardware & USB CDC Architecture: Device identifiers, Full-Speed peripheral configuration, and host port enumeration.
- Deadlock Immunity & Real-Time Audio Protection: Eliminating the blocking hazards inherent in standard libDaisy USB logging.
- Firmware Sample Code: Drop-in C++ driver
gamma_usb.hand implementation patterns. - Host Python Communication Script: Zero-dependency cross-platform utility
gamma_usb.pyfor testing, interactive control, monitoring, and DFU bootloader rebooting.
1. Hardware & Peripheral Overview¶
The Gamma Mini Synth routes the Daisy Seed 2 DFM's Full-Speed USB peripheral to the external USB port on the chassis.
- Daisy Peripheral ID:
UsbHandle::FS_EXTERNAL - Protocol: USB CDC ACM (Communications Device Class / Virtual COM Port)
- USB Speed: Full-Speed (12 Mbps)
- USB Vendor ID (VID):
0x0483(STMicroelectronics / Electrosmith) - USB Product ID (PID):
0x5740(Virtual COM Port) - USB Product String:
"Daisy Seed External" - USB Manufacturer:
"Electrosmith" - Default Baud Rate:
115200(virtual; physical throughput runs at full USB FS packet speed)
Host Port Identification¶
| Operating System | Device Path Pattern | Example |
|---|---|---|
| macOS | /dev/cu.usbmodem<serial> |
/dev/cu.usbmodem3067366D34331 |
| Linux | /dev/ttyACM<n> or /dev/serial/by-id/*Daisy* |
/dev/ttyACM0 |
| Windows | COM<n> |
COM5 |
2. Embedded Real-Time Constraints & Deadlock Hazards¶
When integrating USB CDC serial on real-time synthesis firmware running SAI audio interrupts (e.g. 48 kHz, block size 48), standard blocking patterns can crash or stall the firmware.
Hazard 1: libDaisy Logger<LOGGER_EXTERNAL> Infinite Spinloop¶
libDaisy's built-in daisy::Logger<LOGGER_EXTERNAL> uses TransmitSync(). If a host terminal is disconnected, closed, or stops reading the USB CDC IN endpoint:
TransmitSync()enters an infinitewhile(1)waiting for the USB TX-complete flag.- The main loop freezes indefinitely.
- User controls (knobs, keys, OLED) lock up, and while audio interrupts may continue in hardware, no control updates or state changes can occur.
Hazard 2: USB Interrupt Deadlock via UsbRxCallback¶
When the host transmits a byte to the synth, libDaisy invokes the registered UsbRxCallback in the USB ISR context:
- Calling any blocking log function (like
hw.PrintLineorTransmitSync) from withinUsbRxCallbackcauses an immediate, permanent deadlock: the TX-complete interrupt cannot fire while still servicing the RX interrupt. - Rule: The USB RX callback must never format or transmit data. It must only append incoming bytes into a lock-free ring buffer for consumption by the main thread.
The Solution: Non-Blocking Multi-Buffer Pool with Timeout & Backoff¶
The gamma_usb.h driver resolves this with:
- Multi-Buffer TX Pool: Rotates across 4 static buffers to avoid clobbering in-flight packets.
- 500 µs Maximum Wait: If the host is not actively polling, TX drops after 500 µs rather than stalling the frame loop.
- 200 ms Fast-Fail Backoff: If a transmission times out, subsequent print calls try once without waiting for 200 ms, preventing delay accumulation when no terminal is connected.
- Lock-Free RX Queue: Interrupt-safe single-producer single-consumer ring buffer processed in
UsbComm::Process().
3. Firmware Integration & Sample Code¶
Drop-In Header¶
Copy gamma_usb.h into your firmware directory:
#include "daisy_seed.h"
#include "gamma_usb.h"
using namespace daisy;
using namespace gamma_usb; // NOT `gamma`: that name clashes with libm's gamma() function
static DaisySeed hw;
static GPIO spk_en; // PC3 speaker amp enable
// Dispatch host ASCII commands
void MyCommandHandler(char c)
{
switch(c)
{
case 'm':
case 'M':
UsbComm::PrintLine("[CMD] Mode toggled!");
break;
case 'w':
case 'W':
UsbComm::PrintLine("[CMD] Waveform cycled!");
break;
case 'h':
case '?':
UsbComm::PrintLine("\n--- Gamma Synth USB Commands ---");
UsbComm::PrintLine(" m : Toggle mode");
UsbComm::PrintLine(" w : Cycle waveform");
UsbComm::PrintLine(" b : Reboot into DFU bootloader");
UsbComm::PrintLine(" h : Print this help\n");
break;
default:
break;
}
}
// Optional: runs just before the reboot into the bootloader
void BeforeBootloader()
{
spk_en.Write(false); // Mute the speaker amp to avoid a pop
hw.StopAudio();
// ...draw a "DFU MODE" screen here: the OLED keeps its last frame in the bootloader
}
int main(void)
{
hw.Init();
// Initialize USB CDC communications and register command callback
UsbComm::Init(hw, MyCommandHandler);
UsbComm::SetBootloaderHook(BeforeBootloader);
UsbComm::PrintLine("Gamma Synth Initialized. Ready for commands.");
while(1)
{
// Must be called periodically in the main loop to dispatch queued commands
UsbComm::Process();
// Application tasks (display update, control sampling, etc.)
System::Delay(1);
}
}
[!NOTE] The
'b'(bootloader) command is intercepted automatically insideUsbComm::Process(), which callsUsbComm::RebootToBootloader(). You can also call that from your own code, e.g. on a long press of the encoder. It runs the optional bootloader hook and then callsSystem::ResetToBootloader(System::DAISY_INFINITE_TIMEOUT), so you can reflash over USB without pressing the physical RESET + BOOT buttons. The Daisy bootloader then waits until new firmware arrives.[!WARNING] Never call
System::ResetToBootloader()with no argument on the Gamma. It defaults toBootloaderMode::STM, the STM32 ROM bootloader. That also shows up as0483:df11, but it can only program internal flash, so flashing aBOOT_SRAMapp to0x90040000fails. Always pass aDAISY_*mode.[!NOTE] The header is C++14-compatible (libDaisy builds with
-std=gnu++14) and can be included from multiple.cppfiles.
4. Python Host Tool: gamma_usb.py¶
The repository provides a standalone Python script located at:
gamma_usb.py
It requires no external packages (uses POSIX termios/select on macOS and Linux) and automatically falls back to pyserial if installed.
Common Commands¶
1. Automated Bidirectional Self-Test¶
Runs a diagnostic verification test sending commands, checking responses, and measuring round-trip integrity:
2. Send Specific Commands¶
Send a single ASCII command (e.g. 'h' for help menu, 'w' for waveform cycle):
python3 .agents/skills/gamma-usb-connectivity/scripts/gamma_usb.py --cmd "h"
python3 .agents/skills/gamma-usb-connectivity/scripts/gamma_usb.py --cmd "w"
3. Monitor Serial Stream¶
Listen to real-time firmware logs, diagnostic telemetry, and event messages:
4. Interactive Terminal Mode¶
Provides a raw bidirectional serial terminal where keystrokes are transmitted instantly and synth responses are rendered live:
(Press Ctrl+C or Ctrl+] to exit).
5. Remote DFU Bootloader Reboot¶
Instructs the firmware over USB to reboot into the Daisy DFU bootloader (not the STM32 ROM bootloader) for immediate flashing with dfu-util or flash_firmware.py:
5. Diagnostic & Troubleshooting Checklist¶
| Symptom | Probable Cause | Corrective Action |
|---|---|---|
Port /dev/cu.usbmodem* not appearing |
USB cable is power-only or device is in DFU mode | Verify data cable. Run ioreg -p IOUSB -w0 -l to check if Daisy Seed External (VID 0x0483, PID 0x5740) appears. |
Port appears as DFU in FS Mode |
Device is in bootloader, not running application | Flash valid application firmware using gamma-firmware-flash. |
| Firmware hangs when USB host disconnects | Firmware uses standard daisy::Logger |
Replace Logger<LOGGER_EXTERNAL> with gamma_usb.h or the non-blocking UsbLog struct. |
| Crash / HardFault on receiving USB byte | Print/TX function called within UsbRxCallback |
Buffer incoming characters into an ISR-safe queue and process them in the main while(1) loop. |
| Intermittent dropped characters | RX buffer size too small | Increase kRxQueueSize in gamma_usb.h (default is 128 bytes). |
dfu-util fails writing 0x90040000 after 'b' |
Firmware called System::ResetToBootloader() with no argument and entered the STM32 ROM bootloader, not the Daisy bootloader |
Use UsbComm::RebootToBootloader() or pass System::DAISY_INFINITE_TIMEOUT. If the device is stuck in ROM DFU, tap RESET to get back to the Daisy bootloader. |
'namespace gamma' redeclared as different kind of entity |
gamma clashes with libm's gamma() function |
Use the gamma_usb namespace from the current gamma_usb.h. |