Our Firmware · Volume 1
The Fork — What It Is For, What It Is Not, and Where in OpenFIRE Each Change Lands
Why the light gun runs a private copy of OpenFIRE v6.2, the owner decisions that shape it, the v6.2 files every proposed change touches, the finding that settings travel between gun and App by name, and the whole change list on one page

1.1 What this dive owns
The light gun this category builds runs OpenFIRE, the open firmware for Raspberry Pi Pico light guns. What OpenFIRE is, how its v6.2 source works, how it is flashed, configured and built, and what its licences say are the subject of Open-Source Firmware, which is live and which this dive does not repeat. This dive starts where that one stops, at its Volume 7 §7.10: the project’s own copy of the firmware, changed for this gun.
Every other dive in the category that found something the firmware should do differently left it here. The aiming maths, the trigger, the ejection port, the rumble motor, the inertial sensor and the experimental camera work each ended with a list “for Our Firmware”. This dive gathers those lists into one plan and owns how the copy is run:
- The change list (Volumes 2 and 3): every proposed change with its source dive, the reason, the risk of making it and the bench test that proves it. The reasoning behind each stays in its source dive and is linked, not re-derived.
- Running the fork (Volume 4): where it lives, its branches, how upstream releases are merged in, the pinned toolchain, versioning and releases.
- Testing it (Volume 5): what is tested off the gun, what on the bench with the lab’s Digilent Analog Discovery Pro ADP3450, and what before every release.
- The licence and the alternative (Volume 6): what GPL-3.0 demands the day a kit is sold, quoted from the licence and the repositories, the USB vendor ID question, and an honest assessment of whether writing new firmware would be better.
The sibling dives that feed it are Aiming Maths and Calibration, Controls and Inputs, Moving Ejection Port, Motors and Rumble, Positional and Inertial, Experimental Sensing, Reference Electronics and Solenoid Recoil. The legal treatment of kits in general belongs to Licensing, IP and Kits, which is planned; Volume 6 here covers only what the firmware itself owes.
1.2 The decisions that shape the fork
The owner’s decisions are recorded with their reasons in the category’s decision log. Six of them define what this fork is:
Table 1 — The owner's decisions are recorded with their reasons in the category's decision log. Six of them define what this fork is
| Decision | What it says | What it means for the fork |
|---|---|---|
| OF-1 (proposed by Open-Source Firmware) | OpenFIRE v6.2 in a private fork | The base is TeamOpenFIRE’s RP2040 firmware at the v6.2 tag, not the ESP32 port and not anything written new |
| D10 (2026-10-18) | Fix OpenFIRE bugs in the fork for now; do not report upstream yet | Every fix is carried by the fork across upstream releases until it is reported or upstream fixes it independently (Volume 4) |
| D12 (2026-10-18) | STEP, CAD and firmware stay private until a working model exists | The fork’s source is not published, and these volumes describe changes without publishing code. Short quotations of upstream source appear only for context |
| D11 (2026-10-18) | Kits may be sold later | The licences and USB rules are binding, not advisory (Volume 6) |
| D24 (2026-10-18) | Keep OpenFIRE’s USB vendor ID 0xF143 now; obtain an assigned vendor ID before any kit sale | A change scheduled for later, with consequences for MiSTer and the App (Volume 6) |
| D30 (2026-10-19) | The ESP32-S3 port runs as it is, on a separate track, for the wireless trial; the fork’s fixes are not ported to it now | The fork targets the RP2040 (and the Pico 2 drop-in); the ESP32 port is a source to borrow from, not a second product (Volume 4) |
Two more decisions set the hardware the fork drives: D2 and D19 (a Raspberry Pi Pico first, a Pico 2 as the drop-in) and D18 (the kit camera must be buyable in quantity, which points at the PixArt PAJ7025R3, a camera stock OpenFIRE cannot read; Volume 2).
1.3 What the fork is not
Not a rebrand. OpenFIRE’s README says forks “made for the purposes of rebranding are heavily DISCOURAGED” and asks that every fork “maintain the OpenFIRE name as well as credits to its original authors”, naming That One Seong, Samuel Ballantyne and Mike Lynch. Those are the authors’ requests, not licence terms (Open-Source Firmware, Volume 1 §1.12), but there is also a technical reason to follow them: the App, MiSTer and QMamehook find the gun by its vendor ID and its “OpenFIRE” manufacturer string (Open-Source Firmware, Volume 6 §6.5). The fork keeps both until the vendor ID question of Volume 6 is settled.
Not published, yet. The private repository is a working copy. A private build used on the bench owes nothing under the licences; the obligations begin when a binary leaves the house (Volume 6). The plan is written so that the day a kit ships, the source can be handed over as it stands, with no history to clean up first.
Not a second ESP32 product. The ESP32-S3 port by Alessandro Satanassi is fast-moving: on 19 October 2026 its last full release was v6.2.1 (30 June 2026) and it was on its fifth v7.0.0 release candidate (16 October), with commits that same day. Keeping two firmwares in step would double the work, which is why D30 keeps the wireless trial on the port as published. The port matters to the fork as a source: its PixArt camera driver, its calibration map and its filter fixes are among the changes proposed below.
Not a rewrite. Whether new firmware would be better is a real question with a real answer, set out in Volume 6. The short version: not now.
1.4 The code a fork changes
OpenFIRE v6.2 is an Arduino sketch, OpenFIREmain, plus four libraries kept in its own tree and five git submodules. Read at the v6.2 tag (OpenFIRE-dev at 8b651a25, 19 October 2026), the sketch is 6,849 lines in seventeen files; the in-tree libraries add about 3,100 more; the shared board header, OpenFIREshared.h, adds 890.
Table 2 — 1.4 The code a fork changes
| Part | Lines | What a fork changes there |
|---|---|---|
OpenFIREmain.ino | 1,426 | The main loop, the trigger path, calibration, pause mode: CI-5, CI-6, AM-2, AM-5, AM-10, the ammo state, the run-mode bug (AM-6) |
OpenFIREserial.cpp | 1,383 | Serial commands and the App protocol: FDAx handling for the hold-open (ME-3), any new settings, version reporting |
OpenFIREcommon.cpp | 1,167 | Camera start-up, position, off-screen logic: AM-1, AM-4, AM-7, AM-8, the PixArt camera switch, the IMU bridge (PI) |
OpenFIREdisplay.cpp | 734 | The OLED: the ammo count (D37) |
OpenFIREFeedback.cpp | 258 | Solenoid, rumble, temperature: pulse ended at landing, the D16 sequencing, the port stage and hold-open, MR-4, MR-6, v6.2 findings 2, 3 and 4 |
OpenFIREprefs.cpp/.h | 422 | Stored settings: new named settings for every addition |
libraries/OpenFIREPosition | 1,010 | The homography and the square layout: AM-1, AM-9 |
libraries/LightgunButtons | 580 | The button filter that CI-6 bypasses for the Hall trigger |
libraries/DFRobotIRPositionEx | 681 | The Wii/DFRobot camera driver: v6.2 finding 1, and the place a PixArt driver sits beside |
boards/OpenFIREshared.h (submodule) | 890 | Pin functions and setting names: the new outputs and inputs (Volume 4 §4.4) |
Two things follow from the table. First, most of the work is concentrated: four files carry three quarters of the changes, which is what makes merging upstream releases tractable (Volume 4). Second, one of the files is not OpenFIRE-Firmware’s at all. The board header lives in a separate repository, OpenFIRE-Boards, is pulled in as a submodule and is compiled into the App as well, so a change there is a change to the contract between the gun and the App.
1.5 Settings travel by name: the App boundary
Every addition in this plan needs a setting: the port’s pull-in time, the hold duty, the trigger’s break count, the rumble frequency. The question that decides how much work that is: will the official OpenFIRE App, which the project does not intend to fork, tolerate a gun that knows settings it has never heard of?
Reading both sides of the protocol answers it, and the answer is better than expected.
- The gun sends settings as name–value pairs. In v6.2,
OF_Serial::SerialBatchSend()walks a map of names to indices and, for each, writes the name as a null-terminated string, then a size byte, then the value. The names are the ones in the shared header, which carries the comment “names should be written in plain english, as these name strings are shared with Apps”. - The App skips names it does not know. In App v3.0.3,
AppSerial::BatchStoreSettings()looks each received name up and, if it is not in its own map, logs “No data found for” the name and reads past the value. The code comment is “String not detected, skip over.” - The gun’s save file works the same way.
OF_Prefs::LoadToPtr()reads name, size, value from the LittleFS file and seeks past any name it does not know.

So a fork can add named pin functions and settings without breaking the stock App: docking, calibration, the tests and the existing settings all keep working, and the new values survive in the gun’s flash. What the stock App cannot do is display or change them. That defines the cost of each kind of change (1.6).
There are two traps. The header’s own comment says new non-button slots “should ideally be added at the bottom, above the ‘count’ line”, because the firmware stores them in arrays by index; insert one in the middle and every later index moves. And profile data is different: the header warns that its order “MUST match the order of ProfileData_s … as ProfData is accessed by struct offset”. A new per-profile value is the risky kind of change. Every setting this plan adds is gun-wide, with one exception, the optional four-corner calibration (AM-10), whose per-profile numbers go in a separate fork-owned file rather than into the profile structure (Volume 2 §2.3).
1.6 Three ways to configure a change
Table 3 — 1.6 Three ways to configure a change
| Route | Used for | Why | Cost |
|---|---|---|---|
| Compile-time, in a fork configuration header | Things fixed by the carrier board: the extra pin assignments (EJECT_CMD on GP11, the SPI camera pins), the camera model, which features exist | The carrier is the project’s own PCB; its pins do not change gun to gun. A fork-only board preset means a freshly flashed Pico is right without the App | A different carrier needs a rebuild |
| New named settings, gun-wide | Bench-tuned numbers that vary per gun: port timings and hold duty, trigger break and margins, the rumble frequency, the One-Euro parameters | They survive in flash and are skipped by the stock App (1.5); Moving Ejection Port asks for exactly this “so the bench values go into the gun without a rebuild” | Need a way to set them (below) |
| A forked App | Showing and editing the new settings in a GUI | The only way a non-technical kit buyer could tune them | A second program to carry, under GPL-3.0, across App releases. Deferred |
Setting the named values without an App fork needs a channel. The shared header reserves serial codes by range: “ASCII 128+ should be for the board to send, and invisible ASCII characters/control codes 0-32 should be for the app to send. ASCII 33-127 should be avoided whenever possible”, because that range is the text command space of MAMEHooker and its relatives (Open-Source Firmware, Volume 6). The plan is a small bench tool, a script on the workstation, that docks the gun the way the App does and commits the fork’s named settings through the same name–value commit the App already uses. The fork’s receiver accepts them because they are in its map. No new serial code is needed, which keeps the fork out of the code space upstream may use next.
1.7 The change list on one page
Every change proposed for the fork, grouped and with the volume that details it. Status is the decision log’s: all are Proposed unless marked.
Table 4 — 1.7 The change list on one page
| Group | Rows | Change in one line | Vol |
|---|---|---|---|
| v6.2 fixes | OSF-1 to OSF-12 | The findings of Open-Source Firmware Vol 7 §7.10: sensitivity hotkey, burst-fire off time, first-shot temperature bypass, 2 s serial hold, doc mismatches | 3 |
| Aiming | AM-1, AM-2, AM-5, AM-10 | Exact offset map; boresight from the diagonal intersection, unrotated; average each calibration shot; optional four-corner calibration | 2 |
| Aiming | AM-4, AM-6, AM-7, AM-8, AM-9 | R3 lens correction; run-mode index fix; off-screen hysteresis; One-Euro cursor mode; homography hygiene | 2 |
| Camera | P-RE16, P-RE18, ES-1, ES-2 | PixArt PAJ7025R3 driver on the Pico; frame subtraction and a firmware lock to a pulsed bar (research) | 2 |
| Inertial | PI-1, PI-2 (D36) | LSM6DSOX on I2C1, FIFO polled, the camera–gyro bridge | 2 |
| Trigger | P-RE7, CI-5, CI-6 | Analog Hall trigger on the ADC; three-point calibration firing at the felt break; no button-library debounce for it | 3 |
| Recoil | SR-2 (D13) | Pulse ended at landing plus 2 ms for the regulated stage | 3 |
| Two stages | P-RE21 (D16) | Port stage only after the recoil current has decayed; recoil wins | 3 |
| Ejection port | ME-3 | Port pulse, hold-open state machine on FDAx or own count, 10 s limit | 3 |
| Rumble | MR-4, MR-6 | 20–25 kHz PWM; no rumble start during a pulse | 3 |
| Display | D37 | Ammo count on the OLED, also from the gun’s own count | 3 |
| USB | D24 | Vendor ID 0xF143 now; an assigned ID before any kit | 3, 6 |
| Running it | FW-1 to FW-9 (proposed here) | Private duplicate, branches, pinned toolchain, versioning, tests, VID route, no rewrite | 4–6 |
AM-3 (camera within about 8 mm of the sight line) is a mounting rule, not firmware; it appears in Volume 2 only because the firmware’s parallax correction is the alternative it rejects.
1.8 The order of work
The order follows the hardware, because a change that cannot be tested on the bench should not be merged.
- Reproduce v6.2. Build the unmodified tag with the pinned toolchain and check it behaves as the release UF2 does (Volume 4 §4.5). Every later difference is then the fork’s.
- Stage 1, the stopgap gun (Pico, harvested Wii camera, stock pin map plus EJECT_CMD): the v6.2 fixes, AM-1/2/5/6/7/9, the analog trigger with CI-5/CI-6, pulse at landing, the D16 sequencing, MR-4. These need nothing that has not been bought.
- The carrier with the port and the OLED: ME-3, MR-6, D37, the bench tool for named settings.
- The kit camera when the PixArt samples arrive (D32): the PAJ7025R3 driver, AM-4, then the ES programme.
- The IMU (D36, D38): the LSM6DSOX bridge.
- Before any kit: the vendor ID (D24), the source package and notices (Volume 6), and the release checklist (Volume 5).
1.9 What could not be verified
- Every statement about the code is a reading of the v6.2 source and App v3.0.3 source on 19 October 2026; none of the fork has been built or run.
- That the stock App behaves well with a gun that reports extra names in every exchange, beyond the skip in
BatchStoreSettings(): the App’s commit path was read, not run against a modified gun. - Upstream’s plans: the firmware repository had no push after the v6.2 release of 19 April 2026, and nothing published says what v6.3 or v7 will change.
Sources
- TeamOpenFIRE,
OpenFIRE-Firmwareat8b651a25(v6.2):OpenFIREmain/(all files),libraries/,.gitmodules,README.md, https://github.com/TeamOpenFIRE/OpenFIRE-Firmware — read 2026-10-19. - TeamOpenFIRE,
OpenFIRE-Boards,OpenFIREshared.h, https://github.com/TeamOpenFIRE/OpenFIRE-Boards — read 2026-10-19. - TeamOpenFIRE,
OpenFIRE-Appv3.0.3,src/appserial.cpp(BatchStoreSettings, lines 242–313) andsrc/appmainwindow.cpp, https://github.com/TeamOpenFIRE/OpenFIRE-App — read 2026-10-19. - Alessandro Satanassi,
OpenFIRE-Firmware-ESP32, releases and commits, https://github.com/alessandro-satanassi/OpenFIRE-Firmware-ESP32 — read through the GitHub API 2026-10-19. - The category decision log (
DECISIONS.md), rows D2, D10–D12, D16, D18, D24, D30, D32, D36–D38, OF-1 to OF-5, and the AM, CI, ME, MR, PI, ES and P-RE rows — read 2026-10-19. - Sibling dives as linked.
Comments (0)