Hi
I am wondering whether this sample can work at nRF54L15.
Thank you!

Hi,
No that sample seems to be for the old nRF5 SDK (see here). The nRF54 series only support the nRF Connect SDK. See here instead.
Regards,
Elfving
Hi,
There is a ncs 2.8 version in this github.

And I found this code claim to support nrf52832/nrf52840 and nrf5340. I can only compile nrf52 series. 53's code can not be compiled. What would be the reason?
I am also wondering whether the new nRF54L15 can use this code. I guess some new feature will block this function. Could you help me on this issue?
The main.c is:
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/types.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/usb/usb_device.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/shell/shell.h>
#include <bluetooth/services/nus.h>
#include <dk_buttons_and_leds.h>
#include <nrfx_gpiote.h>
#include <bluetooth/services/nus_client.h>
#include <bluetooth/gatt_dm.h>
#include <bluetooth/scan.h>
#if defined(DPPI_PRESENT)
#include <nrfx_dppi.h>
#else
#include <nrfx_ppi.h>
#endif
#include <zephyr/settings/settings.h>
#include <stdio.h>
#include <zephyr/logging/log.h>
#include "time_sync.h"
#define LOG_MODULE_NAME timesync_central
LOG_MODULE_REGISTER(LOG_MODULE_NAME);
#define STACKSIZE CONFIG_BT_NUS_THREAD_STACK_SIZE
#define PRIORITY 7
#define DEVICE_NAME CONFIG_BT_DEVICE_NAME
#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
#define UART_BUF_SIZE CONFIG_BT_NUS_UART_BUFFER_SIZE
#define UART_WAIT_FOR_BUF_DELAY K_MSEC(50)
#define UART_WAIT_FOR_RX CONFIG_BT_NUS_UART_RX_WAIT_TIME
static K_SEM_DEFINE(ble_init_ok, 0, 1);
// static struct bt_conn *current_conn;
// static struct bt_conn *auth_conn;
static struct bt_conn *default_conn;
static struct bt_nus_client nus_client;
static struct bt_gatt_dm_cb discovery_cb;
static bool m_gpio_trigger_enabled;
static bool m_send_sync_pkt;
// static const struct bt_data ad[] = {
// BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
// BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
// };
// static const struct bt_data sd[] = {
// BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_NUS_VAL),
// };
static const nrfx_gpiote_t gpiote_inst = NRFX_GPIOTE_INSTANCE(NRF_DT_GPIOTE_INST(DT_ALIAS(syncpin), gpios));
static const struct gpio_dt_spec syncpin = GPIO_DT_SPEC_GET(DT_ALIAS(syncpin), gpios);
static nrfx_gpiote_pin_t syncpin_absval;
#if defined(DPPI_PRESENT)
static uint8_t dppi_channel_syncpin;
#endif
static void button_changed(uint32_t button_state, uint32_t has_changed);
// static void connected(struct bt_conn *conn, uint8_t err)
// {
// char addr[BT_ADDR_LE_STR_LEN];
// if (err) {
// LOG_ERR("Connection failed (err %u)", err);
// return;
// }
// bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
// LOG_INF("Connected %s", addr);
// current_conn = bt_conn_ref(conn);
// }
// static void disconnected(struct bt_conn *conn, uint8_t reason)
// {
// char addr[BT_ADDR_LE_STR_LEN];
// bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
// LOG_INF("Disconnected: %s (reason %u)", addr, reason);
// if (auth_conn) {
// bt_conn_unref(auth_conn);
// auth_conn = NULL;
// }
// if (current_conn) {
// bt_conn_unref(current_conn);
// current_conn = NULL;
// }
// }
/* ========= 吞吐量统计相关变量 ========= */
static uint64_t tp_window_start_ticks;
static uint32_t tp_bytes_in_window;
static bool tp_inited;
/* 每次收到数据时更新吞吐量统计 */
static void throughput_update(uint16_t len)
{
uint64_t now_ticks = ts_timestamp_get_ticks_u64();
uint64_t now_us = TIME_SYNC_TIMESTAMP_TO_USEC(now_ticks);
if (!tp_inited) {
tp_inited = true;
tp_window_start_ticks = now_ticks;
tp_bytes_in_window = 0;
return;
}
tp_bytes_in_window += len;
uint64_t start_us = TIME_SYNC_TIMESTAMP_TO_USEC(tp_window_start_ticks);
uint64_t delta_us = now_us - start_us;
/* 满足约 1 秒窗口就打印一次吞吐量 */
if (delta_us >= 1000000ULL) {
/* bits per second = bytes * 8 / (delta_s) */
uint64_t bps = (uint64_t)tp_bytes_in_window * 8ULL * 1000000ULL / delta_us;
/* 转成 kbps,保留 3 位小数(整数 + 小数部分) */
uint32_t kbps_int = (uint32_t)(bps / 1000ULL);
uint32_t kbps_frac = (uint32_t)(bps % 1000ULL);
uint32_t now_ms = (uint32_t)(now_us / 1000ULL);
printk("[TP] t=%u ms, bytes=%u, tp=%u.%03u kbps\n",
now_ms,
tp_bytes_in_window,
kbps_int, kbps_frac);
/* 开启下一个 1 秒窗口 */
tp_bytes_in_window = 0;
tp_window_start_ticks = now_ticks;
}
}
static void exchange_func(struct bt_conn *conn, uint8_t err, struct bt_gatt_exchange_params *params)
{
if (!err) {
LOG_INF("MTU exchange done");
} else {
LOG_WRN("MTU exchange failed (err %" PRIu8 ")", err);
}
}
static void connected(struct bt_conn *conn, uint8_t conn_err)
{
char addr[BT_ADDR_LE_STR_LEN];
int err;
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (conn_err) {
LOG_INF("Failed to connect to %s (err 0x%02x)", addr, conn_err);
if (default_conn == conn) {
bt_conn_unref(default_conn);
default_conn = NULL;
bt_scan_start(BT_SCAN_TYPE_SCAN_ACTIVE);
}
return;
}
LOG_INF("Connected: %s", addr);
default_conn = bt_conn_ref(conn);
static struct bt_gatt_exchange_params exchange_params;
exchange_params.func = exchange_func;
err = bt_gatt_exchange_mtu(conn, &exchange_params);
if (err) {
LOG_WRN("MTU exchange failed (err %d)", err);
}
/* 尝试安全加密并发现 NUS 服务 */
// err = bt_conn_set_security(conn, BT_SECURITY_L2);
// if (err) {
// LOG_WRN("Failed to set security (err %d)", err);
bt_gatt_dm_start(conn, BT_UUID_NUS_SERVICE, &discovery_cb, &nus_client);
// }
err = bt_scan_stop();
if ((!err) && (err != -EALREADY)) {
LOG_ERR("Stop LE scan failed (err %d)", err);
}
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
LOG_INF("Disconnected: %s (reason 0x%02x)", addr, reason);
if (default_conn) {
bt_conn_unref(default_conn);
default_conn = NULL;
}
bt_scan_start(BT_SCAN_TYPE_SCAN_ACTIVE);
}
static void conn_param_updated(struct bt_conn *conn,
uint16_t interval,
uint16_t latency,
uint16_t timeout)
{
printk("Conn params: interval=%.2f ms, latency=%u, timeout=%u ms\n",
interval * 1.25, latency, timeout * 10);
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
.le_param_updated = conn_param_updated,
};
static void scan_filter_match(struct bt_scan_device_info *device_info,
struct bt_scan_filter_match *filter_match,
bool connectable)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(device_info->recv_info->addr, addr, sizeof(addr));
LOG_INF("Found target device: %s", addr);
}
static void scan_connecting(struct bt_scan_device_info *device_info,
struct bt_conn *conn)
{
default_conn = bt_conn_ref(conn);
}
static void scan_connecting_error(struct bt_scan_device_info *device_info)
{
LOG_WRN("Connecting failed");
}
BT_SCAN_CB_INIT(scan_cb, scan_filter_match, NULL,
scan_connecting_error, scan_connecting);
static int scan_init(void)
{
int err;
struct bt_scan_init_param scan_init = {
.connect_if_match = 1,
};
bt_scan_init(&scan_init);
bt_scan_cb_register(&scan_cb);
err = bt_scan_filter_add(BT_SCAN_FILTER_TYPE_UUID, BT_UUID_NUS_SERVICE);
if (err) {
LOG_ERR("Scan filter add failed (err %d)", err);
return err;
}
err = bt_scan_filter_enable(BT_SCAN_UUID_FILTER, false);
if (err) {
LOG_ERR("Scan filter enable failed (err %d)", err);
return err;
}
LOG_INF("Scan initialized");
return 0;
}
/* GATT discovery callbacks */
static void discovery_complete(struct bt_gatt_dm *dm, void *context)
{
struct bt_nus_client *nus = context;
LOG_INF("Service discovery completed");
bt_gatt_dm_data_print(dm);
bt_nus_handles_assign(dm, nus);
bt_nus_subscribe_receive(nus);
bt_gatt_dm_data_release(dm);
}
static void discovery_service_not_found(struct bt_conn *conn, void *context)
{
LOG_INF("Service not found");
}
static void discovery_error(struct bt_conn *conn, int err, void *context)
{
LOG_WRN("Error while discovering GATT database: (%d)", err);
}
static struct bt_gatt_dm_cb discovery_cb = {
.completed = discovery_complete,
.service_not_found = discovery_service_not_found,
.error_found = discovery_error,
};
void error(void)
{
dk_set_leds_state(DK_ALL_LEDS_MSK, DK_NO_LEDS_MSK);
while (true) {
/* Spin for ever */
k_sleep(K_MSEC(1000));
}
}
static void configure_gpio(void)
{
int err;
err = dk_buttons_init(button_changed);
if (err) {
LOG_ERR("Cannot init buttons (err: %d)", err);
}
err = dk_leds_init();
if (err) {
LOG_ERR("Cannot init LEDs (err: %d)", err);
}
}
static void ts_gpio_trigger_enable(void)
{
uint64_t time_now_ticks;
uint32_t time_now_msec;
uint32_t time_target;
int err;
if (m_gpio_trigger_enabled) {
return;
}
// Round up to nearest second to next 1000 ms to start toggling.
// If the receiver has received a valid sync packet within this time, the GPIO toggling polarity will be the same.
time_now_ticks = ts_timestamp_get_ticks_u64();
time_now_msec = TIME_SYNC_TIMESTAMP_TO_USEC(time_now_ticks) / 1000;
time_target = TIME_SYNC_MSEC_TO_TICK(time_now_msec) + (1000 * 2);
time_target = (time_target / 1000) * 1000;
#if defined(DPPI_PRESENT)
err = ts_set_trigger(time_target, dppi_channel_syncpin);
__ASSERT_NO_MSG(err == 0);
#else
err = ts_set_trigger(time_target, nrfx_gpiote_out_task_address_get(&gpiote_inst, syncpin_absval));
__ASSERT_NO_MSG(err == 0);
#endif
nrfx_gpiote_set_task_trigger(&gpiote_inst, syncpin_absval);
m_gpio_trigger_enabled = true;
}
static void ts_gpio_trigger_disable(void)
{
m_gpio_trigger_enabled = false;
}
static void ts_event_handler(const ts_evt_t* evt)
{
switch (evt->type)
{
case TS_EVT_SYNCHRONIZED:
ts_gpio_trigger_enable();
LOG_INF("TS_EVT_SYNCHRONIZED");
break;
case TS_EVT_DESYNCHRONIZED:
ts_gpio_trigger_disable();
LOG_INF("TS_EVT_DESYNCHRONIZED");
break;
case TS_EVT_TRIGGERED:
if (m_gpio_trigger_enabled)
{
uint32_t tick_target;
int err;
/* Small increments here are more sensitive to drift.
* That is, an update from the timing transmitter that causes a jump larger than the
* chosen increment, risk having a trigger target_tick that is in the past.
*/
tick_target = evt->params.triggered.tick_target + 2;
#if defined(DPPI_PRESENT)
err = ts_set_trigger(tick_target, dppi_channel_syncpin);
__ASSERT_NO_MSG(err == 0);
#else
err = ts_set_trigger(tick_target, nrfx_gpiote_out_task_address_get(&gpiote_inst, syncpin_absval));
__ASSERT_NO_MSG(err == 0);
#endif
}
else
{
// Ensure pin is low when triggering is stopped
nrfx_gpiote_clr_task_trigger(&gpiote_inst, syncpin_absval);
}
break;
default:
__ASSERT_NO_MSG(false);
break;
}
}
static void button_changed(uint32_t button_state, uint32_t has_changed)
{
uint32_t buttons = button_state & has_changed;
int err;
if (buttons & DK_BTN1_MSK) {
if (m_send_sync_pkt) {
m_send_sync_pkt = false;
m_gpio_trigger_enabled = false;
err = ts_tx_stop();
__ASSERT_NO_MSG(err == 0);
dk_set_led_off(DK_LED1);
LOG_INF("Stopping sync beacon transmission!");
}
else {
m_send_sync_pkt = true;
err = ts_tx_start(TIME_SYNC_FREQ_AUTO);
__ASSERT_NO_MSG(err == 0);
dk_set_led_on(DK_LED1);
ts_gpio_trigger_enable();
LOG_INF("Starting sync beacon transmission!");
}
}
}
static void configure_sync_timer(void)
{
int err;
err = ts_init(ts_event_handler);
__ASSERT_NO_MSG(err == 0);
ts_rf_config_t rf_config = {
.rf_chn = 80,
.rf_addr = { 0xDE, 0xAD, 0xBE, 0xEF, 0x19 }
};
err = ts_enable(&rf_config);
__ASSERT_NO_MSG(err == 0);
}
static nrfx_gpiote_pin_t pin_absval_get(const struct gpio_dt_spec *gpio_spec)
{
if (gpio_spec->port == DEVICE_DT_GET(DT_NODELABEL(gpio0))) {
return NRF_GPIO_PIN_MAP(0, gpio_spec->pin);
}
#if DT_NODE_EXISTS(DT_NODELABEL(gpio1))
if (gpio_spec->port == DEVICE_DT_GET(DT_NODELABEL(gpio1))) {
return NRF_GPIO_PIN_MAP(1, gpio_spec->pin);
}
#endif
__ASSERT(false, "Port could not be determined");
return 0;
}
static void configure_debug_gpio(void)
{
nrfx_err_t nrfx_err;
int err;
nrfx_gpiote_output_config_t gpiote_cfg = {
.drive = NRF_GPIO_PIN_S0S1,
.input_connect = NRF_GPIO_PIN_INPUT_DISCONNECT,
.pull = NRF_GPIO_PIN_NOPULL,
};
nrfx_gpiote_task_config_t task_cfg = {
.polarity = NRF_GPIOTE_POLARITY_TOGGLE,
.init_val = NRF_GPIOTE_INITIAL_VALUE_LOW,
};
syncpin_absval = pin_absval_get(&syncpin);
err = gpio_pin_configure_dt(&syncpin, GPIO_OUTPUT_LOW | syncpin.dt_flags);
__ASSERT_NO_MSG(err == 0);
if (!nrfx_gpiote_init_check(&gpiote_inst)) {
nrfx_err = nrfx_gpiote_init(&gpiote_inst, 5);
__ASSERT_NO_MSG(nrfx_err == NRFX_SUCCESS);
}
nrfx_err = nrfx_gpiote_channel_alloc(&gpiote_inst, &task_cfg.task_ch);
__ASSERT_NO_MSG(nrfx_err == NRFX_SUCCESS);
nrfx_err = nrfx_gpiote_output_configure(&gpiote_inst, syncpin_absval, &gpiote_cfg, &task_cfg);
__ASSERT_NO_MSG(nrfx_err == NRFX_SUCCESS);
nrfx_gpiote_out_task_enable(&gpiote_inst, syncpin_absval);
#if defined(DPPI_PRESENT)
nrfx_err = nrfx_dppi_channel_alloc(&dppi_channel_syncpin);
__ASSERT_NO_MSG(nrfx_err == NRFX_SUCCESS);
nrf_gpiote_subscribe_set(
NRF_GPIOTE, nrfx_gpiote_out_task_get(syncpin_absval), dppi_channel_syncpin);
nrf_dppi_channels_enable(NRF_DPPIC_NS, BIT(dppi_channel_syncpin));
#endif
}
static uint8_t nus_received_cb(struct bt_nus_client *nus, const uint8_t *data, uint16_t len)
{
/* 打印接收到的数据内容(以字符串形式) */
throughput_update(len);
char str_buf[256];
if (len >= sizeof(str_buf)) {
len = sizeof(str_buf) - 1;
}
memcpy(str_buf, data, len);
str_buf[len] = '\0';
// LOG_INF("NUS received (%d bytes): %s", len, str_buf);
// printk("[NUS RX] %s\n", str_buf);
return BT_GATT_ITER_CONTINUE;
}
static void nus_sent_cb(struct bt_nus_client *nus, uint8_t err,
const uint8_t *const data, uint16_t len)
{
LOG_INF("Data sent, err %u, len %u", err, len);
}
/* 定义初始化参数结构体 */
static const struct bt_nus_client_init_param nus_init = {
.cb = {
.received = nus_received_cb,
.sent = nus_sent_cb,
}
};
// int main(void)
// {
// int err = 0;
// configure_gpio();
// configure_debug_gpio();
// configure_sync_timer();
// err = bt_enable(NULL);
// if (err) {
// error();
// }
// LOG_INF("Bluetooth initialized");
// k_sem_give(&ble_init_ok);
// if (IS_ENABLED(CONFIG_SETTINGS)) {
// settings_load();
// }
// // err = bt_nus_init(&nus_cb);
// // if (err) {
// // LOG_ERR("Failed to initialize UART service (err: %d)", err);
// // return 0;
// // }
// // err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad), sd,
// // ARRAY_SIZE(sd));
// // if (err) {
// // LOG_ERR("Advertising failed to start (err %d)", err);
// // return 0;
// // }
// }
int main(void)
{
int err;
configure_gpio();
configure_debug_gpio();
configure_sync_timer();
err = bt_enable(NULL);
if (err) {
error();
}
LOG_INF("Bluetooth initialized");
if (IS_ENABLED(CONFIG_SETTINGS)) {
settings_load();
}
err = bt_nus_client_init(&nus_client, &nus_init);
if (err) {
LOG_ERR("NUS Client init failed (err %d)", err);
}
err = scan_init();
if (err) {
LOG_ERR("Scan init failed (err %d)", err);
}
err = bt_scan_start(BT_SCAN_TYPE_SCAN_ACTIVE);
if (err) {
LOG_ERR("Scan start failed (err %d)", err);
}
LOG_INF("Scanning for peripheral NUS devices...");
/* 保留 Time Sync Shell 功能 */
// while (1) {
// uint64_t t = ts_timestamp_get_ticks_u64();
// uint32_t ms = TIME_SYNC_TIMESTAMP_TO_USEC(t) / 1000;
// printk("Sync time: %u ms\n", ms);
// k_sleep(K_SECONDS(1));
// }
}
#if CONFIG_SHELL
static int cmd_tx_toggle(const struct shell *sh, size_t argc, char **argv)
{
int err;
if (m_send_sync_pkt) {
m_send_sync_pkt = false;
m_gpio_trigger_enabled = false;
err = ts_tx_stop();
__ASSERT_NO_MSG(err == 0);
dk_set_led_off(DK_LED1);
shell_print(sh, "Stopping sync beacon transmission!");
}
else {
m_send_sync_pkt = true;
err = ts_tx_start(TIME_SYNC_FREQ_AUTO);
__ASSERT_NO_MSG(err == 0);
dk_set_led_on(DK_LED1);
ts_gpio_trigger_enable();
shell_print(sh, "Starting sync beacon transmission!");
}
return 0;
}
static int cmd_time_get(const struct shell *sh, size_t argc, char **argv)
{
uint64_t timestamp_ticks;
uint32_t timestamp_msec;
timestamp_ticks = ts_timestamp_get_ticks_u64();
timestamp_msec = TIME_SYNC_TIMESTAMP_TO_USEC(timestamp_ticks) / 1000;
shell_print(sh, "%d msec", timestamp_msec);
return 0;
}
SHELL_CMD_ARG_REGISTER(tx_toggle, NULL, "Time sync TX toggle",
cmd_tx_toggle, 0, 0);
SHELL_CMD_ARG_REGISTER(time_get, NULL, "Time sync time get",
cmd_time_get, 0, 0);
#endif /* CONFIG_SHELL*/
The time sync function is:
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "time_sync.h"
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <nrfx.h>
#include <nrfx_ppi.h>
#include <hal/nrf_egu.h>
#include <hal/nrf_power.h>
#include <hal/nrf_ppi.h>
#include <hal/nrf_radio.h>
#include <hal/nrf_timer.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/clock_control/nrf_clock_control.h>
#include <mpsl_timeslot.h>
#include <mpsl.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/ring_buffer.h>
#define LOG_MODULE_NAME timesync
LOG_MODULE_REGISTER(LOG_MODULE_NAME);
#if defined ( __CC_ARM )
#define TX_CHAIN_DELAY (699 - 235)
#elif defined ( __ICCARM__ )
#define TX_CHAIN_DELAY (703 - 235)
#elif defined ( __GNUC__ )
#define TX_CHAIN_DELAY (704 - 173)
#endif
#if TIME_SYNC_TIMER_MAX_VAL < 10000
#error Timer values below 10000 not supported
#endif
#define SYNC_PKT_QUEUE_LEN 3
#define TS_LEN_US (1000UL)
#define TX_LEN_EXTENSION_US (1000UL)
#define TS_SAFETY_MARGIN_US (200UL) /**< The timeslot activity should be finished with this much to spare. */
#define TS_EXTEND_MARGIN_US (400UL) /**< The timeslot activity should request an extension this long before end of timeslot. */
#if defined(CONFIG_SOC_SERIES_NRF53X)
#define TS_SWI_IRQn NRFX_CONCAT_3(SWI, CONFIG_TIMESYNC_SWI, _IRQn)
#define TS_EGU_IRQn NRFX_CONCAT_3(EGU, CONFIG_TIMESYNC_EGU, _IRQn)
#elif defined(CONFIG_SOC_SERIES_NRF52X)
#define TS_SWI_IRQn NRFX_CONCAT_3(SWI, CONFIG_TIMESYNC_SWI, NRFX_CONCAT_3(_EGU, CONFIG_TIMESYNC_SWI, _IRQn))
#define TS_EGU_IRQn NRFX_CONCAT_3(SWI, CONFIG_TIMESYNC_EGU, NRFX_CONCAT_3(_EGU, CONFIG_TIMESYNC_EGU, _IRQn))
#if CONFIG_TIMESYNC_SWI == CONFIG_TIMESYNC_EGU
#error Cannot use the same instance of EGU and SWI
#endif /* CONFIG_TIMESYNC_SWI == CONFIG_TIMESYNC_EGU */
#endif /* defined(CONFIG_SOC_SERIES_NRF53X) */
#define FREEWHEEL_TIMER NRFX_CONCAT_2(NRF_TIMER, CONFIG_TIMESYNC_FREEWHEEL_TIMER)
#define COUNTER_TIMER NRFX_CONCAT_2(NRF_TIMER, CONFIG_TIMESYNC_COUNTER_TIMER)
#define EGU_INST NRFX_CONCAT_2(NRF_EGU, CONFIG_TIMESYNC_EGU)
static void ppi_sync_timer_adjust_configure(bool shorten);
static void ppi_sync_timer_adjust_enable(void);
static void ppi_sync_timer_clear_configure(void);
static void ppi_sync_timer_clear_disable(void);
static void ppi_radio_rx_disable(void);
static void ppi_radio_rx_configure(void);
static void ppi_radio_tx_configure(void);
static int ppi_sync_trigger_configure(uint32_t ppi_endpoint);
struct {
uint8_t rf_chn;
uint8_t rf_addr[5];
nrf_ppi_channel_t ppi_chns[7];
nrf_ppi_channel_group_t ppi_chgs[2];
} m_params;
#if CONFIG_TIMESYNC_FREEWHEEL_TIMER == CONFIG_TIMESYNC_COUNTER_TIMER
#error Freewheel timer and counter cannot be the same
#endif
__packed struct sync_pkt {
int32_t timer_val;
int32_t rtc_val;
uint32_t counter_val;
};
BUILD_ASSERT(sizeof(struct sync_pkt) == 12);
static atomic_t m_timeslot_session_open;
static atomic_t m_pending_close;
static atomic_t m_pending_tx_stop;
static atomic_t m_blocked_cancelled_count;
static uint32_t m_total_timeslot_length = 0;
static uint32_t m_timeslot_distance = 0;
static uint32_t m_tx_slot_retry_count = 0;
static uint32_t m_usec_since_sync_packet = 0;
static uint32_t m_usec_since_tx_offset_calc = 0;
static atomic_t m_send_sync_pkt = false;
static atomic_t m_timer_update_in_progress = false;
static bool m_synchronized = false;
static volatile int64_t m_master_counter_diff = 0;
static atomic_t m_rcv_count = 0;
static atomic_t mp_curr_adj_pkt;
static atomic_t m_curr_adj_timer;
static atomic_t m_curr_adj_counter;
static ts_evt_handler_t m_callback;
static atomic_t m_tick_target;
static atomic_t m_sync_packet_count = 0;
static atomic_t m_used_packet_count = 0;
static atomic_t m_last_sync = 0;
static atomic_t m_prev_sync_pkt_timer;
static atomic_t m_prev_sync_pkt_counter;
static nrf_ppi_channel_t m_timestamp_trigger_ppi[2];
static bool m_timestamp_trigger_set = false;
static struct onoff_manager *clk_mgr;
static struct onoff_client clk_cli;
/* Simple circular buffer: no free, only take */
static uint8_t m_sync_pkt_ringbuf[10][sizeof(struct sync_pkt)];
static atomic_t m_sync_pkt_ringbuf_idx;
static volatile enum
{
RADIO_STATE_IDLE, /* Default state */
RADIO_STATE_RX, /* Waiting for packets */
RADIO_STATE_TX /* Trying to transmit packet */
} m_radio_state = RADIO_STATE_IDLE;
/**< This will be used when requesting the first timeslot or any time a timeslot is blocked or cancelled. */
static mpsl_timeslot_request_t m_timeslot_req_earliest = {
.request_type = MPSL_TIMESLOT_REQ_TYPE_EARLIEST,
.params.earliest.hfclk = MPSL_TIMESLOT_HFCLK_CFG_XTAL_GUARANTEED,
.params.earliest.priority = MPSL_TIMESLOT_PRIORITY_NORMAL,
.params.earliest.length_us = TS_LEN_US,
.params.earliest.timeout_us = 100000 /* Expect timeslot within 100 ms */
};
/**< This will be used at the end of each timeslot to request the next timeslot. */
static mpsl_timeslot_request_t m_timeslot_req_normal = {
.request_type = MPSL_TIMESLOT_REQ_TYPE_NORMAL,
.params.normal.hfclk = MPSL_TIMESLOT_HFCLK_CFG_XTAL_GUARANTEED,
.params.normal.priority = MPSL_TIMESLOT_PRIORITY_NORMAL,
.params.normal.distance_us = 0,
.params.normal.length_us = TS_LEN_US,
};
static mpsl_timeslot_signal_return_param_t signal_callback_return_param;
static mpsl_timeslot_session_id_t session_id;
/* Ring buffer for handling low priority signals outside of timeslot callback */
RING_BUF_DECLARE(callback_low_priority_ring_buf, 10);
static void timeslot_begin_handler(void);
static void timeslot_end_handler(void);
static void timeslot_radio_handler(void);
static void ppi_counter_timer_triggered_capture_configure(nrf_ppi_channel_t chn[2], uint32_t eep);
static bool sync_timer_offset_compensate(struct sync_pkt * p_pkt);
static struct sync_pkt * tx_buf_get(void);
static void increment_desync_timeout(uint32_t increment_usec);
ISR_DIRECT_DECLARE(swi_isr)
{
static const char *signal_type_str[] = {
"MPSL_TIMESLOT_SIGNAL_START",
"MPSL_TIMESLOT_SIGNAL_TIMER0",
"MPSL_TIMESLOT_SIGNAL_RADIO",
"MPSL_TIMESLOT_SIGNAL_EXTEND_FAILED",
"MPSL_TIMESLOT_SIGNAL_EXTEND_SUCCEEDED",
"MPSL_TIMESLOT_SIGNAL_BLOCKED",
"MPSL_TIMESLOT_SIGNAL_CANCELLED",
"MPSL_TIMESLOT_SIGNAL_SESSION_IDLE",
"MPSL_TIMESLOT_SIGNAL_INVALID_RETURN",
"MPSL_TIMESLOT_SIGNAL_SESSION_CLOSED",
"MPSL_TIMESLOT_SIGNAL_OVERSTAYED",
};
uint8_t signal_type = 0;
while (!ring_buf_is_empty(&callback_low_priority_ring_buf)) {
if (ring_buf_get(&callback_low_priority_ring_buf, &signal_type, 1) == 1) {
int err;
__ASSERT_NO_MSG(signal_type < ARRAY_SIZE(signal_type_str));
// LOG_INF("Signal: %s", signal_type_str[signal_type]);
if (signal_type != MPSL_TIMESLOT_SIGNAL_CANCELLED &&
signal_type != MPSL_TIMESLOT_SIGNAL_BLOCKED) {
LOG_INF("Signal: %s", signal_type_str[signal_type]);
}
switch (signal_type) {
case MPSL_TIMESLOT_SIGNAL_SESSION_IDLE:
err = mpsl_timeslot_session_close(session_id);
if (err) {
LOG_ERR("mpsl_timeslot_session_close=%d", err);
}
break;
case MPSL_TIMESLOT_SIGNAL_SESSION_CLOSED:
atomic_clear(&m_timeslot_session_open);
atomic_clear(&m_pending_close);
break;
case MPSL_TIMESLOT_SIGNAL_BLOCKED:
case MPSL_TIMESLOT_SIGNAL_CANCELLED:
if (!m_pending_close)
{
int err;
/* This is typically caused by a conflict with an active BLE session. */
if (m_send_sync_pkt && m_tx_slot_retry_count < 3)
{
/* Try a few times to skip the next scheduled event, else get earliest possible. */
++m_tx_slot_retry_count;
m_timeslot_req_normal.params.normal.distance_us = m_timeslot_distance * 2;
err = mpsl_timeslot_request(
session_id,
&m_timeslot_req_normal);
if (err) {
LOG_ERR("mpsl_timeslot_request(earliest)=%d", err);
}
}
else
{
err = mpsl_timeslot_request(
session_id,
&m_timeslot_req_earliest);
if (err) {
LOG_ERR("mpsl_timeslot_request(earliest)=%d", err);
}
}
atomic_add(&m_blocked_cancelled_count, 1);
}
break;
default:
__ASSERT_NO_MSG(false);
break;
}
}
}
return 1;
}
ISR_DIRECT_DECLARE(egu_isr)
{
if (nrf_egu_event_check(EGU_INST, NRF_EGU_EVENT_TRIGGERED0))
{
nrf_egu_event_clear(EGU_INST, NRF_EGU_EVENT_TRIGGERED0);
m_master_counter_diff = ((struct sync_pkt *) mp_curr_adj_pkt)->counter_val - m_curr_adj_counter;
atomic_add(&m_used_packet_count, 1);
atomic_set(&m_last_sync, (m_curr_adj_counter - m_master_counter_diff));
nrf_timer_cc_set(FREEWHEEL_TIMER, NRF_TIMER_CC_CHANNEL0, TIME_SYNC_TIMER_MAX_VAL);
nrf_timer_cc_set(FREEWHEEL_TIMER, NRF_TIMER_CC_CHANNEL2, 0);
atomic_clear(&m_timer_update_in_progress);
if (!m_synchronized)
{
m_synchronized = true;
if (m_callback)
{
ts_evt_t evt =
{
.type = TS_EVT_SYNCHRONIZED,
};
m_callback(&evt);
}
}
}
if (nrf_egu_event_check(EGU_INST, NRF_EGU_EVENT_TRIGGERED2))
{
nrf_egu_event_clear(EGU_INST, NRF_EGU_EVENT_TRIGGERED2);
nrf_ppi_channel_disable(NRF_PPI, m_params.ppi_chns[4]);
if (m_callback)
{
ts_evt_t trigger_evt =
{
.type = TS_EVT_TRIGGERED,
.params.triggered.tick_target = m_tick_target,
.params.triggered.last_sync = m_last_sync,
.params.triggered.sync_packet_count = m_sync_packet_count,
.params.triggered.used_packet_count = m_used_packet_count,
};
m_callback(&trigger_evt);
}
}
if (nrf_egu_event_check(EGU_INST, NRF_EGU_EVENT_TRIGGERED3))
{
nrf_egu_event_clear(EGU_INST, NRF_EGU_EVENT_TRIGGERED3);
if (m_synchronized)
{
m_synchronized = false;
if (m_callback)
{
ts_evt_t evt =
{
.type = TS_EVT_DESYNCHRONIZED,
};
m_callback(&evt);
}
}
}
if (nrf_egu_event_check(EGU_INST, NRF_EGU_EVENT_TRIGGERED4))
{
nrf_egu_event_clear(EGU_INST, NRF_EGU_EVENT_TRIGGERED4);
// TODO: Remove this event, as it is not currently used
}
if (nrf_egu_event_check(EGU_INST, NRF_EGU_EVENT_TRIGGERED5))
{
uint64_t timestamp;
uint32_t counter_val;
uint32_t timer_val_cc5;
uint32_t timer_val_cc2;
nrf_egu_event_clear(EGU_INST, NRF_EGU_EVENT_TRIGGERED5);
counter_val = nrf_timer_cc_get(COUNTER_TIMER, NRF_TIMER_CC_CHANNEL5);
timer_val_cc2 = nrf_timer_cc_get(FREEWHEEL_TIMER, NRF_TIMER_CC_CHANNEL2);
timer_val_cc5 = nrf_timer_cc_get(FREEWHEEL_TIMER, NRF_TIMER_CC_CHANNEL5);
if ((timer_val_cc5 <= 5) || (timer_val_cc5 == TIME_SYNC_TIMER_MAX_VAL) ||
(timer_val_cc2 != 0 && timer_val_cc5 == timer_val_cc2))
{
// Check if counter increment was caught
nrf_timer_task_trigger(COUNTER_TIMER, NRF_TIMER_TASK_CAPTURE5);
if (nrf_timer_cc_get(COUNTER_TIMER, NRF_TIMER_CC_CHANNEL5) != counter_val)
{
counter_val = nrf_timer_cc_get(COUNTER_TIMER, NRF_TIMER_CC_CHANNEL5);
}
}
timestamp = counter_val;
timestamp += m_master_counter_diff;
timestamp *= TIME_SYNC_TIMER_MAX_VAL;
timestamp += timer_val_cc5;
if (m_callback)
{
ts_evt_t evt =
{
.type = TS_EVT_TIMESTAMP,
.params.timestamp = timestamp,
};
m_callback(&evt);
}
}
return 1;
}
static mpsl_timeslot_signal_return_param_t *mpsl_timeslot_callback(
mpsl_timeslot_session_id_t session_id,
uint32_t signal_type)
{
(void) session_id; /* unused parameter */
uint8_t input_data = (uint8_t)signal_type;
uint32_t input_data_len;
mpsl_timeslot_signal_return_param_t *p_ret_val = NULL;
switch (signal_type) {
case MPSL_TIMESLOT_SIGNAL_START:
nrf_timer_cc_set(NRF_TIMER0, NRF_TIMER_CC_CHANNEL0,
(TS_LEN_US - TS_SAFETY_MARGIN_US));
nrf_timer_cc_set(NRF_TIMER0, NRF_TIMER_CC_CHANNEL1,
(TS_LEN_US - TS_EXTEND_MARGIN_US));
if (m_send_sync_pkt) {
nrf_timer_int_enable(NRF_TIMER0, NRF_TIMER_INT_COMPARE0_MASK);
} else {
nrf_timer_int_enable(NRF_TIMER0, NRF_TIMER_INT_COMPARE0_MASK | NRF_TIMER_INT_COMPARE1_MASK);
}
nrf_timer_event_clear(NRF_TIMER0, NRF_TIMER_EVENT_COMPARE0);
nrf_timer_event_clear(NRF_TIMER0, NRF_TIMER_EVENT_COMPARE1);
nrf_timer_task_trigger(NRF_TIMER0, NRF_TIMER_TASK_START);
timeslot_begin_handler();
break;
case MPSL_TIMESLOT_SIGNAL_RADIO:
timeslot_radio_handler();
break;
case MPSL_TIMESLOT_SIGNAL_TIMER0:
if (nrf_timer_event_check(NRF_TIMER0, NRF_TIMER_EVENT_COMPARE0)) {
nrf_timer_event_clear(NRF_TIMER0, NRF_TIMER_EVENT_COMPARE0);
nrf_timer_int_disable(NRF_TIMER0, NRF_TIMER_INT_COMPARE0_MASK);
// This is the "timeslot is about to end" timeout
// Schedule next timeslot
if (m_send_sync_pkt)
{
m_timeslot_req_normal.params.normal.distance_us = m_timeslot_distance;
signal_callback_return_param.params.request.p_next =
&m_timeslot_req_normal;
signal_callback_return_param.callback_action =
MPSL_TIMESLOT_SIGNAL_ACTION_REQUEST;
}
else
{
signal_callback_return_param.params.request.p_next =
&m_timeslot_req_earliest;
signal_callback_return_param.callback_action =
MPSL_TIMESLOT_SIGNAL_ACTION_REQUEST;
}
timeslot_end_handler();
p_ret_val = &signal_callback_return_param;
}
if (nrf_timer_event_check(NRF_TIMER0, NRF_TIMER_EVENT_COMPARE1)) {
nrf_timer_event_clear(NRF_TIMER0, NRF_TIMER_EVENT_COMPARE1);
// This is the "try to extend timeslot" timeout
if (m_total_timeslot_length < (128000000UL - 5000UL - TX_LEN_EXTENSION_US) && !m_send_sync_pkt)
{
// Request timeslot extension if total length does not exceed 128 seconds
signal_callback_return_param.params.request.p_next =
&m_timeslot_req_normal;
signal_callback_return_param.callback_action =
MPSL_TIMESLOT_SIGNAL_ACTION_EXTEND;
}
else if (!m_send_sync_pkt)
{
signal_callback_return_param.params.request.p_next =
&m_timeslot_req_earliest;
signal_callback_return_param.callback_action =
MPSL_TIMESLOT_SIGNAL_ACTION_REQUEST;
}
}
break;
case MPSL_TIMESLOT_SIGNAL_EXTEND_SUCCEEDED:
// Extension succeeded: update timer
{
uint32_t cc0_val;
uint32_t cc1_val;
cc0_val = nrf_timer_cc_get(NRF_TIMER0, NRF_TIMER_CC_CHANNEL0);
cc1_val = nrf_timer_cc_get(NRF_TIMER0, NRF_TIMER_CC_CHANNEL1);
nrf_timer_cc_set(NRF_TIMER0, NRF_TIMER_CC_CHANNEL0, cc0_val + TX_LEN_EXTENSION_US - 25);
nrf_timer_cc_set(NRF_TIMER0, NRF_TIMER_CC_CHANNEL1, cc1_val + TX_LEN_EXTENSION_US - 25);
}
// Keep track of total length
m_total_timeslot_length += TX_LEN_EXTENSION_US;
increment_desync_timeout(TX_LEN_EXTENSION_US);
break;
case MPSL_TIMESLOT_SIGNAL_EXTEND_FAILED:
timeslot_end_handler();
increment_desync_timeout(TX_LEN_EXTENSION_US);
p_ret_val = &signal_callback_return_param;
signal_callback_return_param.params.request.p_next =
&m_timeslot_req_earliest;
signal_callback_return_param.callback_action =
MPSL_TIMESLOT_SIGNAL_ACTION_REQUEST;
break;
case MPSL_TIMESLOT_SIGNAL_SESSION_IDLE:
case MPSL_TIMESLOT_SIGNAL_SESSION_CLOSED:
case MPSL_TIMESLOT_SIGNAL_BLOCKED:
case MPSL_TIMESLOT_SIGNAL_CANCELLED:
input_data_len = ring_buf_put(&callback_low_priority_ring_buf, &input_data, 1);
if (input_data_len != 1) {
LOG_ERR("Full ring buffer, enqueue data with length %d", input_data_len);
k_oops();
}
break;
default:
LOG_ERR("unexpected signal: %u", signal_type);
k_oops();
break;
}
#if defined(CONFIG_SOC_SERIES_NRF53X)
NVIC_SetPendingIRQ(TS_SWI_IRQn);
#elif defined(CONFIG_SOC_SERIES_NRF52X)
NVIC_SetPendingIRQ(TS_SWI_IRQn);
#endif
if (p_ret_val == NULL) {
signal_callback_return_param.callback_action =
MPSL_TIMESLOT_SIGNAL_ACTION_NONE;
signal_callback_return_param.params.request.p_next =
&m_timeslot_req_normal;
p_ret_val = &signal_callback_return_param;
}
return p_ret_val;
}
static void increment_desync_timeout(uint32_t increment_usec)
{
if (m_send_sync_pkt)
{
// No desync as transmitter
return;
}
m_usec_since_sync_packet += increment_usec;
if (m_usec_since_sync_packet >= TIME_SYNC_DESYNC_TIMEOUT)
{
nrf_egu_task_trigger(EGU_INST, NRF_EGU_TASK_TRIGGER3);
m_usec_since_sync_packet = 0; // Reset to suppress needless irq generation
}
}
static struct sync_pkt * tx_buf_get(void)
{
uint32_t idx;
idx = atomic_inc(&m_sync_pkt_ringbuf_idx) % ARRAY_SIZE(m_sync_pkt_ringbuf);
return (struct sync_pkt *) m_sync_pkt_ringbuf[idx];
}
static void update_radio_parameters(struct sync_pkt * p_pkt)
{
const nrf_radio_packet_conf_t pkt_conf = {
.lflen = 0,
.s0len = 0,
.s1len = 0,
.maxlen = sizeof(struct sync_pkt),
.statlen = sizeof(struct sync_pkt),
.balen = 4,
.big_endian = true,
.whiteen = true,
#if defined(RADIO_PCNF0_PLEN_Msk)
.plen = NRF_RADIO_PREAMBLE_LENGTH_16BIT,
#endif
#if defined(RADIO_PCNF0_CRCINC_Msk)
.crcinc = false,
#endif
#if defined(RADIO_PCNF0_S1INCL_Msk)
.s1incl = false,
#endif
#if defined(RADIO_PCNF0_CILEN_Msk)
.cilen = 0,
#endif
#if defined(RADIO_PCNF0_TERMLEN_Msk)
.termlen = 0,
#endif
};
nrf_radio_power_set(NRF_RADIO, true);
nrf_radio_txpower_set(NRF_RADIO, NRF_RADIO_TXPOWER_0DBM);
nrf_radio_mode_set(NRF_RADIO, NRF_RADIO_MODE_NRF_2MBIT);
nrf_radio_modecnf0_set(NRF_RADIO, true, RADIO_MODECNF0_DTX_Center);
nrf_radio_crc_configure(NRF_RADIO, 3, NRF_RADIO_CRC_ADDR_INCLUDE, 0x11021UL);
nrf_radio_crcinit_set(NRF_RADIO, 0xFFFFFFUL);
nrf_radio_packet_configure(NRF_RADIO, &pkt_conf);
nrf_radio_packetptr_set(NRF_RADIO, p_pkt);
nrf_radio_base0_set(NRF_RADIO,
m_params.rf_addr[1] << 24 | m_params.rf_addr[2] << 16 |
m_params.rf_addr[3] << 8 | m_params.rf_addr[4]);
nrf_radio_prefix0_set(NRF_RADIO, m_params.rf_addr[0]);
nrf_radio_txaddress_set(NRF_RADIO, 0);
nrf_radio_rxaddresses_set(NRF_RADIO, BIT(0));
nrf_radio_frequency_set(NRF_RADIO, 2400 + m_params.rf_chn);
nrf_radio_event_clear(NRF_RADIO, NRF_RADIO_EVENT_END);
nrf_radio_int_disable(NRF_RADIO, 0xFFFFFFFF);
nrf_radio_int_enable(NRF_RADIO, NRF_RADIO_INT_END_MASK);
NVIC_SetPriority(RADIO_IRQn, MPSL_HIGH_IRQ_PRIORITY);
NVIC_ClearPendingIRQ(RADIO_IRQn);
NVIC_EnableIRQ(RADIO_IRQn);
}
void timeslot_end_handler(void)
{
nrf_radio_task_trigger(NRF_RADIO, NRF_RADIO_TASK_DISABLE);
nrf_radio_int_disable(NRF_RADIO, 0xFFFFFFFF);
ppi_radio_rx_disable();
nrf_radio_power_set(NRF_RADIO, false);
NVIC_DisableIRQ(RADIO_IRQn);
NVIC_ClearPendingIRQ(RADIO_IRQn);
m_radio_state = RADIO_STATE_IDLE;
}
void timeslot_begin_handler(void)
{
struct sync_pkt * p_pkt;
m_total_timeslot_length = 0;
m_tx_slot_retry_count = 0;
if (m_pending_tx_stop)
{
atomic_clear(&m_send_sync_pkt);
atomic_clear(&m_pending_tx_stop);
m_timeslot_distance = 0;
m_synchronized = false;
}
if (!m_send_sync_pkt)
{
if (m_radio_state != RADIO_STATE_RX ||
nrf_radio_state_get(NRF_RADIO) != NRF_RADIO_STATE_RX)
{
p_pkt = tx_buf_get();
update_radio_parameters(p_pkt);
nrf_radio_shorts_enable(NRF_RADIO, NRF_RADIO_SHORT_READY_START_MASK);
nrf_radio_task_trigger(NRF_RADIO, NRF_RADIO_TASK_RXEN);
ppi_radio_rx_configure();
m_radio_state = RADIO_STATE_RX;
}
return;
}
if (m_radio_state == RADIO_STATE_RX)
{
// Packet transmission has now started (state change from RX).
nrf_radio_event_clear(NRF_RADIO, NRF_RADIO_EVENT_DISABLED);
nrf_radio_task_trigger(NRF_RADIO, NRF_RADIO_TASK_DISABLE);
while (!nrf_radio_event_check(NRF_RADIO, NRF_RADIO_EVENT_DISABLED)) {
__NOP();
}
}
p_pkt = tx_buf_get();
ppi_radio_tx_configure();
update_radio_parameters(p_pkt);
nrf_radio_shorts_enable(NRF_RADIO,
NRF_RADIO_SHORT_READY_START_MASK | NRF_RADIO_SHORT_END_DISABLE_MASK);
nrf_radio_task_trigger(NRF_RADIO, NRF_RADIO_TASK_TXEN);
while (!nrf_radio_event_check(NRF_RADIO, NRF_RADIO_EVENT_READY)) {
// PPI is used to trigger sync timer capture when radio is ready
// Radio will automatically start transmitting once ready, so the captured timer value must be copied into radio packet buffer ASAP
__NOP();
}
p_pkt->timer_val = nrf_timer_cc_get(FREEWHEEL_TIMER, NRF_TIMER_CC_CHANNEL1);
p_pkt->counter_val = nrf_timer_cc_get(COUNTER_TIMER, NRF_TIMER_CC_CHANNEL1);
p_pkt->counter_val += m_master_counter_diff;
atomic_add(&m_sync_packet_count, 1);
m_radio_state = RADIO_STATE_TX;
}
static void timeslot_radio_handler(void)
{
nrf_radio_event_clear(NRF_RADIO, NRF_RADIO_EVENT_END);
if (m_radio_state == RADIO_STATE_RX && nrf_radio_crc_status_check(NRF_RADIO)) {
struct sync_pkt * p_pkt;
bool adjustment_procedure_started;
// LOG_INF("RX");
p_pkt = (struct sync_pkt *) nrf_radio_packetptr_get(NRF_RADIO);
if (p_pkt->timer_val <= 2)
{
// Ignore packet due to potential missed counter increment
// TODO: See if this can be tightened
goto resume_radio_rx;
}
adjustment_procedure_started = sync_timer_offset_compensate(p_pkt);
atomic_add(&m_sync_packet_count, 1);
if (adjustment_procedure_started)
{
atomic_set(&m_prev_sync_pkt_timer, p_pkt->timer_val); // TODO: Necessary?
atomic_set(&m_prev_sync_pkt_counter, p_pkt->counter_val); // TODO: Necessary?
p_pkt = tx_buf_get();
nrf_radio_packetptr_set(NRF_RADIO, p_pkt);
m_usec_since_sync_packet = 0;
}
atomic_add(&m_rcv_count, 1);
}
resume_radio_rx:
nrf_radio_task_trigger(NRF_RADIO, NRF_RADIO_TASK_START);
}
static void timestamp_counter_start(void)
{
// COUNTER_TIMER is used in counter mode to count the number of sync timer overflows/resets
// When timestamp API is used, the number of overflows/resets + current value of sync timer must be added up to give accurate timestamp information
nrf_timer_task_trigger(COUNTER_TIMER, NRF_TIMER_TASK_STOP);
nrf_timer_task_trigger(COUNTER_TIMER, NRF_TIMER_TASK_CLEAR);
nrf_timer_prescaler_set(COUNTER_TIMER, NRF_TIMER_FREQ_16MHz);
nrf_timer_mode_set(COUNTER_TIMER, NRF_TIMER_MODE_COUNTER);
nrf_timer_bit_width_set(COUNTER_TIMER, NRF_TIMER_BIT_WIDTH_32);
nrf_timer_task_trigger(COUNTER_TIMER, NRF_TIMER_TASK_START);
}
static void sync_timer_start(void)
{
// FREEWHEEL_TIMER (NRF_TIMER) is the always-running sync timer
// The timing master never adjusts this timer
// The timing slave(s) adjusts this timer whenever a sync packet is received and the logic determines that there is
nrf_timer_task_trigger(FREEWHEEL_TIMER, NRF_TIMER_TASK_STOP);
nrf_timer_task_trigger(FREEWHEEL_TIMER, NRF_TIMER_TASK_CLEAR);
nrf_timer_prescaler_set(FREEWHEEL_TIMER, NRF_TIMER_FREQ_16MHz);
nrf_timer_mode_set(FREEWHEEL_TIMER, NRF_TIMER_MODE_TIMER);
nrf_timer_bit_width_set(FREEWHEEL_TIMER, NRF_TIMER_BIT_WIDTH_32);
nrf_timer_cc_set(FREEWHEEL_TIMER, NRF_TIMER_CC_CHANNEL0, TIME_SYNC_TIMER_MAX_VAL);
nrf_timer_cc_set(FREEWHEEL_TIMER, NRF_TIMER_CC_CHANNEL1, 0xFFFFFFFF);
nrf_timer_cc_set(FREEWHEEL_TIMER, NRF_TIMER_CC_CHANNEL2, 0xFFFFFFFF);
nrf_timer_cc_set(FREEWHEEL_TIMER, NRF_TIMER_CC_CHANNEL3, 0xFFFFFFFF);
nrf_timer_task_trigger(FREEWHEEL_TIMER, NRF_TIMER_TASK_START);
}
int ts_set_trigger(uint32_t target_tick, uint32_t ppi_endpoint)
{
if (!m_timeslot_session_open)
{
return -EBUSY;
}
if (ppi_sync_trigger_configure(ppi_endpoint) != 0)
{
return -EINVAL;
}
atomic_set(&m_sync_packet_count, 0);
atomic_set(&m_used_packet_count, 0);
// TODO: is there a way to check if the target value is plausible?
nrf_timer_cc_set(COUNTER_TIMER, NRF_TIMER_CC_CHANNEL4, (target_tick - m_master_counter_diff));
atomic_set(&m_tick_target, target_tick);
nrf_ppi_channel_enable(NRF_PPI, m_params.ppi_chns[4]); // activate trigger
return 0;
}
int ts_set_timestamp_trigger(uint32_t ppi_event_endpoint)
{
// TODO: Check if other timers can be used also
if (FREEWHEEL_TIMER != NRF_TIMER3 && FREEWHEEL_TIMER != NRF_TIMER4)
{
return -EBUSY;
}
if (COUNTER_TIMER != NRF_TIMER3 && COUNTER_TIMER != NRF_TIMER4)
{
return -EBUSY;
}
if (!m_timestamp_trigger_set)
{
nrfx_err_t err;
for (int i = 0; i < ARRAY_SIZE(m_timestamp_trigger_ppi); ++i)
{
err = nrfx_ppi_channel_alloc(&m_timestamp_trigger_ppi[i]);
if (err != NRFX_SUCCESS)
{
return err;
}
}
}
ppi_counter_timer_triggered_capture_configure(m_timestamp_trigger_ppi, ppi_event_endpoint);
m_timestamp_trigger_set = true;
return 0;
}
static inline bool sync_timer_offset_compensate(struct sync_pkt * p_pkt)
{
uint32_t peer_timer;
uint32_t local_timer;
int32_t timer_offset;
bool wrapped = false;
if (atomic_set(&m_timer_update_in_progress, true))
{
return false;
}
peer_timer = p_pkt->timer_val;
peer_timer += TX_CHAIN_DELAY;
if (peer_timer >= TIME_SYNC_TIMER_MAX_VAL)
{
peer_timer -= TIME_SYNC_TIMER_MAX_VAL;
p_pkt->counter_val += 1;
wrapped = true;
}
local_timer = nrf_timer_cc_get(FREEWHEEL_TIMER, NRF_TIMER_CC_CHANNEL1);
timer_offset = local_timer - peer_timer;
if (local_timer > TIME_SYNC_TIMER_MAX_VAL)
{
// Todo: Investigate if this is a corner case with a root cause that needs to be handled
atomic_clear(&m_timer_update_in_progress);
return false;
}
if (timer_offset == TIME_SYNC_TIMER_MAX_VAL || timer_offset == 0)
{
// Already in sync
atomic_add(&m_used_packet_count, 1);
atomic_clear(&m_timer_update_in_progress);
return false;
}
if (wrapped && (local_timer >= (TIME_SYNC_TIMER_MAX_VAL - 50)))
{
// Too close
// Todo: see if local counter increment can be accounted for
atomic_clear(&m_timer_update_in_progress);
return false;
}
bool shorten_cycle = timer_offset < 0;
nrf_timer_cc_set(FREEWHEEL_TIMER, NRF_TIMER_CC_CHANNEL2, (TIME_SYNC_TIMER_MAX_VAL + timer_offset));
ppi_sync_timer_adjust_configure(shorten_cycle);
atomic_set(&mp_curr_adj_pkt, (uint32_t) p_pkt);
atomic_set(&m_curr_adj_timer, nrf_timer_cc_get(FREEWHEEL_TIMER, NRF_TIMER_CC_CHANNEL1));
atomic_set(&m_curr_adj_counter, nrf_timer_cc_get(COUNTER_TIMER, NRF_TIMER_CC_CHANNEL1));
uint32_t cc_val;
do
{
// Avoid race condition when disabling and re-enabling PPI channel very quickly
nrf_timer_task_trigger(FREEWHEEL_TIMER, NRF_TIMER_TASK_CAPTURE4);
cc_val = nrf_timer_cc_get(FREEWHEEL_TIMER, NRF_TIMER_CC_CHANNEL4);
} while ((cc_val > (TIME_SYNC_TIMER_MAX_VAL - 15)));
if (shorten_cycle)
{
nrf_timer_cc_set(FREEWHEEL_TIMER, NRF_TIMER_CC_CHANNEL0, (TIME_SYNC_TIMER_MAX_VAL + 10));
ppi_sync_timer_adjust_enable();
}
else
{
ppi_sync_timer_adjust_enable();
ppi_sync_timer_clear_disable();
}
return true;
}
static void ppi_sync_timer_adjust_configure(bool shorten)
{
nrf_ppi_channel_t chn0, chn1, chn2;
nrf_ppi_channel_group_t chg0, chg1;
chn0 = m_params.ppi_chns[0];
chn1 = m_params.ppi_chns[1];
chn2 = m_params.ppi_chns[6];
chg0 = m_params.ppi_chgs[0];
chg1 = m_params.ppi_chgs[1];
// PPI channel 0: clear timer when compare[2] value is reached
nrf_ppi_channel_disable(NRF_PPI, chn0);
nrf_ppi_channel_endpoint_setup(NRF_PPI, chn0,
nrf_timer_event_address_get(FREEWHEEL_TIMER, NRF_TIMER_EVENT_COMPARE2),
nrf_timer_task_address_get(FREEWHEEL_TIMER, NRF_TIMER_TASK_CLEAR));
if (shorten)
{
nrf_ppi_fork_endpoint_setup(NRF_PPI, chn0,
nrf_timer_task_address_get(COUNTER_TIMER, NRF_TIMER_TASK_COUNT));
}
else
{
nrf_ppi_fork_endpoint_setup(NRF_PPI, chn0, 0);
}
// PPI channel 1: disable PPI channel 0 such that the timer is only reset once, and trigger software interrupt
nrf_ppi_channel_disable(NRF_PPI, chn1);
nrf_ppi_channel_and_fork_endpoint_setup(NRF_PPI, chn1,
nrf_timer_event_address_get(FREEWHEEL_TIMER, NRF_TIMER_EVENT_COMPARE2),
nrf_ppi_task_group_disable_address_get(NRF_PPI, chg0),
nrf_egu_task_address_get(EGU_INST, NRF_EGU_TASK_TRIGGER0));
nrf_ppi_channel_disable(NRF_PPI, chn2);
if (shorten)
{
nrf_ppi_channel_endpoint_setup(NRF_PPI, chn2, 0, 0);
}
else
{
// PPI channel 2: Re-enable EVENTS_COMPARE[0] after lengthening cycle
nrf_ppi_channel_endpoint_setup(NRF_PPI, chn2,
nrf_timer_event_address_get(FREEWHEEL_TIMER, NRF_TIMER_EVENT_COMPARE2),
nrf_ppi_task_group_enable_address_get(NRF_PPI, chg1));
}
nrf_ppi_group_disable(NRF_PPI, chg0);
nrf_ppi_channels_include_in_group(NRF_PPI,
BIT(chn0) | BIT(chn1) | BIT(chn2),
chg0);
}
static void ppi_radio_rx_configure(void)
{
uint32_t chn;
chn = m_params.ppi_chns[2];
nrf_ppi_channel_and_fork_endpoint_setup(NRF_PPI, chn,
nrf_radio_event_address_get(NRF_RADIO, NRF_RADIO_EVENT_ADDRESS),
nrf_timer_task_address_get(FREEWHEEL_TIMER, NRF_TIMER_TASK_CAPTURE1),
nrf_timer_task_address_get(COUNTER_TIMER, NRF_TIMER_TASK_CAPTURE1));
nrf_ppi_channel_enable(NRF_PPI, chn);
}
static void ppi_radio_tx_configure(void)
{
uint32_t chn;
chn = m_params.ppi_chns[0];
nrf_ppi_channel_and_fork_endpoint_setup(NRF_PPI, chn,
nrf_radio_event_address_get(NRF_RADIO, NRF_RADIO_EVENT_READY),
nrf_timer_task_address_get(FREEWHEEL_TIMER, NRF_TIMER_TASK_CAPTURE1),
nrf_timer_task_address_get(COUNTER_TIMER, NRF_TIMER_TASK_CAPTURE1));
nrf_ppi_channel_enable(NRF_PPI, chn);
}
static void ppi_timestamp_timer_configure(void)
{
uint32_t chn;
chn = m_params.ppi_chns[3];
nrf_ppi_channel_and_fork_endpoint_setup(NRF_PPI, chn,
nrf_timer_event_address_get(FREEWHEEL_TIMER, NRF_TIMER_EVENT_COMPARE0),
nrf_timer_task_address_get(COUNTER_TIMER, NRF_TIMER_TASK_COUNT),
0);
nrf_ppi_channel_enable(NRF_PPI, chn);
}
static void ppi_counter_timer_capture_configure(uint32_t chn)
{
nrf_ppi_channel_and_fork_endpoint_setup(NRF_PPI, chn,
nrf_egu_event_address_get(EGU_INST, NRF_EGU_EVENT_TRIGGERED1),
nrf_timer_task_address_get(FREEWHEEL_TIMER, NRF_TIMER_TASK_CAPTURE3),
nrf_timer_task_address_get(COUNTER_TIMER, NRF_TIMER_TASK_CAPTURE0));
nrf_ppi_channel_enable(NRF_PPI, chn);
}
static void ppi_counter_timer_triggered_capture_configure(nrf_ppi_channel_t chn[2], uint32_t eep)
{
nrf_ppi_channel_and_fork_endpoint_setup(NRF_PPI, chn[0],
eep,
nrf_timer_task_address_get(FREEWHEEL_TIMER, NRF_TIMER_TASK_CAPTURE5),
nrf_timer_task_address_get(COUNTER_TIMER, NRF_TIMER_TASK_CAPTURE5));
nrf_ppi_channel_and_fork_endpoint_setup(NRF_PPI, chn[1],
eep,
nrf_egu_task_address_get(EGU_INST, NRF_EGU_TASK_TRIGGER5),
0);
nrf_ppi_channels_enable(NRF_PPI, BIT(chn[0]) | BIT(chn[1]));
}
static void ppi_counter_timer_capture_disable(uint32_t chn)
{
nrf_ppi_channel_disable(NRF_PPI, chn);
nrf_ppi_channel_and_fork_endpoint_setup(NRF_PPI, chn, 0, 0, 0);
}
static void ppi_radio_rx_disable(void)
{
nrf_ppi_channel_disable(NRF_PPI, m_params.ppi_chns[2]);
}
static void ppi_sync_timer_adjust_enable(void)
{
nrf_ppi_group_enable(NRF_PPI, m_params.ppi_chgs[0]);
}
static void ppi_sync_timer_clear_configure(void)
{
uint32_t chn;
uint32_t chg;
chn = m_params.ppi_chns[5];
chg = m_params.ppi_chgs[1];
nrf_ppi_channel_disable(NRF_PPI, chn);
nrf_ppi_channel_endpoint_setup(NRF_PPI, chn,
nrf_timer_event_address_get(FREEWHEEL_TIMER, NRF_TIMER_EVENT_COMPARE0),
nrf_timer_task_address_get(FREEWHEEL_TIMER, NRF_TIMER_TASK_CLEAR));
nrf_ppi_channel_enable(NRF_PPI, chn);
nrf_ppi_channel_include_in_group(NRF_PPI, chn, chg);
nrf_ppi_group_enable(NRF_PPI, chg);
}
static void ppi_sync_timer_clear_disable(void)
{
nrf_ppi_group_disable(NRF_PPI, m_params.ppi_chgs[1]);
}
int ppi_sync_trigger_configure(uint32_t ppi_endpoint)
{
nrf_ppi_channel_and_fork_endpoint_setup(NRF_PPI, m_params.ppi_chns[4],
nrf_timer_event_address_get(COUNTER_TIMER, NRF_TIMER_EVENT_COMPARE4),
ppi_endpoint,
nrf_egu_task_address_get(EGU_INST, NRF_EGU_TASK_TRIGGER2));
return 0;
}
static void timers_capture(uint32_t * p_sync_timer_val, uint32_t * p_count_timer_val, int32_t * p_peer_counter_diff)
{
static atomic_t m_timestamp_capture_flag = 0;
volatile int32_t peer_counter_diff;
if (atomic_set(&m_timestamp_capture_flag, true) != 0)
{
// Not thread-safe
__ASSERT_NO_MSG(false);
}
nrf_ppi_channel_t ppi_chn;
nrfx_err_t ret = nrfx_ppi_channel_alloc(&ppi_chn);
__ASSERT_NO_MSG(ret == NRFX_SUCCESS);
(void)ret; /* If CONFIG_ASSERT is not set, ret is unused. */
ppi_counter_timer_capture_configure(ppi_chn);
// Loop if adjustment procedure happened close to timer capture
do
{
NVIC_DisableIRQ(TS_EGU_IRQn);
nrf_egu_event_clear(EGU_INST, NRF_EGU_EVENT_TRIGGERED1);
nrf_egu_task_trigger(EGU_INST, NRF_EGU_TASK_TRIGGER1);
while (!nrf_egu_event_check(EGU_INST, NRF_EGU_EVENT_TRIGGERED1))
{
__NOP();
}
peer_counter_diff = m_master_counter_diff;
NVIC_EnableIRQ(TS_EGU_IRQn);
} while (nrf_timer_cc_get(FREEWHEEL_TIMER, NRF_TIMER_CC_CHANNEL3) < 2);
ppi_counter_timer_capture_disable(ppi_chn);
nrfx_ppi_channel_free(ppi_chn);
*p_sync_timer_val = nrf_timer_cc_get(FREEWHEEL_TIMER, NRF_TIMER_CC_CHANNEL3);
*p_count_timer_val = nrf_timer_cc_get(COUNTER_TIMER, NRF_TIMER_CC_CHANNEL0);
*p_peer_counter_diff = peer_counter_diff;
atomic_clear(&m_timestamp_capture_flag);
}
int ts_init(ts_evt_handler_t evt_handler)
{
nrfx_err_t ret;
m_callback = evt_handler;
for (size_t i = 0; i < sizeof(m_params.ppi_chgs) / sizeof(m_params.ppi_chgs[0]); i++)
{
ret = nrfx_ppi_group_alloc(&m_params.ppi_chgs[i]);
if (ret != NRFX_SUCCESS)
{
return -EBUSY;
}
}
for (size_t i = 0; i < sizeof(m_params.ppi_chns) / sizeof(m_params.ppi_chns[0]); i++)
{
ret = nrfx_ppi_channel_alloc(&m_params.ppi_chns[i]);
if (ret != NRFX_SUCCESS)
{
return -EBUSY;
}
}
IRQ_DIRECT_CONNECT(TS_SWI_IRQn, 1, swi_isr, 0);
irq_enable(TS_SWI_IRQn);
IRQ_DIRECT_CONNECT(TS_EGU_IRQn, TIME_SYNC_EVT_HANDLER_IRQ_PRIORITY, egu_isr, 0);
irq_enable(TS_EGU_IRQn);
// TODO: Implement use of RTC as a low-power (and lower accuracy)
// alternative to 16 MHz TIMER
// if (SYNC_RTC_PRESCALER != m_params.rtc->PRESCALER)
// {
// // TODO: Handle this
// return -EBUSY;
// }
return 0;
}
int ts_enable(const ts_rf_config_t* p_rf_config)
{
int err;
if (p_rf_config == NULL)
{
return -EINVAL;
}
if (m_timeslot_session_open)
{
return -EBUSY;
}
/* Enable crystal oscillator for improved clock accuracy */
clk_mgr = z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_HF);
if (!clk_mgr) {
return -ENODEV;
}
sys_notify_init_spinwait(&clk_cli.notify);
err = onoff_request(clk_mgr, &clk_cli);
if (err < 0) {
LOG_ERR("onoff_request: %d", err);
return err;
}
/* Enable constant latency mode to minimize jitter */
nrf_power_task_trigger(NRF_POWER, NRF_POWER_TASK_CONSTLAT);
memcpy(m_params.rf_addr, p_rf_config->rf_addr, sizeof(m_params.rf_addr));
m_params.rf_chn = p_rf_config->rf_chn;
ppi_timestamp_timer_configure();
// NVIC_ClearPendingIRQ(EGU_INST_irq_type);
// NVIC_SetPriority(EGU_INST_irq_type, TIME_SYNC_EVT_HANDLER_IRQ_PRIORITY);
// NVIC_EnableIRQ(EGU_INST_irq_type);
nrf_egu_int_disable(EGU_INST, NRF_EGU_INT_ALL);
nrf_egu_int_enable(EGU_INST,
NRF_EGU_INT_TRIGGERED0 |
NRF_EGU_INT_TRIGGERED2 |
NRF_EGU_INT_TRIGGERED3 |
NRF_EGU_INT_TRIGGERED4 |
NRF_EGU_INT_TRIGGERED5);
m_blocked_cancelled_count = 0;
m_radio_state = RADIO_STATE_IDLE;
atomic_clear(&m_send_sync_pkt);
timestamp_counter_start();
ppi_sync_timer_clear_configure();
sync_timer_start();
err = mpsl_timeslot_session_open(
mpsl_timeslot_callback,
&session_id);
if (err) {
LOG_ERR("mpsl_timeslot_session_open=%d", err);
return err;
}
err = mpsl_timeslot_request(
session_id,
&m_timeslot_req_earliest);
if (err) {
LOG_ERR("mpsl_timeslot_request(earliest)=%d", err);
return err;
}
atomic_set(&m_timeslot_session_open, true);
return 0;
}
int ts_disable(void)
{
int err;
atomic_set(&m_pending_close, true);
err = mpsl_timeslot_session_close(session_id);
if (err) {
LOG_ERR("mpsl_timeslot_session_close=%d", err);
return err;
}
// TODO: stop timer
nrf_power_task_trigger(NRF_POWER, NRF_POWER_TASK_LOWPWR);
err = onoff_release(clk_mgr);
if (err < 0) {
LOG_ERR("onoff_release: %d", err);
return err;
}
// TODO:
// - Close SoftDevice radio session (sd_radio_session_close())
// - Stop radio activity
// - Stop timers
// - Disable used PPI channels
// - Disable used interrupts
// - Release HFCLK (sd_clock_hfclk_release()),
// - Go back to low-power (mode sd_power_mode_set(NRF_POWER_MODE_LOWPWR))
// Care must be taken to ensure clean stop. Order of tasks above should be reconsidered.
return 0;
}
int ts_tx_start(uint32_t sync_freq_hz)
{
uint32_t distance;
if (sync_freq_hz == TIME_SYNC_FREQ_AUTO)
{
// 20-30 Hz is a good range
// 同步频率
// Higher frequency gives more margin for missed packets, but doesn't improve accuracy that much
uint32_t auto_freq_target = 25;
distance = (NRFX_ROUNDED_DIV(NRFX_ROUNDED_DIV(1000000, auto_freq_target), (TIME_SYNC_TIMER_MAX_VAL / 16))) * (TIME_SYNC_TIMER_MAX_VAL / 16);
}
else
{
distance = (1000000 / sync_freq_hz);
}
if (distance >= MPSL_TIMESLOT_DISTANCE_MAX_US)
{
return -EINVAL;
}
m_timeslot_distance = distance;
m_usec_since_tx_offset_calc = 0;
atomic_set(&m_send_sync_pkt, true);
return 0;
}
int ts_tx_stop()
{
atomic_set(&m_pending_tx_stop, true);
return 0;
}
uint32_t ts_timestamp_get_ticks_u32(void)
{
uint32_t sync_timer_val;
uint32_t count_timer_val;
int32_t peer_diff;
timers_capture(&sync_timer_val, &count_timer_val, &peer_diff);
return (((count_timer_val + peer_diff) * TIME_SYNC_TIMER_MAX_VAL) + sync_timer_val);
}
uint64_t ts_timestamp_get_ticks_u64(void)
{
uint32_t sync_timer_val;
uint32_t count_timer_val;
uint64_t timestamp;
int32_t peer_diff;
timers_capture(&sync_timer_val, &count_timer_val, &peer_diff);
timestamp = (uint64_t) count_timer_val;
timestamp += (uint64_t) peer_diff;
timestamp *= TIME_SYNC_TIMER_MAX_VAL;
timestamp += (uint64_t) sync_timer_val;
return timestamp;
}
Thank you!
Ah right, I overlooked that part mentioning NCS. I am getting some errors compiling that for 54L15 myself, though I am not seeing immediately why this shouldn't be able to support nrf54L15- though it might need some changes for that.
I would recommend using the samples in NCS though, as they are the ones we officially support. Any reason why you can't use those instead?
Regards,
Elfving
Yes I want to use NCS and I am using it, but I cannot compile the code use board nRF5340 or nRF54L15 under NCS, I am wondering wherther the code can run at 54L15 with NCS.
You have issues building projects with NCS?