CircuitPython support files for the Disaster Area Designs MIDI Baby Gen4. Running
CircuitPython on a MIDI Baby turns it into an easily reprogrammable MIDI controller: you edit
code.py in a text editor, save, and the pedal restarts with the new behavior — no toolchain,
no compiler, no programmer.
| Path | What it is |
|---|---|
MB1_CIRCUITPY/ |
Complete drive image for the 1-switch MIDI Baby (demo program) |
MB3_CIRCUITPY/ |
Complete drive image for the 3-switch MIDI Baby 3 (demo program) |
custom/MB1_cc_scroll/ |
Customer program: scroll CC 10→33, hold to reset (1-switch) |
custom/MB1_cc_scroll/MB1_cc_scroll_2MB.uf2 |
One-drag deployable image of that program, built for 2 MB units |
firmware/adafruit-circuitpython-weact_studio_pico_16mb-en_US-9.1.3.uf2 |
CircuitPython 9.1.3 UF2 |
The MIDI Baby Gen4 is built on a WeAct Studio Pico 16MB (RP2040). CircuitPython board ID
weact_studio_pico_16mb. Both drive images were built with Adafruit CircuitPython 9.1.3
(2024-08-29).
| Function | Pin |
|---|---|
| MIDI output (serial) | UART0 TX = GP0, 31250 baud |
| RGB LED | GP25 — NeoPixel, pixel_order=(0, 1, 2) (RGB, not the usual GRB) |
| Footswitch | GP26 — active LOW, use Pull.UP |
| Jack tip | GP28 |
| Jack ring | GP29 |
| I²C EEPROM | address 0x50 on I2C0 — SDA GP2, SCL GP3. 64 kbit / 4096 bytes |
| USB host | D+ GP6, D− GP7 — not supported by CircuitPython |
| Function | Pin |
|---|---|
| MIDI output (serial) | UART1 TX = GP8, 31250 baud |
| RGB LEDs | GP25 — 3 NeoPixels in a chain, pixel_order=(0, 1, 2) |
| Footswitch 1 (left) | GP20 — active LOW |
| Footswitch 2 (middle) | GP19 — active LOW |
| Footswitch 3 (right) | GP18 — active LOW |
Note the switch order: in MB3_CIRCUITPY/code.py the button list is built as
[GP20, GP19, GP18] so that index 0 is the left switch.
Both variants send MIDI to USB and the serial MIDI jack simultaneously — every program here
builds two adafruit_midi.MIDI objects and sends each message to both.
- Hold the MIDI Baby's footswitch (middle switch on the MIDI Baby 3) while plugging in USB, and keep holding through the red/green/blue startup flash. The LED turns magenta (cyan on the MB3) and the pedal reboots into the RP2040 UF2 bootloader as a drive named RPI-RP2. This works only if CircuitPython is already loaded — see below if the pedal is running stock firmware.
- Copy
firmware/adafruit-circuitpython-weact_studio_pico_16mb-en_US-9.1.3.uf2onto RPI-RP2. The pedal reboots as a drive named CIRCUITPY. - Copy the contents of
MB1_CIRCUITPY/(orMB3_CIRCUITPY/) onto CIRCUITPY —code.py,lib/,settings.toml, andsd/. Overwrite anything already there.
If the pedal is running stock MIDI Baby firmware, get to the UF2 bootloader the hardware way: hold the BOOT button on the WeAct Pico while applying power, or use the normal MIDI Baby firmware-update procedure.
Every program in this repo checks the footswitch immediately after the startup LED flash and, if
it is held, calls microcontroller.on_next_reset(RunMode.BOOTLOADER) and reboots. That drops the
pedal into RPI-RP2 so you can drag the normal MIDI Baby firmware .uf2 back on — without
opening the enclosure. Keep this block in any program you write, or you will need a
screwdriver to get at the BOOT button.
CIRCUITPY is a normal USB drive. Open code.py, edit, save — CircuitPython restarts the
program automatically. The serial console (115200 baud on the pedal's USB CDC port, or the
Mu editor) shows print() output and tracebacks.
lib/ holds the required .mpy modules: adafruit_midi/, adafruit_pixelbuf.mpy,
neopixel.mpy. They must match the CircuitPython major version (9.x here); if you upgrade
CircuitPython, pull the matching bundle from
https://circuitpython.org/libraries.
- Tap: Program Change 0→1→2→3→0 on channel 1. LED red / green / blue / white.
- Hold 0.5 s: CC 102, alternating value 0 and 127. LED dims for 0, brightens for 127.
- Each switch sends Program Change 0 / 1 / 2 on channel 1 and lights its own LED blue.
Scrolls through a block of CC numbers, one per tap, with a hold to jump back to the start.
- Tap: send the next CC in the range, value 1, on MIDI channel 1. The sequence is CC 10, 11, 12 … 33, then rolls over to CC 10 again. The first tap after power-up sends CC 10.
- Hold 2 s: return to the first step and re-send CC 10 value 1. The LED flashes white to confirm. Releasing after a hold does not send another CC.
- LED: each of the 24 steps gets its own color from a color wheel, so the position in the sequence is visible at a glance. The LED is dark until the first tap.
The tap message is sent on switch release, not on press. That is deliberate: if the tap fired on press-down, holding the switch would send an unwanted scroll CC before the reset CC 10.
To install, copy the contents of custom/MB1_cc_scroll/ (code.py, lib/, settings.toml)
onto a CIRCUITPY drive that already has CircuitPython 9.1.3 on it.
The range, value, channel and hold time are constants at the top of the file:
CC_FIRST = 10 # first CC number in the scroll
CC_LAST = 33 # last CC number, then it wraps
CC_VALUE = 1 # value sent with every CC
MIDI_CHANNEL = 0 # 0 = MIDI channel 1
HOLD_TIME = 2.0 # seconds to hold for "back to first step"MIDI_CHANNEL is zero-based: 0 is MIDI channel 1, 15 is MIDI channel 16.
Production MIDI Baby Gen4 units have 2 MB of flash. Do not deploy a flash image captured from a 4 MB development board to them.
CircuitPython sizes the CIRCUITPY filesystem from the flash size it detects at first boot, not from the board name in the build. Measured on the same 4 MB dev unit:
| Build flashed | CIRCUITPY drive it created |
|---|---|
weact_studio_pico_16mb 9.1.3 |
3.0 MB (FAT at flash 0x100000–0x400000) |
raspberry_pi_pico 9.1.3 |
3.0 MB (same geometry) |
Both builds produced an identical 3 MB filesystem, so switching to the 2 MB Pico build does not change the captured geometry — the geometry comes from the chip. Any full-flash UF2 taken from a 4 MB unit embeds a FAT that declares 3 MB starting 1 MB in. On a 2 MB part every address at or above 2 MB wraps to the start of flash and aliases onto the firmware, so that image would eventually let the pedal overwrite its own firmware. A 2 MB unit running the same firmware builds itself a 1 MB filesystem instead.
Two steps, and the board builds a filesystem that fits whatever flash it actually has:
- Put the pedal in the UF2 bootloader (hold the footswitch through power-up until the LED turns magenta; on stock firmware use the normal firmware-update procedure). It appears as RPI-RP2.
- Drag the CircuitPython UF2 onto RPI-RP2. The pedal reboots as CIRCUITPY, formatted to the correct size for its own flash.
- Copy
code.py,lib/andsettings.tomlfromcustom/MB1_cc_scroll/onto CIRCUITPY.
The CircuitPython UF2 contains firmware only — no filesystem — so it is safe on any flash size.
firmware/adafruit-circuitpython-weact_studio_pico_16mb-en_US-9.1.3.uf2 is in this repo; the
2 MB raspberry_pi_pico build of the same version is at
https://downloads.circuitpython.org/bin/raspberry_pi_pico/en_US/adafruit-circuitpython-raspberry_pi_pico-en_US-9.1.3.uf2.
custom/MB1_cc_scroll/MB1_cc_scroll_2MB.uf2 is a one-drag image built against 2 MB geometry,
so it is safe on production units. It contains:
| Region | Flash range | Contents |
|---|---|---|
| Firmware | 0x10000000 – 0x100D2E00 |
CircuitPython 9.1.3 raspberry_pi_pico build |
| Filesystem | 0x10100000 – 0x10200000 |
1 MB FAT12 holding code.py, lib/, settings.toml, sd/ |
Deploy: get the pedal to RPI-RP2 (hold the footswitch through power-up until the LED turns magenta), drag the file on, done.
Writing the full 1 MB filesystem region also wipes whatever filesystem the unit had before, so units come out clean rather than inheriting stale files.
Verified on 4 MB hardware. The image was flashed to a 4 MB dev unit, which mounted the 1 MB
filesystem as-is (a 1.0 Mi CIRCUITPY drive — CircuitPython did not reformat it up to the 3 MB
its own flash would allow), kept code.py byte-identical, and ran the program: interrupting over
the USB REPL landed in the main loop at code.py:144, and a soft reboot came back with a clean
code.py output: and no traceback. The 4 MB case is the harder one, since the filesystem is
smaller than the region the firmware computes for itself; on a 2 MB unit the geometry matches
exactly what the board would have built. It has not yet been run on a physical 2 MB part.
To answer the obvious question: one image covers both. A 2 MB unit gets its native layout, a 4 MB unit gets a 1 MB drive instead of 3 MB. Nothing needs to be branched by flash size.
# 1. 1 MB FAT12 filesystem image
dd if=/dev/zero of=fs2mb.img bs=1m count=1
dev=$(hdiutil attach -nomount -imagekey diskimage-class=CRawDiskImage fs2mb.img | awk '{print $1}')
newfs_msdos -F 12 -S 512 -v CIRCUITPY "$dev"
diskutil mount "$dev"
cp -R code.py lib settings.toml "/Volumes/CIRCUITPY" # plus sd/placeholder.txt
diskutil unmount "$dev"; hdiutil detach "$dev"
# 2. Merge firmware UF2 blocks with the filesystem at 0x10100000, family 0xe48bff56,
# renumbering blockNo / numBlocks across the whole file.Do not build this by dumping a programmed 4 MB board with picotool save — that captures the
3 MB filesystem geometry and is unsafe on 2 MB units.
picotool info segfaults with picotool 2.3.0 on macOS (null deref in the info path). save,
verify, load and erase all work.
picotool save -r 0x10000000 0x10127000 image.uf2 # capture used flash
picotool verify image.uf2 # check against device
picotool erase -r 0x10000000 0x10200000 # clean the low 2 MB
picotool load adafruit-circuitpython-...uf2 # flash firmware- USB host is not usable from CircuitPython. The MIDI Baby's USB host port (GP6/GP7) needs the PIO USB host stack, which CircuitPython does not expose for MIDI. Use the stock firmware if you need USB host.
- The
sd/placeholder.txtfile in each image is a reminder that an SD card mounted at/sdhides that path from Python and is not visible over USB. settings.tomlis empty by default; use it forCIRCUITPY_*settings such as renaming the USB drive or enabling the web workflow.