The masthead sensor
The ST60+ masthead unit is two sensors on a five-wire cable. It needs an 8 V supply, normally provided by the display. The display is gone, so that supply now comes from a small buck converter instead.
Angle is a sin/cos sensor. Two wires carry voltages: one follows the sine of the vane angle, the other the cosine. Both are ratiometric to the 8 V supply and swing roughly 2.5–5.5 V across a full rotation. The angle is atan2(sin, cos). Two channels instead of a single potentiometer means no dead spot at the wrap-around, and the reading is self-referencing — only each channel's centre voltage needs calibrating, not its gain.
atan2(blue − 4.0, green − 4.0) recovers it. The amplitude cancels in the division, which is why only the midpoint needs calibrating.Speed is a cup anemometer. The cups drive a square wave on the yellow wire, about 3.2 V / 8 V levels, at a frequency proportional to rotation. Count the edges, scale by a constant, done.
| Wire | Signal |
|---|---|
| Red | +8.0 V supply |
| Screen / bare | 0 V / ground |
| Blue | sine (angle) |
| Green | cosine (angle) |
| Yellow | speed pulse |
Checking the sensor with a multimeter
A dead display doesn't mean a dead sensor, but it's worth ten minutes to confirm before buying or building anything. You need a variable bench supply or the buck converter set to 8.0 V, and a multimeter. A frequency (Hz) range helps for the speed test but isn't essential.
- Disconnect the masthead cable from the old display.
- Set the supply to 8.0 V and verify it with the meter before connecting anything. These sensors run on 8 V; 12 V or a buck still set to its factory ~20 V will damage it. Then connect Red to +8 V and Screen to ground.
- Angle. Meter on DC volts. Measure Blue→Screen and Green→Screen while turning the vane slowly by hand through a full turn. Both should move smoothly between roughly 2.5 V and 5.5 V, about a quarter-turn out of phase with each other. Smooth, full-range movement on both channels means the angle sensor is good. A channel that sits flat, jumps, or pins at a rail is the failed one.
- Speed. Meter on frequency (Hz). Measure Yellow→Screen and spin the cups. You should read a few Hz spinning by hand, rising as you spin faster. No Hz range? On DC volts the reading will flick between about 3 V and 8 V as the cups turn; a value that doesn't change when you spin them means the speed channel is dead.
If both pass, the sensor is fine and the rest is just interfacing it to NMEA 2000.
The interface board and wiring
The board is a Hat Labs HALMET. It's an ESP32 board with NMEA 2000 on the chip's built-in CAN controller, a 16-bit ADS1115 on the analog inputs, ±32 V-tolerant Schmitt-triggered digital inputs, and it's powered from the bus. That maps onto the sensor directly: sin and cos to two analog inputs, the speed pulse to a digital input, power from the backbone.
Three signal wires from the masthead, plus the 8 V supply from the buck:
| Wire | Signal | Connects to |
|---|---|---|
| Red | +8.0 V | buck OUT+ |
| Screen | 0 V | shared ground (buck OUT−, board input ground) |
| Blue | sine | A1 (analog) |
| Green | cosine | A2 (analog) |
| Yellow | speed pulse | D1 / GPIO23 (digital, rising edge) |
The sin/cos terminals (~2.5–5.5 V) go through the board's fixed 10:1 divider and sit well inside the ADS1115's range. The constant-current sources on A1/A2 stay off — this is passive voltage sensing, not a resistive sender. Leave the on-board 120 Ω CAN terminator open; the backbone is already terminated at both ends. Grounding matters: the sensor's screen, the buck's negative, and the board's input-side ground must be the same node, or the angle reads garbage.
Parts
The vane, its cable, and the boat's NMEA 2000 network were already in place. Two things to buy:
| Part | Purpose | Source |
|---|---|---|
| Hat Labs HALMET | interface board — reads sin/cos + pulse, outputs NMEA 2000, hosts the config web page | shop.hatlabs.fi |
| Adjustable buck converter, with voltage display | 12 V → 8.0 V for the sensor | amazon.de |
| Raymarine ST60+ masthead sensor | existing — reused | — |
| ~1 A inline fuse, wire | fuse on the buck's 12 V input; three taps off the existing cable | — |
Firmware
Firmware is SensESP (ESP32 / PlatformIO) with the NMEA2000 library. The signal path is two short pipelines that get latched and sent:
Four calibration values are exposed live, in a web page the board serves itself — no reflash: angle offset, rotation direction (one sign flip if it reads backwards), the two channel centre voltages, and the speed scale (default 0.5144 m/s per Hz for the egg-cup ST60+; ~0.36 for the older square-cup). In practice I didn't touch any of them. It read correctly on the first try — the vane was already aligned on the mast from its original installation, and the defaults matched the sensor, so 0° was at the bow and the speed was right out of the box. No mast trip, no bench sweep.
If a sensor does need centring, the centres can be recovered from data instead of by hand. Log the raw sin/cos over one slow full circle under motor, and tools/ellipse_fit.py fits an ellipse to the sin/cos cloud and prints the four numbers to enter. That part is offline — a script you run on a laptop, not an on-device auto-calibration routine. The math is there; it was never wired into the firmware to run itself, and I never needed it.
It outputs apparent wind only. True wind is left to the chartplotter, so there's a single wind source on the bus rather than two devices computing and broadcasting it.
Two details in that pipeline are worth spelling out, because the obvious version of each is wrong. The smoothing averages the sin/cos vector and takes atan2 of the mean, rather than averaging the angle. Averaging the angle looks equivalent and isn't: either side of the ±180° wrap the samples alternate between about +179° and −179°, and their arithmetic mean is 0°, so running dead downwind would read as wind on the bow. Averaging the vector is continuous everywhere on the circle.
And the no-data check watches the raw terminal voltage, not the computed vector. If the transducer loses power both channels sit at 0 V, which centres to a large, perfectly steady vector — a magnitude check sees nothing wrong and reports a convincing, unchanging −135°. Blue and Green have to be inside 2–6 V for the angle to be published at all; outside that it goes out as no-data on both the N2K and SignalK paths.
The transmit itself is unremarkable — every 100 ms it builds the PGN from the latched values and sends it:
// every 100 ms
event_loop()->onRepeat(100, []() {
tN2kMsg msg;
double awa = isfinite(last_awa_rad)
? (last_awa_rad < 0 ? last_awa_rad + 2*M_PI : last_awa_rad) // −π..π → 0..2π
: N2kDoubleNA;
double aws = isfinite(last_aws_mps) ? last_aws_mps : N2kDoubleNA;
SetN2kWindSpeed(msg, sid, aws, awa, N2kWind_Apparent);
nmea2000->SendMsg(msg);
});
Result
Output is PGN 130306 at 10 Hz from source address 35, plus environment.wind.angleApparent and speedApparent on SignalK over WiFi. Verified on the bus with candump and on the Orca Core 2; bench-tested first, then on the water. The N2K identity is set to a wind sensor (function 130, class 85) with an unregistered manufacturer code, so it doesn't collide with a real product.
Full source, the wiring guide, and the firmware are on GitHub, Apache-2.0.
The firmware and this page were both written with Claude Code (Anthropic).