added softdevice audio sync timer

This commit is contained in:
Dirk Helbig
2024-06-12 13:21:30 +02:00
parent e0fac85885
commit 0d653e4844
7 changed files with 326 additions and 8 deletions

View File

@@ -5,4 +5,12 @@ cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(hci_uart)
target_sources(app PRIVATE src/main.c)
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)
endif()
target_sources(app PRIVATE ${SRCS})

16
Kconfig Normal file
View File

@@ -0,0 +1,16 @@
menu "Modules"
rsource "Kconfig.defaults"
config AUDIO_SYNC_TIMER_USES_RTC
bool
default !BT_LL_ACS_NRF53
select NRFX_RTC0
endmenu
module = AUDIO_SYNC_TIMER
module-str = audio-sync-timer
source "subsys/logging/Kconfig.template.log_config"
source "Kconfig.zephyr"

7
Kconfig.defaults Normal file
View File

@@ -0,0 +1,7 @@
# Audio sync timer
config NRFX_TIMER1
default y
# Audio sync timer
config NRFX_DPPI
default y

View File

@@ -3,6 +3,11 @@
west build -b nrf5340dk_nrf5340_cpuapp -- -DDTC_OVERLAY_FILE=usb.overlay -DOVERLAY_CONFIG=overlay-usb.conf
```
# build for build in serial adapter of the nrf5340_audio_dk a.k. uart0
```sh
west build -b nrf5340_audio_dk_nrf5340_cpuapp
```
# build for uart1 a.k. arduino_serial
```sh
west build -b nrf5340_audio_dk_nrf5340_cpuapp -- -DDTC_OVERLAY_FILE=uart1.overlay

31
src/audio_sync_timer.h Normal file
View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _AUDIO_SYNC_TIMER_H_
#define _AUDIO_SYNC_TIMER_H_
#include <zephyr/kernel.h>
#include <stdint.h>
/**
* @brief Capture a timestamp on the sync timer.
*
* @retval The current timestamp of the audio sync timer.
*/
uint32_t audio_sync_timer_capture(void);
/**
* @brief Returns the last captured value of the sync timer.
*
* The captured time is corresponding to the I2S frame start.
*
* See @ref audio_sync_timer_capture().
*
* @retval The last captured timestamp of the audio sync timer.
*/
uint32_t audio_sync_timer_capture_get(void);
#endif /* _AUDIO_SYNC_TIMER_H_ */

244
src/audio_sync_timer_rtc.c Normal file
View File

@@ -0,0 +1,244 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "audio_sync_timer.h"
#include <zephyr/kernel.h>
#include <zephyr/init.h>
#include <nrfx_dppi.h>
#include <nrfx_i2s.h>
#include <nrfx_ipc.h>
#include <nrfx_rtc.h>
#include <nrfx_timer.h>
#include <nrfx_egu.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(audio_sync_timer, CONFIG_AUDIO_SYNC_TIMER_LOG_LEVEL);
#define AUDIO_SYNC_TIMER_NET_APP_IPC_EVT_CHANNEL 4
#define AUDIO_SYNC_TIMER_NET_APP_IPC_EVT NRF_IPC_EVENT_RECEIVE_4
#define AUDIO_SYNC_HF_TIMER_INSTANCE_NUMBER 1
#define AUDIO_SYNC_HF_TIMER_CURR_TIME_CAPTURE_CHANNEL 1
#define AUDIO_SYNC_HF_TIMER_CURR_TIME_CAPTURE NRF_TIMER_TASK_CAPTURE1
static const nrfx_timer_t audio_sync_hf_timer_instance =
NRFX_TIMER_INSTANCE(AUDIO_SYNC_HF_TIMER_INSTANCE_NUMBER);
#define AUDIO_SYNC_LF_TIMER_INSTANCE_NUMBER 0
#define AUDIO_SYNC_LF_TIMER_CURR_TIME_CAPTURE_CHANNEL 1
#define AUDIO_SYNC_LF_TIMER_CURR_TIME_CAPTURE NRF_RTC_TASK_CAPTURE_1
static uint8_t dppi_channel_curr_time_capture;
static const nrfx_rtc_config_t rtc_cfg = NRFX_RTC_DEFAULT_CONFIG;
static const nrfx_rtc_t audio_sync_lf_timer_instance =
NRFX_RTC_INSTANCE(AUDIO_SYNC_LF_TIMER_INSTANCE_NUMBER);
static uint8_t dppi_channel_timer_sync_with_rtc;
static uint8_t dppi_channel_rtc_start;
static volatile uint32_t num_rtc_overflows;
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 uint32_t timestamp_from_rtc_and_timer_get(uint32_t ticks, uint32_t remainder_us)
{
const uint64_t rtc_ticks_in_femto_units = 30517578125UL;
const uint32_t rtc_overflow_time_us = 512000000UL;
return ((ticks * rtc_ticks_in_femto_units) / 1000000000UL) +
(num_rtc_overflows * rtc_overflow_time_us) +
remainder_us;
}
uint32_t audio_sync_timer_capture(void)
{
/* Ensure that the follow product specification statement is handled:
*
* There is a delay of 6 PCLK16M periods from when the TASKS_CAPTURE[n] is triggered
* until the corresponding CC[n] register is updated.
*
* Lets have a stale value in the CC[n] register and compare that it is different when
* we capture using DPPI.
*
* We ensure it is stale by setting it as the previous tick relative to current
* counter value.
*/
uint32_t tick_stale = nrf_rtc_counter_get(audio_sync_lf_timer_instance.p_reg);
/* Set a stale value in the CC[n] register */
tick_stale--;
nrf_rtc_cc_set(audio_sync_lf_timer_instance.p_reg,
AUDIO_SYNC_LF_TIMER_CURR_TIME_CAPTURE_CHANNEL, tick_stale);
/* Trigger EGU task to capture RTC and TIMER value */
nrf_egu_task_trigger(NRF_EGU0, NRF_EGU_TASK_TRIGGER0);
/* Read captured RTC value */
uint32_t tick = nrf_rtc_cc_get(audio_sync_lf_timer_instance.p_reg,
AUDIO_SYNC_LF_TIMER_CURR_TIME_CAPTURE_CHANNEL);
/* If required, wait until CC[n] register is updated */
while (tick == tick_stale) {
tick = nrf_rtc_cc_get(audio_sync_lf_timer_instance.p_reg,
AUDIO_SYNC_LF_TIMER_CURR_TIME_CAPTURE_CHANNEL);
}
/* Read captured TIMER value */
uint32_t remainder_us = nrf_timer_cc_get(NRF_TIMER1,
AUDIO_SYNC_HF_TIMER_CURR_TIME_CAPTURE_CHANNEL);
return timestamp_from_rtc_and_timer_get(tick, remainder_us);
}
//#include <stdio.h>
//#include "led.h"
//bool led_status = false;
static void unused_timer_isr_handler(nrf_timer_event_t event_type, void *p_context)
{
ARG_UNUSED(event_type);
ARG_UNUSED(p_context);
#if 0
if(event_type == NRF_TIMER_EVENT_COMPARE2)
{
if( led_status ){
led_off( LED_APP_3_GREEN );
led_status = false;
} else {
led_on( LED_APP_3_GREEN );
led_status = true;
}
// char * p_msg = p_context;
// uint32_t remainder_us = nrf_timer_cc_get(NRF_TIMER1,
// AUDIO_SYNC_HF_TIMER_CURR_TIME_CAPTURE_CHANNEL);
// LOG_INF("%d\n", remainder_us );
// printf("Timer finished. Context passed to the handler: >%s<", p_msg);
}
#endif
}
static void rtc_isr_handler(nrfx_rtc_int_type_t int_type)
{
if (int_type == NRFX_RTC_INT_OVERFLOW) {
num_rtc_overflows++;
}
}
/**
* @brief Initialize audio sync timer
*
* @note The audio sync timers is replicating the controller's clock.
* The controller starts or clears the sync timer using a PPI signal
* sent from the controller. This makes the two clocks synchronized.
*
* @return 0 if successful, error otherwise
*/
static int audio_sync_timer_init(void)
{
nrfx_err_t ret;
ret = nrfx_timer_init(&audio_sync_hf_timer_instance, &cfg, unused_timer_isr_handler);
if (ret - NRFX_ERROR_BASE_NUM) {
LOG_ERR("nrfx timer init error: %d", ret);
return -ENODEV;
}
ret = nrfx_rtc_init(&audio_sync_lf_timer_instance, &rtc_cfg, rtc_isr_handler);
if (ret - NRFX_ERROR_BASE_NUM) {
LOG_ERR("nrfx rtc init error: %d", ret);
return -ENODEV;
}
IRQ_CONNECT(RTC0_IRQn, IRQ_PRIO_LOWEST, nrfx_rtc_0_irq_handler, NULL, 0);
nrfx_rtc_overflow_enable(&audio_sync_lf_timer_instance, true);
/* Initialize capturing of current timestamps */
ret = nrfx_dppi_channel_alloc(&dppi_channel_curr_time_capture);
if (ret - NRFX_ERROR_BASE_NUM) {
LOG_ERR("nrfx DPPI channel alloc error (current time capture) - Return value: %d", ret);
return -ENOMEM;
}
nrf_rtc_subscribe_set(audio_sync_lf_timer_instance.p_reg,
AUDIO_SYNC_LF_TIMER_CURR_TIME_CAPTURE,
dppi_channel_curr_time_capture);
nrf_timer_subscribe_set(audio_sync_hf_timer_instance.p_reg,
AUDIO_SYNC_HF_TIMER_CURR_TIME_CAPTURE,
dppi_channel_curr_time_capture);
nrf_egu_publish_set(NRF_EGU0, NRF_EGU_EVENT_TRIGGERED0, dppi_channel_curr_time_capture);
ret = nrfx_dppi_channel_enable(dppi_channel_curr_time_capture);
if (ret - NRFX_ERROR_BASE_NUM) {
LOG_ERR("nrfx DPPI channel enable error (current time capture) - Return value: %d", ret);
return -EIO;
}
/* Initialize functionality for synchronization between APP and NET core */
ret = nrfx_dppi_channel_alloc(&dppi_channel_rtc_start);
if (ret - NRFX_ERROR_BASE_NUM) {
LOG_ERR("nrfx DPPI channel alloc error (timer clear): %d", ret);
return -ENOMEM;
}
nrf_rtc_subscribe_set(audio_sync_lf_timer_instance.p_reg, NRF_RTC_TASK_START,
dppi_channel_rtc_start);
nrf_timer_subscribe_set(audio_sync_hf_timer_instance.p_reg, NRF_TIMER_TASK_START,
dppi_channel_rtc_start);
nrf_ipc_receive_config_set(NRF_IPC, AUDIO_SYNC_TIMER_NET_APP_IPC_EVT_CHANNEL,
NRF_IPC_CHANNEL_4);
nrf_ipc_publish_set(NRF_IPC, AUDIO_SYNC_TIMER_NET_APP_IPC_EVT, dppi_channel_rtc_start);
ret = nrfx_dppi_channel_enable(dppi_channel_rtc_start);
if (ret - NRFX_ERROR_BASE_NUM) {
LOG_ERR("nrfx DPPI channel enable error (timer clear): %d", ret);
return -EIO;
}
/* Initialize functionality for synchronization between RTC and TIMER */
ret = nrfx_dppi_channel_alloc(&dppi_channel_timer_sync_with_rtc);
if (ret - NRFX_ERROR_BASE_NUM) {
LOG_ERR("nrfx DPPI channel alloc error (timer clear): %d", ret);
return -ENOMEM;
}
nrf_rtc_publish_set(audio_sync_lf_timer_instance.p_reg, NRF_RTC_EVENT_TICK,
dppi_channel_timer_sync_with_rtc);
nrf_timer_subscribe_set(audio_sync_hf_timer_instance.p_reg, NRF_TIMER_TASK_CLEAR,
dppi_channel_timer_sync_with_rtc);
nrfx_rtc_tick_enable(&audio_sync_lf_timer_instance, false);
ret = nrfx_dppi_channel_enable(dppi_channel_timer_sync_with_rtc);
if (ret - NRFX_ERROR_BASE_NUM) {
LOG_ERR("nrfx DPPI channel enable error (timer clear): %d", ret);
return -EIO;
}
#if 0
#define TIME_TO_WAIT_MS UINT32_C(2)
// uint32_t desired_ticks = nrfx_timer_ms_to_ticks(&audio_sync_hf_timer_instance, TIME_TO_WAIT_MS);
uint32_t desired_ticks = 0;
nrfx_timer_compare(&audio_sync_hf_timer_instance, NRF_TIMER_CC_CHANNEL2, desired_ticks, true);
IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_TIMER_INST_GET(AUDIO_SYNC_HF_TIMER_INSTANCE_NUMBER)), IRQ_PRIO_LOWEST,
NRFX_TIMER_INST_HANDLER_GET(AUDIO_SYNC_HF_TIMER_INSTANCE_NUMBER), 0, 0);
nrfx_timer_enable(&audio_sync_hf_timer_instance);
#endif
LOG_DBG("Audio sync timer initialized");
return 0;
}
SYS_INIT(audio_sync_timer_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);

View File

@@ -29,6 +29,8 @@
#include <zephyr/bluetooth/buf.h>
#include <zephyr/bluetooth/hci_raw.h>
#include "audio_sync_timer.h"
#define LOG_MODULE_NAME hci_uart
LOG_MODULE_REGISTER(LOG_MODULE_NAME);
@@ -356,30 +358,35 @@ SYS_INIT(hci_uart_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);
#define HCI_CMD_ISO_TIMESYNC (0x100)
struct hci_cmd_iso_timestamp_response {
struct bt_hci_evt_cc_status cc;
uint32_t timestamp;
} __packed;
#include <zephyr/drivers/bluetooth/hci_driver.h>
uint8_t hci_cmd_iso_timesync_cb(struct net_buf *buf)
{
struct net_buf *rsp;
struct bt_hci_evt_cc_status *cc;
struct hci_cmd_iso_timestamp_response *response;
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]);
rsp = bt_hci_cmd_complete_create(BT_OP(BT_OGF_VS, HCI_CMD_ISO_TIMESYNC), sizeof(*cc));
cc = net_buf_add(rsp, sizeof(*cc));
cc->status = BT_HCI_ERR_SUCCESS;
rsp = bt_hci_cmd_complete_create(BT_OP(BT_OGF_VS, HCI_CMD_ISO_TIMESYNC), sizeof(*response));
response = net_buf_add(rsp, sizeof(*response));
response->cc.status = BT_HCI_ERR_SUCCESS;
response->timestamp = audio_sync_timer_capture();
if (IS_ENABLED(CONFIG_BT_HCI_RAW_H4)) {
net_buf_push_u8(rsp, H4_EVT);
}
h4_send( rsp );
h4_send( rsp );
// gpio_pin_configure_dt(&led, (buf->data[0] != 0) ? GPIO_OUTPUT_ACTIVE : GPIO_OUTPUT_INACTIVE);
return BT_HCI_ERR_SUCCESS;
return BT_HCI_ERR_EXT_HANDLED;
}
int main(void)