gnss: rtk: Add basic integration

Incorporate the basic RTK API as well as a basic client integration
(serial) as a way to receive and publish the RTK data.

Signed-off-by: Luis Ubieda <luisf@croxel.com>
This commit is contained in:
Luis Ubieda 2025-07-08 14:29:14 -04:00 committed by Chris Friedt
parent a5d2bdc6ed
commit a000acf85b
18 changed files with 365 additions and 0 deletions

View File

@ -258,3 +258,7 @@ endif()
if(CONFIG_GNSS_SATELLITES)
zephyr_iterable_section(NAME gnss_satellites_callback KVMA RAM_REGION GROUP RODATA_REGION)
endif()
if(CONFIG_GNSS_RTK)
zephyr_iterable_section(NAME gnss_rtk_data_callback KVMA RAM_REGION GROUP RODATA_REGION)
endif()

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2025 Croxel Inc.
* Copyright (c) 2025 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_GNSS_RTK_DECODER_H_
#define ZEPHYR_INCLUDE_GNSS_RTK_DECODER_H_
#include <stdint.h>
#include <stddef.h>
/**
* @brief Get an RTK frame from buffer
*
* Used by RTK clients to extract frames from a data-buffer.
*
* @param[in] buf Buffer holding encoded data.
* @param[in] buf_len Buffer length.
* @param[out] data Pointer to the decoded frame.
* @param[out] data_len Length of the decoded frame
*
* @return Zero if successful.
* @return -ENOENT if no frames have been decoded successfully.
* @return Other negative error code if decoding failed.
*/
int gnss_rtk_decoder_frame_get(uint8_t *buf, size_t buf_len,
uint8_t **data, size_t *data_len);
#endif /* ZEPHYR_INCLUDE_GNSS_RTK_DECODER_H_ */

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2023 Trackunit Corporation
* Copyright (c) 2025 Croxel Inc.
* Copyright (c) 2025 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_GNSS_RTK_RTK_H_
#define ZEPHYR_INCLUDE_GNSS_RTK_RTK_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
struct gnss_rtk_data {
const uint8_t *data;
size_t len;
};
typedef void (*gnss_rtk_data_callback_t)(const struct device *dev,
const struct gnss_rtk_data *data);
struct gnss_rtk_data_callback {
const struct device *dev;
gnss_rtk_data_callback_t callback;
};
#if CONFIG_GNSS_RTK
#define GNSS_RTK_DATA_CALLBACK_DEFINE(_dev, _callback) \
static const STRUCT_SECTION_ITERABLE(gnss_rtk_data_callback, \
_gnss_rtk_data_callback__##_callback) = { \
.dev = _dev, \
.callback = _callback, \
}
#else
#define GNSS_RTK_DATA_CALLBACK_DEFINE(_dev, _callback)
#endif
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_GNSS_RTK_RTK_H_ */

View File

@ -0,0 +1,18 @@
/*
* Copyright (c) 2025 Croxel Inc.
* Copyright (c) 2025 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_GNSS_RTK_PUBLISH_H_
#define ZEPHYR_INCLUDE_GNSS_RTK_PUBLISH_H_
#include <stdint.h>
#include <stddef.h>
#include <zephyr/gnss/rtk/rtk.h>
/* Internal function used by RTK clients to publish data-correction. */
void gnss_rtk_publish_data(const struct gnss_rtk_data *data);
#endif /* ZEPHYR_INCLUDE_GNSS_RTK_PUBLISH_H_ */

View File

@ -68,3 +68,7 @@
#if defined(CONFIG_GNSS_SATELLITES)
ITERABLE_SECTION_ROM(gnss_satellites_callback, Z_LINK_ITERABLE_SUBALIGN)
#endif
#if defined(CONFIG_GNSS_RTK)
ITERABLE_SECTION_ROM(gnss_rtk_data_callback, Z_LINK_ITERABLE_SUBALIGN)
#endif

View File

@ -16,6 +16,7 @@ add_subdirectory(canbus)
add_subdirectory(debug)
add_subdirectory(fb)
add_subdirectory(fs)
add_subdirectory(gnss)
add_subdirectory(ipc)
add_subdirectory(logging)
add_subdirectory(mem_mgmt)

View File

@ -20,6 +20,7 @@ source "subsys/dsp/Kconfig"
source "subsys/emul/Kconfig"
source "subsys/fb/Kconfig"
source "subsys/fs/Kconfig"
source "subsys/gnss/Kconfig"
source "subsys/input/Kconfig"
source "subsys/ipc/Kconfig"
source "subsys/jwt/Kconfig"

View File

@ -0,0 +1,5 @@
# Copyright (c) 2025 Croxel Inc.
# Copyright (c) 2025 CogniPilot Foundation
# SPDX-License-Identifier: Apache-2.0
add_subdirectory_ifdef(CONFIG_GNSS_RTK rtk)

5
subsys/gnss/Kconfig Normal file
View File

@ -0,0 +1,5 @@
# Copyright (c) 2025 Croxel Inc.
# Copyright (c) 2025 CogniPilot Foundation
# SPDX-License-Identifier: Apache-2.0
rsource "rtk/Kconfig"

View File

@ -0,0 +1,9 @@
# Copyright (c) 2025 Croxel Inc.
# Copyright (c) 2025 CogniPilot Foundation
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources(rtk.c)
add_subdirectory(protocol)
add_subdirectory_ifdef(CONFIG_GNSS_RTK_SERIAL serial)

20
subsys/gnss/rtk/Kconfig Normal file
View File

@ -0,0 +1,20 @@
# Copyright (c) 2025 Croxel Inc.
# Copyright (c) 2025 CogniPilot Foundation
# SPDX-License-Identifier: Apache-2.0
menuconfig GNSS_RTK
bool "GNSS RTK client"
select EXPERIMENTAL
help
Enable GNSS RTK data-correction clients
if GNSS_RTK
rsource "protocol/Kconfig"
rsource "serial/Kconfig"
module = GNSS_RTK
module-str = GNSS RTK
source "subsys/logging/Kconfig.template.log_config"
endif

View File

@ -0,0 +1,5 @@
# Copyright (c) 2025 Croxel Inc.
# Copyright (c) 2025 CogniPilot Foundation
# SPDX-License-Identifier: Apache-2.0
zephyr_library_sources_ifdef(CONFIG_GNSS_RTK_PROTOCOL_RTCM3 rtcm3.c)

View File

@ -0,0 +1,17 @@
# Copyright (c) 2025 Croxel Inc.
# Copyright (c) 2025 CogniPilot Foundation
# SPDX-License-Identifier: Apache-2.0
choice GNSS_RTK_PROTOCOL
prompt "GNSS RTK Protocol selection"
default GNSS_RTK_PROTOCOL_RTCM3
help
Select the GNSS RTK Protocol to use in data-correction
config GNSS_RTK_PROTOCOL_RTCM3
bool "RTCM3 Protocol"
select CRC
help
Select RTCM3 protocol as GNSS RTK corrections
endchoice

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) 2025 Croxel Inc.
* Copyright (c) 2025 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>
#include <zephyr/gnss/rtk/decoder.h>
#include <zephyr/sys/crc.h>
#define RTCM3_FRAME_SYNC_SZ 1
#define RTCM3_FRAME_HDR_SZ 2
#define RTCM3_FRAME_CHECKSUM_SZ 3
#define RTCM3_FRAME_OVERHEAD (RTCM3_FRAME_SYNC_SZ + RTCM3_FRAME_HDR_SZ + \
RTCM3_FRAME_CHECKSUM_SZ)
#define RTCM3_SYNC_BYTE 0xD3
#define RTCM3_FRAME_PAYLOAD_SZ(hdr) (sys_be16_to_cpu(hdr) & BIT_MASK(10))
#define RTCM3_FRAME_SZ(payload_len) ((payload_len) + RTCM3_FRAME_OVERHEAD)
struct rtcm3_frame {
uint8_t sync_frame;
uint16_t hdr;
uint8_t payload[];
} __packed;
int gnss_rtk_decoder_frame_get(uint8_t *buf, size_t buf_len,
uint8_t **data, size_t *data_len)
{
for (size_t i = 0 ; (i + RTCM3_FRAME_OVERHEAD - 1) < buf_len ; i++) {
if (buf[i] != RTCM3_SYNC_BYTE) {
continue;
}
struct rtcm3_frame *frame = (struct rtcm3_frame *)&buf[i];
uint16_t payload_len = RTCM3_FRAME_PAYLOAD_SZ(frame->hdr);
uint16_t remaining_bytes = buf_len - i;
if (payload_len == 0 ||
RTCM3_FRAME_SZ(payload_len) > remaining_bytes) {
continue;
}
if (crc24q_rtcm3((const uint8_t *)frame,
RTCM3_FRAME_SZ(payload_len)) == 0) {
*data = (uint8_t *)frame;
*data_len = RTCM3_FRAME_SZ(payload_len);
return 0;
}
}
return -ENOENT;
}

24
subsys/gnss/rtk/rtk.c Normal file
View File

@ -0,0 +1,24 @@
/*
* Copyright (c) 2023 Trackunit Corporation
* Copyright (c) 2025 Croxel Inc.
* Copyright (c) 2025 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/sys/iterable_sections.h>
#include <zephyr/gnss/rtk/rtk.h>
void gnss_rtk_publish_data(const struct gnss_rtk_data *data)
{
static K_SEM_DEFINE(publish_lock, 1, 1);
(void)k_sem_take(&publish_lock, K_FOREVER);
STRUCT_SECTION_FOREACH(gnss_rtk_data_callback, callback) {
callback->callback(callback->dev, data);
}
k_sem_give(&publish_lock);
}

View File

@ -0,0 +1,5 @@
# Copyright (c) 2025 Croxel Inc.
# Copyright (c) 2025 CogniPilot Foundation
# SPDX-License-Identifier: Apache-2.0
zephyr_library_sources(serial.c)

View File

@ -0,0 +1,10 @@
# Copyright (c) 2025 Croxel Inc.
# Copyright (c) 2025 CogniPilot Foundation
# SPDX-License-Identifier: Apache-2.0
config GNSS_RTK_SERIAL
bool "Serial GNSS RTK client"
select SERIAL
select UART_INTERRUPT_DRIVEN
help
Use RTK Serial client to obtain data-correction.

View File

@ -0,0 +1,101 @@
/*
* Copyright (c) 2025 Croxel Inc.
* Copyright (c) 2025 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/init.h>
#include <zephyr/sys/check.h>
#include <zephyr/sys/ring_buffer.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/gnss/rtk/decoder.h>
#include <zephyr/gnss/rtk/rtk_publish.h>
#include <zephyr/gnss/rtk/rtk.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(rtk_serial, CONFIG_GNSS_RTK_LOG_LEVEL);
static const struct device *rtk_serial_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_rtk_serial));
static struct ring_buf process_ringbuf;
static uint8_t process_buf[2048];
static void gnss_rtk_process_work_handler(struct k_work *work)
{
static uint8_t work_buf[2048];
uint32_t len = ring_buf_get(&process_ringbuf, work_buf, sizeof(work_buf));
uint32_t offset = 0;
ARG_UNUSED(work);
do {
uint8_t *frame;
size_t frame_len;
int err;
err = gnss_rtk_decoder_frame_get(work_buf + offset, len - offset,
&frame, &frame_len);
if (err != 0) {
return;
}
LOG_HEXDUMP_DBG(frame, frame_len, "Frame received");
/* Publish results */
struct gnss_rtk_data rtk_data = {
.data = frame,
.len = frame_len,
};
gnss_rtk_publish_data(&rtk_data);
offset += frame_len;
} while (len > offset);
}
static K_WORK_DELAYABLE_DEFINE(gnss_rtk_process_work, gnss_rtk_process_work_handler);
static void rtk_uart_isr_callback(const struct device *dev, void *user_data)
{
ARG_UNUSED(user_data);
(void)uart_irq_update(dev);
if (uart_irq_rx_ready(dev)) {
int ret;
do {
char c;
ret = uart_fifo_read(dev, &c, 1);
if (ret > 0) {
ret = ring_buf_put(&process_ringbuf, &c, 1);
}
} while (ret > 0);
/** Since messages come through in a burst at a period
* (e.g: 1 Hz), wait until all messages are received before
* processing.
*/
(void)k_work_reschedule(&gnss_rtk_process_work, K_MSEC(10));
}
}
static int rtk_serial_client_init(void)
{
int err;
ring_buf_init(&process_ringbuf, ARRAY_SIZE(process_buf), process_buf);
err = uart_irq_callback_user_data_set(rtk_serial_dev, rtk_uart_isr_callback, NULL);
if (err < 0) {
return err;
}
uart_irq_rx_enable(rtk_serial_dev);
return 0;
}
SYS_INIT(rtk_serial_client_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);