The post below is in Portuguese. We will translate critical announcements as the project gains an English-speaking audience.
Gear control, oil protection, flex fuel and configurable I/O: the checklist that defines a standalone ECU, attacked whole before the board exists
Standalone ECUs get compared by checklist — how many inputs, how many outputs, does it protect oil pressure?, does it cut for clutchless shifts?, does it run E85? Over the last two days we attacked that entire checklist: RPM-conditional oil protection, ignition-cut quickshifter, lambda-based flex fuel and a configurable I/O model that never disarms fail-safety. All CI-tested, hardware still in transit.
People shopping for a standalone ECU compare spec tables. How many configurable inputs, how many outputs, does it control the gearbox?, does it protect on oil pressure?, does it run flex fuel? That’s how a FuelTech, a Haltech and a Link get placed side by side — and that’s why we fixed parity with that checklist as the product’s north star (verticalized for V-twins, where those ECUs treat us as second-class citizens).
Over the last two days, we attacked the whole checklist. The board is still in transit.
Oil protection: the floor is a function of RPM
Oil pressure scales with pump speed: ~110 kPa at hot idle is healthy on a Twin Cam; the same 110 kPa at 5500 RPM means the bottom end is starving. A scalar threshold can’t express that — so the floor is a table of RPM→minimum kPa, the way the reference ECUs do it.
Two engineering details that matter more than the feature itself:
The cut rides the same path as overboost. When pressure falls
through the floor (with debounce, latch and recovery hysteresis), the
monitor promotes the state to the same Cutoff the turbo overboost
protection already used. Zero new cut mechanisms to maintain — and the
DTC comes out in the format everyone knows (P0521 through P0524).
A dead sender counts as LOW pressure. The fallback policy is assume-LOW: anyone enabling this protection has an engine worth more than a nuisance stop, so a faulted sensor must never silently disarm the one net protecting the crank. Note that this is the opposite direction of the lean cut — which refuses to act on a dead wideband. Fail-safe isn’t one rule; it’s deciding, protection by protection, which failure direction hurts less.
Gearbox: estimate the gear, cut ignition — through the proven path
The pilot Twin Cam has no gear-position sensor. The GearEstimator infers the engaged gear from the reduction it can observe: engine RPM over wheel speed, matched against per-gear ratio windows. And here’s a detail I like: overlapping windows are a tune validation error, not a runtime tiebreak. If two gears could claim the same reading, the file doesn’t even load.
The quickshifter cuts ignition — never fuel — for the per-gear configured width: the charge keeps flowing, torque collapses, the next gear engages without the clutch. The cut is applied through the same inhibit flag the decoder’s sync gate already used inside the scheduler. Same principle again: new surface, proven mechanism. And every gate fails toward OFF: a stuck switch produces exactly one cut, no estimated gear means no cut, and the width is clamped to 200 ms even if a hand-edited tune tries to sneak more past the validator.
Flex fuel: the lean limit now follows the fuel
This one was a documented footgun in the code, waiting its turn: the lean-mixture protection tripped at AFR 15.5 — calibrated for gasoline. On E85, 15.5 is λ ≈ 1.6: the engine melts well before the “protector” wakes up.
The limit is now constant lambda: the trip point is derived from the active fuel’s stoichiometric ratio. On gasoline the result is bit-identical to the legacy limit (there’s a test comparing the float’s bits); on E85 it becomes ~10.2, on methanol ~6.8. The same physical margin, whatever is in the tank.
Configurable I/O without disarming fail-safety
The big piece of the arc. The model, fixed in an ADR: the board publishes capacity, the tune publishes binding. Each board exports the list of physical channels it has; the basemap binds channel→role. And the roles are a closed enum — oil pressure, ethanol content, clutch switch, shift request, vehicle speed, and so on (10 input roles today). The core consumes roles, never raw channels.
Why not copy the classic “input 7, output 12” numbered model? Because a
closed role allows what a numbered channel can’t: the failure
fallback is part of the binding, as a safety enum — assume-low,
assume-high or assume-value. A sensor outside its plausibility rails, a
malformed calibration, a dead channel: everything collapses onto the
safe assumption declared in the tune. A wrong tune degrades; it doesn’t
disarm fail-safety. And the schema (v2) pins this: an older consumer
rejects a file carrying io_channels instead of silently dropping
a fallback policy that is safety-relevant.
Outputs follow the same doctrine: 7 functions, each one a small, host-testable state machine. The cooling fan fails ON when the temperature reading can’t be trusted (cooling an engine that didn’t need it is harmless; not cooling one that did is not). The nitrous output only mirrors the progressive controller’s flag — the safety handshake stays inviolable, there is no second arming logic to drift. The air shifter fires exactly one pulse per cut; a stuck switch never re-triggers. And no output feeds back into the loop: they are read-only taps of state the loop already computed.
The ECU also started speaking real CAN
The firmware gained its CAN broadcast task: the cadences (100/50/10/1 Hz + a boot frame + DTCs round-robined) come from the protocol spec, not from loose constants. And the ugly case was handled first: on a bench with no transceiver — or a shorted harness on the bike — the peripheral never completes a transmission. The policy is per-mailbox timeout, abort, exponential backoff, peripheral reinit — and nothing in that path blocks the executor: the watchdog keeps getting fed and the control loop never learns the bus died.
Tuner, cloud library — and a real data-loss bug
The Tuner gained Transmission, Oil Pressure and Flex tabs in the map editor, plus a basemap library wired to the cloud — the public API already serves the curated tunes, with gated download and hash-based updates. And along the way a bug surfaced that justifies the arc by itself: the open-and-save round-trip silently dropped any sections the parser didn’t know. Quiet calibration data loss. Fixed with regression tests on both sides (Rust and TypeScript).
What’s still bench work
The usual honesty: the physical side of all this is wire and silicon that hasn’t arrived. The real PWM/GPIO side of the outputs waits for bring-up. The Nucleo has no CAN transceiver — and the emulator doesn’t model the bxCAN peripheral reliably, so TX proof is a logic analyzer on the bench. The real oil-sender curves come from my TC124’s gauge. The v1 quickshifter is a digital switch (a strain-gauge front end is hardware the current board doesn’t have). None of this is hidden: it’s documented in the code, function by function.
Why this matters
The ecu-firmware crossed a thousand tests in this arc (1072, ~674 in the core alone). But the number I watch hardest is another one: golden traces byte-identical across every wave. A tune without the new sections = firmware that behaves bit-for-bit like before, by construction. That’s how you add an entire checklist of features to an ECU without touching the critical path of whoever already trusts it.
Spec checklists sell ECUs. What keeps customers is what’s not on the table: every one of those checkboxes arrived with its failure mode decided, written and tested before it ever touches an engine.