Bare-metal firmware for the FreeWili 2 (Raspberry Pi RP2350B) that turns its IR transmitter, IR receiver, and 480×320 touch LCD into a self-contained IR Swiss-army knife:
- Receive anything — PIO + DMA edge capture of any IR signal, drawn live as a logic-analyzer trace on the LCD.
- Identify it — decoders name the protocol and extract address/command: NEC, NECext, Samsung32, RC5/RC5X, RC6, SIRC/SIRC15/SIRC20, RCA, Kaseikyo. Signals no decoder recognizes stay fully usable as raw timings — capture, save, replay, and assign them anyway ("anycode").
- Transmit anything — protocol encoders and raw timing arrays feed one PIO carrier-modulating transmitter (36/38/40/56 kHz).
- Thumb-drive database — hotplug-mount a FAT32 USB stick, browse
Flipper-IRDB-format
.irfiles on the touchscreen, tap to transmit. - Learn screen — live capture with decode readout and signal trace;
save captures to
learned.ir, replay them, or assign them to a remote. - Universal remote builder — build touch remotes on-device: name buttons
with an on-screen keyboard, assign codes from the database or from learned
captures (long-press), rename/reassign/delete in edit mode. Each remote is
a plain Flipper-compatible
.irfile on the drive.
Everything above is implemented and verified on real hardware — including
the party trick: an unknown-protocol remote was learned as raw timings,
assigned to a touch button, and replayed successfully. Evidence lives in
docs/hardware-notes.md.
| Browse DB | Learn | Remotes | Settings |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
| Part | Detail |
|---|---|
| MCU | RP2350B (48 GPIO, 16 MB flash, 8 MB APS6404L PSRAM memory-mapped) |
| IR TX | GPIO20 (IR transmitter LED, PIO carrier-modulated) |
| IR RX | GPIO24 (TSOP-style demodulating receiver: idle HIGH, mark = LOW) |
| Display | ST7796 480×320 SPI LCD on SPI1 |
| Touch | FT6336U capacitive over I2C1 (polled) |
| LEDs | 16× WS2812 on pio1 (GPIO21) — activity blips |
| USB | Native USB host behind a CH334F hub; port power via the PCAL6524 I/O expander |
| Diagnostics | SEGGER RTT only (no UART/USB stdio) |
Two board traits that bite: the IR rail and the USB port power are gated behind the PCAL6524 I/O expander and are off at power-on — the drivers turn them on during init. The whole firmware is polled (no IRQs for IR or USB): a capture DMA ring, a one-shot TX streamer, and the USB host stack are all serviced from the main loop.
Toolchain: Pico SDK 2.2.0 + ARM GCC (via ~/.pico-sdk), host tests via
MinGW GCC + Ninja. Host: Windows + PowerShell; flashing needs a CMSIS-DAP
probe (SWD) and OpenOCD.
powershell -File tools/build.ps1 # configure + build -> wiliir.uf2 / .elf
powershell -File tools/flash.ps1 # OpenOCD SWD program + verify + reset
powershell -File tools/rtt.ps1 # live RTT console (add -Seconds N for a timed capture)
powershell -File tools/test.ps1 # host unit tests - 14 binaries, no hardware neededNo probe? tools/build.ps1 produces a standard wiliir.uf2 you can drop
onto the RP2350 BOOTSEL drive.
The IR engine's pure-logic core (decoders, encoders, frame builder, .ir
parser/writer, trace renderer, remote-file rewriter) has zero Pico SDK
dependencies and is host-tested — including golden fixtures captured from
real remotes and a negative fixture proving unknown signals fall through
to RAW instead of mis-decoding.
Board + display + LVGL come up and the Home screen appears (LEARN / BROWSE
DB / REMOTES / SETTINGS). Over RTT, the firmware then runs a 10-protocol
loopback self-test (the on-board receiver hears the on-board TX LED —
encode → transmit → capture → decode must round-trip; healthy hardware
prints selftest: 10/10 protocols round-tripped) and starts the live-decode
loop: every IR frame that hits the receiver is decoded and printed, whatever
screen you're on.
Settings (default carrier, repeat count) persist to wiliir.cfg on the
drive. Learned captures append to learned.ir; remotes live under
remotes/. All of it is plain Flipper .ir text — files round-trip with a
Flipper and with the community IRDB.
One idea holds the whole thing together: the universal currency is a raw
timing array (uint32_t µs durations, mark-first). Capture produces one,
TX consumes one, and protocol decode/encode is a pure-logic layer on top —
which is why unknown protocols are first-class citizens everywhere.
src/ir/— capture/decode/encode/TX enginesrc/db/—.irparser/writer, directory index, entry→timings resolversrc/remote/— remote-file model (streamed rewrite: rename/reassign/delete)src/ui/— LVGL v9 screens (32 KB pool: everything paginated, square corners)src/usb/— polled USB host MSC driver (no TinyUSB; vendored from the author's usbmsc) + FatFs gluesrc/app/— settings, polled TX repeat engine, assign flows
Full design spec:
docs/superpowers/specs/2026-07-05-wiliir-ir-toolset-design.md.
The hardware-facing modules were built to the
wilibsp board-support-package
convention from day one and have been harvested into it as bsp/ir/ and
bsp/usbhost/ — see docs/harvest.md.
MIT — see LICENSE. Vendored third-party components (LVGL, FatFs, SEGGER RTT) keep their own permissive licenses, noted in the LICENSE file and in the vendored file headers.
TX makes the IR LED transmit real remote-control codes. Only transmit
to devices you own or have permission to control, and don't use this
tool to interfere with equipment that isn't yours. This project is for
education and personal automation on your own equipment. You are
responsible for how you use it.




