15 Commits

Author SHA1 Message Date
Matthias Ringwald
dd84f76b59 Support time sync on nRF52833 DK 2025-01-10 17:47:28 +01:00
Matthias Ringwald
066fe6201b fix build for nRF52833DK 2025-01-10 17:47:08 +01:00
Matthias Ringwald
e736be6ca4 readme: update 2025-01-10 09:27:00 +01:00
Matthias Ringwald
6bb8af2a3f readme: add nrf54l15 2025-01-09 18:11:19 +01:00
Matthias Ringwald
ce657eb852 main: get us time on nrf54l 2025-01-09 18:10:13 +01:00
Matthias Ringwald
db4e0e32c7 main: use ENABLE_ISO_TIMESYNC, allow compile without 2025-01-09 18:10:13 +01:00
Matthias Ringwald
cf3d9e613a src: add get code to get controller time 2025-01-09 18:10:13 +01:00
Matthias Ringwald
4bfe9c07f6 prj.conf: config LE Audio for 54L15 2025-01-09 18:10:13 +01:00
Matthias Ringwald
5b94c9522d boards: use P1.11 as host timesync pin 2025-01-09 18:02:45 +01:00
Matthias Ringwald
779c31f97d boards: add nrf54l15 overlay files 2025-01-09 18:02:45 +01:00
Matthias Ringwald
a82a9ff85a kconfig: fix spelling 2025-01-09 17:59:49 +01:00
Matthias Ringwald
2a89491008 update ipc_radio config for sysbuild / ncs 2.8 2024-12-17 16:56:42 +01:00
Matthias Ringwald
caffd7b8a1 Update nRF5340 DK information 2024-12-12 15:30:59 +01:00
Matthias Ringwald
b7dd60fd52 Add sysbuild configuration for required for NCS 2.8 2024-12-11 17:41:55 +01:00
Matthias Ringwald
16f411c7dc Update readme info on Time Sync GPIO pin mapping 2024-12-09 16:23:58 +01:00
18 changed files with 1044 additions and 31 deletions

View File

@@ -9,8 +9,14 @@ set(SRCS
${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
)
if (CONFIG_AUDIO_SYNC_TIMER_USES_RTC)
list(APPEND SRCS ${CMAKE_CURRENT_SOURCE_DIR}/src/audio_sync_timer_rtc.c)
if (CONFIG_SOC_COMPATIBLE_NRF52X)
target_sources(app PRIVATE src/controller_time_nrf52.c)
elseif (CONFIG_SOC_COMPATIBLE_NRF5340_CPUAPP)
target_sources(app PRIVATE src/audio_sync_timer_rtc.c)
elseif (CONFIG_SOC_SERIES_NRF54LX OR CONFIG_SOC_SERIES_NRF54HX)
target_sources(app PRIVATE src/controller_time_nrf54.c)
else()
MESSAGE(FATAL_ERROR "Unsupported series")
endif()
target_sources(app PRIVATE ${SRCS})

View File

@@ -4,7 +4,7 @@ menu "Modules"
rsource "Kconfig.defaults"
config AUDIO_SYNC_TIMER_USES_RTC
bool "Enables syncronization between the APP and NET core running SD"
bool "Enables synchronization between the APP and NET core running SD"
default !BT_LL_ACS_NRF53 && SOC_SERIES_NRF53X
select NRFX_RTC0
select NRFX_DPPI

10
Kconfig.sysbuild Normal file
View File

@@ -0,0 +1,10 @@
#
# Copyright (c) 2024 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
config NRF_DEFAULT_IPC_RADIO
default y
source "${ZEPHYR_BASE}/share/sysbuild/Kconfig"

129
README.md
View File

@@ -15,18 +15,99 @@ It has been tested on the nRF5340 Audio DK, but it should work with any nRF5340
- Response: HCI Command Complete Event with status and 4 bytes timestamp in microseconds
## nRF58233 Development Kit
The first Virtual UART (UART1, ...) is Zephyr UART 0
The second Virtual UART (UART2, ...) is Zephyr UART 1
### Pinout
Signal direction as seen from the nRF52833.
| PIN | Arduino | MCU | Direction |
|----------|---------|-------|-----------|
| TX | D0 | P0.05 | out |
| RX | D1 | P0.06 | in |
| RTS | D7 | P0.07 | out |
| CTS | D8 | P0.08 | in |
| Time Sync| D10 | P1.01 | out |
### HCI over UART 0 connected to first Virtual UART in J-Link Probe
Release build:
```sh
west build --pristine -b nrf52833dk/nrf52833
```
Debug build:
```sh
west build --pristine -b nrf52833dk/nrf52833 -- -DOVERLAY_CONFIG=debug.conf
```
To use UART 0 via Arduino headers, the virtual UART of the J-Link probe needs to be disabled, e.g. with the JLink Configuration Tool.
## nRF5340 Development Kit
The first Virtual UART (UART1, ...) is Zephyr UART 1
The second Virtual UART (UART2, ...) is Zephyr UART 0
### Pinout
Signal direction as seen from the nRF5340.
| PIN | Arduino | MCU | Direction |
|----------|---------|-------|-----------|
| TX | D0 | P1.00 | out |
| RX | D1 | P1.01 | in |
| RTS | D7 | P1.11 | out |
| CTS | D8 | P1.10 | in |
| Time Sync| D10 | P1.06 | out |
### HCI over USB CDC
```sh
west build -b nrf5340dk/nrf5340/cpuapp -- -DEXTRA_DTC_OVERLAY_FILE=usb.overlay -DOVERLAY_CONFIG=overlay-usb.conf
west build --pristine -b nrf5340dk/nrf5340/cpuapp -- -DEXTRA_DTC_OVERLAY_FILE=usb.overlay -DOVERLAY_CONFIG=overlay-usb.conf
```
_HCI over UART and timesync not tested._
### HCI over UART 0 connected to second Virtual UART in J-Link Probe
```sh
west build --pristine -b nrf5340dk/nrf5340/cpuapp
```
- HCI over second Virtual port / UART 0
- Boot banner on Arduino Header UART (P1.01) / UART 1
### HCI over UART 1 connected to first Virtual UART in J-Link Probe as well as Arduino Headers
```sh
west build --pristine -b nrf5340dk/nrf5340/cpuapp -- -DEXTRA_DTC_OVERLAY_FILE=uart1.overlay
```
- No Boot Banner on Arduino Header UART
## nRF5340 Audio DK
The first UART (UART1, P1.04, P1.05) is only available via test points (TP60, TP61, ...)
The second UART (UART2, P1.08, P1.09) is connected to the Arduino headers.
### Pinout
Signal direction as seen from the nRF5340.
| PIN | Arduino | MCU | Direction |
|----------|---------|-------|-----------|
| TX | D0 | P1.09 | out |
| RX | D1 | P1.08 | in |
| RTS | D7 | P1.11 | out |
| CTS | D8 | P1.10 | in |
| Time Sync| D10 | P1.01 | out |
### HCI over USB CDC
```sh
@@ -36,7 +117,7 @@ west build --pristine -b nrf5340_audio_dk/nrf5340/cpuapp -- -DEXTRA_DTC_OVERLAY_
### HCI over UART 0 connected to Virtual UART in J-Link Probe
```sh
west build --pristine -b nrf5340_audio_dk_nrf5340_cpuapp
west build --pristine -b nrf5340_audio_dk/nrf5340/cpuapp
```
### HCI over UART 1 connected to Virtual UART in J-Link Probe as well as Arduino Headers
@@ -53,14 +134,38 @@ west build --pristine -b nrf5340_audio_dk/nrf5340/cpuapp -- -DEXTRA_DTC_OVERLAY_
To use UART 1 via Arduino headers, the virtual UART of the J-Link probe needs to be disabled, e.g. with the JLink Configuration Tool.
### UART 1 + Time Sync Pinout
Signal direction as seen from the nRF5340.
| PIN | Arduino | nrf | Direction |
|----------|---------|-------|-----------|
| TX | D0 | P1.09 | out |
| RX | D1 | P1.08 | in |
| RTS | D7 | P1.11 | out |
| CTS | D8 | P1.10 | in |
| Timesync | D10 | P1.01 | out |
## nRF54L15
### HCI over UART 0 connected to second Virtual UART in J-Link Probe
Release build:
```sh
west build -d nrf54l15-iso --pristine -b nrf54l15dk/nrf54l15/cpuapp
```
Debug build:
```sh
west build -d nrf54l15-iso --pristine -b nrf54l15dk/nrf54l15/cpuapp -- -DOVERLAY_CONFIG=debug.conf
```
To use UART 0 via Arduino headers, the virtual UART of the J-Link probe needs to be disabled, e.g. with the JLink Configuration Tool.
### Pinout
Signal direction as seen from the nRF54L15.
| PIN | MCU | Direction |
|----------|-------|-----------|
| TX | P0.00 | out |
| RX | P0.01 | in |
| RTS | P0.02 | out |
| CTS | P0.03 | in |
| Time Sync| P1.11 | out |
## Maintainer Notes
- nRF5340 use Controller configuration in `sybuild/ipc_radio/prj.conf`, while others, e.g. nRF54L15, use configuration from `prj.conf`. Please update both at the same time.
- We can detect nRF5340 SoC in CMake with `if(CONFIG_SOC STREQUAL "nrf5340")` after find_package zephyr.

View File

@@ -0,0 +1,9 @@
#
# Copyright (c) 2024 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
CONFIG_NRFX_TIMER1=y
CONFIG_NRFX_RTC2=y
CONFIG_NRFX_PPI=y

View File

@@ -4,6 +4,17 @@
* SPDX-License-Identifier: Apache-2.0
*/
/ {
host_interface {
compatible = "gpio-outputs";
status = "okay";
timesync: pin_0 {
gpios = <&gpio1 01 GPIO_ACTIVE_HIGH>;
label = "Controller to host timesync pin";
};
};
};
&uart0 {
compatible = "nordic,nrf-uarte";
current-speed = <1000000>;

View File

@@ -0,0 +1,9 @@
#
# Copyright (c) 2024 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
CONFIG_NRFX_TIMER1=y
CONFIG_NRFX_RTC2=y
CONFIG_NRFX_PPI=y

View File

@@ -0,0 +1,23 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/ {
host_interface {
compatible = "gpio-outputs";
status = "okay";
timesync: pin_0 {
gpios = <&gpio1 11 GPIO_ACTIVE_HIGH>;
label = "Controller to host timesync pin";
};
};
};
&uart20 {
compatible = "nordic,nrf-uarte";
current-speed = <1000000>;
status = "okay";
hw-flow-control;
};

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
&uart20 {
compatible = "nordic,nrf-uarte";
current-speed = <1000000>;
status = "okay";
hw-flow-control;
};
&radio {
status = "okay";
/* This is an example number of antennas that may be available
* on antenna matrix board.
*/
dfe-antenna-num = <10>;
/* This is an example switch pattern that will be used to set an
* antenna for Tx PDU (period before start of Tx CTE).
*/
dfe-pdu-antenna = <0x0>;
/* These are example GPIO pin numbers that are provided to
* Radio peripheral. The pins will be acquired by Radio to
* drive antenna switching when AoD is enabled.
*/
dfegpio0-gpios = <&gpio1 4 0>;
dfegpio1-gpios = <&gpio1 5 0>;
dfegpio2-gpios = <&gpio1 6 0>;
dfegpio3-gpios = <&gpio1 7 0>;
};

View File

@@ -21,7 +21,7 @@ CONFIG_BT_CTLR_ADV_ISO_SET=2
CONFIG_BT_CTLR_ADV_ISO_STREAM_COUNT=3
CONFIG_BT_CTLR_SYNC_PERIODIC_ADV_LIST_SIZE=1
# Support two links as a central, or one link as a peripheral
# Support six links as a central, or one link as a peripheral
CONFIG_BT_MAX_CONN=8
CONFIG_BT_CTLR_SDC_PERIPHERAL_COUNT=2

View File

@@ -14,25 +14,38 @@ CONFIG_BT_BUF_EVT_DISCARDABLE_SIZE=255
CONFIG_BT_CTLR_ASSERT_HANDLER=y
CONFIG_BT_MAX_CONN=16
CONFIG_BT_TINYCRYPT_ECC=n
CONFIG_BT_CTLR_DTM_HCI=y
# Enable ISO support
CONFIG_BT_ISO_PERIPHERAL=y
CONFIG_BT_ISO_CENTRAL=y
CONFIG_BT_ISO_BROADCASTER=y
# Setup ISO Buffer
CONFIG_BT_ISO_TX_BUF_COUNT=10
CONFIG_BT_ISO_TX_MTU=251
CONFIG_BT_ISO_RX_BUF_COUNT=10
CONFIG_BT_ISO_RX_MTU=251
# setup rpmsg/Bluetooth buffer
CONFIG_BT_BUF_EVT_RX_COUNT=16
CONFIG_BT_BUF_EVT_RX_SIZE=255
CONFIG_BT_BUF_ACL_RX_SIZE=255
CONFIG_BT_BUF_ACL_TX_SIZE=251
CONFIG_BT_BUF_CMD_TX_SIZE=255
# Enable ISO support
CONFIG_BT_ISO_PERIPHERAL=y
CONFIG_BT_ISO_CENTRAL=y
CONFIG_BT_ISO_BROADCASTER=y
CONFIG_BT_ISO_SYNC_RECEIVER=y
CONFIG_BT_EXT_ADV=y
CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER=y
CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER=y
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=512
# Configure Controller
CONFIG_BT_CTLR_CONN_ISO_GROUPS=1
CONFIG_BT_CTLR_CONN_ISO_STREAMS=3
CONFIG_BT_CTLR_SYNC_ISO_STREAM_COUNT=2
CONFIG_BT_CTLR_ADV_EXT=y
CONFIG_BT_CTLR_ADV_SET=2
CONFIG_BT_CTLR_ADV_ISO_SET=2
CONFIG_BT_CTLR_ADV_ISO_STREAM_COUNT=3
CONFIG_BT_CTLR_SYNC_PERIODIC_ADV_LIST_SIZE=1
# Support six links as a central, or one link as a peripheral
CONFIG_BT_MAX_CONN=8
CONFIG_BT_CTLR_SDC_PERIPHERAL_COUNT=2
# Allow using more than default advertising event length
CONFIG_BT_CTLR_ADV_DATA_LEN_MAX=251
# Workaround: Unable to allocate command buffer when using K_NO_WAIT since
# Host number of completed commands does not follow normal flow control.

40
src/controller_time.h Normal file
View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
// BK: based on / taken from ncs/nrf/samples/bluetooth/conn_time_sync/src/controller_time_sync.h
#ifndef CONTROLLER_TIME_SYNC_H__
#define CONTROLLER_TIME_SYNC_H__
#include <stdint.h>
#include <stdbool.h>
#include <zephyr/kernel.h>
/** @brief Obtain the current Bluetooth controller time.
*
* The timestamps are based upon this clock.
*
* @return The current controller time.
*/
uint64_t controller_time_us_get(void);
/** @brief Set the controller to trigger a PPI event at the given timestamp.
*
* @param timestamp_us The timestamp where it will trigger.
*/
void controller_time_trigger_set(uint64_t timestamp_us);
/** @brief Get the address of the event that will trigger.
*
* @return The address of the event that will trigger.
*/
uint32_t controller_time_trigger_event_addr_get(void);
#endif
/**
* @}
*/

292
src/controller_time_nrf52.c Normal file
View File

@@ -0,0 +1,292 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
/** This file implements controller time management for 52 Series devices
*
* As the controller clock is not directly accessible, the controller
* time is obtained using a mirrored RTC peripheral.
* To achieve microsecond accurate toggling, a timer peripheral is also used.
*/
// BK: taken from ncs/nrf/samples/bluetooth/conn_time_sync/src/controller_time_nrf52.c
#include <zephyr/kernel.h>
#include <zephyr/sys/barrier.h>
#include <mpsl_clock.h>
#include <nrfx_rtc.h>
#include <nrfx_timer.h>
#include <helpers/nrfx_gppi.h>
#include <hal/nrf_egu.h>
#include <soc.h>
#include "controller_time.h"
static const nrfx_rtc_t app_rtc_instance = NRFX_RTC_INSTANCE(2);
static const nrfx_timer_t app_timer_instance = NRFX_TIMER_INSTANCE(1);
static uint8_t ppi_chan_on_rtc_match;
static volatile uint32_t num_rtc_overflows;
static uint32_t offset_ticks_and_controller_to_app_rtc;
static void rtc_isr_handler(nrfx_rtc_int_type_t int_type)
{
if (int_type == NRFX_RTC_INT_OVERFLOW) {
num_rtc_overflows++;
}
}
static void unused_timer_isr_handler(nrf_timer_event_t event_type, void *ctx)
{
ARG_UNUSED(event_type);
ARG_UNUSED(ctx);
}
static int32_t rtc_diff_get(void)
{
uint32_t controller_ticks = nrf_rtc_counter_get(NRF_RTC0);
uint32_t app_ticks = nrf_rtc_counter_get(app_rtc_instance.p_reg);
return controller_ticks - app_ticks;
}
static int rtc_config(void)
{
int ret;
const nrfx_rtc_config_t rtc_cfg = NRFX_RTC_DEFAULT_CONFIG;
ret = nrfx_rtc_init(&app_rtc_instance, &rtc_cfg, rtc_isr_handler);
if (ret != NRFX_SUCCESS) {
printk("Failed initializing RTC (ret: %d)\n", ret - NRFX_ERROR_BASE_NUM);
return -ENODEV;
}
#ifndef BT_CTLR_SDC_BSIM_BUILD
IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_RTC_INST_GET(2)), IRQ_PRIO_LOWEST,
NRFX_RTC_INST_HANDLER_GET(2), NULL, 0);
#else
IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_RTC_INST_GET(2)), 0,
(void *)NRFX_RTC_INST_HANDLER_GET(2), NULL, 0);
#endif
nrfx_rtc_overflow_enable(&app_rtc_instance, true);
nrfx_rtc_tick_enable(&app_rtc_instance, false);
nrfx_rtc_enable(&app_rtc_instance);
/* To obtain the controller timestamp without modifying the state of RTC0,
* we find the offset between RTC0 and our mirrored RTC.
* We achieve this by reading out the counter value of both of them.
* When the diff between those two have been equal twice, we know the
* time difference in ticks.
*/
uint32_t sync_attempts = 10;
while (sync_attempts > 0) {
sync_attempts--;
int32_t diff_measurement_1 = rtc_diff_get();
/* We need to wait half an RTC tick to ensure we are not measuring
* the diff between the two RTCs at the point in time where their
* values are transitioning.
*/
k_busy_wait(15);
int32_t diff_measurement_2 = rtc_diff_get();
if (diff_measurement_1 == diff_measurement_2) {
offset_ticks_and_controller_to_app_rtc = diff_measurement_1;
return 0;
}
}
printk("Controller time sync failure\n");
offset_ticks_and_controller_to_app_rtc = 0;
return -EINVAL;
}
static int timer_config(void)
{
int ret;
uint8_t ppi_chan_timer_clear_on_rtc_tick;
const nrfx_timer_config_t timer_cfg = {
.frequency = NRFX_MHZ_TO_HZ(1UL),
.mode = NRF_TIMER_MODE_TIMER,
.bit_width = NRF_TIMER_BIT_WIDTH_8,
.interrupt_priority = NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY,
.p_context = NULL};
ret = nrfx_timer_init(&app_timer_instance, &timer_cfg, unused_timer_isr_handler);
if (ret != NRFX_SUCCESS) {
printk("Failed initializing timer (ret: %d)\n", ret - NRFX_ERROR_BASE_NUM);
return -ENODEV;
}
/* Clear the TIMER every RTC tick. */
if (nrfx_gppi_channel_alloc(&ppi_chan_timer_clear_on_rtc_tick) != NRFX_SUCCESS) {
printk("Failed allocating for clearing TIMER on RTC TICK\n");
return -ENOMEM;
}
nrfx_gppi_channel_endpoints_setup(ppi_chan_timer_clear_on_rtc_tick,
nrfx_rtc_event_address_get(&app_rtc_instance,
NRF_RTC_EVENT_TICK),
nrfx_timer_task_address_get(&app_timer_instance,
NRF_TIMER_TASK_CLEAR));
nrfx_gppi_channels_enable(BIT(ppi_chan_timer_clear_on_rtc_tick));
nrfx_timer_enable(&app_timer_instance);
return 0;
}
/** Configure the TIMER and RTC in such a way that microsecond accurate timing can be achieved.
*
* To get microsecond accurate toggling, we need to combine both the RTC and TIMER peripheral.
* That is, both a RTC CC value and TIMER CC value needs to be set.
* We should only trigger an EGU task when the TIMER CC value matches after the
* RTC CC value matched.
* This is achieved using a PPI group. The group is enabled when the RTC CC matches and disabled
* again then the TIMER CC matches.
*/
int config_egu_trigger_on_rtc_and_timer_match(void)
{
uint8_t ppi_chan_on_timer_match;
if (nrfx_gppi_channel_alloc(&ppi_chan_on_rtc_match) != NRFX_SUCCESS) {
printk("Failed allocating for RTC match\n");
return -ENOMEM;
}
if (nrfx_gppi_channel_alloc(&ppi_chan_on_timer_match) != NRFX_SUCCESS) {
printk("Failed allocating for TIMER match\n");
return -ENOMEM;
}
nrfx_gppi_group_clear(NRFX_GPPI_CHANNEL_GROUP0);
nrfx_gppi_group_disable(NRFX_GPPI_CHANNEL_GROUP0);
nrfx_gppi_channels_include_in_group(
BIT(ppi_chan_on_timer_match) | BIT(ppi_chan_on_rtc_match),
NRFX_GPPI_CHANNEL_GROUP0);
nrfx_gppi_channel_endpoints_setup(ppi_chan_on_rtc_match,
nrfx_rtc_event_address_get(&app_rtc_instance,
NRF_RTC_EVENT_COMPARE_0),
nrfx_gppi_task_address_get(NRFX_GPPI_TASK_CHG0_EN));
nrfx_gppi_channel_endpoints_setup(ppi_chan_on_timer_match,
nrfx_timer_event_address_get(&app_timer_instance,
NRF_TIMER_EVENT_COMPARE0),
nrf_egu_task_address_get(NRF_EGU0,
NRF_EGU_TASK_TRIGGER0));
nrfx_gppi_fork_endpoint_setup(ppi_chan_on_timer_match,
nrfx_gppi_task_address_get(NRFX_GPPI_TASK_CHG0_DIS));
return 0;
}
int controller_time_init(void)
{
int ret;
ret = rtc_config();
if (ret) {
return ret;
}
ret = timer_config();
if (ret) {
return ret;
}
return config_egu_trigger_on_rtc_and_timer_match();
}
static uint64_t rtc_ticks_to_us(uint32_t rtc_ticks)
{
const uint64_t rtc_ticks_in_femto_units = 30517578125UL;
return (rtc_ticks * rtc_ticks_in_femto_units) / 1000000000UL;
}
static uint32_t us_to_rtc_ticks(uint32_t timestamp_us)
{
const uint64_t rtc_ticks_in_femto_units = 30517578125UL;
return ((uint64_t)(timestamp_us) * 1000000000UL) / rtc_ticks_in_femto_units;
}
uint64_t controller_time_us_get(void)
{
const uint64_t rtc_overflow_time_us = 512000000UL;
/* On the 52 series the RTC has to task to capture the current RTC value.
* Therefore we cannot capture the TIMER and RTC value simultaneously.
* Therefore we only use the RTC to read out the current time here.
* This will result in an error of maximum one RTC tick.
*/
uint32_t captured_rtc_overflows;
uint32_t captured_rtc_ticks;
while (true) {
captured_rtc_overflows = num_rtc_overflows;
/* Read out RTC ticks after reading number of overflows. */
barrier_isync_fence_full();
captured_rtc_ticks = nrf_rtc_counter_get(app_rtc_instance.p_reg);
/* Read out number of overflows after reading number of RTC ticks */
barrier_isync_fence_full();
if (captured_rtc_overflows == num_rtc_overflows) {
/* There were no new overflows after reading ticks.
* That is, we can use the captured value.
*/
break;
}
}
return rtc_ticks_to_us(captured_rtc_ticks) +
(captured_rtc_overflows * rtc_overflow_time_us) +
rtc_ticks_to_us(offset_ticks_and_controller_to_app_rtc);
}
void controller_time_trigger_set(uint64_t timestamp_us)
{
uint64_t timestamp_without_rtc_offset =
timestamp_us - rtc_ticks_to_us(offset_ticks_and_controller_to_app_rtc);
uint32_t num_overflows = timestamp_without_rtc_offset / 512000000UL;
uint64_t overflow_time_us = num_overflows * 512000000UL;
uint32_t rtc_remainder_time_us = timestamp_without_rtc_offset - overflow_time_us;
uint32_t rtc_val = us_to_rtc_ticks(rtc_remainder_time_us);
uint8_t timer_val = timestamp_without_rtc_offset - rtc_ticks_to_us(rtc_val);
/* Ensure the timer value lies between 1 and 30 so that it will
* always be between two RTC ticks.
*/
timer_val = MAX(timer_val, 1);
timer_val = MIN(timer_val, 30);
if (nrfx_rtc_cc_set(&app_rtc_instance, 0, rtc_val, false) != NRFX_SUCCESS) {
printk("Failed setting trigger\n");
}
nrfx_timer_compare(&app_timer_instance, 0, timer_val, false);
nrfx_gppi_channels_enable(BIT(ppi_chan_on_rtc_match));
}
uint32_t controller_time_trigger_event_addr_get(void)
{
return nrf_egu_event_address_get(NRF_EGU0, NRF_EGU_EVENT_TRIGGERED0);
}
SYS_INIT(controller_time_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);

View File

@@ -0,0 +1,270 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
/** This file implements controller time management for 53 Series devices
*
* As the controller clock is not directly accessible, the controller
* time is obtained using a mirrored RTC peripheral.
* The RTC is started upon starting the controller clock on the network core.
* To achieve microsecond accurate toggling, a timer peripheral is also used.
*/
// BK: taken from ncs/nrf/samples/bluetooth/conn_time_sync/src/controller_time_nrf53_app.c
// - we already have a similar code in audio_sync_timer_rtc.c from nrf5340_audio
#include <zephyr/kernel.h>
#include <zephyr/init.h>
#include <zephyr/sys/barrier.h>
#include <helpers/nrfx_gppi.h>
#include <nrfx_rtc.h>
#include <nrfx_timer.h>
#include <hal/nrf_ipc.h>
#include <hal/nrf_egu.h>
#include "conn_time_sync.h"
static const nrfx_rtc_t app_rtc_instance = NRFX_RTC_INSTANCE(0);
static const nrfx_timer_t app_timer_instance = NRFX_TIMER_INSTANCE(0);
static uint8_t ppi_chan_on_rtc_match;
static volatile uint32_t num_rtc_overflows;
static void rtc_isr_handler(nrfx_rtc_int_type_t int_type)
{
if (int_type == NRFX_RTC_INT_OVERFLOW) {
num_rtc_overflows++;
}
}
static void unused_timer_isr_handler(nrf_timer_event_t event_type, void *ctx)
{
ARG_UNUSED(event_type);
ARG_UNUSED(ctx);
}
static int rtc_config(void)
{
int ret;
uint8_t dppi_channel_rtc_start;
const nrfx_rtc_config_t rtc_cfg = NRFX_RTC_DEFAULT_CONFIG;
ret = nrfx_rtc_init(&app_rtc_instance, &rtc_cfg, rtc_isr_handler);
if (ret != NRFX_SUCCESS) {
printk("Failed initializing RTC (ret: %d)\n", ret - NRFX_ERROR_BASE_NUM);
return -ENODEV;
}
#ifndef BT_CTLR_SDC_BSIM_BUILD
IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_RTC_INST_GET(0)), IRQ_PRIO_LOWEST,
NRFX_RTC_INST_HANDLER_GET(0), NULL, 0);
#else
IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_RTC_INST_GET(0)), 0,
(void *)NRFX_RTC_INST_HANDLER_GET(0), NULL, 0);
#endif
nrfx_rtc_overflow_enable(&app_rtc_instance, true);
nrfx_rtc_tick_enable(&app_rtc_instance, false);
nrfx_rtc_enable(&app_rtc_instance);
/* The application core RTC is started synchronously with the controller
* RTC using PPI over IPC.
*/
ret = nrfx_gppi_channel_alloc(&dppi_channel_rtc_start);
if (ret != NRFX_SUCCESS) {
printk("nrfx DPPI channel alloc error for starting RTC: %d", ret);
return -ENODEV;
}
nrf_ipc_receive_config_set(NRF_IPC, 4, NRF_IPC_CHANNEL_4);
nrfx_gppi_channel_endpoints_setup(dppi_channel_rtc_start,
nrfx_rtc_task_address_get(&app_rtc_instance,
NRF_RTC_TASK_CLEAR),
nrf_ipc_event_address_get(NRF_IPC,
NRF_IPC_EVENT_RECEIVE_4));
nrfx_gppi_channels_enable(BIT(dppi_channel_rtc_start));
return 0;
}
static int timer_config(void)
{
int ret;
uint8_t ppi_chan_timer_clear_on_rtc_tick;
const nrfx_timer_config_t timer_cfg = {
.frequency = NRFX_MHZ_TO_HZ(1UL),
.mode = NRF_TIMER_MODE_TIMER,
.bit_width = NRF_TIMER_BIT_WIDTH_8,
.interrupt_priority = NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY,
.p_context = NULL};
ret = nrfx_timer_init(&app_timer_instance, &timer_cfg, unused_timer_isr_handler);
if (ret != NRFX_SUCCESS) {
printk("Failed initializing timer (ret: %d)\n", ret - NRFX_ERROR_BASE_NUM);
return -ENODEV;
}
/* Clear the TIMER every RTC tick. */
if (nrfx_gppi_channel_alloc(&ppi_chan_timer_clear_on_rtc_tick) != NRFX_SUCCESS) {
printk("Failed allocating for clearing TIMER on RTC TICK\n");
return -ENOMEM;
}
nrfx_gppi_channel_endpoints_setup(ppi_chan_timer_clear_on_rtc_tick,
nrfx_rtc_event_address_get(&app_rtc_instance,
NRF_RTC_EVENT_TICK),
nrfx_timer_task_address_get(&app_timer_instance,
NRF_TIMER_TASK_CLEAR));
nrfx_gppi_channels_enable(BIT(ppi_chan_timer_clear_on_rtc_tick));
nrfx_timer_enable(&app_timer_instance);
return 0;
}
/** Configure the TIMER and RTC in such a way that microsecond accurate timing can be achieved.
*
* To get microsecond accurate toggling, we need to combine both the RTC and TIMER peripheral.
* That is, both a RTC CC value and TIMER CC value needs to be set.
* We should only trigger an EGU task when the TIMER CC value
* matches after the RTC CC value matched.
* This is achieved using a PPI group. The group is enabled when the RTC CC matches and disabled
* again then the TIMER CC matches.
*/
int config_egu_trigger_on_rtc_and_timer_match(void)
{
uint8_t ppi_chan_on_timer_match;
if (nrfx_gppi_channel_alloc(&ppi_chan_on_rtc_match) != NRFX_SUCCESS) {
printk("Failed allocating for RTC match\n");
return -ENOMEM;
}
if (nrfx_gppi_channel_alloc(&ppi_chan_on_timer_match) != NRFX_SUCCESS) {
printk("Failed allocating for TIMER match\n");
return -ENOMEM;
}
nrfx_gppi_group_clear(NRFX_GPPI_CHANNEL_GROUP0);
nrfx_gppi_group_disable(NRFX_GPPI_CHANNEL_GROUP0);
nrfx_gppi_channels_include_in_group(
BIT(ppi_chan_on_timer_match) | BIT(ppi_chan_on_rtc_match),
NRFX_GPPI_CHANNEL_GROUP0);
nrfx_gppi_channel_endpoints_setup(ppi_chan_on_rtc_match,
nrfx_rtc_event_address_get(&app_rtc_instance,
NRF_RTC_EVENT_COMPARE_0),
nrfx_gppi_task_address_get(NRFX_GPPI_TASK_CHG0_EN));
nrfx_gppi_channel_endpoints_setup(ppi_chan_on_timer_match,
nrfx_timer_event_address_get(&app_timer_instance,
NRF_TIMER_EVENT_COMPARE0),
nrfx_gppi_task_address_get(NRFX_GPPI_TASK_CHG0_DIS));
nrfx_gppi_fork_endpoint_setup(ppi_chan_on_timer_match,
nrf_egu_task_address_get(NRF_EGU0, NRF_EGU_TASK_TRIGGER0));
return 0;
}
int controller_time_init(void)
{
int ret;
ret = rtc_config();
if (ret) {
return ret;
}
ret = timer_config();
if (ret) {
return ret;
}
return config_egu_trigger_on_rtc_and_timer_match();
}
static uint64_t rtc_ticks_to_us(uint32_t rtc_ticks)
{
const uint64_t rtc_ticks_in_femto_units = 30517578125UL;
return (rtc_ticks * rtc_ticks_in_femto_units) / 1000000000UL;
}
static uint32_t us_to_rtc_ticks(uint32_t timestamp_us)
{
const uint64_t rtc_ticks_in_femto_units = 30517578125UL;
return ((uint64_t)(timestamp_us) * 1000000000UL) / rtc_ticks_in_femto_units;
}
uint64_t controller_time_us_get(void)
{
/* Simply use the RTC time here.
* It is possible to combine RTC and TIMER information here to get a more accurate
* timestamp. That requires setting up a PPI channel to capture both values simultaneously.
*/
const uint64_t rtc_overflow_time_us = 512000000UL;
uint32_t captured_rtc_overflows;
uint32_t captured_rtc_ticks;
while (true) {
captured_rtc_overflows = num_rtc_overflows;
/* Read out RTC ticks after reading number of overflows. */
barrier_isync_fence_full();
captured_rtc_ticks = nrf_rtc_counter_get(app_rtc_instance.p_reg);
/* Read out number of overflows after reading number of RTC ticks */
barrier_isync_fence_full();
if (captured_rtc_overflows == num_rtc_overflows) {
/* There were no new overflows after reading ticks.
* That is, we can use the captured value.
*/
break;
}
}
return rtc_ticks_to_us(captured_rtc_ticks) +
(captured_rtc_overflows * rtc_overflow_time_us);
}
void controller_time_trigger_set(uint64_t timestamp_us)
{
uint32_t num_overflows = timestamp_us / 512000000UL;
uint64_t overflow_time_us = num_overflows * 512000000UL;
uint32_t remainder_time_us = timestamp_us - overflow_time_us;
uint32_t rtc_val = us_to_rtc_ticks(remainder_time_us);
uint8_t timer_val = timestamp_us - rtc_ticks_to_us(rtc_val);
/* Ensure the timer value lies between 1 and 30 so that it will
* always be between two RTC ticks.
*/
timer_val = MAX(timer_val, 1);
timer_val = MIN(timer_val, 30);
if (nrfx_rtc_cc_set(&app_rtc_instance, 0, rtc_val, false) != NRFX_SUCCESS) {
printk("Failed setting trigger\n");
}
nrfx_timer_compare(&app_timer_instance, 0, timer_val, false);
nrfx_gppi_channels_enable(BIT(ppi_chan_on_rtc_match));
}
uint32_t controller_time_trigger_event_addr_get(void)
{
return nrf_egu_event_address_get(NRF_EGU0, NRF_EGU_EVENT_TRIGGERED0);
}
/* The controller time initialization must happen before
* starting the network core.
*/
SYS_INIT(controller_time_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);

View File

@@ -0,0 +1,68 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
/** This file implements controller time management for 54 Series devices
*/
// BK: taken from ncs/nrf/samples/bluetooth/conn_time_sync/src/controller_time_nrf54.c
#include <zephyr/kernel.h>
#include <nrfx_grtc.h>
#include "controller_time.h"
static uint8_t grtc_channel;
int controller_time_init(void)
{
int ret;
ret = nrfx_grtc_channel_alloc(&grtc_channel);
if (ret != NRFX_SUCCESS) {
printk("Failed allocating GRTC channel (ret: %d)\n",
ret - NRFX_ERROR_BASE_NUM);
return -ENODEV;
}
nrf_grtc_sys_counter_compare_event_enable(NRF_GRTC, grtc_channel);
return 0;
}
uint64_t controller_time_us_get(void)
{
int ret;
uint64_t current_time_us;
ret = nrfx_grtc_syscounter_get(&current_time_us);
if (ret != NRFX_SUCCESS) {
printk("Failed obtaining system time (ret: %d)\n", ret - NRFX_ERROR_BASE_NUM);
return 0;
}
return current_time_us;
}
void controller_time_trigger_set(uint64_t timestamp_us)
{
int ret;
nrfx_grtc_channel_t chan_data = {
.channel = grtc_channel,
};
ret = nrfx_grtc_syscounter_cc_absolute_set(&chan_data, timestamp_us, false);
if (ret != NRFX_SUCCESS) {
printk("Failed setting CC (ret: %d)\n", ret - NRFX_ERROR_BASE_NUM);
}
}
uint32_t controller_time_trigger_event_addr_get(void)
{
return nrf_grtc_event_address_get(NRF_GRTC,
nrf_grtc_sys_counter_compare_event_get(grtc_channel));
}
SYS_INIT(controller_time_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);

View File

@@ -31,8 +31,12 @@
#include <zephyr/bluetooth/buf.h>
#include <zephyr/bluetooth/hci_raw.h>
// nRF5340 - from ncs/nrf/nrf5340_audio example
#include "audio_sync_timer.h"
// nRF54L15 - from ncs/nrf/samples/bluetooth/conn_time_sync
#include "controller_time.h"
#define LOG_MODULE_NAME hci_uart
LOG_MODULE_REGISTER(LOG_MODULE_NAME);
@@ -360,7 +364,10 @@ static int hci_uart_init(void)
SYS_INIT(hci_uart_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);
#ifdef CONFIG_AUDIO_SYNC_TIMER_USES_RTC
#define ENABLE_ISO_TIMESYNC
#ifdef ENABLE_ISO_TIMESYNC
#define TIMESYNC_GPIO DT_NODELABEL(timesync)
#if DT_NODE_HAS_STATUS(TIMESYNC_GPIO, okay)
@@ -384,11 +391,12 @@ uint8_t hci_cmd_iso_timesync_cb(struct net_buf *buf)
LOG_INF("buf %p type %u len %u", buf, bt_buf_get_type(buf), buf->len);
LOG_INF("buf[0] = 0x%02x", buf->data[0]);
uint32_t timestamp_second_us;
uint32_t timestamp_second_us = 0;
// Lock interrupts to avoid interrupt between time capture and gpio toggle
uint32_t key = arch_irq_lock();
#ifdef CONFIG_SOC_NRF5340_CPUAPP
// Get current time
uint32_t timestamp_first_us = audio_sync_timer_capture();
@@ -402,6 +410,11 @@ uint8_t hci_cmd_iso_timesync_cb(struct net_buf *buf)
}
timestamp_first_us = timestamp_second_us;
}
#endif
#if defined(CONFIG_SOC_NRF54L15_CPUAPP) || defined(CONFIG_SOC_NRF52833)
timestamp_second_us = (uint32_t) controller_time_us_get();
#endif
#if DT_NODE_HAS_STATUS(TIMESYNC_GPIO, okay)
gpio_pin_toggle_dt( &timesync_pin );
@@ -464,7 +477,7 @@ int main(void)
}
}
#ifdef CONFIG_AUDIO_SYNC_TIMER_USES_RTC
#ifdef ENABLE_ISO_TIMESYNC
/* Register iso_timesync command */
static struct bt_hci_raw_cmd_ext cmd_list = {
.op = BT_OP(BT_OGF_VS, HCI_CMD_ISO_TIMESYNC),

View File

@@ -0,0 +1,53 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
CONFIG_HEAP_MEM_POOL_SIZE=8192
CONFIG_MAIN_STACK_SIZE=2048
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
CONFIG_MBOX=y
CONFIG_IPC_SERVICE=y
CONFIG_BT=y
CONFIG_BT_HCI_RAW=y
CONFIG_BT_CTLR_ASSERT_HANDLER=y
CONFIG_BT_ISO_PERIPHERAL=y
CONFIG_BT_ISO_CENTRAL=y
CONFIG_BT_ISO_BROADCASTER=y
CONFIG_BT_ISO_SYNC_RECEIVER=y
CONFIG_BT_EXT_ADV=y
CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER=y
CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER=y
CONFIG_BT_CTLR_ADV_ISO_SET=2
CONFIG_BT_CTLR_ADV_SET=2
CONFIG_BT_CTLR_ADV_ISO_STREAM_COUNT=2
CONFIG_BT_CTLR_CONN_ISO_GROUPS=1
CONFIG_BT_CTLR_CONN_ISO_STREAMS=2
CONFIG_BT_CTLR_SYNC_ISO_STREAM_COUNT=3
CONFIG_BT_CTLR_SYNC_PERIODIC_ADV_LIST_SIZE=1
# Support six links as a central, or one link as a peripheral
CONFIG_BT_MAX_CONN=8
CONFIG_BT_CTLR_SDC_PERIPHERAL_COUNT=2
# Allow using more than default advertising event length
CONFIG_BT_CTLR_ADV_DATA_LEN_MAX=251
# To present the audio at the right point in time, we need the controller and
# audio clock to be synchronized
CONFIG_MPSL_TRIGGER_IPC_TASK_ON_RTC_START=y
CONFIG_MPSL_TRIGGER_IPC_TASK_ON_RTC_START_CHANNEL=4
# Needed for builds with nrf21540
# Can also be set to 20, but check local restrictions first
#CONFIG_BT_CTLR_TX_PWR_ANTENNA=10
#CONFIG_MPSL_FEM_NRF21540_TX_GAIN_DB=10
CONFIG_IPC_RADIO_BT=y
CONFIG_IPC_RADIO_BT_HCI_IPC=y

View File

@@ -0,0 +1,58 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
CONFIG_HEAP_MEM_POOL_SIZE=8192
CONFIG_MAIN_STACK_SIZE=2048
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
CONFIG_MBOX=y
CONFIG_IPC_SERVICE=y
CONFIG_BT=y
CONFIG_BT_HCI_RAW=y
CONFIG_BT_CTLR_ASSERT_HANDLER=y
CONFIG_BT_ISO_PERIPHERAL=y
CONFIG_BT_ISO_CENTRAL=y
CONFIG_BT_ISO_BROADCASTER=y
CONFIG_BT_ISO_SYNC_RECEIVER=y
CONFIG_BT_EXT_ADV=y
CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER=y
CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER=y
CONFIG_BT_CTLR_ADV_ISO_SET=2
CONFIG_BT_CTLR_ADV_SET=2
CONFIG_BT_CTLR_ADV_ISO_STREAM_COUNT=2
CONFIG_BT_CTLR_CONN_ISO_GROUPS=1
CONFIG_BT_CTLR_CONN_ISO_STREAMS=2
CONFIG_BT_CTLR_SYNC_ISO_STREAM_COUNT=3
CONFIG_BT_CTLR_SYNC_PERIODIC_ADV_LIST_SIZE=1
# Support six links as a central, or one link as a peripheral
CONFIG_BT_MAX_CONN=8
CONFIG_BT_CTLR_SDC_PERIPHERAL_COUNT=2
# Allow using more than default advertising event length
CONFIG_BT_CTLR_ADV_DATA_LEN_MAX=251
# To present the audio at the right point in time, we need the controller and
# audio clock to be synchronized
CONFIG_MPSL_TRIGGER_IPC_TASK_ON_RTC_START=y
CONFIG_MPSL_TRIGGER_IPC_TASK_ON_RTC_START_CHANNEL=4
CONFIG_IPC_RADIO_BT=y
CONFIG_IPC_RADIO_BT_HCI_IPC=y
# General
CONFIG_DEBUG=n
CONFIG_ASSERT=n
CONFIG_STACK_USAGE=n
CONFIG_THREAD_MONITOR=n
CONFIG_SERIAL=n
CONFIG_CONSOLE=n
CONFIG_PRINTK=n
CONFIG_UART_CONSOLE=n
CONFIG_BOOT_BANNER=n