Can Wireless timer synchronization working on nRF54L15?

Hi

I am wondering whether this sample can work at nRF54L15.

Thank you!

Parents
  • 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.

Reply Children
Related