Bus Pirate 6 · Volume 3

Bus Pirate 6 Volume 3 — Firmware Architecture: PIO State Machines, Mode Vtables, and the Build Tree

How the source maps to silicon — pirate/ HAL, mode/ vtables, commands/, binmode/, the four CMake targets, the .pio programs

Contents

SectionTopic
1About this volume
2The repo at a glance
3Top-level source-tree layout
· 3.1src/pirate/ — the hardware abstraction
· 3.2src/mode/ — protocol-mode vtables
· 3.3src/commands/ — global and per-mode commands
· 3.4src/binmode/ — BBIO legacy and BPIO2 modern
· 3.5src/display/, toolbars/, ui/, font/, translation/
· 3.6src/dhara/, src/fatfs/, src/nand/ — on-board filesystem
· 3.7src/boards/, src/platform/, src/cmake/
4The syntax compiler / runner
· 4.1syntax_compile.c — text to bytecode
· 4.2syntax_run.c — bytecode to vtable calls
· 4.3syntax_post.c — formatting output for the terminal
· 4.4Why this separation matters
5The mode vtable
· 5.1Callbacks: start/stop/write/read/clkh/clkl/dath/datl/period
· 5.2Implementation choice: PIO vs hardware peripheral vs bit-banged GPIO
· 5.3Why JTAG/SWD is bit-banged in C, not PIO
6PIO programs
· 6.1The .pio file catalog
· 6.2How a .pio program becomes a state machine
· 6.3State-machine budget on RP2350 — 12 SMs across 3 PIO blocks
· 6.4The look-behind sampler — a dedicated PIO SM on PIO 2
7The four CMake build targets
· 7.1bus_pirate5_rev8 — engineering sample
· 7.2bus_pirate5_rev10 — production BP5
· 7.3bus_pirate5_xl — BP5XL with RP2350A
· 7.4bus_pirate6 / bus_pirate6_rev2 — BP6 REV2 with RP2350B
8Building from source
· 8.1Pico SDK + ARM GCC versions
· 8.2The hermetic Docker compose build
· 8.3Windows: the pico-sdk submodule gotcha
· 8.4Reading the build log
9Release cadence and the auto-build forum thread
10Recent main-branch activity around commit 93aefde
11Cheatsheet updates for Vol 12

1. About this volume

This volume walks the Bus Pirate firmware source tree — what’s in it, how the pieces fit together, and what changes when you swap board variants. Read this if you’re going to build the firmware from source, write a custom mode, port to a new board, or just want to know what the firmware is actually doing when you type a command at the BP6 CLI.

The repo is github.com/DangerousPrototypes/BusPirate5-firmware — note the name retained “5” but the tagline reads “Bus Pirate Firmware for v5 and above”. The same source tree builds the BP5 REV8, BP5 REV10, BP5XL, and BP6 REV2. License: MIT, with optional LGPL3 components (legacy ANSI color codes) disable-able at build time with -DUSE_LGPL3=NO.

Maintainers: Ian Lesnet ([email protected]) as primary, with active community contributors via pull requests. Issues + PRs go through GitHub; design discussion happens at forum.buspirate.com. There is no separate firmware repo for the BP6 — one tree, one main branch, all boards.


2. The repo at a glance

Top-level structure of the repo as of commit 93aefde (Apr 2026):

BusPirate5-firmware/
├── src/                      ← all C source
├── boards/                   ← per-board pin maps and config
├── platform/                 ← per-MCU platform code (rp2040 vs rp2350)
├── cmake/                    ← CMake helper modules
├── attic/                    ← retired / engineering-sample stuff
├── docker-compose.yml        ← hermetic-build container
├── CMakeLists.txt            ← root build script
├── README.md
└── LICENSE

The build invokes CMakeLists.txt in the root, which fans out into the four board-specific build trees (bus_pirate5_rev8, bus_pirate5_rev10, bus_pirate5_xl, bus_pirate6 / bus_pirate6_rev2). The output is one .uf2 per board — you flash the one that matches your hardware (see Vol 1 § 6.1 for the bootloader entry procedure).

Build artifacts go in a sibling build_rp2040/ or build_rp2350/ directory, picked by which MCU family the target board uses. RP2040-targets and RP2350-targets cannot share a build directory because the Pico SDK is configured differently per family.


3. Top-level source-tree layout

The src/ directory is the meat of the firmware. Here’s the directory-level map.

3.1 src/pirate/ — the hardware abstraction

The Hardware Abstraction Layer (HAL). Every silicon-specific piece of code lives here:

  • bio.c / bio.h — buffered I/O pin control. Wraps the per-pin 1T45 chain (Vol 2 § 4): direction, drive level, pull-up enable, voltage read.
  • pullup.c — pull-up state machine. Translates “enable pull-up on IO3” into “drive the PFET gate GPIO low” plus state-tracking so the firmware knows which pins have pull-ups active.
  • psu.c — PSU control. Sets the buck-regulator’s feedback divider via I²C/SPI, reads back current draw, manages enable/disable.
  • amux.c — the CD4067 analog mux (Vol 2 § 7). Walks the 4 address-select GPIOs, waits for settling, reads the analog output via ADC.
  • button.c — the single SPST button. Press / hold / release event generation.
  • rgb.c — the SK6812 chain. Wraps the WS2812-PIO state machine; exposes per-LED color set.
  • lcd.c — the ST7789V display driver. SPI command sequencing, frame-buffer management, the status-bar rendering loop.
  • storage.c — file ops on the on-board NAND. Calls down into FatFS (§ 3.6).
  • amux.c, bio.c, pullup.c, psu.c together form the probe-pin abstraction: every protocol mode talks to the pins through this layer rather than touching RP2350B GPIOs directly. This is what makes the same mode code work across BP5 / BP5XL / BP6 — the per-board pin map (in boards/) tells the HAL which GPIOs are which, and the mode code stays the same.
  • All .pio source files also live here (hwuart.pio, hwi2c.pio, etc.). The PIO programs are silicon-specific but they’re conceptually HAL-level — protocols are implemented “in” the PIO; the C code orchestrates the state machines.

3.2 src/mode/ — protocol-mode vtables

One pair of hw<proto>.{c,h} files per protocol mode:

src/mode/
├── hwuart.c     hwuart.h
├── hwhduart.c   hwhduart.h
├── hwi2c.c      hwi2c.h
├── hwspi.c      hwspi.h
├── hw1wire.c    hw1wire.h
├── hw2wire.c    hw2wire.h     ← used for SLE4442 smart-card timing
├── hw3wire.c    hw3wire.h     ← Microwire 93-series EEPROM
├── jtag.c       jtag.h        ← also serves SWD via subcommand
├── hwled.c      hwled.h
├── infrared.c   infrared.h
├── hwi2s.c      hwi2s.h
├── dio.c        dio.h         ← generic GPIO mode
├── hiz.c        hiz.h         ← all outputs disabled — the safe boot mode
└── binloopback.c              ← internal test mode

Each mode file implements the mode vtable (§ 5) — a set of standardized callbacks the syntax runner calls. The mode is free to implement those callbacks however it wants (PIO state machine, hardware peripheral, bit-banged GPIO).

3.3 src/commands/ — global and per-mode commands

Top-level commands (the things you type at the prompt). Layout:

src/commands/
├── global/      ← commands valid in any mode (W, V, P, $, #, ?, scan, etc.)
└── <mode>/      ← per-mode commands (flash for SPI, sle4442 for 2-wire, etc.)

Examples:

  • commands/global/hash.c — implements the # line-comment behavior (commit 93aefde, Apr 2026)
  • commands/global/scan.c — the I²C scan command (works in I²C mode; lives in global because it’s a discovery command)
  • commands/spi/flash.c — the flash dump|verify|write workflow for 25-series NOR
  • commands/i2c/eeprom.c — the I²C eeprom workflow for 24-series EEPROMs
  • commands/jtag/bluetag.c — the JTAG/SWD pin-finder
  • commands/jtag/openocd.c — the OpenOCD bridge protocol
  • commands/i2c/ddr5.c / commands/i2c/ddr4.c — DDR SPD readout
  • commands/2wire/sle4442.c — SLE4442 smart-card auth + read/write

This separation is the reason flash dump and eeprom read are commands rather than modes: they’re workflows that orchestrate the underlying mode (SPI or I²C). The mode does the bus-level work; the command does the chip-level work.

3.4 src/binmode/ — BBIO legacy and BPIO2 modern

The binary protocols for host-side automation (full Vol 10 walk; this is the architecture-level summary).

src/binmode/
├── bbio.c          ← legacy 0x01-SPI / 0x02-I²C / etc. single-byte commands
├── bpio2.c         ← modern FlatBuffers-over-COBS
├── sump.c          ← Sigrok SUMP protocol (logic-analyzer compat)
├── fala.c          ← FALA logic-analyzer protocol
└── irtoy.c         ← IR Toy compat layer

The binmode command at the CLI offers a menu — pick BBIO (option 1), BPIO2 (option 2), SUMP (option 3), FALA (option 4), IR Toy (option 5). The selected handler takes over the USB-CDC serial channel and the CLI hands off to it.

3.5 src/display/, toolbars/, ui/, font/, translation/

  • display/ — the ST7789V drawing primitives (lines, rects, text rendering).
  • toolbars/ — the VT100 status-bar logic (and the LCD mirror).
  • ui/ — the menu-bar with F-key support (added Mar 2026), the toolbar focus system.
  • font/ — bitmap fonts for the LCD.
  • translation/ — i18n strings. The firmware supports English plus a community-contributed set of other languages.

This is the second largest source area after pirate/. UI work has dominated the Feb-Apr 2026 commit history (see § 10).

3.6 src/dhara/, src/fatfs/, src/nand/ — on-board filesystem

The three-layer filesystem stack (Vol 2 § 8):

  • nand/ — raw NAND I/O (page read, page write, block erase, bad-block detection).
  • dhara/ — the Dhara FTL (vendored from dhara open-source). Maps logical block addresses to physical NAND blocks with bad-block management and wear leveling.
  • fatfs/ — FatFS (the canonical embedded FAT16/32 implementation, vendored). Sits on top of Dhara and presents the FAT filesystem the host’s USB MSC stack consumes.

This layering means corrupted media is recoverable (format re-initializes Dhara + FatFS), bad blocks are transparent to the user, and the host sees a normal USB drive without knowing NAND is underneath.

3.7 src/boards/, src/platform/, src/cmake/

The board-portability glue:

  • boards/<board_name>.h — pin map and config for each board. bus_pirate5_rev10.h, bus_pirate5_xl.h, bus_pirate6_rev2.h. The HAL (src/pirate/) reads these to know which GPIO is connected to what.
  • platform/<mcu_family>/ — per-MCU-family code. Differs between RP2040 (BP5 REV8/REV10) and RP2350 (BP5XL/BP6).
  • cmake/ — CMake helper modules. Mostly Pico SDK glue.

To port the BP firmware to a custom board, you write a new boards/your_board.h with your pin map, possibly add board-specific quirks in platform/, register the board in the root CMakeLists.txt, and run the build with -DBUILD_TARGET=your_board. The vast majority of the firmware doesn’t change.


4. The syntax compiler / runner

When you type [0x90 r:4 0x91 r:4] at the prompt, three files do the work. They live at src/ root, not in a subdirectory — they’re foundational enough to be top-level.

4.1 syntax_compile.c — text to bytecode

This is the parser. It takes the input string and produces a bytecode array. Tokens:

  • [ / ] — START / STOP transaction (compile to BC_START / BC_STOP)
  • > — execute without START (BC_EXEC)
  • 0x55 0b1010 123 "abc" — numeric / string literals (BC_WRITE_BYTE with the value)
  • r / r:N — read (BC_READ with repeat count)
  • :N — repeat the previous token N times (BC_REPEAT modifier on the previous opcode)
  • d / d:N — 1 µs / N µs delay (BC_DELAY_US)
  • D / D:N — 1 ms / N ms delay (BC_DELAY_MS)
  • 0x5a.4 / r.4 — partial-byte (4-bit) write / read (BC_WRITE_BITS / BC_READ_BITS)
  • # — line comment (added 2026-04-07 in commit 93aefde; everything after is discarded)
  • whitespace — numeric separator (no opcode)

The compiler is single-pass and small (~250 lines). It bails on the first parse error with a line-and-column-pointing message rendered to the terminal.

255-character line limit, defined in BC_MAX_LINE.

4.2 syntax_run.c — bytecode to vtable calls

The bytecode array is then walked by syntax_run.c, which calls the appropriate mode-vtable function for each opcode:

  • BC_STARTmode->start()
  • BC_WRITE_BYTEmode->write(byte)
  • BC_READmode->read() (loop count times if repeated)
  • BC_DELAY_US → busy-wait microseconds
  • BC_STOPmode->stop()
  • etc.

The runner doesn’t know which protocol mode is active. It calls the vtable; the mode does what makes sense for itself. This is the abstraction that lets one syntax language span all 12 protocol modes.

4.3 syntax_post.c — formatting output for the terminal

The runner returns a tagged stream of results: bytes read, bytes written, timings, errors. syntax_post.c formats that stream into a human-readable VT100-color-coded output. Output format obeys the o command (binary / decimal / hex / ASCII).

Example: a syntax line [0x90 r:4] against an I²C target produces output like:

I2C START
WRITE: 0x90 ACK
READ: 0x12 ACK
READ: 0x34 ACK
READ: 0x56 ACK
READ: 0x78 NACK
I2C STOP

The colors and word formatting are all syntax_post’s work.

4.4 Why this separation matters

The parser doesn’t know about protocols. The runner doesn’t know about syntax. The mode doesn’t know about formatting.

Changing the syntax language (adding the # line comment, for example) touches syntax_compile.c and nothing else. Adding a new protocol mode — say, CAN bus — touches src/mode/can.c and boards/<board>.h for pin assignments; doesn’t change syntax or output formatting. Adding a new output format — say, JSON-streaming for automated tests — touches syntax_post.c and nothing else.

This separation is the single most important architectural fact about the Bus Pirate firmware. It’s also why syntax_compile, syntax_run, syntax_post live at the src/ root: they’re the load-bearing pillars; everything else specializes from them.


5. The mode vtable

Every mode in src/mode/ exposes the same set of callbacks. The vtable type is defined in src/mode/modes.h (approximately — exact name varies); semantically:

struct mode_t {
    const char *name;          // "I2C", "SPI", "JTAG", etc.
    void (*setup)(void);       // called when entering the mode
    void (*cleanup)(void);     // called when leaving the mode
    void (*start)(void);       // syntax-runner: BC_START
    void (*stop)(void);        // syntax-runner: BC_STOP
    uint32_t (*write)(uint32_t data, uint8_t bits);  // BC_WRITE_BYTE / BC_WRITE_BITS
    uint32_t (*read)(uint8_t bits);                  // BC_READ / BC_READ_BITS
    void (*clkh)(void);        // explicit clock-high (some modes)
    void (*clkl)(void);        // explicit clock-low
    void (*dath)(void);        // explicit data-high
    void (*datl)(void);        // explicit data-low
    int  (*period)(void);      // measure or set period
    const command_t *commands; // per-mode commands array
};

5.1 Callbacks: start/stop/write/read/clkh/clkl/dath/datl/period

Not every mode implements every callback. The runner only calls a callback if the bytecode contains a corresponding opcode AND the mode has registered an implementation. Calling an unimplemented callback returns a “not supported in this mode” error.

For example:

  • I²C mode implements: setup, cleanup, start, stop, write, read. Doesn’t implement clkh/clkl/dath/datl directly (those are byte-level; I²C is byte-streaming).
  • JTAG mode implements: setup, cleanup, clkh, clkl, dath, datl. Doesn’t implement start/stop (JTAG has no START/STOP analog). Doesn’t implement write/read directly — JTAG operates on TDI/TDO bit streams via clkh+dath / clkh+datl sequences.
  • UART mode implements: setup, cleanup, write, read. Doesn’t implement start/stop (UART is asynchronous — each byte stands alone).
  • DIO mode implements: just setup, cleanup and clkh/clkl/dath/datl for raw bit-bang.

The bytecode opcodes are designed to span the union of all protocols. Each mode subsets what’s meaningful.

5.2 Implementation choice: PIO vs hardware peripheral vs bit-banged GPIO

A mode’s callback can be implemented three ways:

  1. PIO state machine (the preferred path for protocols with strict timing requirements): the mode loads a .pio program at setup() time, claims a state machine, and pushes/pulls data via the FIFOs. UART, I²C, SPI, 1-Wire, 2-Wire, 3-Wire, I²S, IR, LED all use this path.
  2. Hardware peripheral (when the RP2350 has a dedicated block that does the job): the mode configures the RP2350’s hardware UART, SPI, or I²C peripheral and uses it directly. This is mostly not used in the Bus Pirate firmware — the PIO path is more flexible (custom baud, custom protocols, custom error injection). The one exception is the ST7789V LCD driver, which uses the hardware SPI peripheral because the display’s protocol is generic and the hardware peripheral is fastest.
  3. Bit-banged GPIO in C (when neither PIO nor a hardware peripheral fits): the mode does direct gpio_put() / gpio_get() calls in a C loop. JTAG and SWD are the canonical examples — see § 5.3.

The choice is per-callback, not per-mode. A mode can have its start() and stop() in C (just toggling a couple of GPIOs) and its write() in PIO (where the timing matters).

5.3 Why JTAG/SWD is bit-banged in C, not PIO

JTAG and SWD are conspicuously absent from src/pirate/*.pio. They’re implemented in src/mode/jtag.c as bit-banged C loops.

Reasons:

  1. JTAG state machines are complex. TDI/TDO/TCK/TMS together encode a state machine with 16 states; the firmware needs to track state and adapt clocking. PIO state machines have ~32 instructions and a small handful of conditional ops — not enough for a real JTAG state-machine implementation.
  2. SWD’s two-line protocol (SWDIO + SWCLK) needs bidirectional SWDIO management — drive on the host’s turn, sample on the target’s turn, with explicit turnaround cycles. PIO can do this but it’s awkward; C loops with the look-behind buffer’s parallel sample (Vol 2 § 5) work cleanly.
  3. The blueTag pin-finder (Vol 7 § 2.3) needs to do speculative scans on unknown pin assignments — it tries every pin pair until it finds the JTAG combo. That’s inherently sequential C-loop logic, not a single PIO program.
  4. Speed isn’t critical. Bit-banged JTAG at the BP6’s clock rate manages roughly 100 kHz-class throughput. That’s slow for flash programming (which is why a dedicated CMSIS-DAP-class debugger wins for that workflow — Vol 11 § 6) but plenty for pin-finding, IDCODE readout, and basic recon.

The decision is documented in design discussion on the forum: PIO is for protocols where the bit-level timing matters and the per-byte behavior is deterministic. JTAG and SWD are state-machine protocols where per-byte decisions matter; that’s better expressed in C.


6. PIO programs

6.1 The .pio file catalog

The PIO source files in src/pirate/:

hwuart.pio       UART TX/RX (any baud)
hwi2c.pio        I²C START/STOP/byte cycles
hw1wire.pio      1-Wire reset + bit timing
hw2wire.pio      2-wire smart-card timing (SLE4442-specific, not I²C)
hw3wire.pio      Microwire (93-series EEPROM)
pwm.pio          PWM generation
rc5.pio          IR RC5 protocol (Philips)
rc5_2.pio        IR RC5 extended
irio.pio         Generic IR carrier modulation
apa102.pio       APA102 / DotStar addressable LED
ws2812.pio       WS2812 / SK6812 addressable LED
spisnif.pio      SPI bus sniffer (for the look-behind sampler — Vol 2 § 5)
onewire_library.pio  shared 1-Wire library routines
i2s_in.pio       I²S audio input
i2s_out.pio      I²S audio output
wavegen.pio      Arbitrary-waveform generator

Each is a small program — typically 5-30 instructions of PIO assembly. The PIO assembler (part of Pico SDK) converts these to a C array of opcodes that gets loaded onto a state machine at runtime.

6.2 How a .pio program becomes a state machine

The flow:

  1. Build time: the Pico SDK’s pioasm tool reads hwspi.pio and emits hwspi.pio.h — a C header with the program opcodes as a constant array, plus helper functions for loading and configuring it.
  2. Run time: the mode’s setup() callback calls pio_add_program() to load the opcodes onto an unused PIO block, claims a state machine with pio_claim_unused_sm(), configures its clock divider with pio_sm_set_clkdiv(), sets its IN/OUT/SIDESET pins via the per-board config, and enables it with pio_sm_set_enabled().
  3. The state machine then runs autonomously — pushing/pulling data to/from its 8-deep RX/TX FIFOs. The C code accesses those FIFOs via pio_sm_put() / pio_sm_get().
  4. DMA can drain a FIFO directly to/from RAM without CPU involvement — used for the look-behind sampler (Vol 2 § 5.3) and the LED chain.
  5. When the mode exits (cleanup()), it tears down the state machine and frees its slot.

The PIO architecture is the single most important hardware feature of the RP2040/RP2350 for a Bus Pirate. Without it, every bit-banged protocol would consume CPU cycles in a tight loop; with it, the CPU is free to handle USB, UI, and orchestration while the protocol runs autonomously.

6.3 State-machine budget on RP2350 — 12 SMs across 3 PIO blocks

The RP2350 has 3 PIO blocks, each with 4 state machines, for 12 total. Each block has its own 32-instruction program memory and 4 FIFO pairs (one per SM).

At a steady-state operating point:

  • 1-2 SMs for the active mode’s protocol (SPI, I²C, etc.)
  • 1 SM for the LED chain (SK6812)
  • 1 SM for the look-behind sampler (PIO 2, dedicated)
  • 0-2 SMs for active commands (PWM gen, freq counter, IR receive)

That’s 5-6 SMs in active use during a typical session, leaving 6-7 free for parallel work. Plenty of headroom for, say, running a SPI dump while a frequency counter watches a target clock pin while the LCD updates in the background.

6.4 The look-behind sampler — a dedicated PIO SM on PIO 2

The BP6’s look-behind sampler (Vol 2 § 5.3) is its own short PIO program loaded onto PIO block 2’s state machine 0:

.program look_behind_sample
.wrap_target
    in pins, 8        ; read 8 sample-GPIO bits in parallel
    push noblock      ; push to RX FIFO; drop if full
.wrap

That’s two instructions wrapped in an infinite loop. The state machine free-runs at the PIO clock divider’s chosen rate (typically 1-100 MHz depending on the workflow). DMA drains the FIFO to a circular SRAM buffer.

PIO 2 is reserved for this on the BP6 — no other firmware feature uses it. That’s part of why the BP6 needs 3 PIO blocks; the BP5 with 2 PIO blocks doesn’t have a spare to dedicate to this.


7. The four CMake build targets

The root CMakeLists.txt defines four board targets. Build is invoked with -DBUILD_TARGET=<target> — the rest of CMake fans out from there.

7.1 bus_pirate5_rev8 — engineering sample

The first BP5 silicon spin. Builds for RP2040. Limited production run; most users won’t have one. Source is in attic/boards/ (retired); included in the active build matrix for the rare REV8 unit still in the field.

7.2 bus_pirate5_rev10 — production BP5

The volume-shipping BP5. RP2040, 30 GPIO + 2× 74HC595 shift registers (for indicator LEDs), 2 PIO blocks / 8 SMs. The most widely-distributed Bus Pirate today.

7.3 bus_pirate5_xl — BP5XL with RP2350A

Same form factor as BP5; same PCB pinout; just the MCU swapped to RP2350A. Gets the RP2350’s improvements: 520 KB SRAM, 3 PIO blocks, hardware FPU, ARM-M33 + Hazard3 RISC-V option. But: same 30 GPIO pin count as BP5, so still uses the 74HC595 shift registers for indicators. No look-behind buffer (no spare GPIOs for it).

7.4 bus_pirate6 / bus_pirate6_rev2 — BP6 REV2 with RP2350B

The current BP6. RP2350B (the 80-pin QFN variant with 48 GPIO), no shift registers, full 74LVC1T45 per-pin + 74LVC8T245 look-behind, the works. The build alias bus_pirate6 resolves to bus_pirate6_rev2 — the only BP6 hardware spin to date.

Future BP6 REV3 (hypothetical) would get a new build target, but the same source tree.


8. Building from source

8.1 Pico SDK + ARM GCC versions

The repo pins a specific Pico SDK version via a git submodule. Check the submodule status after cloning:

git clone --recurse-submodules https://github.com/DangerousPrototypes/BusPirate5-firmware
cd BusPirate5-firmware
git submodule status   # confirm pico-sdk is checked out at the expected commit

If --recurse-submodules was missed: git submodule update --init --recursive.

ARM GCC: any recent arm-none-eabi-gcc works. Pico SDK has been tested against GCC 10/11/12/13 — pick what your distro ships. macOS: brew install --cask gcc-arm-embedded. Linux: apt install gcc-arm-none-eabi. The CMake build picks it up automatically as long as arm-none-eabi-gcc is on PATH.

CMake: 3.13+ (any modern Linux/Mac install satisfies; Windows users often need to install separately).

8.2 The hermetic Docker compose build

The repo ships docker-compose.yml with a containerized build environment — everything pinned and reproducible:

docker-compose run --rm build

This is the easiest path on Linux and macOS where Docker is installed. The container has a known-good Pico SDK + ARM GCC and produces the four .uf2 artifacts in build/. Build time is ~3-5 minutes on a modern laptop.

Use this if you don’t want to wrestle with local toolchain installation.

8.3 Windows: the pico-sdk submodule gotcha

The pico-sdk has historically had submodule resolution issues on Windows — CMake’s submodule auto-fetch fails because of Windows path conventions and Git LFS edge cases. The README documents the workaround:

# In a Git Bash or WSL shell:
git clone --recurse-submodules https://github.com/DangerousPrototypes/BusPirate5-firmware
cd BusPirate5-firmware/pico-sdk
git submodule update --init --recursive   # ← this is the line that fails on Windows

If the inner submodule init fails, the Pico SDK’s own dependencies (tinyusb, mbedtls, etc.) won’t fetch and the build won’t link. Workaround: use Docker (§ 8.2) on Windows; the Linux container avoids the problem entirely. Alternative: clone the submodules manually with explicit URLs.

8.4 Reading the build log

A successful build of bus_pirate6_rev2 ends with output like:

[100%] Linking C executable bus_pirate6_rev2.elf
[100%] Built target bus_pirate6_rev2
$ ls build/bus_pirate6_rev2.uf2
build/bus_pirate6_rev2.uf2

If the build fails, the first error is the one that matters — subsequent errors are usually cascade fallout. Common failure modes:

  • “arm-none-eabi-gcc: command not found” — ARM toolchain missing or not on PATH.
  • “pico-sdk submodule not initialized” — § 8.3.
  • “file not found: pico/stdlib.h” — Pico SDK path misconfigured.
  • “undefined reference to …” — usually a missing source file in add_executable(); check recent commits to see if a file was added that you haven’t pulled.

CMake’s output is verbose; grep for ^\(\[.*\] \)\?Error or use cmake --build . 2>&1 | grep -E 'error|Error' to find the real fail point.


9. Release cadence and the auto-build forum thread

There is no tagged-release cadence. Tagged releases on GitHub are rare and historical (the most recent are custom from Oct 2024 and the Pulseview-patch tag from Jun 2024).

The actual release channel is the main branch, with auto-built UF2s posted to a single living forum thread:

forum.buspirate.com/t/bus-pirate-5-auto-build-main-branch/20

GitHub Actions builds every push to main, packages the four UF2s into a zip, and a Pirate-Bot account posts the artifact to the forum thread with the commit SHA in the message. The last page of the thread is the latest build.

This is the canonical update path for users: open the thread, scroll to the bottom, download the zip, drop the UF2 onto the BP6 BOOTSEL mount.

If you want to track changes between your installed firmware and the latest: each post mentions the commit SHA, so you can git log <your_sha>..<latest_sha> --oneline to see what’s new.


10. Recent main-branch activity around commit 93aefde

tjscientist’s unit is running commit 93aefde. The 3-month window from Feb 2026 through Apr 2026 was dominated by UI / refactor work, not protocol additions:

DateCommit (approx)Topic
Feb 28a5d589a”VT100 updates to linenoise. Full VT100 now centralized” — major refactor consolidating terminal escape-code handling.
Mar 2(PR)Toolbar focus refactor
Mar 3(PR)Tab-to-focus added
Mar 4(PR)Menu bar with F-key support
Mar 4(PR)Reusable GUI element framework
Mar 5(PR #289)Static-variable RAM-usage analyzer (Copilot-assisted)
Mar 6-9(PRs)I²C EEPROM GUI prototype with paging for large EEPROMs
Mar 9(PR)Menu-bar color themes
Mar 13-16(PR #295)HDUART listen mode (passive bus monitoring)
Mar 16(PR)HDUART 8N1 RX fix
Mar 20(PR)README refresh
Apr 793aefde# line-comment support in the syntax parser

No smart-card / DDR5 / JTAG / SWD protocol changes in this window — those are stable mature features. The motion is UI polish + groundwork for a future “every command behind a screen widget” UI. The HDUART listen-mode and 8N1 RX fix are the only protocol-level additions worth tracking — the listen mode wasn’t available pre-Mar 2026, so HDUART workflows on older firmware can’t passively monitor a half-duplex UART without driving it.

If you’re tracking commits past 93aefde, the pattern continues — most April-onward commits are UI-side, not protocol-side. The deep-dive’s protocol-mode chapters (Vols 6-7) should remain accurate against future commits unless a major protocol-architecture change lands.


11. Cheatsheet updates for Vol 12

Items from this volume that belong on the laminate cheatsheet:

  • The repo URL: github.com/DangerousPrototypes/BusPirate5-firmware
  • Build command (Docker): docker-compose run --rm build
  • Build command (native, Linux/Mac): cd build && cmake .. && make
  • The four CMake targets: bus_pirate5_rev8 / bus_pirate5_rev10 / bus_pirate5_xl / bus_pirate6_rev2
  • UF2 update path: type $ at CLI OR hold BOOTSEL button while plugging in USB-C
  • Auto-build thread: forum.buspirate.com/t/bus-pirate-5-auto-build-main-branch/20 (last page = latest UF2)
  • The “wrong UF2 = red blink, not bricked” reassurance (the BOOTSEL is mask-ROM)
  • Source-tree quick navigation: src/pirate/ (HAL), src/mode/ (protocols), src/commands/ (workflows), src/binmode/ (host automation)

End of Volume 3. Volume 4 picks up with the syntax language and the VT100 / on-screen UI — how the syntax we sketched in § 4 actually parses, what every operator means, and how the on-screen display mirrors what the terminal sees.