Add multi-panel Pico UDP firmware and Python LED control.

Pico panels get static IPs on 10.1.1.10–14 with per-panel LED counts, Makefile deploy targets, and Python examples for animations, sync tests, and direct Pi SPI control.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-28 22:46:08 +12:00
parent d8323bb9a3
commit d5cab2efdf
52 changed files with 4677 additions and 1 deletions

125
firmware/panel/README.md Normal file
View File

@@ -0,0 +1,125 @@
# Portal panel firmware (Pico SDK)
C firmware for a **Pico + W5500 + WS2812** panel adapter. Replaces the slow CircuitPython `adapter/code.py` path with native UDP receive and PIO WS2812 output.
## Hardware
Matches `adapter/code.py`:
| Signal | GPIO |
|--------|------|
| W5500 CS | GP9 |
| SPI1 SCK | GP10 |
| SPI1 MOSI | GP11 |
| SPI1 MISO | GP12 |
| W5500 RST | GP13 |
| Status LED | GP25 |
| WS2812 data | GP27 |
Default: **405 LEDs** (45×9 matrix chain), one data pin per panel.
WS2812 output uses [ForsakenNGS/Pico_WS2812](https://github.com/ForsakenNGS/Pico_WS2812) (FORMAT_GRB on GP27).
## Build
Requires [pico-sdk](https://github.com/raspberrypi/pico-sdk) and the ARM GCC toolchain (`arm-none-eabi-gcc`).
```bash
# From repo root
make deploy # build + flash over USB
make build # build only
# Or from this directory
make deploy
```
Manual cmake (one-time pico-sdk setup):
```bash
git clone https://github.com/raspberrypi/pico-sdk.git ~/pico/pico-sdk
cd ~/pico/pico-sdk && git submodule update --init
export PICO_SDK_PATH=~/pico/pico-sdk
mkdir -p build && cd build
cmake ..
make -j$(nproc)
```
UF2 output: `build/panel.uf2` — hold BOOTSEL and copy to the Pico.
### Options
```bash
# Panel 0 of 5 → 10.1.1.10, unique MAC, UDP panel_id filter
make deploy PANEL_ID=0 # 10.1.1.10
make deploy PANEL_ID=1 # 10.1.1.11
# DHCP instead of static 10.1.1.1014
make deploy PANEL_ID=0 NO_DHCP=0
# Fewer LEDs for bench test
make deploy NUM_LEDS=2
```
Static IP and MAC are derived from `PANEL_ID`:
| Panel | IP | MAC |
|-------|-----|-----|
| 0 | 10.1.1.10 | 02:50:52:54:4C:00 |
| 1 | 10.1.1.11 | 02:50:52:54:4C:01 |
| … | … | … |
| 4 | 10.1.1.14 | 02:50:52:54:4C:04 |
| 255 (bench) | 10.1.1.19 | 02:50:52:54:4C:FF |
Edit `board_config.h` for gateway, subnet, and pin changes.
## UDP protocol (port 50007)
| Payload | Action |
|---------|--------|
| **1215 bytes** | Raw RGB (`405 × 3`), show immediately |
| **1216 bytes** | `panel_id` (byte 0) + RGB; `255` = accept on any panel |
| **4 bytes `SHOW`** | Push buffered pixels to the strip (sync helper) |
Pi sends **RGB** order; firmware converts to WS2812 **GRB**.
Target throughput: ~3050 fps per panel (vs ~1020 fps on CircuitPython).
## Pi test sender
```bash
pipenv run python examples/panel_udp_send.py --host 192.168.2.111 --animation rainbow
```
## Flashing
Requires [picotool](https://github.com/raspberrypi/picotool) with libusb support:
```bash
make deploy
```
Or `./scripts/panel_deploy.sh` (same thing).
Or manually:
```bash
picotool load -x -f build/panel.uf2
```
`-f` forces a USB reboot into BOOTSEL when the panel is already running.
Legacy UF2 drag-and-drop also works: hold BOOTSEL, plug USB, copy `panel.uf2` to `RPI-RP2`.
Serial debug:
```bash
make monitor
# or: picocom /dev/ttyACM0
```
Ctrl+A then Ctrl+X to exit picocom.
## Multi-panel (5 Picos)
Flash each Pico with `PANEL_ID` 04 (`make deploy PANEL_ID=N`). Each gets `10.1.1.1N` (1014), a unique MAC, and UDP filtering. Send frames from the Pi to each panels IP, or use a leading `panel_id` byte with `255` broadcast filtering.