Raymarine ST60+ → NMEA 2000 · open-source firmware · Apache-2.0

Replacing a broken ST60+ wind display when the masthead sensor still works

My Raymarine ST60+ wind display stopped working. After some checking, only the display electronics had failed — the masthead sensor was still producing valid data. Rather than replace the whole system, I built a small interface board that reads the original sensor directly and outputs NMEA 2000.

This is the writeup: how the sensor works, how to confirm yours is still good with a multimeter, the wiring, the parts, and the firmware. It assumes you're comfortable with a multimeter and basic DC wiring.

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.

Blue — sine Green — cosine
Blue and green channel voltages across one full rotation of the vane Two sine waves a quarter turn out of phase, each swinging between 2.5 and 5.5 volts about a 4.0 volt midpoint. At a vane angle of 135 degrees the blue channel reads 5.06 volts and the green channel 2.94 volts. 5.5 V 4.0 V 2.5 V 90° 180° 270° 360° vane angle 5.06 V 2.94 V 135°
One full turn of the vane. Both channels swing ±1.5 V about the same 4.0 V midpoint, a quarter turn apart, so every angle produces a unique pair. 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.

ST60+ masthead wires (same colours on ST60 / ST60+ / i60)
WireSignal
Red+8.0 V supply
Screen / bare0 V / ground
Bluesine (angle)
Greencosine (angle)
Yellowspeed 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.

  1. Disconnect the masthead cable from the old display.
  2. 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.
  3. 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.
  4. 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:

WireSignalConnects to
Red+8.0 Vbuck OUT+
Screen0 Vshared ground (buck OUT−, board input ground)
BluesineA1 (analog)
GreencosineA2 (analog)
Yellowspeed pulseD1 / 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.

Set the buck to 8.0 V before wiring. The cheap adjustable buck modules ship at about 20 V. Set the output to 8.0 V with a meter, nothing connected to the output, and only then connect Red. The pot sets the output; the push-button only trims the module's own LED meter.

Parts

The vane, its cable, and the boat's NMEA 2000 network were already in place. Two things to buy:

PartPurposeSource
Hat Labs HALMETinterface board — reads sin/cos + pulse, outputs NMEA 2000, hosts the config web pageshop.hatlabs.fi
Adjustable buck converter, with voltage display12 V → 8.0 V for the sensoramazon.de
Raymarine ST60+ masthead sensorexisting — reused
~1 A inline fuse, wirefuse 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:

Angle
A1 / A2
sin · cos
ADS1115
16-bit read
centre
(V − Vmid)/A
smooth
vector, avg 5
atan2
+ offset, sign
Speed
D1 pulse
rising edge
count
500 ms
frequency
Hz
× K
0.5144 m/s/Hz
Output
PGN 130306
apparent · 10 Hz · src 35
SignalK
WiFi · environment.wind.*

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).