New to electronics? Start here
What we're actually building
If you've ever replaced a crank position sensor or an ignition pickup, you already understand the heart of this. A magnet sweeps past a sensor, the sensor says "tick," and from the rhythm of those ticks you know how fast something is spinning. We do exactly that on each prop shaft — then put both numbers on a screen in the cockpit, with a needle that tells you when the two engines are matched.
The four pieces, in plain terms
A magnet on each shaft
One or two small magnets fixed to a stainless clamp around the shaft. This is the thing the sensor "sees" go by. (No magnets? There's a sensor that reads the coupling bolts instead — see Options.)
A sensor beside it
Mounted a few millimetres away on a solid bracket. Every time a magnet passes, it sends a tick — just like the pickup that already tells your tach how fast the engine's turning.
A tiny computer
A cheap (~$10) module that counts the rhythm of the ticks and works out the RPM. "Arduino" is just the friendly, well-documented way to load a program onto it. You flash the code we provide once, and after that it simply runs.
A screen in the cockpit
A small 4" display showing both shaft RPMs, the average, and the sync needle. When the needle sits centered, your engines are matched.
Why one wire instead of a fat cable bundle
Your boat already does this. Rather than run a separate wire from every sensor all the way to the dash, modern gear shares one pair of wires and takes turns talking — that's what NMEA 2000 (or J1939 on the engine side) is. We borrow the same trick, called CAN. Each engine room gets its own little computer, and both report up a single shared pair of wires to the screen. Adding a third shaft later is just hanging one more box on that pair.
What the build actually involves
Glue a couple of magnets to a stainless clamp on each shaft (or fit a sensor that reads the coupling bolts), mount a sensor a few millimetres away on a solid bracket, run a two-wire bus to the cockpit, mount the screen, and flash the code we hand you using a free program on your laptop. No prior coding needed for the starter version — you copy our sketch, click upload, and watch the number move.
You can prove the whole idea on your kitchen table for about $35, before drilling a single hole. That's the Start here tab.
How it works
Time the magnet, don't count it
The whole accuracy story lives in one decision, and it's worth understanding before buying anything.
Glue a magnet to the shaft, count pulses in a fixed time window. At ~1700 rpm a single magnet only ticks ~28 times a second. In a quarter-second window you'd catch ~7 pulses — being off by a single count is a 14% error. Useless for matching two engines.
The tiny computer timestamps each magnet pass with a microsecond clock and measures the period between passes. A ~35 ms gap timed to 1 µs resolves to roughly 0.003%. Same sensor, same magnet — it's purely how the firmware reads it. That headroom is what makes a believable sync needle.
For sync specifically, what matters is the relative timing of the two shafts. Run both channels identically and average each reading over one full revolution, and most of the per-magnet placement error cancels — the needle settles instead of buzzing.
A bare stainless or bronze shaft is non-magnetic — a Hall sensor pointed at it sees nothing. You either add magnets to the shaft, or switch to an inductive sensor that reads the steel coupling bolts. Both are covered in Options; just know the bare shaft alone won't trigger anything.
Start here
Prove one shaft on the bench
Before anything goes near the boat, get a single channel reading RPM on your desk. One board, one sensor, a tiny screen. An afternoon, ~$35, and you'll understand the whole system.
For the bench demo, any microcontroller works — the simple Arduino already on his shelf is perfect. Nothing here needs an ESP32 yet; save that decision for the real build. The sketch below compiles on both as-is — it picks the right interrupt pin automatically. The only differences on a classic Uno or Nano are physical: the auto-selected sensor pin is D2 (instead of GPIO 4), and the OLED's I²C goes to A4/A5 instead of 21/22. The timing logic is identical.
- Wire the Hall module to the board
Three pins: 3.3V, GND, and signal to a GPIO. The common A3144-based module already has a pull-up and an LED that blinks on each magnet pass — instant visual confirmation.
- Spin a magnet past it by hand
Tape a neodymium magnet to anything that turns — a drill chuck, a bench grinder, a bit of dowel. Watch the module's LED tick. South pole facing the sensor for an A3144.
- Flash the period-timing sketch
The snippet below reads RPM from the interval between pulses and prints it over USB. No screen needed yet — just the serial monitor.
- Add the OLED, then the second channel
Drop in a small I²C OLED for a standalone readout. Once one channel is solid, duplicating it for the second shaft is the easy part.
Wiring — which pin goes where
Everything shares one 3.3V rail and one ground. The Hall sensor's output goes to GPIO 4 (the HALL_PIN in the sketch); the OLED uses the ESP32's standard I²C pins, GPIO 21 and GPIO 22.
| VCC | → | 3V3 |
| GND | → | GND |
| OUT | → | GPIO 4 |
| VCC | → | 3V3 |
| GND | → | GND |
| SDA | → | GPIO 21 |
| SCL | → | GPIO 22 |
Power the Hall module from 3.3V so its output is safe for the ESP32. If your particular A3144 module won't trigger reliably below 4.5V, run it from the 5V (VIN) pin instead and drop its OUT line through a simple two-resistor divider to ~3.3V before GPIO 4 — never feed 5V straight into an ESP32 pin.
// Bench starter — one shaft, RPM by period. Runs on ESP32 or Uno/Nano.
#ifndef IRAM_ATTR
#define IRAM_ATTR // no-op on AVR; real attribute on ESP32
#endif
#if defined(ESP32)
const int HALL_PIN = 4; // any GPIO — matches the wiring diagram
#else
const int HALL_PIN = 2; // Uno/Nano interrupt pin (D2)
#endif
const int MAGNETS_PER_REV = 2; // match your magnet count
volatile uint32_t lastEdge = 0, period = 0;
void IRAM_ATTR onPulse() {
uint32_t now = micros();
uint32_t dt = now - lastEdge;
if (dt > 500) { period = dt; lastEdge = now; } // glitch filter
}
void setup() {
Serial.begin(115200);
pinMode(HALL_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(HALL_PIN), onPulse, FALLING);
}
void loop() {
uint32_t p; noInterrupts(); p = period; interrupts();
float rpm = p ? 60000000.0 / ((float)p * MAGNETS_PER_REV) : 0;
Serial.print("RPM: "); Serial.println(rpm, 0);
delay(200);
}
Bench starter kit
≈ $35 USD| Part | Typical pick | Qty | Approx |
|---|---|---|---|
| ESP32 dev boardThe brain. Arduino-IDE programmable. | ESP32-WROOM DevKitC | 1 | $9 |
| Hall effect moduleDigital out, on-board pull-up + LED. | A3144 3-pin module | 1 | $3 |
| Neodymium disc magnets6×3 mm, grab a pack. | N35 6×3 mm | 1 pk | $8 |
| OLED displayFirst standalone readout. | SSD1306 128×64 I²C | 1 | $5 |
| Breadboard + jumpers | — | 1 | $8 |
| USB cable | probably in a drawer | 1 | $0 |
The full build
Two smart nodes, one bus
Two engine rooms means two long sensor runs to the cockpit — and raw Hall pulses dragged past alternators turn to noise. So put a small brain at each shaft and send finished numbers up a robust two-wire bus.
One per engine room: a small computer + sensor + bus chip, in a sealed box bolted near the coupling. It does the microsecond timing locally and broadcasts a clean RPM number. Doing the timing millimetres from the sensor — not after 10 m of cable — removes a whole class of jitter.
A board + 4" screen in the cockpit, also on the bus. It hears both shafts, computes the average and the difference, and draws the gauges. Adding a third shaft later is just another box on the bus — that's your "combine the rooms later" path, free.
The bus is CAN: two twisted wires, differential, built for exactly this kind of electrically filthy environment — the same physical layer NMEA 2000 rides on. The board has a CAN controller built in; each node just needs a cheap transceiver chip, with a 120 Ω resistor at each end of the run.
If both shafts are in one hull with short runs, you can skip the bus: a single board in the cockpit with two sensor cables works fine. Cheaper and simpler — just harder to extend and more exposed to noise. Noted here so the choice is yours.
Full cockpit build
≈ $150–230 USD| Part | Typical pick | Qty | Approx |
|---|---|---|---|
| Sensing — per shaft | |||
| Shaft sensorHall (with magnets) or inductive (on bolts) — see Options. | A3144 · or LJ12A3-4-Z/BX | 2 | $6–18 |
| Magnets + stainless collarEpoxy magnets to a hose clamp, not the shaft directly. | N35 + SS clamp | 2 | $14 |
| Marine epoxyFor bonding magnets / brackets. | G/flex or JB Weld | 1 | $12 |
| Compute | |||
| Sensor-node boardOne per engine room. | ESP32-WROOM DevKitC | 2 | $18 |
| Display node4" IPS, onboard CAN + RS485, runs LVGL. One board = whole display. | Waveshare ESP32-S3-Touch-LCD-4 | 1 | $40 |
| Bus | |||
| CAN transceiverSensor nodes only — display board has CAN built in. | SN65HVD230 module | 2 | $6 |
| Termination + cable120 Ω each end, twisted tinned pair. | 120 Ω · marine 2-core | — | $15 |
| Power & protection — per node | |||
| Buck regulator12V → 5V. | MP1584 / LM2596 | 3 | $8 |
| Transient + reverse protectionEngine-bank supply is noisy. | SMBJ16A TVS + Schottky + fuse | 3 | $12 |
| Marine hardware | |||
| Sealed enclosuresOne per node. | IP65/67 box + glands | 3 | $30 |
| Wire, heat-shrink, dielectric grease, connectors | tinned · Deutsch DT optional | — | $25 |
Prices are rough USD ballparks for mid-2026 and move with supplier and region — treat them as planning figures, not quotes.
Wiring diagrams
The schematics
Drawn to the recommended parts — ESP32 sensor nodes, Hall sensing, the Waveshare display, distributed CAN. One colour code throughout: power, ground, sensor signal, CAN. Swap the sensor or display and a few pin labels shift.
01 · sensor node
Sensor node
One per engine room: the Hall sensor feeds the ESP32, which times the pulses and broadcasts RPM onto the bus through a transceiver. Power arrives pre-conditioned from diagram 02.
| VCC | → | 3V3 |
| GND | → | GND |
| OUT | → | GPIO 4 |
| D (TX) | → | GPIO 5 |
| R (RX) | → | GPIO 18 |
| VCC | → | 3V3 |
| GND | → | GND |
Reading the coupling bolts with an inductive sensor instead? It's powered from 12V — not 3V3 — and its output must pass through a level interface (open-collector into a 3.3V pull-up, or an optocoupler) before GPIO 4. The rest of the node is unchanged.
02 · power & protection
Power & protection
One chain per node. Takes raw 12V off the boat and makes clean 5V — fused, protected against reversed leads, clamped against engine-bank spikes.
Fuse: a dead short can't start a fire. Reverse block: wire it backwards and nothing dies. TVS: clamps the load-dump spike at engine start. Buck: drops 12V to a steady 5V without cooking. Skip these on an engine bank and you'll eventually lose a node.
03 · can bus
CAN bus topology
All three nodes share one twisted pair — a single line, not a star — with a 120 Ω resistor across the wires at each far end. Middle nodes just tap on.
One: 120 Ω goes on the two physical ends only — never a middle node, never more than two. Two: all nodes need a shared ground reference, so run a third conductor (or rely on a solid common bond) alongside the pair. Node order doesn't matter; which two are the ends does.
04 · display node
Display node
Almost no wiring — the recommended board has the screen and CAN transceiver built in. Two pairs land on it: power, and the bus.
| CANH | → | bus H |
| CANL | → | bus L |
| 5V | → | buck 5V out |
| GND | → | common ground |
Middle of the bus: leave it un-terminated. Physical end of the run: it's one of your two 120 Ω points (see diagram 03). The screen and transceiver are already on the board, so no extra parts here.
05 · mounting
Magnet & sensor mounting
The one physical bit. Magnets ride on a stainless clamp around the shaft; the sensor sits on a rigid bracket a few millimetres away. Magnets go in pairs so the shaft stays balanced.
Don't want to glue anything to a spinning shaft? Mount an inductive proximity sensor facing the steel bolt heads on the coupling flange — it ticks once per bolt, evenly spaced, no magnets and no added rotating mass. Same small, steady gap. See diagram 01 for how its output wires in.
06 · full system
Full system on one sheet
How the five fit together: two sensor nodes in the engine rooms, one display node in the cockpit, all on a single CAN pair, each with its own protected 12V → 5V supply.
Three boxes, one pair of wires between them, terminated at the two ends, a small power chain inside each box. Add a third shaft later and it's simply a fourth box tapping the same pair — the whole "combine the rooms later" promise.
Options to weigh
Five forks in the road
Each has a sensible default (marked) and a reason you might go the other way. Open any one for the tradeoff.
Which microcontroller — do you even need an ESP32?default: esp32›
Timing the gap between magnet passes is easy work — almost any microcontroller can do it, so the sensing side never forces the choice. What actually decides it is the bus and the display.
Chosen mainly because CAN is built into the chip — each node needs only a ~$1 transceiver, no separate controller board. It also has the RAM to drive the 4" LVGL screen, and the all-in-one display board is already an ESP32-S3, so there's one in the cockpit no matter what you do. Bonus: WiFi and Bluetooth come along for free (more below).
Fully capable of the sensing, and the platform he's already learning. Two catches: CAN needs a bolt-on MCP2515 module (~$4) at each node, and a 2 KB-RAM Arduino can't draw a graphical 4" screen — so you pair it with a Nextion smart display, where the Arduino just sends numbers over serial and the screen draws itself. Best fit for the single-box build with no bus.
Cheap, plenty of RAM, drives displays nicely. No built-in CAN, so you add a transceiver (through the Pico's PIO or an external controller).
Because the ESP32 carries WiFi and Bluetooth on board, the same hardware can grow later with no extra parts: mirror the gauges on a phone or tablet, log RPM and sync data across a trip, calibrate or tweak settings from a web page, or push firmware updates over the air — so nobody's crawling into the engine room with a laptop to reflash a node.
How do you sense the shaft?default: hall + magnets›
Cheapest, simplest, and what the bench build uses. You epoxy 2 magnets to a stainless collar on the shaft. Downside: you're adding rotating mass (balance it) and the sensor gap must stay small and steady.
An industrial sensor (LJ12A3-4-Z/BX) reads the steel flange bolts directly — no magnets, ~4 mm range, sealed and shrugging off oil and spray. Very mechanic-friendly. It runs on 12V but its output needs a simple level interface to the 3.3V board (open-collector into a 3.3V pull-up, or an optocoupler). Pulse count is set by bolt count, so they must be evenly spaced — usually are.
How many magnets per revolution?default: 2 at 180°›
Simplest to mount, but its exact placement shows up as a fixed wobble every rev, and it adds an out-of-balance mass.
Balanced (no vibration), and averaging the two readings each rev cancels most placement error — the sync needle you'll be staring at gets noticeably smoother. Best effort-to-payoff ratio.
Highest resolution and fastest update, but more mounting fuss and four magnets to keep balanced and evenly spaced. Overkill unless you want a very twitchy live needle.
Which 4" display?default: all-in-one IPS›
4" 480×480 IPS with CAN and the board already on it, runs LVGL gauge graphics. One part is your entire display node. Bright backlit IPS — great in a shaded cockpit pod, fine in most daylight, not built for direct midday sun.
Transflective TFTs (VIEWE, Crystalfontz, Newhaven) or a reflective RLCD board get brighter in direct sun and sip power, which is the dream for an open helm. Tradeoffs: 4"-ish sizes are pricier and often monochrome, and you'll wire a separate board + CAN. Worth it if the gauge lives in full sun.
~$15–25 for a 4" panel; pair with a separate board + CAN transceiver and drive it with LVGL or TFT_eSPI. Most control, lowest cost, mediocre in sun.
You draw the gauge faces in their editor; the board just sends numbers over serial. Quickest route to a polished-looking gauge with the least graphics code. Sun-readability is mediocre.
Distributed nodes or single box?default: distributed CAN›
A node per engine room, numbers over a two-wire bus. Noise-immune over long boat runs, and extending it (third shaft, merged rooms) is plug-and-play. The right call for two separate engine rooms.
Cheaper and fewer parts. Fine if both shafts are close and in one hull. Harder to extend and more exposed to electrical noise on long runs.
Before it touches saltwater
The marine reality checklist
Anything you add to a spinning shaft is rotating mass. Two magnets at 180° (or four at 90°) keeps it balanced. Don't bolt a single lump on and call it done.
Hall range is only a few millimetres. The bracket must hold the gap steady through vibration and not creep into the spinning collar. Inductive sensors are far more forgiving here.
IP65/67 boxes, cable glands, marine-tinned wire, dielectric grease on connections, and a drip loop so water runs away from the box, not into it.
Tap 12V through an inline fuse, with reverse-polarity and a TVS diode at each node. Engine-start surges and load-dump will find unprotected electronics.
Give the CAN bus a common ground reference across nodes and keep it clear of high-current engine ground loops, or you'll chase phantom noise.
A cockpit pod bakes. Pick an enclosure and display rated for it, and shade the screen if you didn't go transflective.
Firmware shape
What each node actually runs
An interrupt grabs micros() on every magnet pass into a small ring buffer. RPM comes from the averaged interval over a full revolution, with a glitch filter rejecting impossibly short gaps. It publishes a CAN frame (this shaft's RPM) every ~100 ms. That's the whole job — small and boring, which is what you want hanging off an engine.
Listens for both shafts' frames, computes the average and the signed difference, lightly smooths them, and feeds LVGL: two digital readouts, an average, and the center-zero sync needle. If a shaft stops broadcasting, it greys out instead of freezing on a stale number.
The bench snippet in Start here is the kernel of the sensor node. The rest is hardening — the ring-buffer averaging, the CAN publish, the LVGL faces — and it's a natural next step once one channel reads true on the desk.