added softdevice audio sync timer
This commit is contained in:
@@ -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
16
Kconfig
Normal 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
7
Kconfig.defaults
Normal file
@@ -0,0 +1,7 @@
|
||||
# Audio sync timer
|
||||
config NRFX_TIMER1
|
||||
default y
|
||||
|
||||
# Audio sync timer
|
||||
config NRFX_DPPI
|
||||
default y
|
||||
@@ -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
31
src/audio_sync_timer.h
Normal 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
244
src/audio_sync_timer_rtc.c
Normal 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);
|
||||
21
src/main.c
21
src/main.c
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user