21 Commits

Author SHA1 Message Date
Matthias Ringwald
47b59a5b55 Fix merge errors 2024-12-04 15:10:35 +01:00
Matthias Ringwald
b7743a81cb Reset last sdu and sdu interval on HCI Reset 2024-12-04 15:06:34 +01:00
Matthias Ringwald
5d95f344dc Rework alternate toggle api, raise alternate gpio at audio in and lower on sdu sync ref with 60ms presentation time 2024-12-04 15:06:34 +01:00
Matthias Ringwald
6c915fd547 Ignore empty RX ISO packets 2024-12-04 15:06:34 +01:00
Matthias Ringwald
1a9c024c92 Toggle and send 'B' for each ISO TX packet 2024-12-04 15:06:34 +01:00
Matthias Ringwald
280bfc25b4 Less debug output 2024-12-04 15:06:00 +01:00
Matthias Ringwald
9d475bda31 Schedule Alternate Toggle pulse for SDU Sync Ref to Audio Out tests 2024-12-04 15:06:00 +01:00
Matthias Ringwald
8ce9f72f4a try to configure alternate toggle pin and toggle forever 2024-12-04 15:06:00 +01:00
Matthias Ringwald
1189945df0 Implement logic to setup SDU Sync Ref (high) and Audio Out (low) ISR 2024-12-04 15:06:00 +01:00
Matthias Ringwald
9d82bf91e3 main: support for more gpios and timers 2024-12-04 15:06:00 +01:00
Matthias Ringwald
f54d1d3572 Update prj.conf to use timer 2024-12-04 15:06:00 +01:00
Dirk Helbig
7f9db04724 add missing overlay entry for alternate gpio 2024-12-04 15:06:00 +01:00
Matthias Ringwald
557e25897e Toggle on HCI Event Complete for LE Read ISO TX Sync and write out delta. 2024-12-04 15:06:00 +01:00
Matthias Ringwald
634c5d21cd Write packet counter (first payload byte) over UART 2024-12-04 15:06:00 +01:00
Matthias Ringwald
5b81df6507 Verify timestamp from audio sync timer capture - might be a hardware race condition 2024-12-04 15:06:00 +01:00
Matthias Ringwald
9b3b53fd3c Write GPIO Toggle time relative to SDU Sync Reference over UART 2024-12-04 15:06:00 +01:00
Matthias Ringwald
ce91c33272 toggle gpio on every received iso packet, report timestamp of packet and toggle over rtt 2024-12-04 15:06:00 +01:00
Matthias Ringwald
427db6845b Block interrupts between time capture and toggle, verify time capture 2024-12-04 14:58:37 +01:00
Matthias Ringwald
2a0d2366b8 Enable SoftDevice support for Periodic Advertising Sync 2024-12-03 12:25:33 +01:00
Matthias Ringwald
0e041f1967 Update readme 2024-12-03 12:22:10 +01:00
Dirk Helbig
87bddccdd5 updated code for nRF Connect SDK v2.8.0 2024-12-03 12:22:10 +01:00
5 changed files with 334 additions and 42 deletions

View File

@@ -1,9 +1,14 @@
# nRF5340 Bluetooth Controller with Support for LE ISO Timesync
This fork of the Zephyr HCI UART example adds a custom HCI Command to allow for timesync between the Bluetooth Host and the Bluetooth Controller. When the HCI LE Read ISO Clock command is received, the Controller toggles a GPIO and returns its current Bluetooth LE ISO Clock in the Command Complete Event.
This fork of the Zephyr HCI UART example adds a custom HCI Command to allow for timesync between the Bluetooth Host
and the Bluetooth Controller. When the HCI LE Read ISO Clock command is received, the Controller toggles a GPIO and
returns its current Bluetooth LE ISO Clock in the Command Complete Event.
It has been tested on the nRF5340 Audio DK, but it should work with any nRF5340 dev kit.
## Requirements
- nRF Connect SDK v2.8 or newer
## HCI LE Read ISO Clock Command
- OGF: 0x3f, OCF: 0x200
- Parameters: Flags (1 Octet) unused
@@ -15,7 +20,7 @@ It has been tested on the nRF5340 Audio DK, but it should work with any nRF5340
### HCI over USB CDC
```sh
west build -b nrf5340dk_nrf5340_cpuapp -- -DEXTRA_DTC_OVERLAY_FILE=usb.overlay -DOVERLAY_CONFIG=overlay-usb.conf
west build -b nrf5340dk/nrf5340/cpuapp -- -DEXTRA_DTC_OVERLAY_FILE=usb.overlay -DOVERLAY_CONFIG=overlay-usb.conf
```
_HCI over UART and timesync not tested._
@@ -25,25 +30,25 @@ _HCI over UART and timesync not tested._
### HCI over USB CDC
```sh
west build -b nrf5340_audio_dk_nrf5340_cpuapp -- -DEXTRA_DTC_OVERLAY_FILE=usb.overlay -DOVERLAY_CONFIG=overlay-usb.conf
west build --pristine -b nrf5340_audio_dk/nrf5340/cpuapp -- -DEXTRA_DTC_OVERLAY_FILE=usb.overlay -DOVERLAY_CONFIG=overlay-usb.conf
```
### HCI over UART 0 connected to Virtual UART in J-Link Probe
```sh
west build -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
Release build:
```sh
west build -b nrf5340_audio_dk_nrf5340_cpuapp -- -DEXTRA_DTC_OVERLAY_FILE=uart1.overlay
west build --pristine -b nrf5340_audio_dk/nrf5340/cpuapp -- -DEXTRA_DTC_OVERLAY_FILE=uart1.overlay
```
Debug build:
```sh
west build -b nrf5340_audio_dk_nrf5340_cpuapp -- -DEXTRA_DTC_OVERLAY_FILE=uart1.overlay -DOVERLAY_CONFIG=debug.conf
west build --pristine -b nrf5340_audio_dk/nrf5340/cpuapp -- -DEXTRA_DTC_OVERLAY_FILE=uart1.overlay -DOVERLAY_CONFIG=debug.conf
```
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.

View File

@@ -6,7 +6,11 @@
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 {

View File

@@ -19,6 +19,7 @@ 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 two links as a central, or one link as a peripheral
CONFIG_BT_MAX_CONN=8

View File

@@ -41,3 +41,4 @@ CONFIG_BT_BUF_CMD_TX_COUNT=10
# for the timesync command
CONFIG_BT_HCI_RAW_CMD_EXT=y
CONFIG_NRFX_TIMER2=y

View File

@@ -24,20 +24,22 @@
#include <zephyr/usb/usb_device.h>
#include <zephyr/net/buf.h>
#include <zephyr/net_buf.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/l2cap.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/buf.h>
#include <zephyr/bluetooth/hci_raw.h>
#include <nrfx_timer.h>
#include "audio_sync_timer.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 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 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);
@@ -66,6 +68,30 @@ 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);
@@ -183,7 +209,7 @@ static void rx_isr(void)
if (remaining == 0) {
/* Packet received */
LOG_DBG("putting RX packet in queue.");
net_buf_put(&tx_queue, buf);
k_fifo_put(&tx_queue, buf);
state = ST_IDLE;
}
break;
@@ -216,7 +242,7 @@ static void tx_isr(void)
int len;
if (!buf) {
buf = net_buf_get(&uart_tx_queue, K_NO_WAIT);
buf = k_fifo_get(&uart_tx_queue, K_NO_WAIT);
if (!buf) {
uart_irq_tx_disable(hci_uart_dev);
return;
@@ -257,15 +283,24 @@ static void tx_thread(void *p1, void *p2, void *p3)
int err;
/* Wait until a buffer is available */
buf = net_buf_get(&tx_queue, K_FOREVER);
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) {
if (err!=BT_HCI_ERR_EXT_HANDLED) {
LOG_ERR("Unable to send (err %d)", err);
if (err!=BT_HCI_ERR_SUCCESS) {
if (err!=BT_HCI_ERR_EXT_HANDLED) {
LOG_ERR("Unable to send (err %d)", err);
}
net_buf_unref(buf);
}
net_buf_unref(buf);
}
/* Give other threads a chance to run if tx_queue keeps getting
* new data all the time.
@@ -279,7 +314,7 @@ static int h4_send(struct net_buf *buf)
LOG_DBG("buf %p type %u len %u", buf, bt_buf_get_type(buf),
buf->len);
net_buf_put(&uart_tx_queue, buf);
k_fifo_put(&uart_tx_queue, buf);
uart_irq_tx_enable(hci_uart_dev);
return 0;
@@ -361,13 +396,6 @@ 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 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)
@@ -384,15 +412,38 @@ 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]);
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();
uint32_t timestamp_second_us;
// Lock interrupts to avoid interrupt between time capture and gpio toggle
uint32_t key = arch_irq_lock();
// Get current time
uint32_t timestamp_first_us = audio_sync_timer_capture();
while (1){
// get time again and verify that time didn't jump. Work around:
// https://devzone.nordicsemi.com/f/nordic-q-a/116907/bluetooth-netcore-time-capture-not-working-100-for-le-audio
timestamp_second_us = audio_sync_timer_capture();
int32_t timestamp_delta = (int32_t) (timestamp_second_us - timestamp_first_us);
if (timestamp_delta < 10){
break;
}
timestamp_first_us = timestamp_second_us;
}
#if DT_NODE_HAS_STATUS(TIMESYNC_GPIO, okay)
gpio_pin_toggle_dt( &timesync_pin );
#endif
// Unlock interrupts
arch_irq_unlock(key);
// emit event
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 = timestamp_second_us;
if (IS_ENABLED(CONFIG_BT_HCI_RAW_H4)) {
net_buf_push_u8(rsp, H4_EVT);
}
@@ -403,14 +454,135 @@ 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, "UART device is NULL");
__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;
}
/* Enable the raw interface, this will in turn open the HCI driver */
bt_enable_raw(&rx_queue);
@@ -453,6 +625,10 @@ 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
@@ -464,20 +640,125 @@ int main(void)
NULL, NULL, NULL, K_PRIO_COOP(7), 0, K_NO_WAIT);
k_thread_name_set(&tx_thread_data, "HCI uart TX");
#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) {
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
err = h4_send(buf);
if (err) {
LOG_ERR("Failed to send");