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

Figure 1 — How an OpenFIRE release is built: the firmware repository with its local libraries and submodules, compiled by arduino-cli (or the Arduino IDE) against TeamOpenFIRE's patched fork of the arduino-pi…
Figure 1 — How an OpenFIRE release is built: the firmware repository with its local libraries and submodules, compiled by arduino-cli (or the Arduino IDE) against TeamOpenFIRE's patched fork of the arduino-pico core, version 4.5.4-tusb, into one UF2 per board. The compile line is the one the project's continuous-integration workflow runs. The core is pinned well behind upstream because of an unresolved TinyUSB serial bug. Own-work diagram.

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:

  1. Install the IDE.
  2. File → Preferences, and add the board-manager URL above to Additional boards manager URLs.
  3. In the Boards Manager, install the latest “Raspberry Pi Pico/RP2040/RP2350 (TUSB Fix)”.
  4. Put the repository’s contents in the Arduino sketchbook folder “so that the OpenFIREmain folder is next to libraries”, and open the OpenFIREmain sketch. The repository must be cloned with its submodules, or the board presets and the Adafruit display libraries are missing.
  5. 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.
  6. Verify, Upload, or Sketch → Export Compiled Binary, which writes a .uf2 under OpenFIREmain/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 fileFQBN boardFlash option (size_filesystem)Extra
rpipicorpipico2097152_65536 (2 MB, 64 KB FS)—
rpipicow-noBTrpipicow2097152_65536wificountry=worldwide,ipbtstack=ipv4only; CYW43 defines
rpipico2rpipico24194304_65536 (4 MB)arch=arm
rpipico2w-noBTrpipico2w4194304_65536arch=arm, as Pico W
adafruitItsyadafruit_itsybitsy8388608_65536 (8 MB)—
adafruitKB2040adafruit_kb20408388608_65536—
arduinoNanoarduino_nano_connect16777216_65536 (16 MB)needs the WiFiNINA library
vccgndYDvccgnd_yd_rp20402097152_1048576 (1 MB FS)—
waveshareZerowaveshare_rp2040_zero2097152_65536—
generic_rp2350generic_rp23502097152_1048576arch=arm
genericgeneric2097152_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

DefineRelease valueWhy change itCaution from the source
PLAYER_NUMBERundefinedHard-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 kitCan 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_VID0xF143—“MUST be kept at the default 0xF143” for the App and most distributions; the App finds guns by it
DUAL_COREdefinedLeave it”If unsure, leave this uncommented”
MAMEHOOKERdefinedLeave itSerial is also the App’s channel; “undefining this will cause errors”
USES_*, FOURPIN_LED, CUSTOM_NEOPIXELall definedTrim features from a fixed productEverything compiled in can be disabled at run time anyway
PRINT_VERBOSE, DEBUG_SERIALundefinedDevelopmentExtra 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:

  1. 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.
  2. boardsPresetsMap — “a map of what function each GPIO should have as a default”, one entry per GPIO, with btnUnmapped for free pins and unavailable for pins not brought out. “RP2040 and RP235X-A boards should have thirty pins maximum.”
  3. boardBoxPositions — where each pin’s label sits around the board drawing in the App (left, right or middle, and an order), and boardsAltPresets for alternative layouts such as the ItsyBitsy’s SAMCO 1.1 map.
  4. A board drawing in boardPics/, exported as plain SVG, added to vectors.qrc under the board’s identifier, with an invisible highlight shape per usable pin whose ID is OF_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_VERBOSE or DEBUG_SERIAL adds 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.h and the bundled libraries/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

#FindingWhereVolume
1The “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 boundsOpenFIREmain.ino, IncreaseIrSensitivity()2, 5
2Burst fire’s off time evaluates to 1 ms (x << y ? 1 : 0)OpenFIREFeedback.cpp, BurstFire()2
3The first shot of every trigger pull bypasses the temperature check, so the shutoff does not stop rapid single shotsOpenFIREFeedback.cpp, SolenoidActivation()5
4A serial F0x1 can hold the solenoid on for up to 2000 msOpenFIREserial.h, SERIAL_SOLENOID_MAXSHUTOFF6
5The App says the temperature-warning state uses “ON Fast Length * 4”; the code doubles the off timeApp appmainwindow.ui against OpenFIREFeedback.cpp5
6The App says shutoff blocks single shots; the code does not (see 3)as above5
7The temperature average comment says “3ms apart”; samples are 250 ms apartOpenFIREFeedback.cpp2
8No current control or peak-and-hold for the solenoid; plain on/off at 1 ms resolutionOpenFIREFeedback.cpp5
9Only the DFRobot/Wii PixArt camera is supported; the ESP32 port’s PAJ7025R2/R3 code is the nearest source for othersDFRobotIRPositionEx3
10The screen model assumes square-layout emitters on the picture edges; calibration absorbs the rest with linear offsetsOpenFIREcommon.cpp2
11The core is pinned at arduino-pico 4.5.4 for an open TinyUSB serial bugCI workflow, COMPILING.md7.8
12The 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 choiceApp against Hardware Guide5

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

#ChangeWhyDive
13An analog Hall-effect trigger: ADC read, per-gun calibration, press at 50 % and release at 40 % of travel, a fault read as releasedOpenFIRE has no analog trigger functionReference Electronics
14An ejection-port output on its own pin, driving the board’s second power stageTwo ways of moving the port are to be triedReference Electronics
15Solenoid pulse ended at landing, then regulated currentItem 8; most of a fixed 45 ms pulse is heatSolenoid 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

OptionVerdictReason
arduino-cli on TeamOpenFIRE’s arduino-pico fork, pinned rp2040:[email protected], with the CI workflow’s compile linePick, for release buildsThe 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 forkPick, for exploring and debuggingSame 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 adoptionCOMPILING.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 coreRejectedCOMPILING.md says not to install it
PlatformIORejectedNot supported for the RP2040 firmware; only the ESP32 port uses it
Raspberry Pi Pico SDK with CMake, no Arduino layerRejectedThe 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

  1. TeamOpenFIRE, OpenFIRE-Firmware v6.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.
  2. TeamOpenFIRE, arduino-pico fork, release 4.5.4-tusb (2025-05-23), https://github.com/TeamOpenFIRE/arduino-pico/releases — read 2026-10-18.
  3. TeamOpenFIRE, OpenFIRE-Boards README and OpenFIREshared.h, https://github.com/TeamOpenFIRE/OpenFIRE-Boards — read 2026-10-18.
  4. Adafruit, Adafruit_TinyUSB_Arduino issue #293, “Serial crash when receiving incoming data faster than handled”, https://github.com/adafruit/Adafruit_TinyUSB_Arduino/issues/293 — state checked 2026-10-18.
  5. Earle F. Philhower III, arduino-pico latest release 6.3.0 (2026-10-08), https://github.com/earlephilhower/arduino-pico/releases — checked 2026-10-18.
  6. Mike Lynch, Prow7/ir-light-gun README (TinyUSB stack note).
  7. Alessandro Satanassi, OpenFIRE-Firmware-ESP32 README (PlatformIO, PixArt cameras).

Comments (0)

  1. Loading…

Comments are held for moderation — nothing appears until approved.