Methanol injection controller
A simple boost-referenced PWM pump controller. It reads boost straight from the DME over the OBD port, sprays on a curve, watches the pump's current draw and runs the shift light off the same CAN link. A round touch screen and an Android app set it up.
On the bench
This morning's ramp test. The app tells the board to run the pump from 25 % to 100 % and back down over 25 seconds, and the round screen follows along over its USB cable.
What you're looking at
- The board is Rev E in its printed snap-fit case. 12 V and the pump on the blue terminals, the screen's cable in the USB-C in the middle.
- The screen says BENCH because the app's pump test is driving the board. Boost reads two dashes: there's no car on the bench, so no DME to ask.
- The duty climbs in steps the app sends, and the board's own soft start smooths each one.
- 0.6 A at 100 % is the pump with no fluid in it. That dry number is half of what the dry-tank check needs. Wet runs give it the other half.
How it works
Boost in, duty out. Everything else is there to make sure the pump is doing what the duty says.
No sensor to plumb. The board polls the F56's DME over D-CAN on OBD pins 6 and 14, with the same BMW UDS reads E-Sys and F5X Code use. One bundle read brings back RPM, manifold pressure, ambient, load, intake air, lambda and lambda target. Boost is manifold pressure over ambient.
Two to ten points on boost, load or RPM, straight lines between them. The F56 default is 25 % at 2 psi rising to 100 % at 8 psi, and it shuts off again under about 1.5 psi.
1 kHz low-side switching through a UCC27524 gate driver and an Infineon IPD90N06 MOSFET, with a flyback diode across the pump. A solenoid valve rides on the pump's PWM lead.
A 2 mΩ shunt and an INA180 measure the pump every 10 ms. A pump that's dry, unplugged, overloaded or switched on when it shouldn't be all show up in its current. That's the safety system.
Duty can jump straight to the 25 % floor, then climbs 2.5 % every 10 ms, so 25 to 100 % takes 300 ms. It drops as fast as asked. Hitting a stopped pump with 100 % stalled it and dragged the battery under the board's 10.5 V limit.
A WS2812B strip off the RPM the board already reads. It's also where faults show up, so it's the one warning you'll see without looking for it.
Cars that don't answer BMW UDS can feed it a 0-5 V boost sensor through a protected input, or take RPM from the car's CAN broadcast. The curve runs the same either way.
The board
Rev E is 85 × 52 mm, two layers of 2 oz copper, assembled by JLC with the XIAO, the buck module and the terminals left for me to fit. It lives in the glovebox in a printed case. One fused, ignition-switched 12 V feed runs both the pump and the logic, so key off is pump off.
- Seeed XIAO ESP32-S3 Plus: BLE for the app, its USB-C for the screen.
- MP1584 buck to 5 V for the logic, the strip and the screen.
- TCAN1051HV CAN transceiver, held silent by hardware until the firmware is up and needs to talk to the DME.
- Pump stage: UCC27524, IPD90N06S4L03, 12CWQ06 flyback, 2 mΩ shunt into an INA180.
- Sensor input: a 115k divider and clamp on a 0-5 V signal, plus battery voltage sense.
- Shift light through a 74AHCT1G125 so the strip sees a real 5 V data high, its own PTC, and a 300 mA cap in firmware.
- Default off: the pump needs both the driver enable and the PWM pin high. Both have pull-downs, so reset, the bootloader and a watchdog reset all leave it off.
Fitting the XIAO by hand on a hot plate was the weak spot. Rev F, on the drawing board now, has a bare ESP32-S3 module and an LM5164 buck placed by JLC with everything else, a TCAN3414 for CAN and a K-line transceiver for the cars that need one.

The screen
Optional, and it has its own ESP32. A Waveshare ESP32-S3-Touch-LCD-2.1: 480 × 480 round touch panel, an IMU, a clock and a TF card slot. It plugs into the board's USB-C with an ordinary cable. The board runs that port as a USB host, powers the screen, checks it with an HMAC handshake and then streams telemetry to it 25 times a second.
Boot
Power up and it plays an 8-second clip of a meth nozzle spraying into a charge pipe, rendered for this panel and decoded straight into its frame buffers at 24 fps. Tap to skip. Put your own boot480.mjpeg on a FAT32 TF card and it plays that instead, up to 10 seconds of it.
Pages
Swipe left and right. A fault takes over every page except Settings, the editor and Info with a red banner: what happened and what to check.









If the link drops, every value blanks after half a second and Live says LINK LOST. A grey screen isn't proof the pump stopped, and the screen says so.
These are captured straight off the panel's frame buffer, running the screen's built-in demo loop. They're from 10-17, before the duty floor went to 25 %, so the demo curve starts lower than today's default.
The app
Methanol for Android talks to the board over BLE. It finds the board by itself and opens on four screens with nothing to type: every value comes from the board and changes with + and − or a slider.




Shift light
LED count, fill from both ends, brightness, the RPM thresholds or a car's preset, four colours, and the switch that lets methanol faults take over the strip. The preview runs a port of the firmware's own renderer, so what you see is what the strip does. Send applies it without disarming, because the strip never touches the pump.
Advanced
The engineering screens live behind Settings, Advanced: the DME link (which values answer, reads per second, round trip), CAN fields and a frame sniffer, pump health with the learned wet and dry current model, and signed firmware updates over BLE.

Dash, Pump test and Settings are my phone on the bench board on 10-22. Curve and Shift light are the current build, 0.5.1, on an emulator holding the board's default settings.
Setting it up
In order. The board won't run the pump until the first two are done, and won't spray on boost until all of it is.
- Wire it. A fused, ignition-switched 12 V feed and ground, the pump on PUMP, OBD pins 6 and 14 to CAN high and low, the strip on SHIFT LIGHT and the screen into the USB-C. Leave the CAN termination open: the car's bus already has it.
- Set the pump current limit. Settings, Pump current limit. The board ships at zero and refuses to run the pump until it's set. Mine is 11 A.
- Purge the line. Pump test, Purge, nozzle in a container. Up to 100 % and 10 s there to push the air out.
- Check the spray. Ramp test or Hold a duty, and watch the nozzle and the amps.
- Teach it the pump. Advanced, Pump health. Wet and dry sweeps from 20 to 100 % give the board the current the pump should draw at each duty and battery voltage. The dry and open checks measure against that model, and the board won't spray without one.
- Set the curve. The Curve tab on the phone, or drag the points on the screen's editor, Apply to try it, Save to keep it.
- Set the shift light. Thresholds and colours, with methanol faults left on.
- Arm it. Every boot starts disarmed. Arm from the app, or turn on "Arm itself each drive" under Advanced, Input. Disarm holds self-arming off until the next Arm.
In this bench build Arm is switched off in the firmware, so none of this sprays on boost yet. The pump runs from Pump test only.
Safeties
The pump's current is the safety. Every 10 ms the board compares what the pump draws with what the learned model says it should draw at this duty and battery voltage.
| Fault | Trips when | Strip |
|---|---|---|
| Dry tank or blocked line | Under 65 % of the expected current for 400 ms, at 25 % duty or more. It waits 300 ms after a big duty change so the soft start never reads as dry. | Blue |
| Pump not connected | Under 10 % of the expected current. | Amber |
| Overload | Over the pump current limit you set. | Amber |
| Current with the pump off | Over 0.5 A with the duty at zero: a shorted MOSFET or a wiring fault. | Amber |
| Battery out of range | Under 10.5 V or over 16 V while spraying. | Amber |
| Restarted mid-spray | The board reset while the pump was running. | Amber |
| Sensor, current reading, memory | A 0-5 V boost sensor out of range, a pump current reading that can't be right, or bad settings storage. | Amber |
Any of these stops the pump and latches. It stays off until you clear it from the app, which asks you to fix the cause first.
Before it sprays at all
- Armed, with the settings saved and valid and a pump current limit set.
- Engine running over 400 rpm for half a second, from fresh data.
- Boost no older than 100 ms, and on the F56 only from a DME that identifies as a DME_BX8.
- Battery between 10.5 and 16 V, and a pump current reading that makes sense.
- A learned pump model covering the curve's duty range.
Lose any of them and the duty goes to zero until it's back. That's a hold, not a fault, so there's nothing to clear.
Where you see it
The fault replaces the RPM bar: blue for a dry tank, amber for the rest. The two halves take turns while the engine runs. Parked, the lead LED blinks the fault's number.
A red banner over every page with the reason and what to check.
Plain words on Dash with a Clear fault button, and a fault history that keeps what the pump was doing when it tripped: RPM, boost, duty, measured and expected current.
Under all of that: the pump pins are driven low before anything else starts, a task watchdog and the RTC watchdog both run, and the control loop drops the output if it stalls for more than 20 ms.
Where it's at
On the bench. The pump runs from the app and the screen follows the board over USB, and firmware updates go over BLE. Before it goes in the car: wet runs, runs at charging voltage around 14 V, the dry-tank threshold set from those, and then Arm turned on in the firmware. Rev F follows the bench work.