Open-Source Light-Gun Firmware · Volume 7
Building OpenFIRE From Source — The Pinned Toolchain, the Build Switches, Adding a Board, and What a Modified Build Owes
Arduino IDE and arduino-cli on TeamOpenFIRE's patched arduino-pico core, the exact compile line the releases use, how to add a board to the shared presets, debugging a development build, and the list of known issues a fork should start from
7.1 Why build it at all
The release UF2s cover every supported board, and every pin and setting can be changed in the App, so a build that only needs OpenFIRE as published never has to compile anything. Building from source starts to matter when the gun needs something the releases do not do: a fixed player number, a different USB name for a product, a camera the firmware does not support, a change to the recoil logic, or one of the fixes in 7.10. That is the territory of Our Firmware, which decides what to change and how to keep up with upstream. This volume is the bridge: how the project itself builds the firmware, so that a fork starts from a build that reproduces the release exactly.
7.2 The toolchain the project uses

OpenFIRE is an Arduino sketch, built with the Arduino IDE 2.x or arduino-cli, on the arduino-pico core (Earle Philhower’s Arduino port for the RP2040 and RP2350) — but not the upstream release of that core. COMPILING.md is explicit that the board package must come from the project’s own fork, “Raspberry Pi Pico/RP2040/RP2350 (TUSB Fix)”, installed from TeamOpenFIRE’s board-manager URL:
https://github.com/TeamOpenFIRE/arduino-pico/releases/download/global/package_rp2040_index.json
“Using this fork is REQUIRED as it includes a fixed version of Adafruit’s TinyUSB library. Until adafruit/Adafruit_TinyUSB_Arduino#293 is resolved, do not use the upstream Arduino-Pico core or install the separate Adafruit TinyUSB library.” The fork’s only release, 4.5.4-tusb (23 May 2025), describes itself as “based on upstream, with TinyUSB submodule replaced that incorporates the workaround for Serial-related deadlocks”, with TinyUSB updated from 3.4.4 to 3.5.0.
The project does not use PlatformIO. (The ESP32 port in Volume 1 does; it is a separate codebase.) The scope note that started this dive asked whether it was “Arduino-pico/PlatformIO as the project actually uses”; the answer is Arduino-pico through the Arduino tools, on a forked core, and nothing else is supported.
7.3 Building in the Arduino IDE
From COMPILING.md, for Arduino IDE 2.x:
- Install the IDE.
- File → Preferences, and add the board-manager URL above to Additional boards manager URLs.
- In the Boards Manager, install the latest “Raspberry Pi Pico/RP2040/RP2350 (TUSB Fix)”.
- Put the repository’s contents in the Arduino sketchbook folder “so that the
OpenFIREmainfolder is next tolibraries”, and open theOpenFIREmainsketch. The repository must be cloned with its submodules, or the board presets and the Adafruit display libraries are missing. - Under Tools: the board (from the TUSB Fix package), CPU speed 133 MHz, Optimize Even More (-O3), a Flash Size that includes at least a 64 KB file system, and USB Stack: Adafruit TinyUSB (not Host). For RP2350 boards, CPU Architecture: ARM.
- Verify, Upload, or Sketch → Export Compiled Binary, which writes a
.uf2underOpenFIREmain/build/rp2040.rp2040.<board>/for drag-and-drop flashing (Volume 4).
The file-system size is not optional: settings live in LittleFS (Volume 2), and a build with no file system boots with “(No Storage Available)” and cannot save a calibration. The USB stack is not optional either; Prow Enhanced’s README already warned that RP2040 boards “tend to default to the Pico SDK USB stack so you will see a build error until you switch to TinyUSB”.
7.4 Building with arduino-cli
The project’s continuous-integration workflow is the authoritative recipe, because it is what produces the release files. On every push it installs the pinned core —
arduino-cli core install rp2040:[email protected] \
--additional-urls https://github.com/TeamOpenFIRE/arduino-pico/releases/download/global/package_rp2040_index.json
— checks the repository out with submodules, and compiles each board with a fixed set of options. For the Raspberry Pi Pico:
arduino-cli compile -e \
--fqbn rp2040:rp2040:rpipico:freq=133,usbstack=tinyusb,flash=2097152_65536,opt=Optimize3 \
OpenFIREmain --libraries libraries \
--build-property 'build.extra_flags=-DGIT_HASH="<short sha>"'
The output, OpenFIREmain/build/rp2040.rp2040.rpipico/OpenFIREmain.ino.uf2, is renamed to OpenFIREfw.rpipico.uf2. The GIT_HASH define is reported to the App, which shows it beside the version number and links it to the commit — a cheap way to know exactly which build is in a gun.
The per-board options in the workflow, all at freq=133, usbstack=tinyusb, opt=Optimize3:
Table 1 — The per-board options in the workflow, all at freq=133, usbstack=tinyusb, opt=Optimize3
| Release file | FQBN board | Flash option (size_filesystem) | Extra |
|---|---|---|---|
rpipico | rpipico | 2097152_65536 (2 MB, 64 KB FS) | — |
rpipicow-noBT | rpipicow | 2097152_65536 | wificountry=worldwide,ipbtstack=ipv4only; CYW43 defines |
rpipico2 | rpipico2 | 4194304_65536 (4 MB) | arch=arm |
rpipico2w-noBT | rpipico2w | 4194304_65536 | arch=arm, as Pico W |
adafruitItsy | adafruit_itsybitsy | 8388608_65536 (8 MB) | — |
adafruitKB2040 | adafruit_kb2040 | 8388608_65536 | — |
arduinoNano | arduino_nano_connect | 16777216_65536 (16 MB) | needs the WiFiNINA library |
vccgndYD | vccgnd_yd_rp2040 | 2097152_1048576 (1 MB FS) | — |
waveshareZero | waveshare_rp2040_zero | 2097152_65536 | — |
generic_rp2350 | generic_rp2350 | 2097152_1048576 | arch=arm |
generic | generic | 2097152_1048576 | — |
A Pico W Bluetooth build is in the workflow but commented out: “Disabled until issues surrounding interacting with cyw southbridge causing lockups is solved”.
A reproducible fork build is therefore: the same core version, the same options, a clean recursive clone, and a check that the UF2 behaves like the release before any change is made. Our Firmware can put exactly this in its own CI.
7.5 Build switches
OpenFIREDefines.h holds the compile-time options described in Volume 2. The ones a product build is most likely to touch:
Table 2 — OpenFIREDefines.h holds the compile-time options described in [Volume 2](/light-guns/building-the-electronics/open-source-firmware/vol-2/). The ones a product build is most likely to touch
| Define | Release value | Why change it | Caution from the source |
|---|---|---|---|
PLAYER_NUMBER | undefined | Hard-wire a gun to player 1–4 (cabinet builds) | Otherwise the App’s USB presets and XR<n> do the same at run time |
DEVICE_NAME | ”FIRECon” | Product name for a kit | Can also be set at run time (15 characters) |
MANUFACTURER_NAME | ”OpenFIRE” | — | “must be kept at the default” for whitelists; MiSTer matches on the “OpenFIRE ” name prefix (Volume 6) |
DEVICE_VID | 0xF143 | — | “MUST be kept at the default 0xF143” for the App and most distributions; the App finds guns by it |
DUAL_CORE | defined | Leave it | ”If unsure, leave this uncommented” |
MAMEHOOKER | defined | Leave it | Serial is also the App’s channel; “undefining this will cause errors” |
USES_*, FOURPIN_LED, CUSTOM_NEOPIXEL | all defined | Trim features from a fixed product | Everything compiled in can be disabled at run time anyway |
PRINT_VERBOSE, DEBUG_SERIAL | undefined | Development | Extra serial output during run mode (7.7) |
The firmware’s issue template adds a rule for anyone reporting bugs from a custom build: “REMEMBER TO SPECIFY YOUR USING A CUSTOM BUILD & WHAT CHANGES ARE MADE TO THE SKETCH; OTHERWISE YOUR ISSUE MAY BE CLOSED!”, and COMPILING.md asks that a general firmware issue first be reproduced on the precompiled release.
7.6 Adding a board
A custom PCB with an RP2040 or RP2350 can run the generic image and have its pins set in the App. Giving it a proper board entry — its own name in the App, a default pin map, a drawing in the board previewer — is done in the shared OpenFIRE-Boards repository, which the firmware and the App both include as a submodule. Its README describes the four pieces:
- A board identifier in
OpenFIREshared.h, selected by the Arduino board macro (#elifdef ARDUINO_RASPBERRY_PI_PICO→"rpipico"). A custom board needs a board definition in the core that sets such a macro, or a build flag. boardsPresetsMap— “a map of what function each GPIO should have as a default”, one entry per GPIO, withbtnUnmappedfor free pins andunavailablefor pins not brought out. “RP2040 and RP235X-A boards should have thirty pins maximum.”boardBoxPositions— where each pin’s label sits around the board drawing in the App (left, right or middle, and an order), andboardsAltPresetsfor alternative layouts such as the ItsyBitsy’s SAMCO 1.1 map.- A board drawing in
boardPics/, exported as plain SVG, added tovectors.qrcunder the board’s identifier, with an invisible highlight shape per usable pin whose ID isOF_pin<n>and whose opacity is 0, so the App can light the pin when the cursor passes over its label.
The project invites boards upstream: “If you’d like to have your board supported, send a PR to TeamOpenFIRE/OpenFIRE-Boards!” For a kit with its own PCB, that is the clean way to have the official App show the kit’s board by name. The board itself — the PCB that Reference Electronics is producing for Altium — is the obvious candidate.
7.7 Debugging a development build
- Serial output. Any serial terminal shows the gun’s messages in pause and calibration modes; defining
PRINT_VERBOSEorDEBUG_SERIALadds output during run mode. The App also has a Debug Window. The serial port is shared with the App and the hook programs, so only one of them can hold it at a time. - SWD. The Pico’s three debug pads take a CMSIS-DAP probe such as Raspberry Pi’s own Debug Probe or a second Pico running the probe firmware, for breakpoints and flashing without BOOTSEL. The project’s documents do not describe this workflow, and it was not tried for this dive; it is the standard RP2040 debug route, and the lab already has SWD probes.
- Timing. The firmware’s interesting behaviour is in milliseconds — the 209 Hz camera tick, the 45/80/500 ms solenoid timings, 1 ms USB reports — and the cleanest instrument for it is a logic analyser. Toggling a spare GPIO at the start and end of
GetPosition()and watching it next to the camera’s I²C and the solenoid gate on the Analog Discovery shows the whole timing picture in one capture.
7.8 The pinned core and where upstream is
The release workflow pins rp2040:[email protected], with a comment that the version must be selected by hand “as our -tusb version is currently considered prior to upstream 4.5.4”. Upstream arduino-pico had reached 6.3.0 on 8 October 2026, and the TinyUSB issue the fork works around — adafruit/Adafruit_TinyUSB_Arduino #293, “Serial crash when receiving incoming data faster than handled”, opened 27 April 2023 — was still open on 18 October 2026. In that issue a Pico receiving serial data quickly “becomes unresponsive. Serial seems dead” and “won’t even reboot”. For a light gun that is fed serial commands by MAMEHooker in the middle of a game, that is exactly the failure that matters, and the README’s own known-issues list still includes “rare instances where a serial exit signal is received … might cause the gun to freeze.”
So a fork faces a choice the project has made for now by standing still: stay on the pinned 4.5.4 core and inherit a year and a half of upstream fixes not taken, or move to a current core and either carry the TinyUSB patch forward or show that the bug no longer occurs. The test for the second path is cheap with the commands in Volume 6: a script that floods the gun’s serial port with feedback commands while it is in use.
7.9 What a modified build owes
This restates Volume 1’s reading of the licences from the point of view of someone who has just changed the code; the analysis is Licensing, IP and Kits’s.
- The firmware is LGPL-2.1, and it compiles in two GPL-3.0-or-later parts:
OpenFIREshared.hand the bundledlibraries/LightgunButtons(its COPYING file and header are GPL v3; corrected 2026-10-19). A modified UF2 given to anyone else should come with, or with an offer of, the complete source as built — including the board header, any changed libraries, and the build options — under GPL v3 terms. - The README’s own line: “any entities that wish to incorporate part of or all of OpenFIRE’s Firmware components must make their modifications available to the public upon request.”
- The authors’ requests, which are not licence terms: keep the OpenFIRE name and the credits to That One Seong, Samuel Ballantyne and Mike Lynch; do not fork to rebrand; raise commercial plans in the repository’s Discussions or by email; never charge for the firmware itself.
- Contributions back are welcome under stated rules: a pull request must explain its reasoning, and larger changes belong on a separate branch.
A private build used only on the bench owes nothing. The obligations start when a UF2 or a gun leaves the house. Kits may be sold later, which makes these obligations binding for this project, not advisory: a private fork is fine while the firmware stays private, but the first kit makes the fork’s source as shipped available to every buyer.
7.10 A starting list for a fork
Everything this dive found in the v6.2 source that a fork might change, collected in one place. Each is a reading of the code unless marked otherwise, and each has a bench test before anything is changed.
Table 3 — 7.10 A starting list for a fork
| # | Finding | Where | Volume |
|---|---|---|---|
| 1 | The “increase IR sensitivity” hotkey calls sens-1, so it decreases, and from level 0 stores −1 and indexes the camera’s register tables out of bounds | OpenFIREmain.ino, IncreaseIrSensitivity() | 2, 5 |
| 2 | Burst fire’s off time evaluates to 1 ms (x << y ? 1 : 0) | OpenFIREFeedback.cpp, BurstFire() | 2 |
| 3 | The first shot of every trigger pull bypasses the temperature check, so the shutoff does not stop rapid single shots | OpenFIREFeedback.cpp, SolenoidActivation() | 5 |
| 4 | A serial F0x1 can hold the solenoid on for up to 2000 ms | OpenFIREserial.h, SERIAL_SOLENOID_MAXSHUTOFF | 6 |
| 5 | The App says the temperature-warning state uses “ON Fast Length * 4”; the code doubles the off time | App appmainwindow.ui against OpenFIREFeedback.cpp | 5 |
| 6 | The App says shutoff blocks single shots; the code does not (see 3) | as above | 5 |
| 7 | The temperature average comment says “3ms apart”; samples are 250 ms apart | OpenFIREFeedback.cpp | 2 |
| 8 | No current control or peak-and-hold for the solenoid; plain on/off at 1 ms resolution | OpenFIREFeedback.cpp | 5 |
| 9 | Only the DFRobot/Wii PixArt camera is supported; the ESP32 port’s PAJ7025R2/R3 code is the nearest source for others | DFRobotIRPositionEx | 3 |
| 10 | The screen model assumes square-layout emitters on the picture edges; calibration absorbs the rest with linear offsets | OpenFIREcommon.cpp | 2 |
| 11 | The core is pinned at arduino-pico 4.5.4 for an open TinyUSB serial bug | CI workflow, COMPILING.md | 7.8 |
| 12 | The App’s four-pin LED help says common anode to 5 V; the Hardware Guide wires it to 3.3 V, which is the safe choice | App against Hardware Guide | 5 |
The sibling dives add changes of their own that only a fork can carry:
Table 4 — The sibling dives add changes of their own that only a fork can carry
| # | Change | Why | Dive |
|---|---|---|---|
| 13 | An analog Hall-effect trigger: ADC read, per-gun calibration, press at 50 % and release at 40 % of travel, a fault read as released | OpenFIRE has no analog trigger function | Reference Electronics |
| 14 | An ejection-port output on its own pin, driving the board’s second power stage | Two ways of moving the port are to be tried | Reference Electronics |
| 15 | Solenoid pulse ended at landing, then regulated current | Item 8; most of a fixed 45 ms pulse is heat | Solenoid Recoil |
These are fixed in the project’s own fork, not reported upstream for now. That is the project’s decision for the present. Upstream issues and pull requests may follow once the fixes are proven on the bench. It does not change what a distributed build owes (7.9): the README asks that modifications be made “available to the public upon request”, and GPL-3.0 requires the source of any binary that leaves the house. The fork stays private until there is a working model, and private bench builds owe nothing. The cost of not reporting is that the fork must carry each fix across every upstream release. How to stay level with upstream is where Our Firmware begins.
7.11 Why this toolchain, and why not the others
The release toolchain for this build’s RP2040 firmware is chosen on four criteria: it must reproduce the official v6.2 UF2 before anything is changed, so that any later difference is the fork’s and not the toolchain’s; it must be supported by the project, so problems can be compared with upstream; it must be scriptable, so that a release is one command and a kit’s source offer can state exactly how the binary was built; and it must run on the Linux workstation the lab uses.
Table 5 — 7.11 Why this toolchain, and why not the others
| Option | Verdict | Reason |
|---|---|---|
arduino-cli on TeamOpenFIRE’s arduino-pico fork, pinned rp2040:[email protected], with the CI workflow’s compile line | Pick, for release builds | The exact build the project’s own workflow runs, so a v6.2 UF2 can be reproduced; scriptable; the command line is the record a GPL source offer needs |
| Arduino IDE 2.x on the same fork | Pick, for exploring and debugging | Same core and options, so its builds match; but settings live in menus, which makes a release hard to reproduce exactly |
| Upstream arduino-pico (6.3.0) | Rejected for now; tested before adoption | COMPILING.md calls the fork “REQUIRED” until TinyUSB #293 is fixed, and that issue was still open on 2026-10-18. The serial flood test of 7.8 decides when to move |
| Separate Adafruit TinyUSB library on any core | Rejected | COMPILING.md says not to install it |
| PlatformIO | Rejected | Not supported for the RP2040 firmware; only the ESP32 port uses it |
| Raspberry Pi Pico SDK with CMake, no Arduino layer | Rejected | The firmware is an Arduino sketch built on Arduino libraries; leaving them is a port, not a build |
The board options are the ones in 7.3: the Pico (RP2040) at 133 MHz, -O3, a flash layout with at least a 64 KB file system, and the Adafruit TinyUSB stack. The Pico 2 build is the same command with the RP2350 board selected, kept for the drop-in trial.
7.12 What could not be verified
- No build was run for this dive; the steps are the project’s documents and CI workflow, read on 18 October 2026.
- Whether TinyUSB issue #293 still reproduces on arduino-pico 6.3.0.
- Arduino IDE 2.x debugger support details for the RP2350 with the forked core.
Sources
- TeamOpenFIRE,
OpenFIRE-Firmwarev6.2:COMPILING.md,README.md,OpenFIREmain/OpenFIREDefines.h,.github/workflows/arduino-cli-builds.yml,.github/ISSUE_TEMPLATE/, https://github.com/TeamOpenFIRE/OpenFIRE-Firmware — read 2026-10-18. - TeamOpenFIRE,
arduino-picofork, release 4.5.4-tusb (2025-05-23), https://github.com/TeamOpenFIRE/arduino-pico/releases — read 2026-10-18. - TeamOpenFIRE,
OpenFIRE-BoardsREADME andOpenFIREshared.h, https://github.com/TeamOpenFIRE/OpenFIRE-Boards — read 2026-10-18. - Adafruit,
Adafruit_TinyUSB_Arduinoissue #293, “Serial crash when receiving incoming data faster than handled”, https://github.com/adafruit/Adafruit_TinyUSB_Arduino/issues/293 — state checked 2026-10-18. - Earle F. Philhower III,
arduino-picolatest release 6.3.0 (2026-10-08), https://github.com/earlephilhower/arduino-pico/releases — checked 2026-10-18. - Mike Lynch,
Prow7/ir-light-gunREADME (TinyUSB stack note). - Alessandro Satanassi,
OpenFIRE-Firmware-ESP32README (PlatformIO, PixArt cameras).
Comments (0)