- Python 92.3%
- C 5.8%
- CMake 1.9%
| docs | ||
| firmware | ||
| service | ||
| tests | ||
| .gitignore | ||
| chipbarn.service | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
| requirements.txt | ||
| test_api.py | ||
Chipbarn — RPi4 + Pico (RP2040 / RP2350) + ESP32 (any chip)
HTTP service on port 8080 lets remote machines flash firmware and stream console output.
Picos are wired to fixed RPi4 GPIO pins (see service/config.py) — any
Pico SDK target works (original Pico, Pico W, Pico 2, Pico 2 W). ESP32
boards of any variant (S3, S2, C3, C6, H2, P4, original ESP32) are
auto-discovered — plug in as many as you like; each is addressed by
its USB serial number and the chip is auto-detected at flash time.
GET /devices lists everything currently available.
Base URL: http://<your-host>:8080 — replace <your-host> with your Pi's
hostname or IP (e.g. chipbarn.local or 192.168.x.x).
Service
Install dependencies
cd ~/chipbarn
pip install -r requirements.txt # fastapi, uvicorn[standard], pyserial, esptool
On Debian/RPi OS (PEP 668) install into a venv first — python3 -m venv .venv && . .venv/bin/activate — or pass --break-system-packages.
ESP-IDF users already have esptool in their environment and can omit it.
Start (foreground)
python3 -m uvicorn service.main:app --host 0.0.0.0 --port 8080
systemd (auto-start on boot)
First edit chipbarn.service and set User/Group to your username (defaults to a REPLACE_ME placeholder). The python3 in ExecStart must have the dependencies above installed, or point it at a venv. Then:
sudo ln -s "$PWD/chipbarn.service" /etc/systemd/system/ # run from the repo root
sudo systemctl daemon-reload && sudo systemctl enable --now chipbarn
sudo journalctl -u chipbarn -f # logs
Security: the service has no authentication and binds to
0.0.0.0:8080— anyone who can reach the host can flash firmware and read device consoles. Keep it behind a VPN/firewall or a reverse proxy with auth before exposing it.
Configuration
No edits are needed to run — the service resolves openocd/esptool from PATH automatically and auto-detects each ESP32's chip family at flash time. These environment variables are available for advanced setups (all optional):
| Variable | Default | Purpose |
|---|---|---|
OPENOCD |
openocd on PATH |
OpenOCD binary (Pico SWD flash + monitor) |
ESPTOOL |
esptool on PATH |
esptool binary (ESP32 flash); IDF users already have it |
MAX_FIRMWARE_BYTES |
10737418240 (10 GiB) |
Max accepted firmware upload size; overruns return HTTP 413 |
OPENOCD_FLASH_TIMEOUT |
120 |
Wall-clock cap (seconds) on one Pico flash operation |
Pico SWD pin mapping, per-Pico chip selection (rp2040 vs rp2350), and the supported USB-serial bridge list live in service/config.py.
Interactive API docs
http://<your-host>:8080/docs
Discover devices
curl http://<your-host>:8080/devices
Returns { picos: [{id, chip, swdio, swclk}, ...], esp32: [{id, port}, ...] }.
Call this first to learn the <id> of each connected ESP32. The Pico chip
field reports rp2040 or rp2350 so callers know which SDK to build against.
ESP32 chip detection happens lazily at flash time, so it is not listed here.
Flash Firmware
Pico — upload .elf
Works for any Pico SDK target — original Pico / Pico W (RP2040) or Pico 2 / Pico 2 W (RP2350). The chip is selected per-board in service/config.py.
curl -X POST http://<your-host>:8080/flash/pico1 -F "firmware=@build/blink.elf"
curl -X POST http://<your-host>:8080/flash/pico2 -F "firmware=@build/blink.elf"
Returns JSON with OpenOCD output when done.
ESP32 — upload build tarball (any chip)
After idf.py build, from the project root:
tar -czf fw.tar.gz -C build \
flasher_args.json \
<project>.bin \
bootloader/bootloader.bin \
partition_table/partition-table.bin
The chip family is read from flasher_args.json (IDF writes it during build), so the same flow works for ESP32, S2, S3, C3, C6, H2, P4 without any parameters.
Flash a specific board by its USB serial (<id> from /devices):
curl -X POST http://<your-host>:8080/flash/esp32/5970047764 -F "firmware=@fw.tar.gz"
Or the legacy no-id endpoint (targets the first detected ESP32):
curl -X POST http://<your-host>:8080/flash/esp32 -F "firmware=@fw.tar.gz"
A merged single binary (.bin) works too — the chip is auto-detected via esptool chip_id:
curl -X POST http://<your-host>:8080/flash/esp32/5970047764 -F "firmware=@merged.bin"
To skip auto-detection (e.g. for a stripped binary that doesn't enter the bootloader cleanly), pass ?chip=:
curl -X POST "http://<your-host>:8080/flash/esp32/5970047764?chip=esp32c3" -F "firmware=@merged.bin"
Successful responses include "chip": "<family>" so you can confirm what was flashed.
If the named ESP32 isn't plugged in the service returns 503 with the list of
currently connected boards — plug it in and retry.
Monitor (SSE — use -N to disable curl buffering)
curl -N http://<your-host>:8080/monitor/pico1 # Pico #1 semihosting via SWD
curl -N http://<your-host>:8080/monitor/pico2 # Pico #2 semihosting via SWD
curl -N http://<your-host>:8080/monitor/esp32/5970047764 # specific ESP32 by USB serial
curl -N http://<your-host>:8080/monitor/esp32 # legacy: first detected ESP32
Optional timeout: curl -N "http://<your-host>:8080/monitor/pico1?timeout=60"
Default timeout: 300 s. Ctrl-C to stop early.
Status
curl http://<your-host>:8080/status
Hardware
Supported boards
| Family | Chips | Connection |
|---|---|---|
| Pico SDK | RP2040 (Pico, Pico W), RP2350 (Pico 2, Pico 2 W) | SWD via RPi4 GPIO |
| ESP-IDF | ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6, ESP32-H2, ESP32-P4 | USB-UART (auto-discovered) |
ESP8266 is not supported — modern ESP-IDF dropped it, and its ROM bootloader reset sequence differs.
Pico SWD Wiring
3-pad connector, bottom edge, USB facing up: GND / SD / SC (left to right). Same pad layout on the original Pico, Pico W, Pico 2, and Pico 2 W.
| Signal | Pico pad | Pico #1 RPi4 GPIO | Pico #2 RPi4 GPIO |
|---|---|---|---|
| SWDIO | SD (middle) | GPIO 24 — pin 18 | GPIO 22 — pin 15 |
| SWDCLK | SC (right) | GPIO 25 — pin 22 | GPIO 23 — pin 16 |
| GND | GND (left) | any GND | any GND |
3.3 V both sides, no level shifter. No UART wires needed — console goes over SWD via semihosting.
ESP32
Discovery is automatic — the service scans /sys/class/tty/ for known
USB-to-UART bridges (CH340/CH343, CP210x, FTDI, and Espressif's native
USB-CDC on VID 0x303a — see ESP_USB_BRIDGES in service/config.py)
and addresses each board by its USB serial number. No udev rule is
required; plug boards into any USB port. User must be in the dialout
group (no sudo).
To find the serial number of a board without the service:
udevadm info -q property -n /dev/ttyACM0 | grep ID_USB_SERIAL_SHORT
Adding a new Pico
One line in service/config.py — the chip drives the OpenOCD target config:
PicoDevice("pico3", chip="rp2040", swdio=18, swclk=17, gdb_port=3335, tcl_port=6668, telnet_port=4446),
Restart the service. /flash/pico3 and /monitor/pico3 are now available.
Adding a new ESP-style USB-UART bridge
One line in service/config.py — use pid=None to match every product from a vendor:
ESP_USB_BRIDGES: list[tuple[str, str | None]] = [
...,
("abcd", None), # imaginary vendor; any PID
]
Firmware Examples
Source lives in firmware/:
| Directory | Target | Notes |
|---|---|---|
firmware/pico_blink/ |
RP2350 (Pico 2 / Pico 2 W) | Blinks LED at 250 ms, prints blink #N via semihosting |
firmware/esp32s3_hello/ |
ESP32-S3 | Logs chip info on boot, blinks GPIO2 at 1 Hz, prints blink #N |
The same flash flow works for any Pico SDK or ESP-IDF target — only the
build differs. For a different chip, change set-target (ESP-IDF) or the
SDK / link flags (Pico SDK) and rebuild; the chipbarn side needs no
configuration change.
Pico SDK — build notes
- RP2350 (Pico 2 W): LED is wired through the CYW43 WiFi chip, not a direct GPIO.
PICO_DEFAULT_LED_PINis undefined. Linkpico_cyw43_arch_none; drive LED viacyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1). - RP2040 (original Pico): LED is on a direct GPIO,
PICO_DEFAULT_LED_PINis defined; no CYW43 link needed. - RP2040 (Pico W): same CYW43 LED path as Pico 2 W.
- Enable semihosting in CMakeLists.txt:
pico_enable_stdio_semihosting(blink 1)/pico_enable_stdio_uart(blink 0). - Build with a clean PATH — ESP-IDF prepends xtensa cross-assemblers that break
pioasm/picotoolhost builds. Strip the environment:env -i HOME=$HOME PATH=/usr/local/bin:/usr/bin:/bin cmake ...
ESP-IDF — build notes
- ESP-IDF v6.0.x (install via the official ESP-IDF installer).
- Activate:
source $IDF_PATH/export.sh(or your IDF activation script). - First build:
idf.py set-target <chip>where<chip>isesp32,esp32s2,esp32s3,esp32c3,esp32c6,esp32h2, oresp32p4. - Build:
idf.py build - The service flashes via the tarball described above — no need to flash from the RPi4 directly.