16 Commits

Author SHA1 Message Date
pstruebi 3218a20d52 adaptions for auracaster 2025-02-13 16:57:36 +01:00
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
24 changed files with 1114 additions and 319 deletions
+2 -1
View File
@@ -1,7 +1,8 @@
.settings
.cproject
.project
build
build*/
app.jdebug
app.jdebug.user
*.jdebug.user
app.zip
+8 -2
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})
+1 -1
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
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"
+117 -12
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.
+9
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
+11
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>;
+9
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
+4
View File
@@ -2,3 +2,7 @@ CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_DEVICE_PRODUCT="Zephyr HCI UART sample"
CONFIG_USB_CDC_ACM=y
CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=n
CONFIG_NRFX_TIMER1=y
CONFIG_NRFX_RTC2=y
CONFIG_NRFX_PPI=y
@@ -6,11 +6,7 @@
gpios = <&arduino_header 10 GPIO_ACTIVE_HIGH>;
label = "Controller to host timesync pin";
};
alternate_toggle: pin_1 {
gpios = <&arduino_header 11 GPIO_ACTIVE_HIGH>;
label = "alternate toggle pin";
};
};
};
};
&uart0 {
+23
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;
};
@@ -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>;
};
+1 -1
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
+3
View File
@@ -1,5 +1,8 @@
CONFIG_ASSERT=y
#CONFIG_BT_DEBUG_MONITOR_RTT=y # custom protocol - bluez needed
CONFIG_BT_DEBUG_MONITOR_RTT=y
#CONFIG_THREAD_NAME=y
#CONFIG_THREAD_ANALYZER=y
#CONFIG_THREAD_ANALYZER_AUTO=y
+3
View File
@@ -0,0 +1,3 @@
#! /bin/bash
nrfutil pkg generate --hw-version 52 --sd-req=0x00 --application ./build_nrf52dongle/merged.hex --application-version 1 app.zip
nrfutil device program --firmware app.zip --traits nordicDfu
+43 -14
View File
@@ -1,3 +1,23 @@
# Configure for multiple broadcasters
CONFIG_BT_EXT_ADV_MAX_ADV_SET=5
CONFIG_BT_CTLR_ADV_SET=5
CONFIG_BT_CTLR_ADV_ISO_SET=5
CONFIG_BT_CTLR_ADV_ISO_STREAM_COUNT=5
CONFIG_BT_CTLR_SYNC_ISO_STREAM_COUNT=5
CONFIG_BT_CTLR_ADV_ISO_STREAM_MAX=5
CONFIG_BT_CTLR_SYNC_PERIODIC_ADV_LIST_SIZE=5
CONFIG_BT_BUF_EVT_RX_COUNT=16
CONFIG_BT_CTLR_ADV_ISO_PDU_LEN_MAX=247
# ISO Transmissions
CONFIG_BT_CTLR_ISOAL_SOURCES=5
CONFIG_BT_CTLR_ISO_TX_BUFFERS=8
CONFIG_BT_CTLR_ISO_TX_BUFFER_SIZE=255
##
CONFIG_CONSOLE=n
CONFIG_STDOUT_CONSOLE=n
CONFIG_UART_CONSOLE=n
@@ -8,31 +28,41 @@ CONFIG_BT=y
CONFIG_BT_HCI_RAW=y
CONFIG_BT_HCI_RAW_H4=y
CONFIG_BT_HCI_RAW_H4_ENABLE=y
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
CONFIG_BT_BUF_EVT_DISCARDABLE_SIZE=255
CONFIG_BT_CTLR_ASSERT_HANDLER=y
CONFIG_BT_MAX_CONN=16
#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=5
CONFIG_BT_CTLR_ADV_EXT=y
# 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=512
# Workaround: Unable to allocate command buffer when using K_NO_WAIT since
# Host number of completed commands does not follow normal flow control.
@@ -41,4 +71,3 @@ CONFIG_BT_BUF_CMD_TX_COUNT=10
# for the timesync command
CONFIG_BT_HCI_RAW_CMD_EXT=y
CONFIG_NRFX_TIMER2=y
+40
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
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);
+270
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);
+68
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);
+38 -283
View File
@@ -31,15 +31,17 @@
#include <zephyr/bluetooth/buf.h>
#include <zephyr/bluetooth/hci_raw.h>
#include <nrfx_timer.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);
static const struct device *const hci_uart_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_bt_c2h_uart));
static const struct device *const gmap_uart_dev = DEVICE_DT_GET(DT_NODELABEL(uart1));
static const struct device *const hci_uart_dev =
DEVICE_DT_GET(DT_CHOSEN(zephyr_bt_c2h_uart));
static K_THREAD_STACK_DEFINE(tx_thread_stack, CONFIG_BT_HCI_TX_STACK_SIZE);
static struct k_thread tx_thread_data;
static K_FIFO_DEFINE(tx_queue);
@@ -68,30 +70,6 @@ static K_FIFO_DEFINE(uart_tx_queue);
*/
#define H4_DISCARD_LEN 33
#ifdef CONFIG_AUDIO_SYNC_TIMER_USES_RTC
#define TIMESYNC_GPIO DT_NODELABEL(timesync)
#if DT_NODE_HAS_STATUS(TIMESYNC_GPIO, okay)
static const struct gpio_dt_spec timesync_pin = GPIO_DT_SPEC_GET(TIMESYNC_GPIO, gpios);
#else
#error "No timesync gpio available!"
#endif
#define ALTERNATE_TOGGLE_GPIO DT_NODELABEL(alternate_toggle)
static const struct gpio_dt_spec alternate_toggle_pin = GPIO_DT_SPEC_GET(ALTERNATE_TOGGLE_GPIO, gpios);
#endif
// enable to have alternate toggle indicate time from audio in to sdu sync ref in UGT to UGG direction
#define PERIPHERAL_TO_CENTRAL_AUDIO_IN_TO_SDU_SYNC_REF
// Presentation time from Audio to SDU Sync Ref (UGT->UGG) resp. SDU Sync Ref to Audio Out (UGG->UGT)
// HS timer on nRF5340 seems to be off: 60000 ticks = 59372 us
#define PRESENTATION_TIME_US 60634
static uint32_t last_sdu_sync_ref_us;
static uint32_t sdu_interval_us;
static int h4_read(const struct device *uart, uint8_t *buf, size_t len)
{
int rx = uart_fifo_read(uart, buf, len);
@@ -284,15 +262,6 @@ static void tx_thread(void *p1, void *p2, void *p3)
/* Wait until a buffer is available */
buf = k_fifo_get(&tx_queue, K_FOREVER);
#if DT_NODE_HAS_STATUS(TIMESYNC_GPIO, okay)
// toggle GPIO and send 'B' for each TX packet to be able to capture by Logic Analzyer
if (bt_buf_get_type(buf) == BT_BUF_ISO_OUT) {
gpio_pin_toggle_dt( &timesync_pin );
uart_poll_out(gmap_uart_dev, 'B');
}
#endif
/* Pass buffer to the stack */
err = bt_send(buf);
if (err!=BT_HCI_ERR_SUCCESS) {
@@ -395,7 +364,17 @@ 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)
static const struct gpio_dt_spec timesync_pin = GPIO_DT_SPEC_GET(TIMESYNC_GPIO, gpios);
#else
#error "No timesync gpio available!"
#endif
#define HCI_CMD_ISO_TIMESYNC (0x200)
@@ -412,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();
@@ -430,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 );
@@ -454,135 +439,14 @@ uint8_t hci_cmd_iso_timesync_cb(struct net_buf *buf)
}
#endif
uint16_t little_endian_read_16(const uint8_t * buffer, int position){
return (uint16_t)(((uint16_t) buffer[position]) | (((uint16_t)buffer[position+1]) << 8));
}
static uint32_t little_endian_read_32(const uint8_t * buffer, int position){
return ((uint32_t) buffer[position]) | (((uint32_t)buffer[position+1]) << 8) | (((uint32_t)buffer[position+2]) << 16) | (((uint32_t) buffer[position+3]) << 24);
}
#if DT_NODE_HAS_STATUS(TIMESYNC_GPIO, okay)
// make sure there's no IRQ between getting the time and the toggle
// verify timestamp as it's 100% coorect
#define LOCK_IRQS_FOR_SYNC_TOGGLE
static uint32_t toggle_and_get_bluetooth_time_us(void) {
uint32_t timestamp_toggle_us;
#ifdef LOCK_IRQS_FOR_SYNC_TOGGLE
// Lock interrupts
uint32_t key = arch_irq_lock();
#endif
while (1){
// Get current time once
timestamp_toggle_us = audio_sync_timer_capture();
// Get current time again
uint32_t timestamp_toggle_us_verify = audio_sync_timer_capture();
// check if time didn't jump
int32_t timestamp_delta = (int32_t) (timestamp_toggle_us_verify - timestamp_toggle_us);
if ((timestamp_delta >= 0) && (timestamp_delta < 10)){
break;
}
}
// Toggle
gpio_pin_toggle_dt( &timesync_pin );
#ifdef LOCK_IRQS_FOR_SYNC_TOGGLE
// Unlock interrupts
arch_irq_unlock(key);
#endif
return timestamp_toggle_us;
}
#endif
#define SYNC_TOGGLE_TIMER_INSTANCE_NUMBER 2
static const nrfx_timer_t sync_toggle_timer_instance =
NRFX_TIMER_INSTANCE(SYNC_TOGGLE_TIMER_INSTANCE_NUMBER);
static nrfx_timer_config_t cfg = {.frequency = NRFX_MHZ_TO_HZ(1UL),
.mode = NRF_TIMER_MODE_TIMER,
.bit_width = NRF_TIMER_BIT_WIDTH_32,
.interrupt_priority = NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY,
.p_context = NULL};
static volatile enum {
ALTERNATE_TOGGLE_STATE_IDLE,
ALTERNATE_TOGGLE_STATE_W2_RISE,
ALTERNATE_TOGGLE_STATE_W2_FALL
} alternate_toggle_state = ALTERNATE_TOGGLE_STATE_IDLE;
static volatile uint32_t alternate_toggle_fall_us;
static uint32_t get_local_time_us(void) {
nrf_timer_cc_set(NRF_TIMER2, NRF_TIMER_CC_CHANNEL2, 0);
nrf_timer_task_trigger(NRF_TIMER2, nrf_timer_capture_task_get(NRF_TIMER_CC_CHANNEL2));
uint32_t current_time_us = nrf_timer_cc_get(NRF_TIMER2, NRF_TIMER_CC_CHANNEL2);
while (current_time_us == 0) {
current_time_us = nrf_timer_cc_get(NRF_TIMER2, NRF_TIMER_CC_CHANNEL2);
}
return current_time_us;
}
static void alternate_toggle_timer_isr_handler(nrf_timer_event_t event_type, void *p_context){
ARG_UNUSED(p_context);
if(event_type == NRF_TIMER_EVENT_COMPARE1){
switch (alternate_toggle_state) {
case ALTERNATE_TOGGLE_STATE_W2_RISE:
alternate_toggle_state = ALTERNATE_TOGGLE_STATE_W2_FALL;
gpio_pin_set_dt(&alternate_toggle_pin, 1);
nrfx_timer_compare(&sync_toggle_timer_instance, NRF_TIMER_CC_CHANNEL1, alternate_toggle_fall_us, true);
break;
case ALTERNATE_TOGGLE_STATE_W2_FALL:
alternate_toggle_state = ALTERNATE_TOGGLE_STATE_IDLE;
gpio_pin_set_dt(&alternate_toggle_pin, 0);
break;
default:
__ASSERT(0, "Unknown state");
break;
}
}
}
static void alternate_toggle_setup(uint32_t rise_us, uint32_t fall_us) {
alternate_toggle_fall_us = fall_us;
alternate_toggle_state = ALTERNATE_TOGGLE_STATE_W2_RISE;
nrfx_timer_compare(&sync_toggle_timer_instance, NRF_TIMER_CC_CHANNEL1, rise_us, true);
}
int main(void)
{
// toggle timer setup
nrfx_err_t ret;
ret = nrfx_timer_init(&sync_toggle_timer_instance, &cfg, alternate_toggle_timer_isr_handler);
if (ret - NRFX_ERROR_BASE_NUM) {
LOG_ERR("nrfx timer init error: %d", ret);
return -ENODEV;
}
IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_TIMER_INST_GET(SYNC_TOGGLE_TIMER_INSTANCE_NUMBER)), IRQ_PRIO_LOWEST,
NRFX_TIMER_INST_HANDLER_GET(SYNC_TOGGLE_TIMER_INSTANCE_NUMBER), 0, 0);
nrfx_timer_enable(&sync_toggle_timer_instance);
alternate_toggle_state = ALTERNATE_TOGGLE_STATE_IDLE;
/* incoming events and data from the controller */
static K_FIFO_DEFINE(rx_queue);
int err;
LOG_DBG("Start");
__ASSERT(hci_uart_dev, "HCI UART device is NULL");
if (!device_is_ready(gmap_uart_dev)) {
printk("GMAP UART device is not ready\n");
return -1;
}
__ASSERT(hci_uart_dev, "UART device is NULL");
/* Enable the raw interface, this will in turn open the HCI driver */
bt_enable_raw(&rx_queue);
@@ -613,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),
@@ -625,10 +489,6 @@ int main(void)
gpio_pin_configure_dt(&timesync_pin, GPIO_OUTPUT_INACTIVE);
#endif
#if DT_NODE_HAS_STATUS(TIMESYNC_GPIO, okay)
gpio_pin_configure_dt(&alternate_toggle_pin, GPIO_OUTPUT_INACTIVE);
#endif
bt_hci_raw_cmd_ext_register(&cmd_list, 1);
#endif
@@ -640,125 +500,20 @@ int main(void)
NULL, NULL, NULL, K_PRIO_COOP(7), 0, K_NO_WAIT);
k_thread_name_set(&tx_thread_data, "HCI uart TX");
while (1) {
#if 0
while (1) {
uint8_t c = 'A';
uart_fifo_fill(hci_uart_dev, &c, 1);
uart_irq_tx_enable(hci_uart_dev);
k_sleep( K_MSEC(100) );
}
return 0;
#endif
while (1) {
struct net_buf *buf;
buf = net_buf_get(&rx_queue, K_FOREVER);
#if DT_NODE_HAS_STATUS(TIMESYNC_GPIO, okay)
// ISO RX Measurement Code
const uint8_t * packet = buf->data;
if (packet[0] == H4_ISO){
// TODO: check if packets contains timestamp. all Controller -> Host packets have it so far
// get rx timestamp = sdu sync reference: Packet Type (1) | ISO Header (4) | Timestamp (if TS flag is set)
const uint32_t timestamp_sdu_sync_reference_us = little_endian_read_32(packet, 5);
// ignore empty ISO packets
const uint16_t data_total_length = little_endian_read_16(packet, 3);
if (data_total_length > 8) {
// get current Bluetooth time + toggle GPIO
const uint32_t bluetooth_time_us = toggle_and_get_bluetooth_time_us();
// calculate time of toggle relative to sdu sync reference usually
// - negative for UGT->UGG and
// - positive in UGT->UGG direction)
const int32_t delta_us = (int32_t)(bluetooth_time_us - timestamp_sdu_sync_reference_us);
// convert to string and send over UART and RTT
char delta_string[15];
const uint8_t first_payload_byte = packet[13];
snprintf(delta_string, sizeof(delta_string), "R%+06d@%02X!", delta_us,first_payload_byte);
for (size_t i = 0; delta_string[i] != '\0'; i++) {
uart_poll_out(gmap_uart_dev, delta_string[i]);
}
LOG_INF("Toggle %8u - SDU Sync Reference %8u -> delta %s", bluetooth_time_us, timestamp_sdu_sync_reference_us, delta_string);
}
}
if (packet[0] == H4_EVT) {
if (packet[1] == 0x0e) {
uint16_t opcode = little_endian_read_16(packet, 4);
const uint16_t hci_opcode_reset = 0x0c03;;
if (opcode == hci_opcode_reset) {
LOG_INF("HCI Reset");
last_sdu_sync_ref_us = 0;
sdu_interval_us = 0;
}
const uint16_t hci_opcode_le_read_tx_iso_sync = 0x2061;
if (opcode == hci_opcode_le_read_tx_iso_sync) {
const uint8_t * return_params = &packet[6];
// get current Bluetooth time + toggle GPIO
const uint32_t bluetooth_time_us = toggle_and_get_bluetooth_time_us();
// get current local time
const uint32_t local_time_us = get_local_time_us();
// status: 0
uint16_t handle = little_endian_read_16(return_params, 1);
ARG_UNUSED(handle);
// get packet sequence number (assuming counter == packet_sequence_number & 0xff)
const uint16_t packet_sequence_number = little_endian_read_16(return_params, 3);
// get tx timestamp = sdu sync reference: Packet Type (1) | ISO Header (4) | Timestamp (if TS flag is set)
const uint32_t iso_tx_us = little_endian_read_32(return_params, 5);
// get SDU interval based on sdu sync ref interval (round to the next 100 us)
if (last_sdu_sync_ref_us > 0) {
const uint32_t sdu_interval_reported_us = iso_tx_us - last_sdu_sync_ref_us;
sdu_interval_us = ((sdu_interval_reported_us + 50) / 100 ) * 100;
}
last_sdu_sync_ref_us = iso_tx_us;
// calculate time of toggle relative to sdu sync reference (usually negative as the packet is received before it should be played)
const int32_t delta_us = (int32_t)(bluetooth_time_us - iso_tx_us);
// convert to string and send over UART and RTT
char delta_string[15];
snprintf(delta_string, sizeof(delta_string), "T%+06d@%02X!", delta_us,packet_sequence_number & 0xff);
for (size_t i = 0; delta_string[i] != '\0'; i++) {
uart_poll_out(gmap_uart_dev, delta_string[i]);
}
// LOG_INF("Toggle %8u - TX %8u - %02X-> delta %s", bluetooth_time_us, iso_tx_us, packet_sequence_number, delta_string);
#ifdef PERIPHERAL_TO_CENTRAL_AUDIO_IN_TO_SDU_SYNC_REF
// schedule Audio In to Sync Ref for Peripheral to Central direction
if (sdu_interval_us > 0) {
if (alternate_toggle_state == ALTERNATE_TOGGLE_STATE_IDLE) {
// note: for Peripheral to Central, the SDU Sync Ref matches the ISO TX Timestamp
// calculate next audio in time such that:
// - it's in the future
// - audio_in_us + presentatioon time = sdu_sync_ref
uint32_t audio_in_us = iso_tx_us - PRESENTATION_TIME_US;
while (audio_in_us < (bluetooth_time_us + 10000)) {
audio_in_us += sdu_interval_us;
}
// convert into local time reference
const uint32_t rise_us = local_time_us + (audio_in_us - bluetooth_time_us);
const uint32_t fall_us = rise_us + PRESENTATION_TIME_US;
// LOG_INF("bt %u, tx %u, audio in %u // sdu %u // now %u, rise %u",
// bluetooth_time_us, iso_tx_us, audio_in_us,
// sdu_interval_us,
// local_time_us, rise_us);
alternate_toggle_setup(rise_us, fall_us);
}
}
#endif
}
}
}
#endif
buf = k_fifo_get(&rx_queue, K_FOREVER);
err = h4_send(buf);
if (err) {
LOG_ERR("Failed to send");
+58
View File
@@ -0,0 +1,58 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
# Addedby me
CONFIG_BT_EXT_ADV_MAX_ADV_SET=2
##
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
+58
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
+12
View File
@@ -15,3 +15,15 @@
compatible = "zephyr,cdc-acm-uart";
};
};
/ {
host_interface {
compatible = "gpio-outputs";
status = "okay";
timesync: pin_0 {
gpios = <&gpio1 01 GPIO_ACTIVE_HIGH>;
label = "Controller to host timesync pin";
};
};
};