diff --git a/CMakeLists.txt b/CMakeLists.txt index 51a2518..2a5e458 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}) diff --git a/Kconfig b/Kconfig new file mode 100644 index 0000000..1e3a9a6 --- /dev/null +++ b/Kconfig @@ -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" diff --git a/Kconfig.defaults b/Kconfig.defaults new file mode 100644 index 0000000..f90f240 --- /dev/null +++ b/Kconfig.defaults @@ -0,0 +1,7 @@ +# Audio sync timer +config NRFX_TIMER1 + default y + +# Audio sync timer +config NRFX_DPPI + default y diff --git a/README.md b/README.md index 4ea5f67..a56573a 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/audio_sync_timer.h b/src/audio_sync_timer.h new file mode 100644 index 0000000..90f0da0 --- /dev/null +++ b/src/audio_sync_timer.h @@ -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 +#include + +/** + * @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_ */ diff --git a/src/audio_sync_timer_rtc.c b/src/audio_sync_timer_rtc.c new file mode 100644 index 0000000..a3c3862 --- /dev/null +++ b/src/audio_sync_timer_rtc.c @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "audio_sync_timer.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +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 +//#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); diff --git a/src/main.c b/src/main.c index 3fb250d..b778bc3 100644 --- a/src/main.c +++ b/src/main.c @@ -29,6 +29,8 @@ #include #include +#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 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)