ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ubx.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file ubx.h
6 * @brief u-blox UBX binary protocol codec (PC_ENABLE_UBX) - the GNSS receiver control/nav protocol.
7 *
8 * A UBX frame is `B5 62 <class> <id> <len-LE:2> <payload...> <CK_A> <CK_B>`: two sync chars, a
9 * class/id message selector, a little-endian payload length, the payload, and an 8-bit Fletcher
10 * checksum computed over everything between the sync chars and the checksum (class..payload end).
11 * This codec builds a frame (adding the sync chars, length, and checksum), builds a zero-length
12 * poll request (how UBX asks a receiver to emit a message), parses one complete frame (validating
13 * the length and checksum), and - because a u-blox receiver multiplexes UBX with ASCII NMEA on the
14 * same UART - provides a streaming demultiplexer that pulls UBX frames out of a byte stream and
15 * hands every non-UBX byte back to the caller (for an NMEA line assembler). Little-endian payload
16 * readers and an ACK helper decode the common replies. Pure codec, host-tested; the UART is the
17 * application's (a plain HardwareSerial link, commonly 9600 baud).
18 *
19 * @author Douglas Quigg (dstroy0)
20 * @date 2026
21 */
22
23#ifndef PROTOCORE_UBX_H
24#define PROTOCORE_UBX_H
25
26#include "protocore_config.h"
27
28#if PC_ENABLE_UBX
29
30#include <stddef.h>
31#include <stdint.h>
32
33#define PC_UBX_SYNC1 0xB5u ///< first sync char
34#define PC_UBX_SYNC2 0x62u ///< second sync char
35
36/** @brief A parsed UBX frame. @p payload aliases the caller's / stream's buffer (@p len octets). */
37struct pc_ubx
38{
39 uint8_t cls; ///< message class
40 uint8_t id; ///< message id
41 uint16_t len; ///< payload length
42 const uint8_t *payload; ///< payload (len octets); references an external buffer
43};
44
45/**
46 * @brief 8-bit Fletcher checksum over @p body (the class..payload-end span - everything the frame
47 * checksums). Writes @p ck_a / @p ck_b.
48 */
49void pc_ubx_checksum(const uint8_t *body, size_t len, uint8_t *ck_a, uint8_t *ck_b);
50
51/**
52 * @brief Build a UBX frame into @p buf: `B5 62 cls id len(LE) payload CK_A CK_B`.
53 * @return total frame length (8 + @p len) or 0 on overflow / bad args (@p payload may be NULL
54 * only when @p len is 0).
55 */
56size_t pc_ubx_build(uint8_t *buf, size_t cap, uint8_t cls, uint8_t id, const uint8_t *payload, uint16_t len);
57
58/** @brief Build a zero-length poll request for (@p cls, @p id) - how UBX asks for a message. */
59size_t pc_ubx_build_poll(uint8_t *buf, size_t cap, uint8_t cls, uint8_t id);
60
61/**
62 * @brief Parse exactly one complete UBX frame at the front of @p s. Validates the sync chars, the
63 * declared length against @p len, and the checksum; fills @p out (payload aliases @p s). Returns
64 * false on a short / malformed / bad-checksum frame.
65 */
66bool pc_ubx_parse(const uint8_t *s, size_t len, pc_ubx *out);
67
68/**
69 * @brief ACK helper. Returns 1 for UBX-ACK-ACK, 0 for UBX-ACK-NAK, -1 if @p m is not an ACK frame.
70 * When it is an ACK, writes the acknowledged class/id (the payload's two octets) to @p acked_cls /
71 * @p acked_id.
72 */
73int pc_ubx_ack(const pc_ubx *m, uint8_t *acked_cls, uint8_t *acked_id);
74
75/** @brief Little-endian readers for a payload at byte offset @p off (caller bounds-checks @p off). */
76uint16_t pc_ubx_u16(const uint8_t *p, size_t off);
77uint32_t pc_ubx_u32(const uint8_t *p, size_t off);
78int16_t pc_ubx_i16(const uint8_t *p, size_t off);
79int32_t pc_ubx_i32(const uint8_t *p, size_t off);
80
81// -- NAV-PVT: the u-blox all-in-one navigation solution (position / velocity / time) --
82
83#define PC_UBX_CLASS_NAV 0x01 ///< navigation-results message class
84#define PC_UBX_NAV_PVT 0x07 ///< NAV-PVT message id (class NAV)
85#define PC_UBX_NAV_PVT_LEN 92 ///< NAV-PVT payload length (u-blox 8 / M8)
86#define PC_UBX_PVT_FIX_OK 0x01u ///< NAV-PVT flags bit 0: a valid fix (within DOP / accuracy masks)
87
88/** @brief NAV-PVT fixType values. */
89enum pc_ubx_fix_type
90{
91 PC_UBX_FIX_NONE = 0, ///< no fix
92 PC_UBX_FIX_DR = 1, ///< dead-reckoning only
93 PC_UBX_FIX_2D = 2, ///< 2D fix
94 PC_UBX_FIX_3D = 3, ///< 3D fix
95 PC_UBX_FIX_GNSS_DR = 4, ///< GNSS + dead reckoning
96 PC_UBX_FIX_TIME = 5 ///< time-only fix
97};
98
99/** @brief Decoded UBX-NAV-PVT payload (the fields an application usually needs; native integer scales). */
100struct pc_ubx_nav_pvt // NOSONAR(cpp:S1820): UBX-NAV-PVT is one fixed protocol message; its ~30 fields mirror the
101 // wire layout, so splitting the struct would be artificial, not clearer
102{
103 uint32_t itow_ms; ///< GPS time of week of the solution (ms)
104 uint16_t year; ///< UTC year
105 uint8_t month; ///< UTC month (1..12)
106 uint8_t day; ///< UTC day (1..31)
107 uint8_t hour; ///< UTC hour (0..23)
108 uint8_t minute; ///< UTC minute (0..59)
109 uint8_t second; ///< UTC second (0..60)
110 uint8_t valid; ///< validity flags (validDate / validTime / fullyResolved / validMag)
111 int32_t nano; ///< fraction of second, -1e9..1e9 (ns)
112 uint32_t time_acc_ns; ///< time accuracy estimate (ns)
113 uint8_t fix_type; ///< @ref pc_ubx_fix_type
114 uint8_t flags; ///< fix status flags (bit 0 = @ref PC_UBX_PVT_FIX_OK)
115 uint8_t num_sv; ///< number of satellites used in the solution
116 int32_t lon_1e7; ///< longitude (1e-7 deg)
117 int32_t lat_1e7; ///< latitude (1e-7 deg)
118 int32_t height_mm; ///< height above the ellipsoid (mm)
119 int32_t hmsl_mm; ///< height above mean sea level (mm)
120 uint32_t h_acc_mm; ///< horizontal accuracy estimate (mm)
121 uint32_t v_acc_mm; ///< vertical accuracy estimate (mm)
122 int32_t vel_n_mm_s; ///< NED north velocity (mm/s)
123 int32_t vel_e_mm_s; ///< NED east velocity (mm/s)
124 int32_t vel_d_mm_s; ///< NED down velocity (mm/s)
125 int32_t gspeed_mm_s; ///< 2-D ground speed (mm/s)
126 int32_t head_mot_1e5; ///< heading of motion (1e-5 deg)
127 uint32_t s_acc_mm_s; ///< speed accuracy estimate (mm/s)
128 uint32_t head_acc_1e5; ///< heading accuracy estimate (1e-5 deg)
129 uint16_t pdop_1e2; ///< position DOP (0.01)
130};
131
132/**
133 * @brief Decode a UBX-NAV-PVT frame into @p out (per the u-blox interface description).
134 * @return true iff @p m is a NAV-PVT frame (class 0x01 / id 0x07) of at least @ref PC_UBX_NAV_PVT_LEN
135 * octets; false (and @p out untouched) otherwise.
136 */
137bool pc_ubx_parse_nav_pvt(const pc_ubx *m, pc_ubx_nav_pvt *out);
138
139// -- NAV-SAT: per-satellite signal + usage info (variable length) --
140
141#define PC_UBX_NAV_SAT 0x35 ///< NAV-SAT message id (class NAV)
142#define PC_UBX_NAV_SAT_HDR_LEN 8 ///< NAV-SAT fixed header (iTOW + version + numSvs + reserved)
143#define PC_UBX_NAV_SAT_ENTRY_LEN 12 ///< NAV-SAT per-satellite block length
144#define PC_UBX_SAT_QUALITY_MASK 0x07u ///< flags bits 0..2: signal quality indicator
145#define PC_UBX_SAT_USED 0x08u ///< flags bit 3: this satellite is used in the navigation solution
146
147/** @brief NAV-SAT fixed header. */
148struct pc_ubx_nav_sat_hdr
149{
150 uint32_t itow_ms; ///< GPS time of week (ms)
151 uint8_t version; ///< message version (1)
152 uint8_t num_svs; ///< number of satellite blocks that follow
153};
154
155/** @brief One NAV-SAT satellite block. */
156struct pc_ubx_sat
157{
158 uint8_t gnss_id; ///< GNSS identifier (0 GPS, 2 Galileo, 3 BeiDou, 5 QZSS, 6 GLONASS, ...)
159 uint8_t sv_id; ///< satellite identifier within the GNSS
160 uint8_t cno_dbhz; ///< carrier-to-noise density ratio (dB-Hz)
161 int8_t elev_deg; ///< elevation (deg, -90..90; out of range if unknown)
162 int16_t azim_deg; ///< azimuth (deg, 0..360)
163 int16_t pr_res_01m; ///< pseudorange residual (0.1 m)
164 uint32_t flags; ///< bitfield: quality (@ref PC_UBX_SAT_QUALITY_MASK), used (@ref PC_UBX_SAT_USED), ...
165};
166
167/**
168 * @brief Decode a UBX-NAV-SAT frame's fixed header (per the u-blox interface description).
169 * @return true iff @p m is a NAV-SAT frame (class 0x01 / id 0x35) whose declared length holds the header
170 * plus its @c num_svs blocks; false otherwise. Walk the blocks with @ref pc_ubx_nav_sat_get.
171 */
172bool pc_ubx_parse_nav_sat(const pc_ubx *m, pc_ubx_nav_sat_hdr *out);
173
174/**
175 * @brief Decode satellite block @p index (0-based) from a NAV-SAT frame into @p out.
176 * @return true on success, false if @p index is out of range or @p m is not a valid NAV-SAT frame.
177 */
178bool pc_ubx_nav_sat_get(const pc_ubx *m, uint8_t index, pc_ubx_sat *out);
179
180// -- NAV-TIMEUTC: the validated UTC time solution (for a GNSS time source) --
181
182#define PC_UBX_NAV_TIMEUTC 0x21 ///< NAV-TIMEUTC message id (class NAV)
183#define PC_UBX_NAV_TIMEUTC_LEN 20 ///< NAV-TIMEUTC payload length
184#define PC_UBX_TIMEUTC_VALID_TOW 0x01u ///< valid flags bit 0: the time of week is valid
185#define PC_UBX_TIMEUTC_VALID_WKN 0x02u ///< valid flags bit 1: the week number is valid
186#define PC_UBX_TIMEUTC_VALID_UTC 0x04u ///< valid flags bit 2: the UTC time is valid (leap seconds known)
187
188/** @brief Decoded UBX-NAV-TIMEUTC payload. */
189struct pc_ubx_nav_time_utc
190{
191 uint32_t itow_ms; ///< GPS time of week (ms)
192 uint32_t time_acc_ns; ///< time accuracy estimate (ns)
193 int32_t nano; ///< fraction of second, -1e9..1e9 (ns)
194 uint16_t year; ///< UTC year (1999..2099)
195 uint8_t month; ///< UTC month (1..12)
196 uint8_t day; ///< UTC day (1..31)
197 uint8_t hour; ///< UTC hour (0..23)
198 uint8_t minute; ///< UTC minute (0..59)
199 uint8_t second; ///< UTC second (0..60)
200 uint8_t valid; ///< validity flags (@ref PC_UBX_TIMEUTC_VALID_TOW / _WKN / _UTC + utcStandard)
201 bool utc_valid; ///< convenience: the UTC-valid bit is set (leap seconds resolved)
202};
203
204/**
205 * @brief Decode a UBX-NAV-TIMEUTC frame into @p out (the receiver's UTC time, for a GNSS time source).
206 * @return true iff @p m is a NAV-TIMEUTC frame (class 0x01 / id 0x21) of at least 20 octets; false otherwise.
207 */
208bool pc_ubx_parse_nav_timeutc(const pc_ubx *m, pc_ubx_nav_time_utc *out);
209
210// -- CFG: configure the receiver (which messages to emit, and how fast) --
211
212#define PC_UBX_CLASS_CFG 0x06 ///< configuration-input message class
213#define PC_UBX_CFG_MSG 0x01 ///< CFG-MSG: set a message's output rate
214#define PC_UBX_CFG_RATE 0x08 ///< CFG-RATE: set the measurement / navigation rate
215#define PC_UBX_TIME_REF_UTC 0 ///< CFG-RATE timeRef: align measurements to UTC
216#define PC_UBX_TIME_REF_GPS 1 ///< CFG-RATE timeRef: align measurements to GPS time
217
218/**
219 * @brief Build a CFG-MSG that sets how often (@p cls, @p id) is emitted on the current port.
220 *
221 * @p rate is in navigation solutions: 0 disables the message, 1 emits it every solution, N every Nth.
222 * This is the short (3-octet) form that targets the port the command arrives on.
223 * @return the frame length (11) or 0 on overflow / a null buffer.
224 */
225size_t pc_ubx_build_cfg_msg(uint8_t *buf, size_t cap, uint8_t cls, uint8_t id, uint8_t rate);
226
227/**
228 * @brief Build a CFG-RATE that sets the measurement + navigation rate.
229 * @param meas_rate_ms the measurement period in ms (e.g. 200 for 5 Hz).
230 * @param nav_rate navigation solutions per measurement cycle (usually 1).
231 * @param time_ref the reference the measurements align to (@ref PC_UBX_TIME_REF_UTC / _GPS).
232 * @return the frame length (14) or 0 on overflow / a null buffer.
233 */
234size_t pc_ubx_build_cfg_rate(uint8_t *buf, size_t cap, uint16_t meas_rate_ms, uint16_t nav_rate, uint16_t time_ref);
235
236/** @brief Result of feeding one byte to the streaming demultiplexer. */
237enum pc_ubx_feed
238{
239 PC_UBX_NONE = 0, ///< byte consumed inside a (partial or discarded) UBX frame
240 PC_UBX_FRAME = 1, ///< a complete, checksum-valid frame is in @p out
241 PC_UBX_PASSTHROUGH = 2, ///< byte is not part of any UBX frame (returned in @p passthrough)
242 PC_UBX_OVERFLOW = 3 ///< a frame whose declared length exceeds PC_UBX_MAX_PAYLOAD; skipped
243};
244
245/** @brief Streaming demultiplexer state for a mixed NMEA + UBX byte stream. Zero-initialize / init. */
246struct pc_ubx_stream
247{
248 uint8_t state; ///< internal parser state
249 uint8_t cls; ///< class of the frame in progress
250 uint8_t id; ///< id of the frame in progress
251 uint16_t len; ///< declared payload length of the frame in progress
252 uint16_t idx; ///< payload bytes received so far
253 uint32_t skip; ///< bytes left to discard for an over-long frame
254 uint8_t ck_a; ///< running checksum A
255 uint8_t ck_b; ///< running checksum B
256 uint8_t rx_ck_a; ///< received checksum A (awaiting B to compare)
257 uint8_t buf[PC_UBX_MAX_PAYLOAD]; ///< payload accumulator
258};
259
260/** @brief Reset a demux to the idle (hunting-for-sync) state. */
261void pc_ubx_stream_init(pc_ubx_stream *st);
262
263/**
264 * @brief Feed one byte. Returns a ::pc_ubx_feed code: PC_UBX_FRAME (@p out filled, payload aliases
265 * the stream buffer, valid until the next feed), PC_UBX_PASSTHROUGH (@p passthrough set to a
266 * non-UBX byte - hand it to your NMEA assembler), PC_UBX_OVERFLOW (an over-long frame was skipped),
267 * or PC_UBX_NONE.
268 */
269int pc_ubx_stream_feed(pc_ubx_stream *st, uint8_t b, pc_ubx *out, uint8_t *passthrough);
270
271#endif // PC_ENABLE_UBX
272#endif // PROTOCORE_UBX_H
User-facing configuration for ProtoCore.