Skip to content

Bambu duck: support 'standard XIAO + cobbled parts' build alongside ducky PCB #49

Description

@dderuntz

We have two hardware targets in active use:

  1. Ducky PCB (current default) — WROOM-1 module on custom PCB. ICS-43432 mic + MAX98357 amp on a single shared I2S bus (full-duplex, shared BCLK + WS, only data pins differ). PCB antenna. WS2812B status LED.

  2. Standard XIAO Seeed S3 + cobbled parts — stock XIAO S3 dev board with external ICS-43434 mic and MAX98357 amp on separate breakouts. Two independent I2S ports (mic on I2S0, speaker on I2S1, each with its own clocks). Different pin map. No status LED.

The cobbled build is what one of our 4 target users has. Also valuable for the open-source/DIY narrative — most people building this from parts won't have the PCB.

Diff between targets

Ducky XIAO
Mic ICS-43432, BCLK 13, WS 12, SD 1 ICS-43434, BCLK D8, WS D10, SD D9
Spk MAX98357, shares BCLK 13 + WS 12, DIN 7 MAX98357, BCLK D1, WS D0, DIN D2
I2S topology One port, full-duplex Two ports, separate clocks
Button GPIO 2 D5
Servo GPIO 3 D3
LED WS2812B GPIO 38 (none)

Source of truth for XIAO build's pin assignments + topology: firmware/rubber_duck_s3/Config.h (lines 32-65, MIC_TYPE 2 / I2S_NUM_1 split).

Implementation

bambu/firmware/main/config.h — add a variant selector. Default is ducky (no behavior change for existing flow).

#ifndef DUCK_VARIANT_XIAO
  // Ducky PCB — full-duplex I2S on one port
  #define AUDIO_I2S_DUPLEX 1
  // ... existing pin block unchanged
#else
  // Standard XIAO — split I2S, two ports
  #define AUDIO_I2S_SPLIT 1
  #define MIC_I2S_PORT  0
  #define SPK_I2S_PORT  1
  #define I2S_PIN_MIC_BCLK  D8
  #define I2S_PIN_MIC_WS    D10
  #define MIC_PIN_SD        D9
  #define I2S_PIN_SPK_BCLK  D1
  #define I2S_PIN_SPK_WS    D0
  #define SPK_PIN_DIN       D2
  #define BUTTON_PIN        D5
  #define SERVO_PIN         D3
#endif

bambu/firmware/main/audio.c#ifdef AUDIO_I2S_SPLIT block around the I2S channel-init path. The split case allocates two channel handles instead of one full-duplex pair. Everything past audio_mic_read / audio_spk_write stays unchanged.

Estimated 30-50 lines of forked init code in audio.c, plus the config block.

bambu/firmware/main/CMakeLists.txt — pick up DUCK_VARIANT env var:

if(DEFINED ENV{DUCK_VARIANT} AND "$ENV{DUCK_VARIANT}" STREQUAL "XIAO")
    target_compile_definitions(${COMPONENT_LIB} PRIVATE DUCK_VARIANT_XIAO=1)
endif()

Build commands (separate build dirs — no fullclean between switches):

idf.py -B build_ducky build flash                          # default
DUCK_VARIANT=XIAO idf.py -B build_xiao build flash         # XIAO

Add to a bambu/firmware/Makefile so users don't have to remember the incantation.

Out of scope

  • LED behavior on the XIAO build — ducky has WS2812B, XIAO doesn't. The LED helpers in main.c are already no-ops; leave them that way for XIAO. If we ever want a status indicator, the XIAO has the onboard user LED on a different pin — separate issue.
  • Any difference in mic gain / DC offset / scaling between ICS-43432 and ICS-43434 — both should be 24-bit signed I2S, same protocol. If audio levels differ noticeably, file a follow-up.

Acceptance

  • idf.py -B build_ducky build produces an image that boots on the ducky PCB and behaves identically to today.
  • DUCK_VARIANT=XIAO idf.py -B build_xiao build produces an image that boots on a standard XIAO + cobbled parts and runs through APSTA onboarding, captures mic, plays chirps + agent voice.
  • README + DEPLOY.md (when it exists) document both build flavors.

Triggered by: side conversation post the multi-tenant req doc — DIY users probably build from parts, not PCB.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions