NMEA 2000
NMEA 2000 (IEC 61162-3) is the network marine electronics speak: chartplotters, GPS receivers, wind and depth sensors, engine gateways, battery monitors. Under the hood it is J1939 with a marine dictionary, the same 29-bit CAN framing, the same PGN/source-address structure, the same ISO address claim. Three things differ: the message catalogue, the multi-frame transport (Fast Packet), and how fields are packed.
It's J1939 at the wire level
Every NMEA 2000 frame is a standard CAN 2.0B frame with a 29-bit extended identifier, and the identifier is split exactly the way J1939 does it:
| Bits | Field | Meaning |
|---|---|---|
| 28–26 | Priority | 0 = highest |
| 25–24 | Reserved / Data Page | extends the PGN range |
| 23–16 | PDU Format (PF) | ≥ 0xF0 → broadcast (PGN includes byte 15–8) |
| 15–8 | PDU Specific (PS) | destination addr (PF<0xF0) or PGN low byte |
| 7–0 | Source Address | which device sent it |
The PGN (Parameter Group Number) identifies what the message
is, e.g. 127250 Vessel Heading, 130306 Wind Data. sloppyCAN
reuses its J1939 ID parser verbatim and just swaps in a marine PGN dictionary when you
pick NMEA 2000 from the protocol dropdown in the J1939 / N2K tab.
Fast Packet
A single CAN frame carries at most 8 data bytes. J1939 moves longer messages with its Transport Protocol (TP.CM / TP.DT); NMEA 2000 does not. It uses Fast Packet, which packs the sequencing into the data field of ordinary frames of the same PGN. Whether a PGN is single-frame or Fast Packet is a fixed property of that PGN (it's in the dictionary).
Byte 0 of every Fast Packet frame is a control byte: the top 3 bits are a sequence counter (0–7, ties one logical message together) and the low 5 bits are a frame counter (the ordinal within the message).
Byte 1 is the total payload length; bytes 2–7 carry the first 6 payload bytes.
Each continuation carries 7 payload bytes, placed at offset
6 + (frameCounter − 1) × 7. Collect frames until you have
LEN bytes, then decode the whole buffer as that PGN. A Fast Packet message
can be up to 223 bytes.
(PGN, source, sequence) and drops
a slot that makes no progress for ~1 s, so one lost middle frame can't wedge it. In the
PGN Monitor and Frame Log, reassembled messages get an FP badge.
Field encoding
NMEA 2000 fields are little-endian and frequently bit-packed: a field can be 2, 4 or 6 bits wide and start anywhere, not just on a byte boundary. (J1939's whole-byte SPN model can't express this, so the marine decoder works in bit offsets.) Each field has a resolution, an optional offset, and a unit; the all-ones value means data not available and the next value down means out of range.
| Quantity | Resolution | Note |
|---|---|---|
| Angle (heading, wind, COG) | 0.0001 rad | shown in degrees |
| Temperature | 0.01 K | shown in °C (−273.15) |
| Pressure | 100 Pa | shown in kPa / hPa |
| Speed | 0.01 m/s | |
| Latitude / Longitude | 1e-7 deg | 32-bit signed (rapid) |
Worked Example: 130306 Wind Data
Wind Data is a single-frame PGN. The 8 data bytes lay out as:
Payload: FF 4E 06 60 16 02 FF FF
- Wind Speed = bytes 1–2 LE =
0x064E= 1614 × 0.01 m/s = 16.14 m/s - Wind Angle = bytes 3–4 LE =
0x1660= 5728 × 0.0001 rad = 0.5728 rad = 32.8° - Reference = byte 5 low bits =
2= Apparent
That's exactly what the PGN Monitor shows when you run Demo with the dropdown set to NMEA 2000. It streams heading, engine RPM, position, wind, and a Fast-Packet engine-dynamic message so you can watch reassembly happen with no hardware attached.
Worked Example: 127245 Rudder
Rudder is a single-frame PGN. sloppyCAN's own boat only ever has an applied position to report, never a distinct commanded one, so the Direction Order and Angle Order fields stay not available and only Position gets written:
Payload: FF FF FF FF AF F1 FF FF
- Position = bytes 4–5 LE =
0xF1AF= a signed 16-bit value, so subtract 65536:61871 − 65536 = −3665× 0.0001 rad = −0.3665 rad = −21.0° (to port)
That −21° is exactly the contract's rudder_actual (−60%) converted through
nmea2000.js's own hard-over constant — 35° of full throw — the one
percentage-to-degrees conversion this file declares, because PGN 127245 carries a real
angle and the contract's rudder signals are percentages.
Worked Example: 127237 Heading/Track Control
A 21-byte Fast Packet message (so it arrives as three CAN frames — the shape from the
Fast Packet section above). sloppyCAN's boat only ever fills two of its many fields: the
Steering Mode bits (which of the pilot's own two states it is in) and the
Heading-To-Steer, both of which double as the closest legal expression of the contract's
nav_mode_actual / heading_target. Every other field — the four
limit-exceeded flags, Turn Mode, both rudder-order fields, the track and cross-track
limits — stays not available, because this boat runs heading-hold alone and has
no route, no cross-track error, and no distinct rudder order to publish.
Reassembled payload (byte 0 and bytes 2–4, 7–20 all 0xFF):
- byte 1, bits 0–2 =
0xFB→ low 3 bits011= 3, Heading Control Standalone — the pilot is actively holding a course - bytes 5–6 LE =
0x14 0xB8=0xB814= 47124 × 0.0001 rad = 4.7124 rad = 270.0°, the Heading-To-Steer
In STANDBY the same two fields read 0 (Heading Control off) and the boat's
current heading — never a sentinel, because the contract wants a bus that engages the
pilot with no course of its own to capture whatever heading the boat is already on rather
than steer to an arbitrary bearing.
Worked Example: 130577 Direction Data
A 14-byte Fast Packet message. Like the Heading/Track Control message above, most of its fields (Data Mode, COG Reference, SID, COG, SOG, Heading, Speed Through Water) stay not available — each already has its own dedicated PGN above with its own source address, so repeating them here would be the same value published twice from two different "instruments." Only Set and Drift, the tidal-current pair with no PGN of their own elsewhere, get written:
Reassembled payload (bytes 0–9 all 0xFF):
- bytes 10–11 LE =
0x5B 0x88=0x885B= 34907 × 0.0001 rad = 3.4907 rad = 200.0°, the Set (bearing the current flows toward) - bytes 12–13 LE =
0x28 0x00= 40 × 0.01 m/s = 0.40 m/s, the Drift
What makes a marine dash different from a truck's
The same physical quantity has two names on a boat that a land vehicle never has to tell apart, because a boat's medium moves under it:
| Pair | Through/apparent | Over/true |
|---|---|---|
| Speed | STW (128259) — through the WATER, what a paddlewheel log reads | SOG (129026) — over the GROUND; the gap is the tide's own push or drag |
| Wind | AWA / AWS (130306, apparent) — what the hull actually feels, bow-referenced | TWD / TWS (130306, true) — the wind's real bearing and speed, with the hull's own motion subtracted back out |
| Course | Heading (127250) — where the bow POINTS | COG (129026) — the course actually MADE; the gap is leeway plus the set of the tide, exactly what 130577's Set/Drift describes |
Depth has its own version of the same trap: 128267 reports water under the transducer, which rides wherever the hull floats it — not under the keel, and not to the sea bed from the surface. A shoaling reading and a squatting hull both move that number the same direction, and the PGN has no way to tell you which.
sheet (how far the boom may swing) and sail_angle (where the
wind put it) ride the base CAN map and the uplink respectively, because NMEA 2000 defines
no sail PGN — not an oversight in this project, but what the standard itself is. A real
marine bus carries what its committee thought worth standardising, and sail trim was
never that.