reid.wiki

Projects / Embedded & Control

Turbine Engine ECU

A from-scratch engine control unit for a miniature turbojet: custom STM32 PCB, safety-first bare-metal firmware, and a desktop bench app, all built after the factory ECU died.

Status
Active
Timeline
2026 to present
Last updated
June 2026
STM32G431C (bare-metal)KiCadPythonPySide6CMakeCOBS/CRC16

Why

I have an R.A. Microjets miniature turbojet with a dead factory ECU. Replacement controllers exist, but where’s the fun in that. An engine controller is a perfect excuse to build a complete embedded product: board, firmware, protocol, tooling, and the safety story that makes you willing to stand next to the thing.

The board

A 4-layer STM32G431 board designed entirely from Python scripts driving headless KiCad: netlist, placement, autorouting, and verification, with no hand-drawn schematic. Final board: 163×124 mm, 87 components, 55 nets, 0 DRC violations, 0 ERC errors, 0 schematic-parity issues. Fabricated at JLCPCB with a fully verified DigiKey BOM and a per-pin GPIO audit against the datasheet.

TurbineECU, routed 4-layer board, 3D render
As fabricated: 163×124 mm, 87 components, 0 DRC violations.

The analog side covers the whole engine interface: MAX31855 thermocouple input for exhaust gas temperature, an LM393 comparator front-end with hysteresis for RPM pickup, shunt-based current sensing on the actuators, a buck converter power stage, reverse-polarity ideal-diode protection, and USB-C with ESD protection.

Front-copper trace routing of the TurbineECU board
Front-copper routing, generated entirely from Python. No hand-drawn schematic.

The firmware

The core is portable, HAL-free C that compiles to the same object code on the STM32 and on the host, so the entire control system runs under CTest before it ever touches hardware. Five test suites, including a 20,000-iteration structural fuzz of the engine state machine and fault-injection coverage of every interlock.

The design rule that matters: the safety layer runs last on every 1 kHz tick and has final authority. It can only push outputs toward the safe state (fuel off, gas off). Hardware pull-downs keep actuators off through reset and brownout, and a ~250 ms watchdog backs it all up. Footprint: 24.2 KB flash, 8.4 KB RAM.

The wire protocol (COBS framing + CRC-16/CCITT) is generated from a single Python spec into C headers, a Python codec, protocol docs, and cross-language golden test vectors, making firmware/host drift structurally impossible.

The bench

A PySide6 desktop app (~1,900 lines) with live telemetry plots, a command console, parameter editor for the 22-entry tunable table, fault log, YAML-scripted test sequences that cannot bypass interlocks, and SQLite run logging with post-mortem analysis.

Before the hardware arrived, a software-in-the-loop simulator with a turbine plant model ran the full stack over TCP, including reproducing the original engine’s historical ignition-timeout failure mode: exactly the fault that killed the factory ECU’s usefulness in the first place.

Turbine ECU Bench — □ ✕ Target: tcp:127.0.0.1:5760 Connect Disconnect RPM EGT Actuator PWM Shunt currents Battery 1000050000 3001500 8004000 15007500 800040000 rpm °C mA mV 012 345 6 time (s) Control Command Params Faults Sequence Analysis Engine OFF READY IGNITION FUEL_RAMP RUNNING RPM: 12,207 EGT: 379.8 °C Vecu: 8.00 V  Vrx: 0.00 V Fault: NONE link: UP START ABORT Throttle 84.3 % Save params → flash Identify HELLO seq=1.6.0.1.32 0 faults telemetry 1 kHz
The bench app replaying a simulated start over TCP: light-off at t=2s, glow circuit drops out after ignition, and the state machine settles into RUNNING at a stable idle.

Status

All six software phases are built and verified in simulation. The PCB is fabricated and in hand. Current work is hardware bring-up: flashing, USB-CDC wiring, sensor calibration, and metering real engine values before the first supervised start.