Browse Source

ubx: Refactor codebase in order to improve usability

During testing and code inspection, there were various anti-patterns
on this (and U-Blox driver) codebase(s), including obfuscation, and
lack of data validation. This made it increasingly difficult to
introduce further variants of u-blox GNSS modems.

With this patch, both the UBX modem and the M8 driver have been
refactored to ease the reliability and maintainability of these
codebases. Here are some highlights:

WRT UBX modem:
- Helper macros to easily create UBX frames, (including checksum
calculation), at compile time; thus, making it easier to extend UBX
commands.
- Logic validation by the inclusion of the modem_ubx testsuite, used to
refactor the code through TDD.
- Ability to receive unsolicited messages, in order to enable U-Blox
drivers to rely on modem_ubx to transceive all commands, and avoid
hopping between modem_ubx and modem_chat.

WRT M8 driver:
- Remove GNSS specific protocol header files. Instead, unify them under
modem/ubx/protocol.h. Background: After a survey and looking at ubxlib
SDK I conclude the UBX protocol is by definition a GNSS protocol (there
are non-GNSS u-blox modems, but they're not interfaced through UBX
protocol).
- Establish pattern to create and send/receive commands using new
foundations on modem ubx.
- Remove dependency of Modem chat, and instead use UBX unsolicited
messages to get Navigation and Satellites data.
- Switch from the auto-baudrate detection pattern to a pattern of
transitioning between an initial known baudrate to a desired baudrate,
in order to improve initialization time.
- Add dts property to configure default fix-rate.

Signed-off-by: Luis Ubieda <luisf@croxel.com>
pull/90887/head
Luis Ubieda 2 months ago committed by Benjamin Cabé
parent
commit
94a7f028ef
  1. 4
      drivers/gnss/CMakeLists.txt
  2. 3
      drivers/gnss/Kconfig.u_blox_m8
  3. 1219
      drivers/gnss/gnss_u_blox_m8.c
  4. 180
      drivers/gnss/gnss_u_blox_protocol/gnss_u_blox_protocol.c
  5. 251
      drivers/gnss/gnss_u_blox_protocol/gnss_u_blox_protocol.h
  6. 258
      drivers/gnss/gnss_u_blox_protocol/gnss_u_blox_protocol_defines.h
  7. 147
      drivers/gnss/gnss_ubx_common.c
  8. 44
      drivers/gnss/gnss_ubx_common.h
  9. 19
      dts/bindings/gnss/u-blox,m8.yaml
  10. 120
      include/zephyr/modem/ubx.h
  11. 38
      include/zephyr/modem/ubx/checksum.h
  12. 478
      include/zephyr/modem/ubx/protocol.h
  13. 2
      samples/drivers/gnss/boards/mimxrt1062_fmurt6.overlay
  14. 2
      samples/drivers/gnss/boards/vmu_rt1170_mimxrt1176_cm7.overlay
  15. 362
      subsys/modem/modem_ubx.c
  16. 1
      tests/drivers/build_all/gnss/app.overlay
  17. 11
      tests/subsys/modem/modem_ubx/CMakeLists.txt
  18. 9
      tests/subsys/modem/modem_ubx/prj.conf
  19. 561
      tests/subsys/modem/modem_ubx/src/main.c
  20. 13
      tests/subsys/modem/modem_ubx/testcase.yaml

4
drivers/gnss/CMakeLists.txt

@ -10,6 +10,6 @@ zephyr_library_sources_ifdef(CONFIG_GNSS_NMEA0183 gnss_nmea0183.c)
zephyr_library_sources_ifdef(CONFIG_GNSS_NMEA0183_MATCH gnss_nmea0183_match.c) zephyr_library_sources_ifdef(CONFIG_GNSS_NMEA0183_MATCH gnss_nmea0183_match.c)
zephyr_library_sources_ifdef(CONFIG_GNSS_NMEA_GENERIC gnss_nmea_generic.c) zephyr_library_sources_ifdef(CONFIG_GNSS_NMEA_GENERIC gnss_nmea_generic.c)
zephyr_library_sources_ifdef(CONFIG_GNSS_QUECTEL_LCX6G gnss_quectel_lcx6g.c) zephyr_library_sources_ifdef(CONFIG_GNSS_QUECTEL_LCX6G gnss_quectel_lcx6g.c)
zephyr_library_sources_ifdef(CONFIG_GNSS_U_BLOX_M8 gnss_u_blox_m8.c) zephyr_library_sources_ifdef(CONFIG_GNSS_U_BLOX_M8 gnss_u_blox_m8.c
zephyr_library_sources_ifdef(CONFIG_GNSS_U_BLOX_PROTOCOL gnss_u_blox_protocol/gnss_u_blox_protocol.c) gnss_ubx_common.c)
zephyr_library_sources_ifdef(CONFIG_GNSS_LUATOS_AIR530Z gnss_luatos_air530z.c) zephyr_library_sources_ifdef(CONFIG_GNSS_LUATOS_AIR530Z gnss_luatos_air530z.c)

3
drivers/gnss/Kconfig.u_blox_m8

@ -9,11 +9,8 @@ config GNSS_U_BLOX_M8
depends on GNSS_REFERENCE_FRAME_WGS84 depends on GNSS_REFERENCE_FRAME_WGS84
select MODEM_MODULES select MODEM_MODULES
select MODEM_BACKEND_UART select MODEM_BACKEND_UART
select MODEM_CHAT
select MODEM_UBX select MODEM_UBX
select GNSS_PARSE select GNSS_PARSE
select GNSS_NMEA0183
select GNSS_NMEA0183_MATCH
select GNSS_U_BLOX_PROTOCOL select GNSS_U_BLOX_PROTOCOL
select UART_USE_RUNTIME_CONFIGURE select UART_USE_RUNTIME_CONFIGURE
help help

1219
drivers/gnss/gnss_u_blox_m8.c

File diff suppressed because it is too large Load Diff

180
drivers/gnss/gnss_u_blox_protocol/gnss_u_blox_protocol.c

@ -1,180 +0,0 @@
/*
* Copyright 2024 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "gnss_u_blox_protocol.h"
const uint32_t ubx_baudrate[UBX_BAUDRATE_COUNT] = {
4800,
9600,
19200,
38400,
57600,
115200,
230400,
460800,
921600,
};
static inline int ubx_validate_payload_size_ack(uint8_t msg_id, uint16_t payload_size)
{
switch (msg_id) {
case UBX_ACK_ACK:
return payload_size == UBX_CFG_ACK_PAYLOAD_SZ ? 0 : -1;
case UBX_ACK_NAK:
return payload_size == UBX_CFG_NAK_PAYLOAD_SZ ? 0 : -1;
default:
return -1;
}
}
static inline int ubx_validate_payload_size_cfg(uint8_t msg_id, uint16_t payload_size)
{
switch (msg_id) {
case UBX_CFG_RATE:
return payload_size == UBX_CFG_RATE_PAYLOAD_SZ ? 0 : -1;
case UBX_CFG_PRT:
return (payload_size == UBX_CFG_PRT_POLL_PAYLOAD_SZ ||
payload_size == UBX_CFG_PRT_SET_PAYLOAD_SZ) ? 0 : -1;
case UBX_CFG_RST:
return payload_size == UBX_CFG_RST_PAYLOAD_SZ ? 0 : -1;
case UBX_CFG_NAV5:
return payload_size == UBX_CFG_NAV5_PAYLOAD_SZ ? 0 : -1;
case UBX_CFG_GNSS:
return ((payload_size - UBX_CFG_GNSS_PAYLOAD_INIT_SZ) %
UBX_CFG_GNSS_PAYLOAD_CFG_BLK_SZ == 0) ? 0 : -1;
case UBX_CFG_MSG:
return payload_size == UBX_CFG_MSG_PAYLOAD_SZ ? 0 : -1;
default:
return -1;
}
}
static inline int ubx_validate_payload_size(uint8_t msg_cls, uint8_t msg_id, uint16_t payload_size)
{
if (payload_size == 0) {
return 0;
}
if (payload_size > UBX_PAYLOAD_SZ_MAX) {
return -1;
}
switch (msg_cls) {
case UBX_CLASS_ACK:
return ubx_validate_payload_size_ack(msg_id, payload_size);
case UBX_CLASS_CFG:
return ubx_validate_payload_size_cfg(msg_id, payload_size);
default:
return -1;
}
}
int ubx_create_and_validate_frame(uint8_t *ubx_frame, uint16_t ubx_frame_size, uint8_t msg_cls,
uint8_t msg_id, const void *payload, uint16_t payload_size)
{
if (ubx_validate_payload_size(msg_cls, msg_id, payload_size)) {
return -1;
}
return modem_ubx_create_frame(ubx_frame, ubx_frame_size, msg_cls, msg_id, payload,
payload_size);
}
void ubx_cfg_ack_payload_default(struct ubx_cfg_ack_payload *payload)
{
payload->message_class = UBX_CLASS_CFG;
payload->message_id = UBX_CFG_PRT;
}
void ubx_cfg_rate_payload_default(struct ubx_cfg_rate_payload *payload)
{
payload->meas_rate_ms = 1000;
payload->nav_rate = 1;
payload->time_ref = UBX_CFG_RATE_TIME_REF_UTC;
}
void ubx_cfg_prt_poll_payload_default(struct ubx_cfg_prt_poll_payload *payload)
{
payload->port_id = UBX_PORT_NUMBER_UART;
}
void ubx_cfg_prt_set_payload_default(struct ubx_cfg_prt_set_payload *payload)
{
payload->port_id = UBX_PORT_NUMBER_UART;
payload->reserved0 = UBX_CFG_PRT_RESERVED0;
payload->tx_ready_pin_conf = UBX_CFG_PRT_TX_READY_PIN_CONF_POL_HIGH;
payload->port_mode = UBX_CFG_PRT_PORT_MODE_CHAR_LEN_8 | UBX_CFG_PRT_PORT_MODE_PARITY_NONE |
UBX_CFG_PRT_PORT_MODE_STOP_BITS_1;
payload->baudrate = ubx_baudrate[3];
payload->in_proto_mask = UBX_CFG_PRT_IN_PROTO_UBX | UBX_CFG_PRT_IN_PROTO_NMEA |
UBX_CFG_PRT_IN_PROTO_RTCM;
payload->out_proto_mask = UBX_CFG_PRT_OUT_PROTO_UBX | UBX_CFG_PRT_OUT_PROTO_NMEA |
UBX_CFG_PRT_OUT_PROTO_RTCM3;
payload->flags = UBX_CFG_PRT_FLAGS_DEFAULT;
payload->reserved1 = UBX_CFG_PRT_RESERVED1;
}
void ubx_cfg_rst_payload_default(struct ubx_cfg_rst_payload *payload)
{
payload->nav_bbr_mask = UBX_CFG_RST_NAV_BBR_MASK_HOT_START;
payload->reset_mode = UBX_CFG_RST_RESET_MODE_CONTROLLED_SOFT_RESET;
payload->reserved0 = UBX_CFG_RST_RESERVED0;
}
void ubx_cfg_nav5_payload_default(struct ubx_cfg_nav5_payload *payload)
{
payload->mask = UBX_CFG_NAV5_MASK_ALL;
payload->dyn_model = UBX_DYN_MODEL_PORTABLE;
payload->fix_mode = UBX_FIX_AUTO_FIX;
payload->fixed_alt = UBX_CFG_NAV5_FIXED_ALT_DEFAULT;
payload->fixed_alt_var = UBX_CFG_NAV5_FIXED_ALT_VAR_DEFAULT;
payload->min_elev = UBX_CFG_NAV5_MIN_ELEV_DEFAULT;
payload->dr_limit = UBX_CFG_NAV5_DR_LIMIT_DEFAULT;
payload->p_dop = UBX_CFG_NAV5_P_DOP_DEFAULT;
payload->t_dop = UBX_CFG_NAV5_T_DOP_DEFAULT;
payload->p_acc = UBX_CFG_NAV5_P_ACC_DEFAULT;
payload->t_acc = UBX_CFG_NAV5_T_ACC_DEFAULT;
payload->static_hold_threshold = UBX_CFG_NAV5_STATIC_HOLD_THRESHOLD_DEFAULT;
payload->dgnss_timeout = UBX_CFG_NAV5_DGNSS_TIMEOUT_DEFAULT;
payload->cno_threshold_num_svs = UBX_CFG_NAV5_CNO_THRESHOLD_NUM_SVS_DEFAULT;
payload->cno_threshold = UBX_CFG_NAV5_CNO_THRESHOLD_DEFAULT;
payload->reserved0 = UBX_CFG_NAV5_RESERVED0;
payload->static_hold_dist_threshold = UBX_CFG_NAV5_STATIC_HOLD_DIST_THRESHOLD;
payload->utc_standard = UBX_CFG_NAV5_UTC_STANDARD_DEFAULT;
}
static struct ubx_cfg_gnss_payload_config_block ubx_cfg_gnss_payload_config_block_default = {
.gnss_id = UBX_GNSS_ID_GPS,
.num_res_trk_ch = 0x00,
.max_num_trk_ch = 0x00,
.reserved0 = UBX_CFG_GNSS_RESERVED0,
.flags = UBX_CFG_GNSS_FLAG_ENABLE | UBX_CFG_GNSS_FLAG_SGN_CNF_GPS_L1C_A,
};
void ubx_cfg_gnss_payload_default(struct ubx_cfg_gnss_payload *payload)
{
payload->msg_ver = UBX_CFG_GNSS_MSG_VER;
payload->num_trk_ch_hw = UBX_CFG_GNSS_NUM_TRK_CH_HW_DEFAULT;
payload->num_trk_ch_use = UBX_CFG_GNSS_NUM_TRK_CH_USE_DEFAULT;
for (int i = 0; i < payload->num_config_blocks; ++i) {
payload->config_blocks[i] = ubx_cfg_gnss_payload_config_block_default;
}
}
void ubx_cfg_msg_payload_default(struct ubx_cfg_msg_payload *payload)
{
payload->message_class = UBX_CLASS_NMEA;
payload->message_id = UBX_NMEA_GGA;
payload->rate = UBX_CFG_MSG_RATE_DEFAULT;
}

251
drivers/gnss/gnss_u_blox_protocol/gnss_u_blox_protocol.h

@ -1,251 +0,0 @@
/*
* Copyright 2024 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/types.h>
#include <zephyr/modem/ubx.h>
#include "gnss_u_blox_protocol_defines.h"
#ifndef ZEPHYR_U_BLOX_PROTOCOL_
#define ZEPHYR_U_BLOX_PROTOCOL_
#define UBX_BAUDRATE_COUNT 9
/* When a configuration frame is sent, the device requires some delay to reflect the changes. */
/* TODO: check what is the precise waiting time for each message. */
#define UBX_CFG_RST_WAIT_MS 6000
#define UBX_CFG_GNSS_WAIT_MS 6000
#define UBX_CFG_NAV5_WAIT_MS 6000
extern const uint32_t ubx_baudrate[UBX_BAUDRATE_COUNT];
#define UBX_FRM_GET_PAYLOAD_SZ 0
#define UBX_CFG_ACK_PAYLOAD_SZ 2
#define UBX_CFG_NAK_PAYLOAD_SZ 2
#define UBX_CFG_RATE_PAYLOAD_SZ 6
#define UBX_CFG_PRT_POLL_PAYLOAD_SZ 1
#define UBX_CFG_PRT_POLL_FRM_SZ (UBX_FRM_SZ_WO_PAYLOAD + UBX_CFG_PRT_POLL_PAYLOAD_SZ)
#define UBX_CFG_PRT_SET_PAYLOAD_SZ 20
#define UBX_CFG_PRT_SET_FRM_SZ (UBX_FRM_SZ_WO_PAYLOAD + UBX_CFG_PRT_SET_PAYLOAD_SZ)
#define UBX_CFG_RST_PAYLOAD_SZ 4
#define UBX_CFG_RST_FRM_SZ (UBX_FRM_SZ_WO_PAYLOAD + UBX_CFG_RST_PAYLOAD_SZ)
#define UBX_CFG_NAV5_PAYLOAD_SZ 36
#define UBX_CFG_NAV5_FRM_SZ (UBX_FRM_SZ_WO_PAYLOAD + UBX_CFG_NAV5_PAYLOAD_SZ)
#define UBX_CFG_MSG_PAYLOAD_SZ 3
#define UBX_CFG_MSG_FRM_SZ (UBX_FRM_SZ_WO_PAYLOAD + UBX_CFG_MSG_PAYLOAD_SZ)
#define UBX_CFG_GNSS_PAYLOAD_INIT_SZ 4
#define UBX_CFG_GNSS_PAYLOAD_CFG_BLK_SZ 8
#define UBX_CFG_GNSS_PAYLOAD_SZ(n) \
(UBX_CFG_GNSS_PAYLOAD_INIT_SZ + UBX_CFG_GNSS_PAYLOAD_CFG_BLK_SZ * n)
#define UBX_CFG_GNSS_FRM_SZ(n) (UBX_FRM_SZ_WO_PAYLOAD + UBX_CFG_GNSS_PAYLOAD_SZ(n))
int ubx_create_and_validate_frame(uint8_t *ubx_frame, uint16_t ubx_frame_size, uint8_t msg_cls,
uint8_t msg_id, const void *payload, uint16_t payload_size);
struct ubx_cfg_ack_payload {
uint8_t message_class;
uint8_t message_id;
};
void ubx_cfg_ack_payload_default(struct ubx_cfg_ack_payload *payload);
#define UBX_CFG_RATE_TIME_REF_UTC 0 /* Align measurements to UTC time. */
#define UBX_CFG_RATE_TIME_REF_GPS 1 /* Align measurements to GPS time. */
#define UBX_CFG_RATE_TIME_REF_GLO 2 /* Align measurements to GLONASS time. */
#define UBX_CFG_RATE_TIME_REF_BDS 3 /* Align measurements to BeiDou time. */
#define UBX_CFG_RATE_TIME_REF_GAL 4 /* Align measurements to Galileo time. */
struct ubx_cfg_rate_payload {
uint16_t meas_rate_ms;
uint16_t nav_rate;
uint16_t time_ref;
};
void ubx_cfg_rate_payload_default(struct ubx_cfg_rate_payload *payload);
struct ubx_cfg_prt_poll_payload {
uint8_t port_id;
};
void ubx_cfg_prt_poll_payload_default(struct ubx_cfg_prt_poll_payload *payload);
#define UBX_CFG_PRT_IN_PROTO_UBX BIT(0)
#define UBX_CFG_PRT_IN_PROTO_NMEA BIT(1)
#define UBX_CFG_PRT_IN_PROTO_RTCM BIT(2)
#define UBX_CFG_PRT_IN_PROTO_RTCM3 BIT(5)
#define UBX_CFG_PRT_OUT_PROTO_UBX BIT(0)
#define UBX_CFG_PRT_OUT_PROTO_NMEA BIT(1)
#define UBX_CFG_PRT_OUT_PROTO_RTCM3 BIT(5)
#define UBX_CFG_PRT_PORT_MODE_CHAR_LEN_5 0U
#define UBX_CFG_PRT_PORT_MODE_CHAR_LEN_6 BIT(6)
#define UBX_CFG_PRT_PORT_MODE_CHAR_LEN_7 BIT(7)
#define UBX_CFG_PRT_PORT_MODE_CHAR_LEN_8 (BIT(6) | BIT(7))
#define UBX_CFG_PRT_PORT_MODE_PARITY_EVEN 0U
#define UBX_CFG_PRT_PORT_MODE_PARITY_ODD BIT(9)
#define UBX_CFG_PRT_PORT_MODE_PARITY_NONE BIT(11)
#define UBX_CFG_PRT_PORT_MODE_STOP_BITS_1 0U
#define UBX_CFG_PRT_PORT_MODE_STOP_BITS_1_HALF BIT(12)
#define UBX_CFG_PRT_PORT_MODE_STOP_BITS_2 BIT(13)
#define UBX_CFG_PRT_PORT_MODE_STOP_BITS_HALF (BIT(12) | BIT(13))
#define UBX_CFG_PRT_RESERVED0 0x00
#define UBX_CFG_PRT_TX_READY_PIN_CONF_DEFAULT 0x0000
#define UBX_CFG_PRT_TX_READY_PIN_CONF_EN BIT(0)
#define UBX_CFG_PRT_TX_READY_PIN_CONF_POL_LOW BIT(1)
#define UBX_CFG_PRT_TX_READY_PIN_CONF_POL_HIGH 0U
#define UBX_CFG_PRT_RESERVED1 0x00
#define UBX_CFG_PRT_FLAGS_DEFAULT 0x0000
#define UBX_CFG_PRT_FLAGS_EXTENDED_TX_TIMEOUT BIT(0)
struct ubx_cfg_prt_set_payload {
uint8_t port_id;
uint8_t reserved0;
uint16_t tx_ready_pin_conf;
uint32_t port_mode;
uint32_t baudrate;
uint16_t in_proto_mask;
uint16_t out_proto_mask;
uint16_t flags;
uint8_t reserved1;
};
void ubx_cfg_prt_set_payload_default(struct ubx_cfg_prt_set_payload *payload);
#define UBX_CFG_RST_NAV_BBR_MASK_HOT_START 0x0000
#define UBX_CFG_RST_NAV_BBR_MASK_WARM_START 0x0001
#define UBX_CFG_RST_NAV_BBR_MASK_COLD_START 0xFFFF
#define UBX_CFG_RST_RESET_MODE_HARD_RESET 0x00
#define UBX_CFG_RST_RESET_MODE_CONTROLLED_SOFT_RESET 0x01
#define UBX_CFG_RST_RESET_MODE_CONTROLLED_SOFT_RESET_GNSS_ONLY 0x02
#define UBX_CFG_RST_RESET_MODE_HARD_RESET_AFTER_SHUTDOWN 0x04
#define UBX_CFG_RST_RESET_MODE_CONTROLLED_GNSS_STOP 0x08
#define UBX_CFG_RST_RESET_MODE_CONTROLLED_GNSS_START 0x09
#define UBX_CFG_RST_RESERVED0 0x00
struct ubx_cfg_rst_payload {
uint16_t nav_bbr_mask;
uint8_t reset_mode;
uint8_t reserved0;
};
void ubx_cfg_rst_payload_default(struct ubx_cfg_rst_payload *payload);
#define UBX_CFG_NAV5_MASK_ALL 0x05FF
#define UBX_CFG_NAV5_FIX_MODE_DEFAULT UBX_FIX_AUTO_FIX
#define UBX_CFG_NAV5_FIXED_ALT_DEFAULT 0
#define UBX_CFG_NAV5_FIXED_ALT_VAR_DEFAULT 1U
#define UBX_CFG_NAV5_MIN_ELEV_DEFAULT 5
#define UBX_CFG_NAV5_DR_LIMIT_DEFAULT 3U
#define UBX_CFG_NAV5_P_DOP_DEFAULT 100U
#define UBX_CFG_NAV5_T_DOP_DEFAULT 100U
#define UBX_CFG_NAV5_P_ACC_DEFAULT 100U
#define UBX_CFG_NAV5_T_ACC_DEFAULT 350U
#define UBX_CFG_NAV5_STATIC_HOLD_THRESHOLD_DEFAULT 0U
#define UBX_CFG_NAV5_DGNSS_TIMEOUT_DEFAULT 60U
#define UBX_CFG_NAV5_CNO_THRESHOLD_NUM_SVS_DEFAULT 0U
#define UBX_CFG_NAV5_CNO_THRESHOLD_DEFAULT 0U
#define UBX_CFG_NAV5_RESERVED0 0U
#define UBX_CFG_NAV5_STATIC_HOLD_DIST_THRESHOLD 0U
#define UBX_CFG_NAV5_UTC_STANDARD_DEFAULT UBX_UTC_AUTOUTC
struct ubx_cfg_nav5_payload {
uint16_t mask;
uint8_t dyn_model;
uint8_t fix_mode;
int32_t fixed_alt;
uint32_t fixed_alt_var;
int8_t min_elev;
uint8_t dr_limit;
uint16_t p_dop;
uint16_t t_dop;
uint16_t p_acc;
uint16_t t_acc;
uint8_t static_hold_threshold;
uint8_t dgnss_timeout;
uint8_t cno_threshold_num_svs;
uint8_t cno_threshold;
uint16_t reserved0;
uint16_t static_hold_dist_threshold;
uint8_t utc_standard;
};
void ubx_cfg_nav5_payload_default(struct ubx_cfg_nav5_payload *payload);
#define UBX_CFG_GNSS_MSG_VER 0x00
#define UBX_CFG_GNSS_NUM_TRK_CH_HW_DEFAULT 0x31
#define UBX_CFG_GNSS_NUM_TRK_CH_USE_DEFAULT 0x31
#define UBX_CFG_GNSS_RESERVED0 0x00
#define UBX_CFG_GNSS_FLAG_ENABLE BIT(0)
#define UBX_CFG_GNSS_FLAG_DISABLE 0U
#define UBX_CFG_GNSS_FLAG_SGN_CNF_SHIFT 16
/* When gnss_id is 0 (GPS) */
#define UBX_CFG_GNSS_FLAG_SGN_CNF_GPS_L1C_A (0x01 << UBX_CFG_GNSS_FLAG_SGN_CNF_SHIFT)
#define UBX_CFG_GNSS_FLAG_SGN_CNF_GPS_L2C (0x10 << UBX_CFG_GNSS_FLAG_SGN_CNF_SHIFT)
#define UBX_CFG_GNSS_FLAG_SGN_CNF_GPS_L5 (0x20 << UBX_CFG_GNSS_FLAG_SGN_CNF_SHIFT)
/* When gnss_id is 1 (SBAS) */
#define UBX_CFG_GNSS_FLAG_SGN_CNF_SBAS_L1C_A (0x01 << UBX_CFG_GNSS_FLAG_SGN_CNF_SHIFT)
/* When gnss_id is 2 (Galileo) */
#define UBX_CFG_GNSS_FLAG_SGN_CNF_GALILEO_E1 (0x01 << UBX_CFG_GNSS_FLAG_SGN_CNF_SHIFT)
#define UBX_CFG_GNSS_FLAG_SGN_CNF_GALILEO_E5A (0x10 << UBX_CFG_GNSS_FLAG_SGN_CNF_SHIFT)
#define UBX_CFG_GNSS_FLAG_SGN_CNF_GALILEO_E5B (0x20 << UBX_CFG_GNSS_FLAG_SGN_CNF_SHIFT)
/* When gnss_id is 3 (BeiDou) */
#define UBX_CFG_GNSS_FLAG_SGN_CNF_BEIDOU_B1I (0x01 << UBX_CFG_GNSS_FLAG_SGN_CNF_SHIFT)
#define UBX_CFG_GNSS_FLAG_SGN_CNF_BEIDOU_B2I (0x10 << UBX_CFG_GNSS_FLAG_SGN_CNF_SHIFT)
#define UBX_CFG_GNSS_FLAG_SGN_CNF_BEIDOU_B2A (0x80 << UBX_CFG_GNSS_FLAG_SGN_CNF_SHIFT)
/* When gnss_id is 4 (IMES) */
#define UBX_CFG_GNSS_FLAG_SGN_CNF_IMES_L1 (0x01 << UBX_CFG_GNSS_FLAG_SGN_CNF_SHIFT)
/* When gnss_id is 5 (QZSS) */
#define UBX_CFG_GNSS_FLAG_SGN_CNF_QZSS_L1C_A (0x01 << UBX_CFG_GNSS_FLAG_SGN_CNF_SHIFT)
#define UBX_CFG_GNSS_FLAG_SGN_CNF_QZSS_L1S (0x04 << UBX_CFG_GNSS_FLAG_SGN_CNF_SHIFT)
#define UBX_CFG_GNSS_FLAG_SGN_CNF_QZSS_L2C (0x10 << UBX_CFG_GNSS_FLAG_SGN_CNF_SHIFT)
#define UBX_CFG_GNSS_FLAG_SGN_CNF_QZSS_L5 (0x20 << UBX_CFG_GNSS_FLAG_SGN_CNF_SHIFT)
/* When gnss_id is 6 (GLONASS) */
#define UBX_CFG_GNSS_FLAG_SGN_CNF_GLONASS_L1 (0x01 << UBX_CFG_GNSS_FLAG_SGN_CNF_SHIFT)
#define UBX_CFG_GNSS_FLAG_SGN_CNF_GLONASS_L2 (0x10 << UBX_CFG_GNSS_FLAG_SGN_CNF_SHIFT)
struct ubx_cfg_gnss_payload_config_block {
uint8_t gnss_id;
uint8_t num_res_trk_ch;
uint8_t max_num_trk_ch;
uint8_t reserved0;
uint32_t flags;
};
struct ubx_cfg_gnss_payload {
uint8_t msg_ver;
uint8_t num_trk_ch_hw;
uint8_t num_trk_ch_use;
uint8_t num_config_blocks;
struct ubx_cfg_gnss_payload_config_block config_blocks[];
};
void ubx_cfg_gnss_payload_default(struct ubx_cfg_gnss_payload *payload);
#define UBX_CFG_MSG_RATE_DEFAULT 1
struct ubx_cfg_msg_payload {
uint8_t message_class;
uint8_t message_id;
uint8_t rate;
};
void ubx_cfg_msg_payload_default(struct ubx_cfg_msg_payload *payload);
#endif /* ZEPHYR_U_BLOX_PROTOCOL_ */

258
drivers/gnss/gnss_u_blox_protocol/gnss_u_blox_protocol_defines.h

@ -1,258 +0,0 @@
/*
* Copyright 2024 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
/* Referred some enum definitions from file "include/zephyr/drivers/gnss/ublox_neo_m8_defines.h"
* from the pull request #46447 (link - https://github.com/zephyrproject-rtos/zephyr/pull/46447).
*/
#ifndef ZEPHYR_U_BLOX_PROTOCOL_DEFINES_
#define ZEPHYR_U_BLOX_PROTOCOL_DEFINES_
enum ubx_gnss_id {
UBX_GNSS_ID_GPS = 0,
UBX_GNSS_ID_SBAS = 1,
UBX_GNSS_ID_GALILEO = 2,
UBX_GNSS_ID_BEIDOU = 3,
UBX_GNSS_ID_IMES = 4,
UBX_GNSS_ID_QZSS = 5,
UBX_GNSS_ID_GLONASS = 6,
};
enum ubx_port_number {
UBX_PORT_NUMBER_DDC = 0,
UBX_PORT_NUMBER_UART,
UBX_PORT_NUMBER_USB,
UBX_PORT_NUMBER_SPI,
};
enum ubx_dynamic_model {
UBX_DYN_MODEL_PORTABLE = 0,
UBX_DYN_MODEL_STATIONARY = 2,
UBX_DYN_MODEL_PEDESTRIAN = 3,
UBX_DYN_MODEL_AUTOMOTIVE = 4,
UBX_DYN_MODEL_SEA = 5,
UBX_DYN_MODEL_AIRBORNE1G = 6,
UBX_DYN_MODEL_AIRBORNE2G = 7,
UBX_DYN_MODEL_AIRBORNE4G = 8,
UBX_DYN_MODEL_WRIST = 9,
UBX_DYN_MODEL_BIKE = 10,
};
enum ubx_fix_mode {
UBX_FIX_P_2D = 1,
UBX_FIX_P_3D,
UBX_FIX_AUTO_FIX,
};
enum ubx_utc_standard {
UBX_UTC_AUTOUTC = 0,
UBX_UTC_GPS = 3,
UBX_UTC_GALILEO = 5,
UBX_UTC_GLONASS,
UBX_UTC_BEIDOU,
UBX_UTC_NAVIC,
};
enum ubx_msg_class {
UBX_CLASS_NAV = 0x01,
UBX_CLASS_RXM = 0x02,
UBX_CLASS_INF = 0x04,
UBX_CLASS_ACK = 0x05,
UBX_CLASS_CFG = 0x06,
UBX_CLASS_UPD = 0x09,
UBX_CLASS_MON = 0x0A,
UBX_CLASS_AID = 0x0B,
UBX_CLASS_TIM = 0x0D,
UBX_CLASS_ESF = 0x10,
UBX_CLASS_MGA = 0x13,
UBX_CLASS_LOG = 0x21,
UBX_CLASS_SEC = 0x27,
UBX_CLASS_HNR = 0x28,
UBX_CLASS_NMEA = 0xF0,
};
enum ubx_ack_message {
UBX_ACK_ACK = 0x01,
UBX_ACK_NAK = 0x00,
};
enum ubx_config_message {
UBX_CFG_ANT = 0x13,
UBX_CFG_BATCH = 0x93,
UBX_CFG_CFG = 0x09,
UBX_CFG_DAT = 0x06,
UBX_CFG_DGNSS = 0x70,
UBX_CFG_DOSC = 0x61,
UBX_CFG_ESFALG = 0x56,
UBX_CFG_ESFAE = 0x4C,
UBX_CFG_ESFGE = 0x4D,
UBX_CFG_ESFWTE = 0x82,
UBX_CFG_ESRCE = 0x60,
UBX_CFG_GEOFENCE = 0x69,
UBX_CFG_GNSS = 0x3E,
UBX_CFG_HNR = 0x5C,
UBX_CFG_INF = 0x02,
UBX_CFG_ITFM = 0x39,
UBX_CFG_LOGFILTER = 0x47,
UBX_CFG_MSG = 0x01,
UBX_CFG_NAV5 = 0x24,
UBX_CFG_NAVX5 = 0x23,
UBX_CFG_NMEA = 0x17,
UBX_CFG_ODO = 0x1E,
UBX_CFG_PM2 = 0x3B,
UBX_CFG_PMS = 0x86,
UBX_CFG_PRT = 0x00,
UBX_CFG_PWR = 0x57,
UBX_CFG_RATE = 0x08,
UBX_CFG_RINV = 0x34,
UBX_CFG_RST = 0x04,
UBX_CFG_RXM = 0x11,
UBX_CFG_SBAS = 0x16,
UBX_CFG_SENIF = 0x88,
UBX_CFG_SLAS = 0x8D,
UBX_CFG_SMGR = 0x62,
UBX_CFG_SPT = 0x64,
UBX_CFG_TMODE2 = 0x3D,
UBX_CFG_TMODE3 = 0x71,
UBX_CFG_TP5 = 0x31,
UBX_CFG_TXSLOT = 0x53,
UBX_CFG_USB = 0x1B,
};
enum ubx_information_message {
UBX_INF_DEBUG = 0x04,
UBX_INF_ERROR = 0x00,
UBX_INF_NOTICE = 0x02,
UBX_INF_TEST = 0x03,
UBX_INF_WARNING = 0x01,
};
enum ubx_logging_message {
UBX_LOG_BATCH = 0x11,
UBX_LOG_CREATE = 0x07,
UBX_LOG_ERASE = 0x03,
UBX_LOG_FINDTIME = 0x0E,
UBX_LOG_INFO = 0x08,
UBX_LOG_RETRIEVEBATCH = 0x10,
UBX_LOG_RETRIEVEPOSEXTRA = 0x0f,
UBX_LOG_RETRIEVEPOS = 0x0b,
UBX_LOG_RETRIEVESTRING = 0x0d,
UBX_LOG_RETRIEVE = 0x09,
UBX_LOG_STRING = 0x04,
};
enum ubx_multiple_gnss_assistance_message {
UBX_MGA_ACK = 0x60,
UBX_MGA_ANO = 0x20,
UBX_MGA_BDS = 0x03,
UBX_MGA_DBD = 0x80,
UBX_MGA_FLASH = 0x21,
UBX_MGA_GAL = 0x02,
UBX_MGA_GLO = 0x06,
UBX_MGA_GPS = 0x00,
UBX_MGA_INI = 0x40,
UBX_MGA_QZSS = 0x05,
};
enum ubx_monitoring_message {
UBX_MON_BATCH = 0x32,
UBX_MON_GNSS = 0x28,
UBX_MON_HW2 = 0x0B,
UBX_MON_HW = 0x09,
UBX_MON_IO = 0x02,
UBX_MON_MSGPP = 0x06,
UBX_MON_PATCH = 0x27,
UBX_MON_RXBUF = 0x07,
UBX_MON_RXR = 0x21,
UBX_MON_SMGR = 0x2E,
UBX_MON_SPT = 0x2F,
UBX_MON_TXBUF = 0x08,
UBX_MON_VER = 0x04,
};
enum ubx_nagivation_results_message {
UBX_NAV_AOPSTATUS = 0x60,
UBX_NAV_ATT = 0x05,
UBX_NAV_CLOCK = 0x22,
UBX_NAV_COV = 0x36,
UBX_NAV_DGPS = 0x31,
UBX_NAV_DOP = 0x04,
UBX_NAV_EELL = 0x3d,
UBX_NAV_EOE = 0x61,
UBX_NAV_GEOFENCE = 0x39,
UBX_NAV_HPPOSECEF = 0x13,
UBX_NAV_HPPOSLLH = 0x14,
UBX_NAV_NMI = 0x28,
UBX_NAV_ODO = 0x09,
UBX_NAV_ORB = 0x34,
UBX_NAV_POSECEF = 0x01,
UBX_NAV_POSLLH = 0x02,
UBX_NAV_PVT = 0x07,
UBX_NAV_RELPOSNED = 0x3C,
UBX_NAV_RESETODO = 0x10,
UBX_NAV_SAT = 0x35,
UBX_NAV_SBAS = 0x32,
UBX_NAV_SLAS = 0x42,
UBX_NAV_SOL = 0x06,
UBX_NAV_STATUS = 0x03,
UBX_NAV_SVINFO = 0x30,
UBX_NAV_SVIN = 0x3B,
UBX_NAV_TIMEBDS = 0x24,
UBX_NAV_TIMEGAL = 0x25,
UBX_NAV_TIMEGLO = 0x23,
UBX_NAV_TIMEGPS = 0x20,
UBX_NAV_TIMELS = 0x26,
UBX_NAV_TIMEUTC = 0x21,
UBX_NAV_VELECEF = 0x11,
UBX_NAV_VELNED = 0x12,
};
enum ubx_receiver_manager_message {
UBX_RXM_IMES = 0x61,
UBX_RXM_MEASX = 0x14,
UBX_RXM_PMREQ = 0x41,
UBX_RXM_RAWX = 0x15,
UBX_RXM_RLM = 0x59,
UBX_RXM_RTCM = 0x32,
UBX_RXM_SFRBX = 0x13,
};
enum ubx_timing_message {
UBX_TIM_DOSC = 0x11,
UBX_TIM_FCHG = 0x16,
UBX_TIM_HOC = 0x17,
UBX_TIM_SMEAS = 0x13,
UBX_TIM_SVIN = 0x04,
UBX_TIM_TM2 = 0x03,
UBX_TIM_TOS = 0x12,
UBX_TIM_TP = 0x01,
UBX_TIM_VCOCAL = 0x15,
UBX_TIM_VRFY = 0x06,
};
enum ubx_nmea_message_id {
UBX_NMEA_DTM = 0x0A,
UBX_NMEA_GBQ = 0x44,
UBX_NMEA_GBS = 0x09,
UBX_NMEA_GGA = 0x00,
UBX_NMEA_GLL = 0x01,
UBX_NMEA_GLQ = 0x43,
UBX_NMEA_GNQ = 0x42,
UBX_NMEA_GNS = 0x0D,
UBX_NMEA_GPQ = 0x40,
UBX_NMEA_GRS = 0x06,
UBX_NMEA_GSA = 0x02,
UBX_NMEA_GST = 0x07,
UBX_NMEA_GSV = 0x03,
UBX_NMEA_RMC = 0x04,
UBX_NMEA_THS = 0x0E,
UBX_NMEA_TXT = 0x41,
UBX_NMEA_VLW = 0x0F,
UBX_NMEA_VTG = 0x05,
UBX_NMEA_ZDA = 0x08,
};
#endif /* ZEPHYR_U_BLOX_PROTOCOL_DEFINES_ */

147
drivers/gnss/gnss_ubx_common.c

@ -0,0 +1,147 @@
/*
* Copyright (c) 2025 Croxel Inc.
* Copyright (c) 2025 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/gnss.h>
#include <zephyr/drivers/gnss/gnss_publish.h>
#include "gnss_ubx_common.h"
void gnss_ubx_common_pvt_callback(struct modem_ubx *ubx, const struct ubx_frame *frame,
size_t len, void *user_data)
{
if (len < UBX_FRAME_SZ(sizeof(struct ubx_nav_pvt))) {
return;
}
struct gnss_ubx_common_data *data = user_data;
const struct device *dev = data->gnss;
const struct ubx_nav_pvt *nav_pvt = (const struct ubx_nav_pvt *)frame->payload_and_checksum;
enum gnss_fix_quality fix_quality = GNSS_FIX_QUALITY_INVALID;
enum gnss_fix_status fix_status = GNSS_FIX_STATUS_NO_FIX;
if ((nav_pvt->flags & UBX_NAV_PVT_FLAGS_GNSS_FIX_OK) &&
!(nav_pvt->nav.flags3 & UBX_NAV_PVT_FLAGS3_INVALID_LLH)) {
switch (nav_pvt->fix_type) {
case UBX_NAV_FIX_TYPE_DR:
case UBX_NAV_FIX_TYPE_GNSS_DR_COMBINED:
fix_quality = GNSS_FIX_QUALITY_ESTIMATED;
fix_status = GNSS_FIX_STATUS_ESTIMATED_FIX;
break;
case UBX_NAV_FIX_TYPE_2D:
case UBX_NAV_FIX_TYPE_3D:
fix_quality = GNSS_FIX_QUALITY_GNSS_SPS;
fix_status = GNSS_FIX_STATUS_GNSS_FIX;
break;
default:
break;
}
}
struct gnss_data gnss_data = {
.info = {
.satellites_cnt = nav_pvt->nav.num_sv,
.hdop = nav_pvt->nav.pdop * 10,
.geoid_separation = (nav_pvt->nav.height -
nav_pvt->nav.hmsl),
.fix_status = fix_status,
.fix_quality = fix_quality,
},
.nav_data = {
.latitude = (int64_t)nav_pvt->nav.latitude * 100,
.longitude = (int64_t)nav_pvt->nav.longitude * 100,
.bearing = (((nav_pvt->nav.head_motion < 0) ?
(nav_pvt->nav.head_motion + (360 * 100000)) :
(nav_pvt->nav.head_motion)) / 100),
.speed = nav_pvt->nav.ground_speed,
.altitude = nav_pvt->nav.hmsl,
},
.utc = {
.hour = nav_pvt->time.hour,
.minute = nav_pvt->time.minute,
.millisecond = (nav_pvt->time.second * 1000) +
(nav_pvt->time.nano / 1000000),
.month_day = nav_pvt->time.day,
.month = nav_pvt->time.month,
.century_year = (nav_pvt->time.year % 100),
},
};
gnss_publish_data(dev, &gnss_data);
}
#if CONFIG_GNSS_SATELLITES
void gnss_ubx_common_satellite_callback(struct modem_ubx *ubx, const struct ubx_frame *frame,
size_t len, void *user_data)
{
if (len < UBX_FRAME_SZ(sizeof(struct ubx_nav_sat))) {
return;
}
const struct ubx_nav_sat *ubx_sat = (const struct ubx_nav_sat *)frame->payload_and_checksum;
int num_satellites = (len - UBX_FRAME_SZ_WITHOUT_PAYLOAD - sizeof(struct ubx_nav_sat)) /
sizeof(struct ubx_nav_sat_info);
struct gnss_ubx_common_data *data = user_data;
const struct device *dev = data->gnss;
num_satellites = MIN(num_satellites, data->satellites.size);
for (size_t i = 0 ; i < num_satellites ; i++) {
enum gnss_system gnss_system = 0;
switch (ubx_sat->sat[i].gnss_id) {
case UBX_GNSS_ID_GPS:
gnss_system = GNSS_SYSTEM_GPS;
break;
case UBX_GNSS_ID_SBAS:
gnss_system = GNSS_SYSTEM_SBAS;
break;
case UBX_GNSS_ID_GALILEO:
gnss_system = GNSS_SYSTEM_GALILEO;
break;
case UBX_GNSS_ID_BEIDOU:
gnss_system = GNSS_SYSTEM_BEIDOU;
break;
case UBX_GNSS_ID_QZSS:
gnss_system = GNSS_SYSTEM_QZSS;
break;
case UBX_GNSS_ID_GLONASS:
gnss_system = GNSS_SYSTEM_GLONASS;
break;
default:
break;
}
struct gnss_satellite sat = {
/** TODO: Determine how to determine PRN from UBX sat info.
* For now passing SV_ID.
*/
.prn = ubx_sat->sat[i].sv_id,
.snr = ubx_sat->sat[i].cno,
.elevation = ubx_sat->sat[i].elevation,
.azimuth = ubx_sat->sat[i].azimuth,
.system = gnss_system,
.is_tracked = (ubx_sat->sat[i].flags & UBX_NAV_SAT_FLAGS_SV_USED),
};
data->satellites.data[i] = sat;
}
gnss_publish_satellites(dev, data->satellites.data, num_satellites);
}
#endif
void gnss_ubx_common_init(struct gnss_ubx_common_data *data,
const struct gnss_ubx_common_config *config)
{
data->gnss = config->gnss;
#if CONFIG_GNSS_SATELLITES
data->satellites.data = config->satellites.buf;
data->satellites.size = config->satellites.size;
#endif
}

44
drivers/gnss/gnss_ubx_common.h

@ -0,0 +1,44 @@
/*
* Copyright (c) 2023 Trackunit Corporation
* Copyright (c) 2025 Croxel Inc.
* Copyright (c) 2025 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_DRIVERS_GNSS_GNSS_UBX_COMMON_H_
#define ZEPHYR_DRIVERS_GNSS_GNSS_UBX_COMMON_H_
#include <zephyr/device.h>
#include <zephyr/drivers/gnss.h>
#include <zephyr/modem/ubx.h>
struct gnss_ubx_common_data {
const struct device *gnss;
struct gnss_data data;
#if CONFIG_GNSS_SATELLITES
struct {
struct gnss_satellite *data;
size_t size;
} satellites;
#endif
};
struct gnss_ubx_common_config {
const struct device *gnss;
struct {
struct gnss_satellite *buf;
size_t size;
} satellites;
};
void gnss_ubx_common_pvt_callback(struct modem_ubx *ubx, const struct ubx_frame *frame,
size_t len, void *user_data);
void gnss_ubx_common_satellite_callback(struct modem_ubx *ubx, const struct ubx_frame *frame,
size_t len, void *user_data);
void gnss_ubx_common_init(struct gnss_ubx_common_data *data,
const struct gnss_ubx_common_config *config);
#endif /* ZEPHYR_DRIVERS_GNSS_GNSS_UBX_COMMON_H_ */

19
dts/bindings/gnss/u-blox,m8.yaml

@ -9,9 +9,20 @@ include:
- uart-device.yaml - uart-device.yaml
properties: properties:
uart-baudrate: initial-baudrate:
type: int type: int
description: | description: |
Baudrate for communication on the UART port. Initial baudrate to establish Baudrate for communication on the UART port.
default: 115200 This will be used for initial modem communication, which afterwards will
enum: [4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600] be changed to the baudrate set on the peripheral. For instance: Starting
at 9600 bps, but then switched up to 115200.
default: 9600
enum: [4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800]
fix-rate:
type: int
default: 1000
description: |
Initial fix-rate GNSS modem will be operating on. May be adjusted at
run-time through GNSS APIs. Must be greater than 50-ms.
Default is power-on setting.

120
include/zephyr/modem/ubx.h

@ -1,5 +1,7 @@
/* /*
* Copyright 2024 NXP * Copyright (c) 2024 NXP
* Copyright (c) 2025 Croxel Inc.
* Copyright (c) 2025 CogniPilot Foundation
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@ -9,6 +11,7 @@
#include <zephyr/sys/atomic.h> #include <zephyr/sys/atomic.h>
#include <zephyr/modem/pipe.h> #include <zephyr/modem/pipe.h>
#include <zephyr/modem/ubx/protocol.h>
#ifndef ZEPHYR_MODEM_UBX_ #ifndef ZEPHYR_MODEM_UBX_
#define ZEPHYR_MODEM_UBX_ #define ZEPHYR_MODEM_UBX_
@ -24,76 +27,70 @@ extern "C" {
* @{ * @{
*/ */
#define UBX_FRM_HEADER_SZ 6 struct modem_ubx;
#define UBX_FRM_FOOTER_SZ 2
#define UBX_FRM_SZ_WITHOUT_PAYLOAD (UBX_FRM_HEADER_SZ + UBX_FRM_FOOTER_SZ) typedef void (*modem_ubx_match_callback)(struct modem_ubx *ubx,
#define UBX_FRM_SZ(payload_size) (payload_size + UBX_FRM_SZ_WITHOUT_PAYLOAD) const struct ubx_frame *frame,
size_t len,
#define UBX_PREAMBLE_SYNC_CHAR_1 0xB5 void *user_data);
#define UBX_PREAMBLE_SYNC_CHAR_2 0x62
struct modem_ubx_match {
#define UBX_FRM_PREAMBLE_SYNC_CHAR_1_IDX 0 struct ubx_frame_match filter;
#define UBX_FRM_PREAMBLE_SYNC_CHAR_2_IDX 1 modem_ubx_match_callback handler;
#define UBX_FRM_MSG_CLASS_IDX 2
#define UBX_FRM_MSG_ID_IDX 3
#define UBX_FRM_PAYLOAD_SZ_L_IDX 4
#define UBX_FRM_PAYLOAD_SZ_H_IDX 5
#define UBX_FRM_PAYLOAD_IDX 6
#define UBX_FRM_CHECKSUM_START_IDX 2
#define UBX_FRM_CHECKSUM_STOP_IDX(frame_len) (frame_len - 2)
#define UBX_PAYLOAD_SZ_MAX 256
#define UBX_FRM_SZ_MAX UBX_FRM_SZ(UBX_PAYLOAD_SZ_MAX)
struct ubx_frame {
uint8_t preamble_sync_char_1;
uint8_t preamble_sync_char_2;
uint8_t message_class;
uint8_t message_id;
uint8_t payload_size_low;
uint8_t payload_size_high;
uint8_t payload_and_checksum[];
}; };
struct modem_ubx_script { #define MODEM_UBX_MATCH_ARRAY_DEFINE(_name, ...) \
struct ubx_frame *request; struct modem_ubx_match _name[] = {__VA_ARGS__};
struct ubx_frame *response;
struct ubx_frame *match; #define MODEM_UBX_MATCH_DEFINE(_class_id, _msg_id, _handler) \
{ \
.filter = { \
.class = _class_id, \
.id = _msg_id, \
}, \
.handler = _handler, \
}
struct modem_ubx_script {
struct {
const struct ubx_frame *buf;
uint16_t len;
} request;
struct {
uint8_t *buf;
uint16_t buf_len;
uint16_t received_len;
} response;
struct modem_ubx_match match;
uint16_t retry_count; uint16_t retry_count;
k_timeout_t timeout; k_timeout_t timeout;
}; };
struct modem_ubx { struct modem_ubx {
void *user_data; void *user_data;
atomic_t attached;
atomic_t state;
uint8_t *receive_buf; uint8_t *receive_buf;
uint16_t receive_buf_size; uint16_t receive_buf_size;
uint16_t receive_buf_offset;
uint8_t *work_buf; struct modem_ubx_script *script;
uint16_t work_buf_size;
uint16_t work_buf_len;
bool ubx_preamble_sync_chars_received;
const struct modem_ubx_script *script;
struct modem_pipe *pipe; struct modem_pipe *pipe;
struct k_work send_work;
struct k_work process_work; struct k_work process_work;
struct k_sem script_stopped_sem; struct k_sem script_stopped_sem;
struct k_sem script_running_sem; struct k_sem script_running_sem;
struct {
const struct modem_ubx_match *array;
size_t size;
} unsol_matches;
}; };
struct modem_ubx_config { struct modem_ubx_config {
void *user_data; void *user_data;
uint8_t *receive_buf; uint8_t *receive_buf;
uint16_t receive_buf_size; uint16_t receive_buf_size;
uint8_t *work_buf; struct {
uint16_t work_buf_size; const struct modem_ubx_match *array;
size_t size;
} unsol_matches;
}; };
/** /**
@ -143,27 +140,16 @@ int modem_ubx_init(struct modem_ubx *ubx, const struct modem_ubx_config *config)
* 2. timeout (denoted by script.timeout) occurs. * 2. timeout (denoted by script.timeout) occurs.
* @param ubx Modem Ubx instance * @param ubx Modem Ubx instance
* @param script Script to be executed * @param script Script to be executed
* @note The length of ubx frame in the script.request should not exceed UBX_FRM_SZ_MAX * @note The length of ubx frame in the script.request should not exceed UBX_FRAME_SZ_MAX
* @note Modem Ubx instance must be attached to a pipe instance * @note Modem Ubx instance must be attached to a pipe instance
* @returns 0 if device acknowledged via UBX-ACK and no "get" response was received * @returns 0 if successful
* @returns positive integer denoting the length of "get" response that was received
* @returns negative errno code if failure * @returns negative errno code if failure
*/ */
int modem_ubx_run_script(struct modem_ubx *ubx, const struct modem_ubx_script *script); int modem_ubx_run_script(struct modem_ubx *ubx, struct modem_ubx_script *script);
int modem_ubx_run_script_for_each(struct modem_ubx *ubx, struct modem_ubx_script *script,
struct ubx_frame *array, size_t array_size);
/**
* @brief Initialize ubx frame
* @param ubx_frame Ubx frame buffer
* @param ubx_frame_size Ubx frame buffer size
* @param msg_cls Message class
* @param msg_id Message id
* @param payload Payload buffer
* @param payload_size Payload buffer size
* @returns positive integer denoting the length of the ubx frame created
* @returns negative errno code if failure
*/
int modem_ubx_create_frame(uint8_t *ubx_frame, uint16_t ubx_frame_size, uint8_t msg_cls,
uint8_t msg_id, const void *payload, uint16_t payload_size);
/** /**
* @} * @}
*/ */

38
include/zephyr/modem/ubx/checksum.h

@ -0,0 +1,38 @@
/*
* Copyright (c) 2025 Croxel Inc.
* Copyright (c) 2025 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_MODEM_UBX_CHECKSUM_
#define ZEPHYR_MODEM_UBX_CHECKSUM_
/** Macrobatics to compute UBX checksum at compile time */
#define UBX_CSUM_A(...) UBX_CSUM_A_(__VA_ARGS__)
#define UBX_CSUM_A_(...) UBX_CSUM_A_I(__VA_ARGS__, \
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
#define UBX_CSUM_A_I(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, \
a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, ...) \
((a1) + (a2) + (a3) + (a4) + (a5) + (a6) + (a7) + (a8) + (a9) + (a10) + \
(a11) + (a12) + (a13) + (a14) + (a15) + (a16) + (a17) + (a18) + (a19) + (a20)) & 0xFF
#define UBX_CSUM_B(...) UBX_CSUM_B_(__VA_ARGS__)
#define UBX_CSUM_B_(...) UBX_CSUM_B_I(NUM_VA_ARGS(__VA_ARGS__), __VA_ARGS__, \
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
#define UBX_CSUM_B_I(len, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, \
a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, ...) \
(((len) * a1) + ((len - 1) * a2) + ((len - 2) * a3) + ((len - 3) * a4) + \
((len - 4) * a5) + ((len - 5) * a6) + ((len - 6) * a7) + ((len - 7) * a8) + \
((len - 8) * a9) + ((len - 9) * a10) + ((len - 10) * a11) + ((len - 11) * a12) + \
((len - 12) * a13) + ((len - 13) * a14) + ((len - 14) * a15) + ((len - 15) * a16) + \
((len - 16) * a17) + ((len - 17) * a18) + ((len - 18) * a19) + ((len - 19) * a20)) & 0xFF
#define UBX_CSUM(...) UBX_CSUM_A(__VA_ARGS__), UBX_CSUM_B(__VA_ARGS__)
#endif /* ZEPHYR_MODEM_UBX_CHECKSUM_ */

478
include/zephyr/modem/ubx/protocol.h

@ -0,0 +1,478 @@
/*
* Copyright (c) 2025 Croxel Inc.
* Copyright (c) 2025 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_MODEM_UBX_PROTOCOL_
#define ZEPHYR_MODEM_UBX_PROTOCOL_
#include <stdint.h>
#include <zephyr/modem/ubx/checksum.h>
#define UBX_FRAME_HEADER_SZ 6
#define UBX_FRAME_FOOTER_SZ 2
#define UBX_FRAME_SZ_WITHOUT_PAYLOAD (UBX_FRAME_HEADER_SZ + UBX_FRAME_FOOTER_SZ)
#define UBX_FRAME_SZ(payload_size) (payload_size + UBX_FRAME_SZ_WITHOUT_PAYLOAD)
#define UBX_PREAMBLE_SYNC_CHAR_1 0xB5
#define UBX_PREAMBLE_SYNC_CHAR_2 0x62
#define UBX_FRAME_PREAMBLE_SYNC_CHAR_1_IDX 0
#define UBX_FRAME_PREAMBLE_SYNC_CHAR_2_IDX 1
#define UBX_FRAME_MSG_CLASS_IDX 2
#define UBX_PAYLOAD_SZ_MAX 512
#define UBX_FRAME_SZ_MAX UBX_FRAME_SZ(UBX_PAYLOAD_SZ_MAX)
struct ubx_frame {
uint8_t preamble_sync_char_1;
uint8_t preamble_sync_char_2;
uint8_t class;
uint8_t id;
uint16_t payload_size;
uint8_t payload_and_checksum[];
};
struct ubx_frame_match {
uint8_t class;
uint8_t id;
struct {
uint8_t *buf;
uint16_t len;
} payload;
};
enum ubx_class_id {
UBX_CLASS_ID_NAV = 0x01, /* Navigation Results Messages */
UBX_CLASS_ID_RXM = 0x02, /* Receiver Manager Messages */
UBX_CLASS_ID_INF = 0x04, /* Information Messages */
UBX_CLASS_ID_ACK = 0x05, /* Ack/Nak Messages */
UBX_CLASS_ID_CFG = 0x06, /* Configuration Input Messages */
UBX_CLASS_ID_UPD = 0x09, /* Firmware Update Messages */
UBX_CLASS_ID_MON = 0x0A, /* Monitoring Messages */
UBX_CLASS_ID_TIM = 0x0D, /* Timing Messages */
UBX_CLASS_ID_MGA = 0x13, /* Multiple GNSS Assistance Messages */
UBX_CLASS_ID_LOG = 0x21, /* Logging Messages */
UBX_CLASS_ID_SEC = 0x27, /* Security Feature Messages */
UBX_CLASS_ID_NMEA_STD = 0xF0, /* Note: Only used to configure message rate */
UBX_CLASS_ID_NMEA_PUBX = 0xF1, /* Note: Only used to configure message rate */
};
enum ubx_msg_id_nav {
UBX_MSG_ID_NAV_PVT = 0x07,
UBX_MSG_ID_NAV_SAT = 0x35,
};
enum ubx_nav_fix_type {
UBX_NAV_FIX_TYPE_NO_FIX = 0,
UBX_NAV_FIX_TYPE_DR = 1,
UBX_NAV_FIX_TYPE_2D = 2,
UBX_NAV_FIX_TYPE_3D = 3,
UBX_NAV_FIX_TYPE_GNSS_DR_COMBINED = 4,
UBX_NAV_FIX_TYPE_TIME_ONLY = 5,
};
#define UBX_NAV_PVT_VALID_DATE BIT(0)
#define UBX_NAV_PVT_VALID_TIME BIT(1)
#define UBX_NAV_PVT_VALID_UTC_TOD BIT(2)
#define UBX_NAV_PVT_VALID_MAGN BIT(3)
#define UBX_NAV_PVT_FLAGS_GNSS_FIX_OK BIT(0)
#define UBX_NAV_PVT_FLAGS3_INVALID_LLH BIT(0)
struct ubx_nav_pvt {
struct {
uint32_t itow;
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
uint8_t valid;
uint32_t tacc;
int32_t nano;
} __packed time;
uint8_t fix_type; /** See ubx_nav_fix_type */
uint8_t flags;
uint8_t flags2;
struct {
uint8_t num_sv;
int32_t longitude; /* Longitude. Degrees. scaling: 1e-7 */
int32_t latitude; /* Latitude. Degrees. scaling: 1e-7 */
int32_t height; /* Height above ellipsoid. mm */
int32_t hmsl; /* Height above mean sea level. mm */
uint32_t horiz_acc; /* Horizontal accuracy estimate. mm */
uint32_t vert_acc; /* Vertical accuracy estimate. mm */
int32_t vel_north; /* NED north velocity. mm/s */
int32_t vel_east; /* NED east velocity. mm/s */
int32_t vel_down; /* NED down velocity. mm/s */
int32_t ground_speed; /* Ground Speed (2D). mm/s */
int32_t head_motion; /* Heading of Motion (2D). Degrees. scaling: 1e-5 */
uint32_t speed_acc; /* Speed accuracy estimated. mm/s */
uint32_t head_acc; /** Heading accuracy estimate (both motion and vehicle).
* Degrees. scaling: 1e-5.
*/
uint16_t pdop; /* scaling: 1e-2 */
uint16_t flags3;
uint32_t reserved;
int32_t head_vehicle; /* Heading of vehicle (2D). Degrees. Valid if
* flags.head_vehicle_valid is set.
*/
int16_t mag_decl; /* Magnetic declination. Degrees. */
uint16_t magacc; /* Magnetic declination accuracy. Degrees. scaling: 1e-2 */
} __packed nav;
} __packed;
enum ubx_nav_sat_health {
UBX_NAV_SAT_HEALTH_UNKNOWN = 0,
UBX_NAV_SAT_HEALTH_HEALTHY = 1,
UBX_NAV_SAT_HEALTH_UNHEALTHY = 2,
};
enum ubx_gnss_id {
UBX_GNSS_ID_GPS = 0,
UBX_GNSS_ID_SBAS = 1,
UBX_GNSS_ID_GALILEO = 2,
UBX_GNSS_ID_BEIDOU = 3,
UBX_GNSS_ID_QZSS = 5,
UBX_GNSS_ID_GLONASS = 6,
};
#define UBX_NAV_SAT_FLAGS_SV_USED BIT(3)
struct ubx_nav_sat {
uint32_t itow;
uint8_t version; /* Message version. */
uint8_t num_sv;
uint16_t reserved1;
struct ubx_nav_sat_info {
uint8_t gnss_id; /* See ubx_gnss_id */
uint8_t sv_id;
uint8_t cno; /* Carrier-to-noise ratio. dBHz */
int8_t elevation; /* Elevation (range: +/- 90). Degrees */
int16_t azimuth; /* Azimuth (range: 0 - 360). Degrees */
int16_t pseu_res; /* Pseudorange Residual. Meters */
uint32_t flags;
} sat[];
};
enum ubx_msg_id_ack {
UBX_MSG_ID_ACK = 0x01,
UBX_MSG_ID_NAK = 0x00
};
enum ubx_msg_id_cfg {
UBX_MSG_ID_CFG_PRT = 0x00,
UBX_MSG_ID_CFG_MSG = 0x01,
UBX_MSG_ID_CFG_RST = 0x04,
UBX_MSG_ID_CFG_RATE = 0x08,
UBX_MSG_ID_CFG_NAV5 = 0x24,
};
enum ubx_msg_id_mon {
UBX_MSG_ID_MON_VER = 0x04,
UBX_MSG_ID_MON_GNSS = 0x28,
};
struct ubx_ack {
uint8_t class;
uint8_t id;
};
#define UBX_GNSS_SELECTION_GPS BIT(0)
#define UBX_GNSS_SELECTION_GLONASS BIT(1)
#define UBX_GNSS_SELECTION_BEIDOU BIT(2)
#define UBX_GNSS_SELECTION_GALILEO BIT(3)
struct ubx_mon_gnss {
uint8_t ver;
struct {
uint8_t supported;
uint8_t default_enabled;
uint8_t enabled;
} selection;
uint8_t simultaneous;
uint8_t reserved1[3];
} __packed;
enum ubx_cfg_port_id {
UBX_CFG_PORT_ID_DDC = 0,
UBX_CFG_PORT_ID_UART = 1,
UBX_CFG_PORT_ID_USB = 2,
UBX_CFG_PORT_ID_SPI = 3,
};
enum ubx_cfg_char_len {
UBX_CFG_PRT_PORT_MODE_CHAR_LEN_5 = 0, /* Not supported */
UBX_CFG_PRT_PORT_MODE_CHAR_LEN_6 = 1, /* Not supported */
UBX_CFG_PRT_PORT_MODE_CHAR_LEN_7 = 2, /* Supported only with parity */
UBX_CFG_PRT_PORT_MODE_CHAR_LEN_8 = 3,
};
enum ubx_cfg_parity {
UBX_CFG_PRT_PORT_MODE_PARITY_EVEN = 0,
UBX_CFG_PRT_PORT_MODE_PARITY_ODD = 1,
UBX_CFG_PRT_PORT_MODE_PARITY_NONE = 4,
};
enum ubx_cfg_stop_bits {
UBX_CFG_PRT_PORT_MODE_STOP_BITS_1 = 0,
UBX_CFG_PRT_PORT_MODE_STOP_BITS_1_5 = 1,
UBX_CFG_PRT_PORT_MODE_STOP_BITS_2 = 2,
UBX_CFG_PRT_PORT_MODE_STOP_BITS_0_5 = 3,
};
#define UBX_CFG_PRT_MODE_CHAR_LEN(val) (((val) & BIT_MASK(2)) << 6)
#define UBX_CFG_PRT_MODE_PARITY(val) (((val) & BIT_MASK(3)) << 9)
#define UBX_CFG_PRT_MODE_STOP_BITS(val) (((val) & BIT_MASK(2)) << 12)
#define UBX_CFG_PRT_PROTO_MASK_UBX BIT(0)
#define UBX_CFG_PRT_PROTO_MASK_NMEA BIT(1)
#define UBX_CFG_PRT_PROTO_MASK_RTCM3 BIT(5)
struct ubx_cfg_prt {
uint8_t port_id; /* See ubx_cfg_port_id */
uint8_t reserved1;
uint16_t rx_ready_pin;
uint32_t mode;
uint32_t baudrate;
uint16_t in_proto_mask;
uint16_t out_proto_mask;
uint16_t flags;
uint16_t reserved2;
};
enum ubx_dyn_model {
UBX_DYN_MODEL_PORTABLE = 0,
UBX_DYN_MODEL_STATIONARY = 2,
UBX_DYN_MODEL_PEDESTRIAN = 3,
UBX_DYN_MODEL_AUTOMOTIVE = 4,
UBX_DYN_MODEL_SEA = 5,
UBX_DYN_MODEL_AIRBORNE_1G = 6,
UBX_DYN_MODEL_AIRBORNE_2G = 7,
UBX_DYN_MODEL_AIRBORNE_4G = 8,
UBX_DYN_MODEL_WRIST = 9,
UBX_DYN_MODEL_BIKE = 10,
};
enum ubx_fix_mode {
UBX_FIX_MODE_2D_ONLY = 1,
UBX_FIX_MODE_3D_ONLY = 2,
UBX_FIX_MODE_AUTO = 3,
};
enum ubx_utc_standard {
UBX_UTC_STANDARD_AUTOMATIC = 0,
UBX_UTC_STANDARD_GPS = 3,
UBX_UTC_STANDARD_GALILEO = 5,
UBX_UTC_STANDARD_GLONASS = 6,
UBX_UTC_STANDARD_BEIDOU = 7,
};
#define UBX_CFG_NAV5_APPLY_DYN BIT(0)
#define UBX_CFG_NAV5_APPLY_FIX_MODE BIT(2)
struct ubx_cfg_nav5 {
uint16_t apply;
uint8_t dyn_model; /* Dynamic platform model. See ubx_dyn_model */
uint8_t fix_mode; /* Position fixing mode. See ubx_fix_mode */
int32_t fixed_alt; /* Fixed altitude for 2D fix mode. Meters */
uint32_t fixed_alt_var; /* Variance for Fixed altitude in 2D mode. Sq. meters */
int8_t min_elev; /* Minimum Elevation to use a GNSS satellite in Navigation. Degrees */
uint8_t dr_limit; /* Reserved */
uint16_t p_dop; /* Position DOP mask */
uint16_t t_dop; /* Time DOP mask */
uint16_t p_acc; /* Position accuracy mask. Meters */
uint16_t t_acc; /* Time accuracy mask. Meters */
uint8_t static_hold_thresh; /* Static hold threshold. cm/s */
uint8_t dgnss_timeout; /* DGNSS timeout. Seconds */
uint8_t cno_thresh_num_svs; /* Number of satellites required above cno_thresh */
uint8_t cno_thresh; /* C/N0 threshold for GNSS signals. dbHz */
uint8_t reserved1[2];
uint16_t static_hold_max_dist; /* Static hold distance threshold. Meters */
uint8_t utc_standard; /* UTC standard to be used. See ubx_utc_standard */
uint8_t reserved2[5];
} __packed;
enum ubx_cfg_rst_start_mode {
UBX_CFG_RST_HOT_START = 0x0000,
UBX_CFG_RST_WARM_START = 0x0001,
UBX_CFG_RST_COLD_START = 0xFFFF,
};
enum ubx_cfg_rst_mode {
UBX_CFG_RST_MODE_HW = 0x00,
UBX_CFG_RST_MODE_SW = 0x01,
UBX_CFG_RST_MODE_GNSS_STOP = 0x08,
UBX_CFG_RST_MODE_GNSS_START = 0x09,
};
struct ubx_cfg_rst {
uint16_t nav_bbr_mask;
uint8_t reset_mode;
uint8_t reserved;
};
enum ubx_cfg_rate_time_ref {
UBX_CFG_RATE_TIME_REF_UTC = 0,
UBX_CFG_RATE_TIME_REF_GPS = 1,
UBX_CFG_RATE_TIME_REF_GLONASS = 2,
UBX_CFG_RATE_TIME_REF_BEIDOU = 3,
UBX_CFG_RATE_TIME_REF_GALILEO = 4,
UBX_CFG_RATE_TIME_REF_NAVIC = 5,
};
struct ubx_cfg_rate {
uint16_t meas_rate_ms;
uint16_t nav_rate;
uint16_t time_ref;
};
enum ubx_msg_id_nmea_std {
UBX_MSG_ID_NMEA_STD_DTM = 0x0A,
UBX_MSG_ID_NMEA_STD_GBQ = 0x44,
UBX_MSG_ID_NMEA_STD_GBS = 0x09,
UBX_MSG_ID_NMEA_STD_GGA = 0x00,
UBX_MSG_ID_NMEA_STD_GLL = 0x01,
UBX_MSG_ID_NMEA_STD_GLQ = 0x43,
UBX_MSG_ID_NMEA_STD_GNQ = 0x42,
UBX_MSG_ID_NMEA_STD_GNS = 0x0D,
UBX_MSG_ID_NMEA_STD_GPQ = 0x40,
UBX_MSG_ID_NMEA_STD_GRS = 0x06,
UBX_MSG_ID_NMEA_STD_GSA = 0x02,
UBX_MSG_ID_NMEA_STD_GST = 0x07,
UBX_MSG_ID_NMEA_STD_GSV = 0x03,
UBX_MSG_ID_NMEA_STD_RMC = 0x04,
UBX_MSG_ID_NMEA_STD_THS = 0x0E,
UBX_MSG_ID_NMEA_STD_TXT = 0x41,
UBX_MSG_ID_NMEA_STD_VLW = 0x0F,
UBX_MSG_ID_NMEA_STD_VTG = 0x05,
UBX_MSG_ID_NMEA_STD_ZDA = 0x08,
};
enum ubx_msg_id_nmea_pubx {
UBX_MSG_ID_NMEA_PUBX_CONFIG = 0x41,
UBX_MSG_ID_NMEA_PUBX_POSITION = 0x00,
UBX_MSG_ID_NMEA_PUBX_RATE = 0x40,
UBX_MSG_ID_NMEA_PUBX_SVSTATUS = 0x03,
UBX_MSG_ID_NMEA_PUBX_TIME = 0x04,
};
struct ubx_cfg_msg_rate {
uint8_t class;
uint8_t id;
uint8_t rate;
};
struct ubx_mon_ver {
char sw_ver[30];
char hw_ver[10];
};
static inline uint16_t ubx_calc_checksum(const struct ubx_frame *frame, size_t len)
{
uint8_t ck_a = 0;
uint8_t ck_b = 0;
const uint8_t *data = (const uint8_t *)frame;
/** Mismatch in expected and actual length results in an invalid frame */
if (len != UBX_FRAME_SZ(frame->payload_size)) {
return 0xFFFF;
}
for (int i = UBX_FRAME_MSG_CLASS_IDX ; i < (UBX_FRAME_SZ(frame->payload_size) - 2) ; i++) {
ck_a = ck_a + data[i];
ck_b = ck_b + ck_a;
}
return ((ck_a & 0xFF) | ((ck_b & 0xFF) << 8));
}
static inline int ubx_frame_encode(uint8_t class, uint8_t id,
const uint8_t *payload, size_t payload_len,
uint8_t *buf, size_t buf_len)
{
if (buf_len < UBX_FRAME_SZ(payload_len)) {
return -EINVAL;
}
struct ubx_frame *frame = (struct ubx_frame *)buf;
frame->preamble_sync_char_1 = UBX_PREAMBLE_SYNC_CHAR_1;
frame->preamble_sync_char_2 = UBX_PREAMBLE_SYNC_CHAR_2;
frame->class = class;
frame->id = id;
frame->payload_size = payload_len;
memcpy(frame->payload_and_checksum, payload, payload_len);
uint16_t checksum = ubx_calc_checksum(frame, UBX_FRAME_SZ(payload_len));
frame->payload_and_checksum[payload_len] = checksum & 0xFF;
frame->payload_and_checksum[payload_len + 1] = (checksum >> 8) & 0xFF;
return UBX_FRAME_SZ(payload_len);
}
#define UBX_FRAME_DEFINE(_name, _frame) \
const static struct ubx_frame _name = _frame
#define UBX_FRAME_ARRAY_DEFINE(_name, ...) \
const struct ubx_frame *_name[] = {__VA_ARGS__};
#define UBX_FRAME_ACK_INITIALIZER(_class_id, _msg_id) \
UBX_FRAME_INITIALIZER_PAYLOAD(UBX_CLASS_ID_ACK, UBX_MSG_ID_ACK, _class_id, _msg_id)
#define UBX_FRAME_NAK_INITIALIZER(_class_id, _msg_id) \
UBX_FRAME_INITIALIZER_PAYLOAD(UBX_CLASS_ID_ACK, UBX_MSG_ID_NAK, _class_id, _msg_id)
#define UBX_FRAME_CFG_RST_INITIALIZER(_start_mode, _reset_mode) \
UBX_FRAME_INITIALIZER_PAYLOAD(UBX_CLASS_ID_CFG, UBX_MSG_ID_CFG_RST, \
(_start_mode & 0xFF), ((_start_mode >> 8) & 0xFF), \
_reset_mode, 0)
#define UBX_FRAME_CFG_RATE_INITIALIZER(_meas_rate_ms, _nav_rate, _time_ref) \
UBX_FRAME_INITIALIZER_PAYLOAD(UBX_CLASS_ID_CFG, UBX_MSG_ID_CFG_RATE, \
(_meas_rate_ms & 0xFF), ((_meas_rate_ms >> 8) & 0xFF), \
(_nav_rate & 0xFF), ((_nav_rate >> 8) & 0xFF), \
(_time_ref & 0xFF), ((_time_ref >> 8) & 0xFF))
#define UBX_FRAME_CFG_MSG_RATE_INITIALIZER(_class_id, _msg_id, _rate) \
UBX_FRAME_INITIALIZER_PAYLOAD(UBX_CLASS_ID_CFG, UBX_MSG_ID_CFG_MSG, \
_class_id, _msg_id, _rate)
#define UBX_FRAME_INITIALIZER_PAYLOAD(_class_id, _msg_id, ...) \
_UBX_FRAME_INITIALIZER_PAYLOAD(_class_id, _msg_id, __VA_ARGS__)
#define _UBX_FRAME_INITIALIZER_PAYLOAD(_class_id, _msg_id, ...) \
{ \
.preamble_sync_char_1 = UBX_PREAMBLE_SYNC_CHAR_1, \
.preamble_sync_char_2 = UBX_PREAMBLE_SYNC_CHAR_2, \
.class = _class_id, \
.id = _msg_id, \
.payload_size = (NUM_VA_ARGS(__VA_ARGS__)) & 0xFFFF, \
.payload_and_checksum = { \
__VA_ARGS__, \
UBX_CSUM(_class_id, _msg_id, \
((NUM_VA_ARGS(__VA_ARGS__)) & 0xFF), \
(((NUM_VA_ARGS(__VA_ARGS__)) >> 8) & 0xFF), \
__VA_ARGS__), \
}, \
}
#define UBX_FRAME_GET_INITIALIZER(_class_id, _msg_id) \
{ \
.preamble_sync_char_1 = UBX_PREAMBLE_SYNC_CHAR_1, \
.preamble_sync_char_2 = UBX_PREAMBLE_SYNC_CHAR_2, \
.class = _class_id, \
.id = _msg_id, \
.payload_size = 0, \
.payload_and_checksum = { \
UBX_CSUM(_class_id, _msg_id, 0, 0), \
}, \
}
#endif /* ZEPHYR_MODEM_UBX_PROTOCOL_ */

2
samples/drivers/gnss/boards/mimxrt1062_fmurt6.overlay

@ -20,6 +20,6 @@
gnss: u_blox_m10 { gnss: u_blox_m10 {
status = "okay"; status = "okay";
compatible = "u-blox,m8"; compatible = "u-blox,m8";
uart-baudrate = <115200>; initial-baudrate = <115200>;
}; };
}; };

2
samples/drivers/gnss/boards/vmu_rt1170_mimxrt1176_cm7.overlay

@ -17,6 +17,6 @@
gnss: gnss { gnss: gnss {
status = "okay"; status = "okay";
compatible = "u-blox,m8"; compatible = "u-blox,m8";
uart-baudrate = <115200>; initial-baudrate = <115200>;
}; };
}; };

362
subsys/modem/modem_ubx.c

@ -1,262 +1,138 @@
/* /*
* Copyright 2024 NXP * Copyright (c) 2024 NXP
* Copyright (c) 2025 Croxel Inc.
* Copyright (c) 2025 CogniPilot Foundation
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
#include <zephyr/modem/ubx.h> #include <zephyr/modem/ubx.h>
#include <string.h> #include <zephyr/sys/check.h>
#include <zephyr/logging/log.h> #include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(modem_ubx, CONFIG_MODEM_MODULES_LOG_LEVEL); LOG_MODULE_REGISTER(modem_ubx, CONFIG_MODEM_MODULES_LOG_LEVEL);
#define MODEM_UBX_STATE_ATTACHED_BIT 0 static void modem_ubx_pipe_callback(struct modem_pipe *pipe,
enum modem_pipe_event event,
static int modem_ubx_validate_frame_size(uint16_t ubx_frame_size, uint8_t msg_cls, uint8_t msg_id, void *user_data)
uint16_t payload_size)
{
if (ubx_frame_size > UBX_FRM_SZ_MAX ||
ubx_frame_size < UBX_FRM_SZ_WITHOUT_PAYLOAD ||
ubx_frame_size < UBX_FRM_SZ_WITHOUT_PAYLOAD + payload_size) {
return -1;
}
return 0;
}
int modem_ubx_create_frame(uint8_t *ubx_frame, uint16_t ubx_frame_size, uint8_t msg_cls,
uint8_t msg_id, const void *payload, uint16_t payload_size)
{
if (modem_ubx_validate_frame_size(ubx_frame_size, msg_cls, msg_id, payload_size)) {
return -1;
}
struct ubx_frame *frame = (struct ubx_frame *) ubx_frame;
frame->preamble_sync_char_1 = UBX_PREAMBLE_SYNC_CHAR_1;
frame->preamble_sync_char_2 = UBX_PREAMBLE_SYNC_CHAR_2;
frame->message_class = msg_cls;
frame->message_id = msg_id;
frame->payload_size_low = payload_size;
frame->payload_size_high = payload_size >> 8;
memcpy(frame->payload_and_checksum, payload, payload_size);
uint16_t ubx_frame_len = payload_size + UBX_FRM_SZ_WITHOUT_PAYLOAD;
uint8_t ckA = 0, ckB = 0;
for (unsigned int i = UBX_FRM_CHECKSUM_START_IDX;
i < (UBX_FRM_CHECKSUM_STOP_IDX(ubx_frame_len)); i++) {
ckA += ubx_frame[i];
ckB += ckA;
}
frame->payload_and_checksum[payload_size] = ckA;
frame->payload_and_checksum[payload_size + 1] = ckB;
return ubx_frame_len;
}
static void modem_ubx_reset_received_ubx_preamble_sync_chars(struct modem_ubx *ubx)
{
ubx->ubx_preamble_sync_chars_received = false;
}
static void modem_ubx_reset_parser(struct modem_ubx *ubx)
{
modem_ubx_reset_received_ubx_preamble_sync_chars(ubx);
}
static int modem_ubx_get_payload_length(struct ubx_frame *frame)
{
uint16_t payload_len = frame->payload_size_high;
payload_len = payload_len << 8;
return payload_len | frame->payload_size_low;
}
static int modem_ubx_get_frame_length(struct ubx_frame *frame)
{
return modem_ubx_get_payload_length(frame) + UBX_FRM_SZ_WITHOUT_PAYLOAD;
}
static bool modem_ubx_match_frame_type(struct ubx_frame *frame_1, struct ubx_frame *frame_2)
{
if (frame_1->message_class == frame_2->message_class
&& frame_1->message_id == frame_2->message_id) {
return true;
} else {
return false;
}
}
static bool modem_ubx_match_frame_full(struct ubx_frame *frame_1, struct ubx_frame *frame_2)
{ {
if (modem_ubx_get_frame_length(frame_1) != modem_ubx_get_frame_length(frame_2)) { struct modem_ubx *ubx = (struct modem_ubx *)user_data;
return false;
}
if (memcmp(frame_1, frame_2, modem_ubx_get_frame_length(frame_1)) == 0) { if (event == MODEM_PIPE_EVENT_RECEIVE_READY) {
return true; k_work_submit(&ubx->process_work);
} else {
return false;
} }
} }
static void modem_ubx_script_init(struct modem_ubx *ubx, const struct modem_ubx_script *script) int modem_ubx_run_script(struct modem_ubx *ubx, struct modem_ubx_script *script)
{
ubx->script = script;
}
static int modem_ubx_run_script_helper(struct modem_ubx *ubx, const struct modem_ubx_script *script)
{ {
int ret; int ret;
bool wait_for_rsp = script->match.filter.class != 0;
if (ubx->pipe == NULL) { ret = k_sem_take(&ubx->script_running_sem, script->timeout);
return -EPERM; if (ret != 0) {
return -EBUSY;
} }
ubx->script = script;
k_sem_reset(&ubx->script_stopped_sem); k_sem_reset(&ubx->script_stopped_sem);
modem_ubx_reset_parser(ubx); int tries = ubx->script->retry_count + 1;
int32_t ms_per_attempt = (uint64_t)k_ticks_to_ms_floor64(script->timeout.ticks) / tries;
k_work_submit(&ubx->send_work); do {
ret = modem_pipe_transmit(ubx->pipe,
(const uint8_t *)ubx->script->request.buf,
ubx->script->request.len);
if (ubx->script->match == NULL) { if (wait_for_rsp) {
return 0; ret = k_sem_take(&ubx->script_stopped_sem, K_MSEC(ms_per_attempt));
} }
tries--;
} while ((tries > 0) && (ret < 0));
ret = k_sem_take(&ubx->script_stopped_sem, script->timeout); k_sem_give(&ubx->script_running_sem);
if (ret < 0) {
return ret;
}
return 0; return (ret > 0) ? 0 : ret;
} }
int modem_ubx_run_script(struct modem_ubx *ubx, const struct modem_ubx_script *script) enum ubx_process_result {
UBX_PROCESS_RESULT_NO_DATA_FOUND,
UBX_PROCESS_RESULT_FRAME_INCOMPLETE,
UBX_PROCESS_RESULT_FRAME_FOUND
};
static inline enum ubx_process_result process_incoming_data(const uint8_t *data,
size_t len,
const struct ubx_frame **frame_start,
size_t *frame_len,
size_t *iterator)
{ {
int ret, attempt; for (int i = (*iterator) ; i < len ; i++) {
if (data[i] == UBX_PREAMBLE_SYNC_CHAR_1) {
if (modem_ubx_get_frame_length(script->request) > UBX_FRM_SZ_MAX) { const struct ubx_frame *frame = (const struct ubx_frame *)&data[i];
return -EFBIG; size_t remaining_bytes = len - i;
}
if (atomic_test_bit(&ubx->state, MODEM_UBX_STATE_ATTACHED_BIT) == false) { /* Wait until we've got the full header to keep processing data */
return -EPERM; if (UBX_FRAME_HEADER_SZ > remaining_bytes) {
*frame_start = frame;
*frame_len = remaining_bytes;
return UBX_PROCESS_RESULT_FRAME_INCOMPLETE;
} }
ret = k_sem_take(&ubx->script_running_sem, K_FOREVER); /* Filter false-positive: Sync-byte 1 contained in payload */
if (ret < 0) { if (frame->preamble_sync_char_2 != UBX_PREAMBLE_SYNC_CHAR_2) {
return ret; continue;
} }
modem_ubx_script_init(ubx, script); /* Invalid length filtering */
if (UBX_FRAME_SZ(frame->payload_size) > UBX_FRAME_SZ_MAX) {
for (attempt = 0; attempt < script->retry_count; ++attempt) { continue;
ret = modem_ubx_run_script_helper(ubx, script);
if (ret > -1) {
LOG_INF("Successfully executed script on attempt: %d.", attempt);
break;
} else if (ret == -EPERM) {
break;
}
} }
if (ret < 0) { /* Check if we should wait until packet is completely received */
LOG_ERR("Failed to execute script successfully. Attempts: %d.", attempt); if (UBX_FRAME_SZ(frame->payload_size) > remaining_bytes) {
goto unlock; *frame_start = frame;
*frame_len = remaining_bytes;
return UBX_PROCESS_RESULT_FRAME_INCOMPLETE;
} }
unlock: /* We should have all the packet, so we validate checksum. */
k_sem_give(&ubx->script_running_sem); uint16_t valid_checksum = ubx_calc_checksum(frame,
UBX_FRAME_SZ(frame->payload_size));
return ret; uint16_t ck_a = frame->payload_and_checksum[frame->payload_size];
} uint16_t ck_b = frame->payload_and_checksum[frame->payload_size + 1];
uint16_t actual_checksum = ck_a | (ck_b << 8);
static void modem_ubx_pipe_callback(struct modem_pipe *pipe, enum modem_pipe_event event,
void *user_data)
{
struct modem_ubx *ubx = (struct modem_ubx *)user_data;
if (event == MODEM_PIPE_EVENT_RECEIVE_READY) { if (valid_checksum != actual_checksum) {
k_work_submit(&ubx->process_work); continue;
} }
}
static void modem_ubx_send_handler(struct k_work *item) *frame_start = frame;
{ *frame_len = UBX_FRAME_SZ(frame->payload_size);
struct modem_ubx *ubx = CONTAINER_OF(item, struct modem_ubx, send_work);
int ret, tx_frame_len;
tx_frame_len = modem_ubx_get_frame_length(ubx->script->request); *iterator = i + 1;
ret = modem_pipe_transmit(ubx->pipe, (const uint8_t *) ubx->script->request, tx_frame_len); return UBX_PROCESS_RESULT_FRAME_FOUND;
if (ret < tx_frame_len) {
LOG_ERR("Ubx frame transmission failed. Returned %d.", ret);
return;
} }
}
static int modem_ubx_process_received_ubx_frame(struct modem_ubx *ubx)
{
int ret;
struct ubx_frame *received = (struct ubx_frame *) ubx->work_buf;
if (modem_ubx_match_frame_full(received, ubx->script->match) == true) {
/* Frame matched successfully. Terminate the script. */
k_sem_give(&ubx->script_stopped_sem);
ret = 0;
} else if (modem_ubx_match_frame_type(received, ubx->script->request) == true) {
/* Response received successfully. Script not ended. */
memcpy(ubx->script->response, ubx->work_buf, ubx->work_buf_len);
ret = -1;
} else {
/* Ignore the received frame. The device may automatically send periodic frames.
* These frames are not relevant for our script's execution and must be ignored.
*/
ret = -1;
} }
modem_ubx_reset_parser(ubx); return UBX_PROCESS_RESULT_NO_DATA_FOUND;
return ret;
} }
static int modem_ubx_process_received_byte(struct modem_ubx *ubx, uint8_t byte) static inline bool matches_filter(const struct ubx_frame *frame,
const struct ubx_frame_match *filter)
{ {
static uint8_t prev_byte; if ((frame->class == filter->class) &&
static uint16_t rx_ubx_frame_len; (frame->id == filter->id) &&
((filter->payload.len == 0) ||
if (ubx->ubx_preamble_sync_chars_received == false) { ((frame->payload_size == filter->payload.len) &&
if (prev_byte == UBX_PREAMBLE_SYNC_CHAR_1 && byte == UBX_PREAMBLE_SYNC_CHAR_2) { (0 == memcmp(frame->payload_and_checksum,
ubx->ubx_preamble_sync_chars_received = true; filter->payload.buf,
ubx->work_buf[0] = UBX_PREAMBLE_SYNC_CHAR_1; filter->payload.len))))) {
ubx->work_buf[1] = UBX_PREAMBLE_SYNC_CHAR_2; return true;
ubx->work_buf_len = 2;
}
} else { } else {
ubx->work_buf[ubx->work_buf_len] = byte; return false;
++ubx->work_buf_len;
if (ubx->work_buf_len == UBX_FRM_HEADER_SZ) {
uint16_t rx_ubx_payload_len = ubx->work_buf[UBX_FRM_PAYLOAD_SZ_H_IDX];
rx_ubx_payload_len = ubx->work_buf[UBX_FRM_PAYLOAD_SZ_H_IDX] << 8;
rx_ubx_payload_len |= ubx->work_buf[UBX_FRM_PAYLOAD_SZ_L_IDX];
rx_ubx_frame_len = rx_ubx_payload_len + UBX_FRM_SZ_WITHOUT_PAYLOAD;
}
if (ubx->work_buf_len == rx_ubx_frame_len) {
return modem_ubx_process_received_ubx_frame(ubx);
}
} }
prev_byte = byte;
return -1;
} }
static void modem_ubx_process_handler(struct k_work *item) static void modem_ubx_process_handler(struct k_work *item)
@ -264,26 +140,59 @@ static void modem_ubx_process_handler(struct k_work *item)
struct modem_ubx *ubx = CONTAINER_OF(item, struct modem_ubx, process_work); struct modem_ubx *ubx = CONTAINER_OF(item, struct modem_ubx, process_work);
int ret; int ret;
ret = modem_pipe_receive(ubx->pipe, ubx->receive_buf, ubx->receive_buf_size); ret = modem_pipe_receive(ubx->pipe,
if (ret < 1) { &ubx->receive_buf[ubx->receive_buf_offset],
return; (ubx->receive_buf_size - ubx->receive_buf_offset));
}
const uint8_t *received_data = ubx->receive_buf;
const size_t length = ret; size_t length = ret > 0 ? (ret + ubx->receive_buf_offset) : 0;
const struct ubx_frame *frame = NULL;
size_t frame_len = 0;
size_t iterator = 0;
enum ubx_process_result process_result;
do {
process_result = process_incoming_data(received_data, length,
&frame, &frame_len,
&iterator);
switch (process_result) {
case UBX_PROCESS_RESULT_FRAME_FOUND:
/** Serve script first */
if (matches_filter(frame, &ubx->script->match.filter)) {
memcpy(ubx->script->response.buf, frame, frame_len);
ubx->script->response.received_len = frame_len;
for (int i = 0; i < length; i++) { k_sem_give(&ubx->script_stopped_sem);
ret = modem_ubx_process_received_byte(ubx, ubx->receive_buf[i]);
if (ret == 0) { /* Frame matched successfully. Terminate the script. */
break;
} }
/** Check for unsolicited matches */
for (size_t i = 0 ; i < ubx->unsol_matches.size ; i++) {
if (ubx->unsol_matches.array[i].handler &&
matches_filter(frame, &ubx->unsol_matches.array[i].filter)) {
ubx->unsol_matches.array[i].handler(ubx, frame, frame_len,
ubx->user_data);
} }
}
k_work_submit(&ubx->process_work); break;
case UBX_PROCESS_RESULT_FRAME_INCOMPLETE:
/** If we had an incomplete packet, discard prior data
* and offset next pipe-receive to process remaining
* info.
*/
memcpy(ubx->receive_buf, frame, frame_len);
ubx->receive_buf_offset = frame_len;
break;
case UBX_PROCESS_RESULT_NO_DATA_FOUND:
ubx->receive_buf_offset = 0;
break;
default:
CODE_UNREACHABLE;
}
} while (process_result == UBX_PROCESS_RESULT_FRAME_FOUND);
} }
int modem_ubx_attach(struct modem_ubx *ubx, struct modem_pipe *pipe) int modem_ubx_attach(struct modem_ubx *ubx, struct modem_pipe *pipe)
{ {
if (atomic_test_and_set_bit(&ubx->state, MODEM_UBX_STATE_ATTACHED_BIT) == true) { if (atomic_test_and_set_bit(&ubx->attached, 0) == true) {
return 0; return 0;
} }
@ -298,17 +207,14 @@ void modem_ubx_release(struct modem_ubx *ubx)
{ {
struct k_work_sync sync; struct k_work_sync sync;
if (atomic_test_and_clear_bit(&ubx->state, MODEM_UBX_STATE_ATTACHED_BIT) == false) { if (atomic_test_and_clear_bit(&ubx->attached, 0) == false) {
return; return;
} }
modem_pipe_release(ubx->pipe); modem_pipe_release(ubx->pipe);
k_work_cancel_sync(&ubx->send_work, &sync);
k_work_cancel_sync(&ubx->process_work, &sync); k_work_cancel_sync(&ubx->process_work, &sync);
k_sem_reset(&ubx->script_stopped_sem); k_sem_reset(&ubx->script_stopped_sem);
k_sem_reset(&ubx->script_running_sem); k_sem_reset(&ubx->script_running_sem);
ubx->work_buf_len = 0;
modem_ubx_reset_parser(ubx);
ubx->pipe = NULL; ubx->pipe = NULL;
} }
@ -318,20 +224,18 @@ int modem_ubx_init(struct modem_ubx *ubx, const struct modem_ubx_config *config)
__ASSERT_NO_MSG(config != NULL); __ASSERT_NO_MSG(config != NULL);
__ASSERT_NO_MSG(config->receive_buf != NULL); __ASSERT_NO_MSG(config->receive_buf != NULL);
__ASSERT_NO_MSG(config->receive_buf_size > 0); __ASSERT_NO_MSG(config->receive_buf_size > 0);
__ASSERT_NO_MSG(config->work_buf != NULL);
__ASSERT_NO_MSG(config->work_buf_size > 0);
memset(ubx, 0x00, sizeof(*ubx)); memset(ubx, 0x00, sizeof(*ubx));
ubx->user_data = config->user_data; ubx->user_data = config->user_data;
ubx->receive_buf = config->receive_buf; ubx->receive_buf = config->receive_buf;
ubx->receive_buf_size = config->receive_buf_size; ubx->receive_buf_size = config->receive_buf_size;
ubx->work_buf = config->work_buf;
ubx->work_buf_size = config->work_buf_size;
ubx->pipe = NULL; ubx->pipe = NULL;
k_work_init(&ubx->send_work, modem_ubx_send_handler); ubx->unsol_matches.array = config->unsol_matches.array;
ubx->unsol_matches.size = config->unsol_matches.size;
k_work_init(&ubx->process_work, modem_ubx_process_handler); k_work_init(&ubx->process_work, modem_ubx_process_handler);
k_sem_init(&ubx->script_stopped_sem, 0, 1); k_sem_init(&ubx->script_stopped_sem, 0, 1);
k_sem_init(&ubx->script_running_sem, 1, 1); k_sem_init(&ubx->script_running_sem, 1, 1);

1
tests/drivers/build_all/gnss/app.overlay

@ -12,6 +12,7 @@
test_uart: uart@0 { test_uart: uart@0 {
compatible = "vnd,serial"; compatible = "vnd,serial";
reg = <0x0 0x1000>; reg = <0x0 0x1000>;
current-speed = <9600>;
status = "okay"; status = "okay";
gnss_nmea_generic: gnss-nmea-generic { gnss_nmea_generic: gnss-nmea-generic {

11
tests/subsys/modem/modem_ubx/CMakeLists.txt

@ -0,0 +1,11 @@
# Copyright (c) 2023 Trackunit Corporation
# Copyright (c) 2025 Croxel Inc
# Copyright (c) 2025 CogniPilot Foundation
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(modem_ubx_test)
target_sources(app PRIVATE src/main.c ../mock/modem_backend_mock.c)
target_include_directories(app PRIVATE ../mock)

9
tests/subsys/modem/modem_ubx/prj.conf

@ -0,0 +1,9 @@
# Copyright (c) 2023 Trackunit Corporation
# Copyright (c) 2025 Croxel Inc.
# Copyright (c) 2025 CogniPilot Foundation
# SPDX-License-Identifier: Apache-2.0
CONFIG_MODEM_MODULES=y
CONFIG_MODEM_UBX=y
CONFIG_ZTEST=y

561
tests/subsys/modem/modem_ubx/src/main.c

@ -0,0 +1,561 @@
/*
* Copyright (c) 2022 Trackunit Corporation
* Copyright (c) 2025 Croxel Inc.
* Copyright (c) 2025 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/ztest.h>
#include <zephyr/kernel.h>
#include <zephyr/modem/ubx.h>
#include <zephyr/modem/ubx/protocol.h>
#include <modem_backend_mock.h>
static struct modem_ubx cmd;
static uint32_t cmd_user_data = 0x145212;
static uint8_t cmd_receive_buf[128];
static uint8_t cmd_response[128];
static struct modem_backend_mock mock;
static uint8_t mock_rx_buf[128];
static uint8_t mock_tx_buf[128];
static struct modem_pipe *mock_pipe;
#define MODEM_UBX_UTEST_ON_NAK_RECEIVED_BIT (0)
#define MODEM_UBX_UTEST_ON_ACK_RECEIVED_BIT (1)
static atomic_t callback_called;
static void on_nak_received(struct modem_ubx *ubx, const struct ubx_frame *frame, size_t len,
void *user_data)
{
atomic_set_bit(&callback_called, MODEM_UBX_UTEST_ON_NAK_RECEIVED_BIT);
}
static void on_ack_received(struct modem_ubx *ubx, const struct ubx_frame *frame, size_t len,
void *user_data)
{
atomic_set_bit(&callback_called, MODEM_UBX_UTEST_ON_ACK_RECEIVED_BIT);
}
MODEM_UBX_MATCH_ARRAY_DEFINE(unsol_matches,
MODEM_UBX_MATCH_DEFINE(UBX_CLASS_ID_ACK, UBX_MSG_ID_ACK, on_ack_received),
MODEM_UBX_MATCH_DEFINE(UBX_CLASS_ID_ACK, UBX_MSG_ID_NAK, on_nak_received)
);
static struct ubx_frame test_req = UBX_FRAME_ACK_INITIALIZER(0x01, 0x02);
struct script_runner {
struct modem_ubx_script script;
struct {
bool done;
int ret;
} result;
};
static struct script_runner test_script_runner;
static void *test_setup(void)
{
const struct modem_ubx_config cmd_config = {
.user_data = &cmd_user_data,
.receive_buf = cmd_receive_buf,
.receive_buf_size = ARRAY_SIZE(cmd_receive_buf),
.unsol_matches = {
.array = unsol_matches,
.size = ARRAY_SIZE(unsol_matches),
},
};
zassert(modem_ubx_init(&cmd, &cmd_config) == 0, "Failed to init modem CMD");
const struct modem_backend_mock_config mock_config = {
.rx_buf = mock_rx_buf,
.rx_buf_size = ARRAY_SIZE(mock_rx_buf),
.tx_buf = mock_tx_buf,
.tx_buf_size = ARRAY_SIZE(mock_tx_buf),
.limit = 128,
};
mock_pipe = modem_backend_mock_init(&mock, &mock_config);
zassert(modem_pipe_open(mock_pipe, K_SECONDS(10)) == 0, "Failed to open mock pipe");
zassert(modem_ubx_attach(&cmd, mock_pipe) == 0, "Failed to attach pipe mock to modem CMD");
return NULL;
}
static inline void restore_ubx_script(void)
{
static const struct ubx_frame frame_restored = UBX_FRAME_ACK_INITIALIZER(0x01, 0x02);
static const struct script_runner script_runner_restored = {
.script = {
.request = {
.buf = &test_req,
.len = UBX_FRAME_SZ(frame_restored.payload_size),
},
.match = MODEM_UBX_MATCH_DEFINE(UBX_CLASS_ID_ACK, UBX_MSG_ID_ACK, NULL),
.response = {
.buf = cmd_response,
.buf_len = sizeof(cmd_response),
},
.timeout = K_SECONDS(1),
},
};
test_script_runner = script_runner_restored;
test_req = frame_restored;
}
static void test_before(void *f)
{
atomic_set(&callback_called, 0);
modem_backend_mock_reset(&mock);
restore_ubx_script();
}
ZTEST_SUITE(modem_ubx, NULL, test_setup, test_before, NULL, NULL);
static K_THREAD_STACK_ARRAY_DEFINE(stacks, 3, 2048);
static struct k_thread threads[3];
static void script_runner_handler(void *val, void *unused1, void *unused2)
{
struct script_runner *runner = (struct script_runner *)val;
int ret = modem_ubx_run_script(&cmd, &runner->script);
runner->result.done = true;
runner->result.ret = ret;
}
static inline void script_runner_start(struct script_runner *runner, uint8_t idx)
{
k_thread_create(&threads[idx],
stacks[idx],
K_THREAD_STACK_SIZEOF(stacks[idx]),
script_runner_handler,
runner, NULL, NULL,
K_PRIO_COOP(CONFIG_NUM_COOP_PRIORITIES - 1),
0,
K_NO_WAIT);
k_thread_start(&threads[idx]);
}
static inline void test_thread_yield(void)
{
/** Used instead of k_yield() since internals of modem pipe may rely on
* multiple thread interactions which may not be served by simply
* yielding.
*/
k_sleep(K_MSEC(1));
}
ZTEST(modem_ubx, test_cmd_no_rsp_is_non_blocking)
{
/** Keep in mind this only happens if there isn't an on-going transfer
* already. If that happens, it will wait until the other script
* finishes or this request times out. Check test-case:
* test_script_is_thread_safe for details.
*/
uint8_t buf[256];
int len;
/* Setting filter class to 0 means no response is to be awaited */
test_script_runner.script.match.filter.class = 0;
script_runner_start(&test_script_runner, 0);
test_thread_yield();
len = modem_backend_mock_get(&mock, buf, sizeof(buf));
zassert_true(test_script_runner.result.done, "Script should be done");
zassert_ok(test_script_runner.result.ret, "%d", test_script_runner.result.ret);
zassert_equal(UBX_FRAME_SZ(test_req.payload_size), len, "expected: %d, got: %d",
UBX_FRAME_SZ(test_req.payload_size), len);
}
ZTEST(modem_ubx, test_cmd_rsp_retries_and_times_out)
{
uint8_t buf[512];
test_script_runner.script.timeout = K_SECONDS(3);
test_script_runner.script.retry_count = 2; /* 2 Retries -> 3 Tries */
script_runner_start(&test_script_runner, 0);
test_thread_yield();
zassert_false(test_script_runner.result.done, "Script should not be done");
for (size_t i = 0 ; i < (test_script_runner.script.retry_count + 1) ; i++) {
int len = modem_backend_mock_get(&mock, buf, sizeof(buf));
zassert_false(test_script_runner.result.done, "Script should not be done. "
"Iteration: %d", i);
zassert_equal(UBX_FRAME_SZ(test_req.payload_size), len,
"Payload Sent does not match. "
"Expected: %d, Received: %d, Iteration: %d",
UBX_FRAME_SZ(test_req.payload_size), len, i);
k_sleep(K_SECONDS(1));
}
zassert_true(test_script_runner.result.done, "Script should be done");
zassert_equal(test_script_runner.result.ret, -EAGAIN, "Script should time out");
}
ZTEST(modem_ubx, test_cmd_rsp_blocks_and_receives_rsp)
{
static struct ubx_frame test_rsp = UBX_FRAME_ACK_INITIALIZER(0x02, 0x03);
script_runner_start(&test_script_runner, 0);
test_thread_yield();
zassert_false(test_script_runner.result.done, "Script should not be done");
modem_backend_mock_put(&mock, (uint8_t *)&test_rsp, UBX_FRAME_SZ(test_rsp.payload_size));
test_thread_yield();
zassert_true(test_script_runner.result.done, "Script should be done");
zassert_ok(test_script_runner.result.ret);
zassert_equal(UBX_FRAME_SZ(test_rsp.payload_size),
test_script_runner.script.response.received_len,
"expected: %d, got: %d",
UBX_FRAME_SZ(test_rsp.payload_size),
test_script_runner.script.response.received_len);
}
ZTEST(modem_ubx, test_script_is_thread_safe)
{
static struct ubx_frame test_rsp = UBX_FRAME_ACK_INITIALIZER(0x02, 0x03);
struct script_runner script_runner_1 = {
.script = {
.request = {
.buf = &test_req,
.len = UBX_FRAME_SZ(test_req.payload_size),
},
.match = MODEM_UBX_MATCH_DEFINE(UBX_CLASS_ID_ACK, UBX_MSG_ID_ACK, NULL),
.response = {
.buf = cmd_response,
.buf_len = sizeof(cmd_response),
},
.timeout = K_SECONDS(1),
},
};
struct script_runner script_runner_2 = {
.script = {
.request = {
.buf = &test_req,
.len = UBX_FRAME_SZ(test_req.payload_size),
},
.match = MODEM_UBX_MATCH_DEFINE(UBX_CLASS_ID_ACK, UBX_MSG_ID_ACK, NULL),
.response = {
.buf = cmd_response,
.buf_len = sizeof(cmd_response),
},
.timeout = K_SECONDS(1),
},
};
script_runner_start(&script_runner_1, 0);
script_runner_start(&script_runner_2, 1);
test_thread_yield();
zassert_false(script_runner_1.result.done);
zassert_false(script_runner_2.result.done);
modem_backend_mock_put(&mock, (uint8_t *)&test_rsp, UBX_FRAME_SZ(test_rsp.payload_size));
test_thread_yield();
zassert_true(script_runner_1.result.done);
zassert_ok(script_runner_1.result.ret);
zassert_false(script_runner_2.result.done);
modem_backend_mock_put(&mock, (uint8_t *)&test_rsp, UBX_FRAME_SZ(test_rsp.payload_size));
test_thread_yield();
zassert_true(script_runner_2.result.done);
zassert_ok(script_runner_2.result.ret);
}
ZTEST(modem_ubx, test_rsp_filters_out_bytes_before_payload)
{
static struct ubx_frame test_rsp = UBX_FRAME_ACK_INITIALIZER(0x02, 0x03);
/** Create a buf that contains an "AT command" followed by the UBX frame
* we're expecting. This should be handled by the modem_ubx.
*/
char atcmd[] = "Here's an AT command: AT\r\nOK.";
uint8_t buf[256];
size_t buf_len = 0;
memcpy(buf, atcmd, sizeof(atcmd));
buf_len += sizeof(atcmd);
memcpy(buf + buf_len, &test_rsp, UBX_FRAME_SZ(test_rsp.payload_size));
buf_len += UBX_FRAME_SZ(test_rsp.payload_size);
script_runner_start(&test_script_runner, 0);
test_thread_yield();
zassert_false(test_script_runner.result.done, "Script should not be done");
modem_backend_mock_put(&mock, (uint8_t *)buf, buf_len);
test_thread_yield();
zassert_true(test_script_runner.result.done, "Script should be done");
zassert_ok(test_script_runner.result.ret);
zassert_equal(UBX_FRAME_SZ(test_rsp.payload_size),
test_script_runner.script.response.received_len,
"expected: %d, got: %d",
UBX_FRAME_SZ(test_rsp.payload_size),
test_script_runner.script.response.received_len);
zassert_mem_equal(&test_rsp,
test_script_runner.script.response.buf,
UBX_FRAME_SZ(test_rsp.payload_size));
}
ZTEST(modem_ubx, test_rsp_incomplete_packet_discarded)
{
static struct ubx_frame test_rsp = UBX_FRAME_ACK_INITIALIZER(0x02, 0x03);
uint8_t buf[256];
size_t buf_len = 0;
memcpy(buf, &test_rsp, UBX_FRAME_SZ(test_rsp.payload_size) - 5);
buf_len += UBX_FRAME_SZ(test_rsp.payload_size) - 5;
script_runner_start(&test_script_runner, 0);
test_thread_yield();
zassert_false(test_script_runner.result.done, "Script should not be done");
modem_backend_mock_put(&mock, (uint8_t *)buf, buf_len);
test_thread_yield();
zassert_false(test_script_runner.result.done, "Script should not be done");
k_sleep(K_SECONDS(1));
zassert_true(test_script_runner.result.done, "Script should be done");
zassert_equal(-EAGAIN, test_script_runner.result.ret);
}
ZTEST(modem_ubx, test_rsp_discards_invalid_len)
{
static struct ubx_frame test_rsp = UBX_FRAME_ACK_INITIALIZER(0x02, 0x03);
/** Invalidate checksum */
size_t frame_size = UBX_FRAME_SZ(test_rsp.payload_size);
test_rsp.payload_size = 0xFFFF;
script_runner_start(&test_script_runner, 0);
test_thread_yield();
zassert_false(test_script_runner.result.done, "Script should not be done");
modem_backend_mock_put(&mock, (uint8_t *)&test_rsp, frame_size);
test_thread_yield();
zassert_false(test_script_runner.result.done, "Script should not be done");
k_sleep(K_SECONDS(1));
zassert_true(test_script_runner.result.done, "Script should be done");
zassert_equal(-EAGAIN, test_script_runner.result.ret);
}
ZTEST(modem_ubx, test_rsp_discards_invalid_checksum)
{
static struct ubx_frame test_rsp = UBX_FRAME_ACK_INITIALIZER(0x02, 0x03);
/** Invalidate checksum */
test_rsp.payload_and_checksum[test_rsp.payload_size] = 0xDE;
test_rsp.payload_and_checksum[test_rsp.payload_size + 1] = 0xAD;
script_runner_start(&test_script_runner, 0);
test_thread_yield();
zassert_false(test_script_runner.result.done, "Script should not be done");
modem_backend_mock_put(&mock, (uint8_t *)&test_rsp, UBX_FRAME_SZ(test_rsp.payload_size));
test_thread_yield();
zassert_false(test_script_runner.result.done, "Script should not be done");
k_sleep(K_SECONDS(1));
zassert_true(test_script_runner.result.done, "Script should be done");
zassert_equal(-EAGAIN, test_script_runner.result.ret);
}
ZTEST(modem_ubx, test_rsp_split_in_two_events)
{
static struct ubx_frame test_rsp = UBX_FRAME_ACK_INITIALIZER(0x02, 0x03);
uint8_t *data_ptr = (uint8_t *)&test_rsp;
script_runner_start(&test_script_runner, 0);
test_thread_yield();
zassert_false(test_script_runner.result.done, "Script should not be done");
/** The first portion of the packet. At this point the data should not be discarded,
* understanding there's more data to come.
*/
modem_backend_mock_put(&mock, data_ptr, UBX_FRAME_SZ(test_rsp.payload_size) - 5);
test_thread_yield();
zassert_false(test_script_runner.result.done, "Script should not be done");
/** The other portion of the packet. This should complete the packet reception */
modem_backend_mock_put(&mock, &data_ptr[UBX_FRAME_SZ(test_rsp.payload_size) - 5], 5);
test_thread_yield();
zassert_true(test_script_runner.result.done, "Script should be done");
zassert_ok(test_script_runner.result.ret);
zassert_equal(UBX_FRAME_SZ(test_rsp.payload_size),
test_script_runner.script.response.received_len,
"expected: %d, got: %d",
UBX_FRAME_SZ(test_rsp.payload_size),
test_script_runner.script.response.received_len);
}
ZTEST(modem_ubx, test_rsp_filters_out_non_matches)
{
static struct ubx_frame test_rsp_non_match = UBX_FRAME_NAK_INITIALIZER(0x02, 0x03);
static struct ubx_frame test_rsp_match = UBX_FRAME_ACK_INITIALIZER(0x02, 0x03);
uint8_t buf[256];
size_t buf_len = 0;
/** We're passing a valid packet, but not what we're expecing. We
* should not get an event out of this one.
*/
memcpy(buf, &test_rsp_non_match, UBX_FRAME_SZ(test_rsp_non_match.payload_size));
buf_len += UBX_FRAME_SZ(test_rsp_non_match.payload_size);
script_runner_start(&test_script_runner, 0);
test_thread_yield();
zassert_false(test_script_runner.result.done, "Script should not be done");
modem_backend_mock_put(&mock, (uint8_t *)buf, buf_len);
test_thread_yield();
zassert_false(test_script_runner.result.done, "Script should not be done");
/** Now we're passing two valid packets, on the same event: one which
* does not match, one which matches. We should get the latter.
*/
memcpy(buf, &test_rsp_match, UBX_FRAME_SZ(test_rsp_match.payload_size));
buf_len += UBX_FRAME_SZ(test_rsp_match.payload_size);
modem_backend_mock_put(&mock, (uint8_t *)buf, buf_len);
test_thread_yield();
zassert_true(test_script_runner.result.done, "Script should be done");
zassert_ok(test_script_runner.result.ret);
zassert_equal(UBX_FRAME_SZ(test_rsp_match.payload_size),
test_script_runner.script.response.received_len,
"expected: %d, got: %d",
UBX_FRAME_SZ(test_rsp_match.payload_size),
test_script_runner.script.response.received_len);
zassert_mem_equal(&test_rsp_match,
test_script_runner.script.response.buf,
UBX_FRAME_SZ(test_rsp_match.payload_size));
}
ZTEST(modem_ubx, test_rsp_match_with_payload)
{
static struct ubx_frame test_rsp_non_match = UBX_FRAME_ACK_INITIALIZER(0x02, 0x03);
static struct ubx_frame test_rsp_match = UBX_FRAME_ACK_INITIALIZER(0x03, 0x04);
test_script_runner.script.match.filter.payload.buf = test_rsp_match.payload_and_checksum;
test_script_runner.script.match.filter.payload.len = test_rsp_match.payload_size;
uint8_t buf[256];
size_t buf_len = 0;
/** We're passing a valid packet, but not what we're expecing. We
* should not get an event out of this one.
*/
memcpy(buf, &test_rsp_non_match, UBX_FRAME_SZ(test_rsp_non_match.payload_size));
buf_len += UBX_FRAME_SZ(test_rsp_non_match.payload_size);
script_runner_start(&test_script_runner, 0);
test_thread_yield();
zassert_false(test_script_runner.result.done, "Script should not be done");
modem_backend_mock_put(&mock, (uint8_t *)buf, buf_len);
test_thread_yield();
zassert_false(test_script_runner.result.done, "Script should not be done");
/** Now we're passing two valid packets, on the same event: one which
* does not match, one which matches. We should get the latter.
*/
memcpy(buf, &test_rsp_match, UBX_FRAME_SZ(test_rsp_match.payload_size));
buf_len += UBX_FRAME_SZ(test_rsp_match.payload_size);
modem_backend_mock_put(&mock, (uint8_t *)buf, buf_len);
test_thread_yield();
zassert_true(test_script_runner.result.done, "Script should be done");
zassert_ok(test_script_runner.result.ret);
zassert_equal(UBX_FRAME_SZ(test_rsp_match.payload_size),
test_script_runner.script.response.received_len,
"expected: %d, got: %d",
UBX_FRAME_SZ(test_rsp_match.payload_size),
test_script_runner.script.response.received_len);
zassert_mem_equal(&test_rsp_match,
test_script_runner.script.response.buf,
UBX_FRAME_SZ(test_rsp_match.payload_size));
}
ZTEST(modem_ubx, test_unsol_matches_trigger_cb)
{
static struct ubx_frame ack_frame = UBX_FRAME_ACK_INITIALIZER(0x01, 0x02);
static struct ubx_frame nak_frame = UBX_FRAME_NAK_INITIALIZER(0x01, 0x02);
zassert_false(atomic_test_bit(&callback_called, MODEM_UBX_UTEST_ON_ACK_RECEIVED_BIT));
zassert_false(atomic_test_bit(&callback_called, MODEM_UBX_UTEST_ON_NAK_RECEIVED_BIT));
modem_backend_mock_put(&mock,
(const uint8_t *)&ack_frame,
UBX_FRAME_SZ(ack_frame.payload_size));
test_thread_yield();
zassert_true(atomic_test_bit(&callback_called, MODEM_UBX_UTEST_ON_ACK_RECEIVED_BIT));
zassert_false(atomic_test_bit(&callback_called, MODEM_UBX_UTEST_ON_NAK_RECEIVED_BIT));
modem_backend_mock_put(&mock,
(const uint8_t *)&nak_frame,
UBX_FRAME_SZ(nak_frame.payload_size));
test_thread_yield();
zassert_true(atomic_test_bit(&callback_called, MODEM_UBX_UTEST_ON_NAK_RECEIVED_BIT));
}
ZTEST(modem_ubx, test_ubx_frame_encode_matches_compile_time_macro)
{
static struct ubx_frame ack_frame = UBX_FRAME_ACK_INITIALIZER(0x01, 0x02);
uint8_t buf[256];
struct ubx_ack ack = {
.class = 0x01,
.id = 0x02,
};
int len = ubx_frame_encode(UBX_CLASS_ID_ACK, UBX_MSG_ID_ACK,
(const uint8_t *)&ack, sizeof(ack),
buf, sizeof(buf));
zassert_equal(len, UBX_FRAME_SZ(sizeof(ack)), "Expected: %d, got: %d",
UBX_FRAME_SZ(sizeof(ack)), len);
zassert_mem_equal(buf, &ack_frame, UBX_FRAME_SZ(sizeof(ack)));
}

13
tests/subsys/modem/modem_ubx/testcase.yaml

@ -0,0 +1,13 @@
# Copyright (c) 2023 Trackunit Corporation
# Copyright (c) 2025 Croxel Inc.
# Copyright (c) 2025 CogniPilot Foundation
# SPDX-License-Identifier: Apache-2.0
tests:
modem.modem_ubx:
tags: modem_ubx
harness: ztest
platform_allow:
- native_sim
integration_platforms:
- native_sim
Loading…
Cancel
Save