From 564b47b01641177397b92f85436c9e51050c5aec Mon Sep 17 00:00:00 2001 From: Rubin Gerritsen Date: Fri, 14 Jun 2024 13:28:16 +0200 Subject: [PATCH 1/2] Bluetooth: Add "Radio Notification callback" feature The Radio Notification callback feature replaces the radio notification feature. It can be used to synchronize data sampling with Bluetooth connection events. The old feature had several weaknesses: - It was not possible to know which connection the notification belonged to. - The trigger also happened upon flash access. - The time from trigger until event start was fixed. That is, not configurable per use case. The new feature is built on top of the connection event trigger feature and is therefore able to known which connection a given notification belongs to. The feature is marked as experimental. Currently this feature will cancels out peripheral latency which we indent to change. Signed-off-by: Rubin Gerritsen --- doc/nrf/protocols/bt/ble/images/ConnEvtCb.svg | 119 +++++++++++ .../ble/images/ConnEvtCbPeripheralLatency.svg | 124 +++++++++++ .../bt/ble/radio_notification_conn_cb.rst | 39 ++++ doc/nrf/protocols/bt/index.rst | 1 + include/bluetooth/radio_notification_cb.h | 95 +++++++++ subsys/bluetooth/Kconfig | 1 + .../bluetooth/host_extensions/CMakeLists.txt | 1 + subsys/bluetooth/host_extensions/Kconfig | 76 +++++++ .../radio_notification_conn_cb.c | 200 ++++++++++++++++++ 9 files changed, 656 insertions(+) create mode 100644 doc/nrf/protocols/bt/ble/images/ConnEvtCb.svg create mode 100644 doc/nrf/protocols/bt/ble/images/ConnEvtCbPeripheralLatency.svg create mode 100644 doc/nrf/protocols/bt/ble/radio_notification_conn_cb.rst create mode 100644 include/bluetooth/radio_notification_cb.h create mode 100644 subsys/bluetooth/host_extensions/Kconfig create mode 100644 subsys/bluetooth/host_extensions/radio_notification_conn_cb.c diff --git a/doc/nrf/protocols/bt/ble/images/ConnEvtCb.svg b/doc/nrf/protocols/bt/ble/images/ConnEvtCb.svg new file mode 100644 index 000000000000..042c3a76c39f --- /dev/null +++ b/doc/nrf/protocols/bt/ble/images/ConnEvtCb.svg @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Page-1 + + + Sheet.1 + + + + Sheet.2 + + + + Sheet.4 + Callback + + + + Callback + + Sheet.7 + Connection event + + + + Connection event + + Sheet.9 + Prepare_distance_us + + + + + Prepare_distance_us + + Sheet.10 + + + + Sheet.11 + Connection event + + + + Connection event + + Sheet.12 + Prepare_distance_us + + + + + Prepare_distance_us + + Sheet.13 + Callback + + + + Callback + + Sheet.24 + Connection interval + + + + + Connection interval + + diff --git a/doc/nrf/protocols/bt/ble/images/ConnEvtCbPeripheralLatency.svg b/doc/nrf/protocols/bt/ble/images/ConnEvtCbPeripheralLatency.svg new file mode 100644 index 000000000000..4970a1f06a62 --- /dev/null +++ b/doc/nrf/protocols/bt/ble/images/ConnEvtCbPeripheralLatency.svg @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Page-2 + + + Sheet.1 + + + + Sheet.3 + + + + Sheet.12 + + + + Sheet.13 + + + + Sheet.14 + + + + Sheet.16 + Data provided in callback + + + + Data provided in callback + + Sheet.17 + + + + Sheet.18 + + + + Sheet.19 + + + + Sheet.20 + + + + Sheet.21 + + + + Sheet.22 + + + + Sheet.23 + + + + Sheet.24 + + + + Sheet.25 + + + + Sheet.28 + interval + + + + + interval + + diff --git a/doc/nrf/protocols/bt/ble/radio_notification_conn_cb.rst b/doc/nrf/protocols/bt/ble/radio_notification_conn_cb.rst new file mode 100644 index 000000000000..6da938719646 --- /dev/null +++ b/doc/nrf/protocols/bt/ble/radio_notification_conn_cb.rst @@ -0,0 +1,39 @@ +.. _ug_radio_notification_conn_cb: + +Radio Notification callback +########################### + +The Bluetooth Radio Notification connection callback feature is used to inform the application about upcoming radio activity for a Bluetooth connection. +The :c:member:`bt_radio_notification_conn_cb.prepare` callback is triggered right before a Bluetooth connection event. +This allows the application to trigger sensor data collection for transmission during the upcoming connection event. + +.. figure:: images/ConnEvtCb.svg + :alt: Radio Notification callback + + The prepare callback is called before a connection event + +The timing of the prepare callback is configured with the parameter ``prepare_time_us``. +You must set the parameter value large enough to allow the Bluetooth stack to enqueue a data packet for the upcoming connection event. + +The :ref:`ble_radio_notification_conn_cb` sample demonstrates how you can use this feature to reduce the latency between data sampling and data transmission. + +You can use this feature only with the :ref:`SoftDevice Controller ` and only when the Bluetooth controller and application are located on the same core. + +Radio Notification connection callback with peripheral latency +************************************************************** + +When a connection is configured with peripheral latency, the peripheral device may sleep for ``peripheral_latency`` consecutive connection events to save power. +The device may wake up more often if, for example, it has data to send. +The central device assumes that the peripheral will wake up at every connection event. + +The :c:member:`ble_radio_notification_conn_cb.prepare` callback will be called before every connection event for both the peripheral and central device. +If the peripheral application provides data to be sent in this callback, the peripheral device will wake up for that given connection event. + +.. figure:: images/ConnEvtCbPeripheralLatency.svg + :alt: Radio Notification callback with peripheral latency + + The peripheral wakes up if data is provided in a prepare callback + +For this configuration, the application must configure the prepare callback to trigger at least :c:macro:`BT_RADIO_NOTIFICATION_CONN_CB_PREPARE_DISTANCE_US_RECOMMENDED` before the connection event starts. +This allows the Bluetooth stack to start the high frequency clock and radio peripheral if they are not already running. +If the data is provided too close to the start of the connection event, the data will be sent in upcoming events. diff --git a/doc/nrf/protocols/bt/index.rst b/doc/nrf/protocols/bt/index.rst index c69fba4669fd..932bebdecc99 100644 --- a/doc/nrf/protocols/bt/index.rst +++ b/doc/nrf/protocols/bt/index.rst @@ -12,5 +12,6 @@ To enable Bluetooth LE in your application, you can use the standard HCI-based a :caption: Subpages: ble/index.rst + ble/radio_notification_conn_cb.rst bt_mesh/index.rst bt_qualification/index.rst diff --git a/include/bluetooth/radio_notification_cb.h b/include/bluetooth/radio_notification_cb.h new file mode 100644 index 000000000000..718aeb698649 --- /dev/null +++ b/include/bluetooth/radio_notification_cb.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef BT_RADIO_NOTIFICATION_CB_H__ +#define BT_RADIO_NOTIFICATION_CB_H__ + +/** + * @file + * @defgroup bt_radio_notification_cb Radio Notification callback + * @{ + * @brief APIs to set up radio notification callbacks + * + * The radio notification callbacks are triggered relative to the + * start of a Link Layer event. + * + * This information can be used to reduce the time from data sampling + * until data is sent on air. + */ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Recommended prepare distance for the connection event callback. + * + * The selected distance assumes a maximum processing delay and a maximum + * of 2 seconds between packets from devices with worst case clock accuracy. + * + * When shorter connection intervals are used or when it known that the local + * clock accuracy is better a shorter prepare distance can be used. + * + * See @ref bt_radio_notification_conn_cb_register for more details about + * prepare distance selection and clock drift. + */ +#define BT_RADIO_NOTIFICATION_CONN_CB_PREPARE_DISTANCE_US_RECOMMENDED 3000 + +/** Radio Notification connection callbacks + * + * The callbacks will be called from the system workqueue. + */ +struct bt_radio_notification_conn_cb { + /** Radio Notification prepare callback + * + * The prepare callback will be triggered a configurable amount + * of time before the connection event starts. + * + * @param[in] conn The connection context. + */ + void (*prepare)(struct bt_conn *conn); +}; + +/** Register a radio notification callback struct for connections. + * + * When the prepare callback is used to provide data to the Bluetooth stack, + * the application needs to reserve enough time to allow the data to be + * forwarded to the link layer. + * + * When used with a peripheral connection, the prepare distance also needs to take + * clock drift into account to avoid that the callback triggers too late. + * The clock drift is determined by the distance between packets and the clock + * accuracy of both the central and the peripheral. + * The Bluetooth specification allows a worst case clock accurary of 500 ppm. + * That gives a worst case combined clock accurary of 1000 ppm. + * This results in 1 ms drift per second. + + * See Bluetooth Core_v5.4, Vol 6, Part 4.2.4 for more details on clock drift. + * + * @param[in] cb The callback structure to be used. + * The memory pointed to needs to be kept alive by the user. + * @param[in] prepare_distance_us The distance in time from the start of the + * prepare callback to the start of the connection event. + * See also @ref BT_RADIO_NOTIFICATION_CONN_CB_PREPARE_DISTANCE_US_RECOMMENDED. + * + * @retval 0 The callback structure has been successfully registered. + * @retval -EALREADY A callback has already been registered. + * @retval -ENOMEM There are not enough PPI channels available to use this feature. + */ +int bt_radio_notification_conn_cb_register(const struct bt_radio_notification_conn_cb *cb, + uint32_t prepare_distance_us); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* BT_RADIO_NOTIFICATION_CB_H__ */ diff --git a/subsys/bluetooth/Kconfig b/subsys/bluetooth/Kconfig index 63f4aac51e05..d7ae1014ca54 100644 --- a/subsys/bluetooth/Kconfig +++ b/subsys/bluetooth/Kconfig @@ -38,6 +38,7 @@ config BT_LL_SOFTDEVICE_HEADERS_INCLUDE if BT_CTLR rsource "controller/Kconfig" endif +rsource "host_extensions/Kconfig" comment "BLE Libraries" rsource "Kconfig.pool" diff --git a/subsys/bluetooth/host_extensions/CMakeLists.txt b/subsys/bluetooth/host_extensions/CMakeLists.txt index c459dc1846cd..a3ecea60da39 100644 --- a/subsys/bluetooth/host_extensions/CMakeLists.txt +++ b/subsys/bluetooth/host_extensions/CMakeLists.txt @@ -5,3 +5,4 @@ # zephyr_sources(host_extensions.c) +zephyr_sources_ifdef(CONFIG_BT_RADIO_NOTIFICATION_CONN_CB radio_notification_conn_cb.c) diff --git a/subsys/bluetooth/host_extensions/Kconfig b/subsys/bluetooth/host_extensions/Kconfig new file mode 100644 index 000000000000..59d9c3ecaed6 --- /dev/null +++ b/subsys/bluetooth/host_extensions/Kconfig @@ -0,0 +1,76 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menuconfig BT_RADIO_NOTIFICATION_CONN_CB + bool "Radio Notification connection callback [EXPERIMENTAL]" + depends on BT_LL_SOFTDEVICE + select BT_CTLR_SDC_EVENT_TRIGGER + select NRFX_PPI if HAS_HW_NRF_PPI + select NRFX_DPPI if HAS_HW_NRF_DPPIC + select NRFX_EGU0 + select EXPERIMENTAL + help + Enables the support for setting up a radio notification + callback to trigger a configurable amount of time before the + connection event starts. + This feature can be used to synchronize data sampling + with on-air data transmission. + +if BT_RADIO_NOTIFICATION_CONN_CB + +choice BT_RADIO_NOTIFICATION_CONN_CB_EGU_INST + bool "Radio Notification connection callback EGU instance" + help + Radio Notification connection callback implementation uses + an EGU instance. This config selects which instance to be + used. + +config BT_RADIO_NOTIFICATION_CONN_CB_EGU_INST_EGU0 + bool "Use EGU0" + depends on $(dt_nodelabel_has_compat,egu0,$(DT_COMPAT_NORDIC_NRF_EGU)) + select NRFX_EGU0 + +config BT_RADIO_NOTIFICATION_CONN_CB_EGU_INST_EGU1 + bool "Use EGU1" + depends on $(dt_nodelabel_has_compat,egu1,$(DT_COMPAT_NORDIC_NRF_EGU)) + select NRFX_EGU1 + +config BT_RADIO_NOTIFICATION_CONN_CB_EGU_INST_EGU2 + bool "Use EGU2" + depends on $(dt_nodelabel_has_compat,egu2,$(DT_COMPAT_NORDIC_NRF_EGU)) + select NRFX_EGU2 + +config BT_RADIO_NOTIFICATION_CONN_CB_EGU_INST_EGU3 + bool "Use EGU3" + depends on $(dt_nodelabel_has_compat,egu3,$(DT_COMPAT_NORDIC_NRF_EGU)) + select NRFX_EGU3 + +config BT_RADIO_NOTIFICATION_CONN_CB_EGU_INST_EGU4 + bool "Use EGU3" + depends on $(dt_nodelabel_has_compat,egu4,$(DT_COMPAT_NORDIC_NRF_EGU)) + select NRFX_EGU4 + +config BT_RADIO_NOTIFICATION_CONN_CB_EGU_INST_EGU5 + bool "Use EGU5" + depends on $(dt_nodelabel_has_compat,egu5,$(DT_COMPAT_NORDIC_NRF_EGU)) + select NRFX_EGU5 + +config BT_RADIO_NOTIFICATION_CONN_CB_EGU_INST_EGU020 + bool "Use EGU020" + depends on $(dt_nodelabel_has_compat,egu020,$(DT_COMPAT_NORDIC_NRF_EGU)) + select NRFX_EGU020 + +endchoice + +config BT_RADIO_NOTIFICATION_CONN_CB_EGU_TRIGGER_ISR_PRIO + int + depends on BT_RADIO_NOTIFICATION_CONN_CB + default 2 + help + The ISR priority of the connection event trigger + used to setup the timer for the callback. + +endif diff --git a/subsys/bluetooth/host_extensions/radio_notification_conn_cb.c b/subsys/bluetooth/host_extensions/radio_notification_conn_cb.c new file mode 100644 index 000000000000..6de8afc71cc3 --- /dev/null +++ b/subsys/bluetooth/host_extensions/radio_notification_conn_cb.c @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include + +#include + +#if defined(CONFIG_BT_RADIO_NOTIFICATION_CONN_CB_EGU_INST_EGU0) +#define EGU_INST_IDX 0 +#elif defined(CONFIG_BT_RADIO_NOTIFICATION_CONN_CB_EGU_INST_EGU1) +#define EGU_INST_IDX 1 +#elif defined(CONFIG_BT_RADIO_NOTIFICATION_CONN_CB_EGU_INST_EGU2) +#define EGU_INST_IDX 2 +#elif defined(CONFIG_BT_RADIO_NOTIFICATION_CONN_CB_EGU_INST_EGU3) +#define EGU_INST_IDX 3 +#elif defined(CONFIG_BT_RADIO_NOTIFICATION_CONN_CB_EGU_INST_EGU4) +#define EGU_INST_IDX 4 +#elif defined(CONFIG_BT_RADIO_NOTIFICATION_CONN_CB_EGU_INST_EGU5) +#define EGU_INST_IDX 5 +#elif defined(CONFIG_BT_RADIO_NOTIFICATION_CONN_CB_EGU_INST_EGU020) +#define EGU_INST_IDX 020 +#else +#error "Unknown EGU instance" +#endif + +static nrfx_egu_t egu_inst = NRFX_EGU_INSTANCE(EGU_INST_IDX); +static uint32_t configured_prepare_distance_us; +static const struct bt_radio_notification_conn_cb *registered_cb; + +static struct bt_conn *conn_pointers[CONFIG_BT_MAX_CONN]; +static uint8_t ppi_channels[CONFIG_BT_MAX_CONN]; +static struct k_work_delayable work_queue_items[CONFIG_BT_MAX_CONN]; + +static uint32_t conn_interval_us_get(const struct bt_conn *conn) +{ + struct bt_conn_info info; + + if (bt_conn_get_info(conn, &info)) { + return 0; + } + + return BT_CONN_INTERVAL_TO_US(info.le.interval); +} + +static void work_handler(struct k_work *work) +{ + struct bt_conn_info info; + uint8_t index = ARRAY_INDEX(work_queue_items, work); + struct bt_conn *conn = conn_pointers[index]; + + if (bt_conn_get_info(conn, &info)) { + return; + } + + if (info.state == BT_CONN_STATE_CONNECTED) { + registered_cb->prepare(conn); + + uint32_t conn_interval_us = conn_interval_us_get(conn); + + /* Schedule the callback in case the connection event does not occur because + * of peripheral latency or scheduling conflicts. + */ + k_work_reschedule(&work_queue_items[index], K_USEC(conn_interval_us)); + } +} + +static void egu_isr_handler(uint8_t event_idx, void *context) +{ + ARG_UNUSED(context); + + __ASSERT_NO_MSG(event_idx < ARRAY_SIZE(work_queue_items)); + + struct bt_conn *conn = conn_pointers[event_idx]; + uint32_t conn_interval_us = conn_interval_us_get(conn); + + __ASSERT_NO_MSG(conn_interval_us > configured_prepare_distance_us); + + const uint32_t timer_distance_us = + conn_interval_us - configured_prepare_distance_us; + + k_work_reschedule(&work_queue_items[event_idx], K_USEC(timer_distance_us)); +} + +static int setup_connection_event_trigger(struct bt_conn *conn) +{ + int err; + uint8_t conn_index; + uint16_t conn_handle; + + err = bt_hci_get_conn_handle(conn, &conn_handle); + if (err) { + return err; + } + + conn_index = bt_conn_index(conn); + + sdc_hci_cmd_vs_get_next_conn_event_counter_t get_next_conn_event_count_params = { + .conn_handle = conn_handle, + }; + sdc_hci_cmd_vs_get_next_conn_event_counter_return_t get_next_conn_event_count_ret_params; + + err = hci_vs_sdc_get_next_conn_event_counter( + &get_next_conn_event_count_params, &get_next_conn_event_count_ret_params); + if (err) { + return err; + } + + const sdc_hci_cmd_vs_set_conn_event_trigger_t params = { + .conn_handle = conn_handle, + .role = SDC_HCI_VS_CONN_EVENT_TRIGGER_ROLE_CONN, + .ppi_ch_id = ppi_channels[conn_index], + .period_in_events = 1, /* Trigger every connection event. */ + + /* The connection event trigger feature requires conn_evt_counter_start + * to be in the future. + */ + .conn_evt_counter_start = + get_next_conn_event_count_ret_params.next_conn_event_counter + 10, + .task_endpoint = nrfx_egu_task_address_get(&egu_inst, + nrf_egu_trigger_task_get(conn_index)), + }; + + return hci_vs_sdc_set_conn_event_trigger(¶ms); +} + +static void on_connected(struct bt_conn *conn, uint8_t conn_err) +{ + if (!registered_cb) { + return; + } + + if (conn_err) { + return; + } + + uint8_t conn_index = bt_conn_index(conn); + + conn_pointers[conn_index] = conn; + + setup_connection_event_trigger(conn); +} + +BT_CONN_CB_DEFINE(conn_params_updated_cb) = { + .connected = on_connected, +}; + +int bt_radio_notification_conn_cb_register(const struct bt_radio_notification_conn_cb *cb, + uint32_t prepare_distance_us) +{ + if (registered_cb) { + return -EALREADY; + } + + for (size_t i = 0; i < CONFIG_BT_MAX_CONN; i++) { + __ASSERT_NO_MSG(nrfx_gppi_channel_alloc(&ppi_channels[i]) == NRFX_SUCCESS); + nrfx_egu_int_enable(&egu_inst, nrf_egu_channel_int_get(i)); + k_work_init_delayable(&work_queue_items[i], work_handler); + } + + registered_cb = cb; + configured_prepare_distance_us = prepare_distance_us; + + return 0; +} + +static int driver_init(const struct device *dev) +{ + nrfx_err_t err; + + err = nrfx_egu_init(&egu_inst, + CONFIG_BT_RADIO_NOTIFICATION_CONN_CB_EGU_TRIGGER_ISR_PRIO, + egu_isr_handler, + NULL); + if (err != NRFX_SUCCESS) { + return -EALREADY; + } + + IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_EGU_INST_GET(EGU_INST_IDX)), + CONFIG_BT_RADIO_NOTIFICATION_CONN_CB_EGU_TRIGGER_ISR_PRIO, + nrfx_isr, + NRFX_EGU_INST_HANDLER_GET(EGU_INST_IDX), + 0); + irq_enable(NRFX_IRQ_NUMBER_GET(NRF_EGU_INST_GET(EGU_INST_IDX))); + + return 0; +} + +DEVICE_DEFINE(conn_event_notification, "radion_notification_conn_cb", + driver_init, NULL, + NULL, NULL, + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + NULL); From 04ec46d6b2137c2d7d49a53e12c7c6025ff3d1fd Mon Sep 17 00:00:00 2001 From: Rubin Gerritsen Date: Thu, 20 Jun 2024 10:35:59 +0200 Subject: [PATCH 2/2] Bluetooth: Samples: Add Radio Notification callback sample The sample demonstrates how the Radio Notification connection callback feature can used to minimize latency between data sampling and on air transmission. Signed-off-by: Rubin Gerritsen --- .../radio_notification_cb/CMakeLists.txt | 17 ++ .../radio_notification_cb/README.rst | 145 +++++++++++ .../bluetooth/radio_notification_cb/prj.conf | 25 ++ .../radio_notification_cb/sample.yaml | 13 + .../radio_notification_cb/src/central.c | 80 ++++++ .../radio_notification_cb/src/main.c | 232 ++++++++++++++++++ .../radio_notification_cb/src/peripheral.c | 26 ++ .../radio_notification_cb/sysbuild.cmake | 13 + .../radio_notification_cb/sysbuild.conf | 1 + 9 files changed, 552 insertions(+) create mode 100644 samples/bluetooth/radio_notification_cb/CMakeLists.txt create mode 100644 samples/bluetooth/radio_notification_cb/README.rst create mode 100644 samples/bluetooth/radio_notification_cb/prj.conf create mode 100644 samples/bluetooth/radio_notification_cb/sample.yaml create mode 100644 samples/bluetooth/radio_notification_cb/src/central.c create mode 100644 samples/bluetooth/radio_notification_cb/src/main.c create mode 100644 samples/bluetooth/radio_notification_cb/src/peripheral.c create mode 100644 samples/bluetooth/radio_notification_cb/sysbuild.cmake create mode 100644 samples/bluetooth/radio_notification_cb/sysbuild.conf diff --git a/samples/bluetooth/radio_notification_cb/CMakeLists.txt b/samples/bluetooth/radio_notification_cb/CMakeLists.txt new file mode 100644 index 000000000000..b5250b38fd1d --- /dev/null +++ b/samples/bluetooth/radio_notification_cb/CMakeLists.txt @@ -0,0 +1,17 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(NONE) + +zephyr_sources_ifdef(CONFIG_BT_CENTRAL src/central.c) +zephyr_sources_ifdef(CONFIG_BT_PERIPHERAL src/peripheral.c) + +target_sources(app PRIVATE + src/main.c +) diff --git a/samples/bluetooth/radio_notification_cb/README.rst b/samples/bluetooth/radio_notification_cb/README.rst new file mode 100644 index 000000000000..4297c6480a97 --- /dev/null +++ b/samples/bluetooth/radio_notification_cb/README.rst @@ -0,0 +1,145 @@ +.. _ble_radio_notification_conn_cb: + +Bluetooth: Radio Notification callback +###################################### + +.. contents:: + :local: + :depth: 2 + +This sample demonstrates how to use the :ref:`ug_radio_notification_conn_cb` feature. +It uses the :ref:`latency_readme` and the :ref:`latency_client_readme` to showcase how you can use this feature to minimize the time between data sampling and data transmission. + +Requirements +************ + +The sample supports the following development kits: + +.. table-from-sample-yaml:: + +You can use any two of the development kits mentioned above and mix different development kits. + +Additionally, the sample requires a connection to a computer with a serial terminal for each of the development kits. + +.. note:: + The feature involves triggering a (D)PPI task directly from the SoftDevice Controller link layer. + The application and SoftDevice Controller are supposed to be running on the same core. + +Building and running +******************** +.. |sample path| replace:: :file:`samples/bluetooth/radio_notification_cb` + +.. include:: /includes/build_and_run.txt + +Testing +======= + +After programming the sample to both development kits, perform the following steps to test it: + +1. Connect to both kits with a terminal emulator (for example, `nRF Connect Serial Terminal`_). + See :ref:`test_and_optimize` for the required settings and steps. +#. Reset both kits. +#. In one of the terminal emulators, type ``c`` to start the application on the connected board in the central role. +#. In the other terminal emulator, type ``p`` to start the application in the peripheral role. +#. Observe that latency measurements are printed in the terminals. + +Sample output +============= + +The result should look similar to the following output. + +For the central:: + + Starting radio notification callback sample. + I: SoftDevice Controller build revision: + I: d6 da c7 ae 08 db 72 6f |......ro + I: 2a a3 26 49 2a 4d a8 b3 |*.&I*M.. + I: 98 0e 07 7f |.... + I: HW Platform: Nordic Semiconductor (0x0002) + I: HW Variant: nRF52x (0x0002) + I: Firmware: Standard Bluetooth controller (0x00) Version 214.51162 Build 1926957230 + I: Identity: FA:BB:79:57:D6:45 (random) + I: HCI: version 5.4 (0x0d) revision 0x11fb, manufacturer 0x0059 + I: LMP: version 5.4 (0x0d) subver 0x11fb + Choose device role - type c (central) or p (peripheral): + Central. Starting scanning + Scanning started + Device found: CF:99:32:A5:4B:11 (random) (RSSI -43) + Connected: CF:99:32:A5:4B:11 (random) + Service discovery completed + Latency: 3771 us, round trip: 53771 us + Latency: 3741 us, round trip: 53741 us + Latency: 3741 us, round trip: 53741 us + Latency: 3741 us, round trip: 53741 us + Latency: 3771 us, round trip: 53771 us + Latency: 3771 us, round trip: 53771 us + Latency: 3741 us, round trip: 53741 us + Latency: 3741 us, round trip: 53741 us + Latency: 3741 us, round trip: 53741 us + Latency: 3771 us, round trip: 53771 us + Latency: 3771 us, round trip: 53771 us + Latency: 3741 us, round trip: 53741 us + Latency: 3741 us, round trip: 53741 us + +For the peripheral:: + + Starting radio notification callback sample. + I: SoftDevice Controller build revision: + I: d6 da c7 ae 08 db 72 6f |......ro + I: 2a a3 26 49 2a 4d a8 b3 |*.&I*M.. + I: 98 0e 07 7f |.... + I: HW Platform: Nordic Semiconductor (0x0002) + I: HW Variant: nRF52x (0x0002) + I: Firmware: Standard Bluetooth controller (0x00) Version 214.51162 Build 1926957230 + I: Identity: CF:99:32:A5:4B:11 (random) + I: HCI: version 5.4 (0x0d) revision 0x11fb, manufacturer 0x0059 + I: LMP: version 5.4 (0x0d) subver 0x11fb + Choose device role - type c (central) or p (peripheral): + Peripheral. Starting advertising + Advertising started + Connected: FA:BB:79:57:D6:45 (random) + Service discovery completed + Latency: 3649 us, round trip: 53649 us + Latency: 3649 us, round trip: 53649 us + Latency: 3619 us, round trip: 53619 us + Latency: 3649 us, round trip: 53649 us + Latency: 3649 us, round trip: 53649 us + Latency: 3649 us, round trip: 53649 us + Latency: 3649 us, round trip: 53649 us + Latency: 3619 us, round trip: 53619 us + Latency: 3649 us, round trip: 53649 us + Latency: 3649 us, round trip: 53649 us + Latency: 3649 us, round trip: 53649 us + Latency: 3649 us, round trip: 53649 us + Latency: 3649 us, round trip: 53649 us + Latency: 3649 us, round trip: 53649 us + +Dependencies +************* + +This sample uses the following |NCS| libraries: + +* :ref:`latency_readme` +* :ref:`latency_client_readme` + +This sample uses the following `sdk-nrfxlib`_ libraries: + +* :ref:`nrfxlib:softdevice_controller` + +In addition, it uses the following Zephyr libraries: + +* :file:`include/console.h` +* :ref:`zephyr:kernel_api`: + + * :file:`include/kernel.h` + +* :file:`include/sys/printk.h` +* :file:`include/zephyr/types.h` +* :ref:`zephyr:bluetooth_api`: + + * :file:`include/bluetooth/bluetooth.h` + * :file:`include/bluetooth/conn.h` + * :file:`include/bluetooth/gatt.h` + * :file:`include/bluetooth/scan.h` + * :file:`include/bluetooth/gatt_dm.h` + * :file:`include/bluetooth/evt_cb.h` diff --git a/samples/bluetooth/radio_notification_cb/prj.conf b/samples/bluetooth/radio_notification_cb/prj.conf new file mode 100644 index 000000000000..2c5f07729a95 --- /dev/null +++ b/samples/bluetooth/radio_notification_cb/prj.conf @@ -0,0 +1,25 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_NCS_SAMPLES_DEFAULTS=y + +CONFIG_BT_DEVICE_NAME="Radio notif cb" +CONFIG_BT=y +CONFIG_BT_RADIO_NOTIFICATION_CONN_CB=y +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_CENTRAL=y +CONFIG_BT_MAX_CONN=2 +CONFIG_BT_CTLR_SDC_PERIPHERAL_COUNT=1 +CONFIG_BT_LATENCY=y +CONFIG_BT_LATENCY_CLIENT=y +CONFIG_BT_GATT_DM=y + +CONFIG_CONSOLE=y +CONFIG_CONSOLE_SUBSYS=y +CONFIG_CONSOLE_HANDLER=y +CONFIG_CONSOLE_GETCHAR=y + +CONFIG_HEAP_MEM_POOL_SIZE=2048 diff --git a/samples/bluetooth/radio_notification_cb/sample.yaml b/samples/bluetooth/radio_notification_cb/sample.yaml new file mode 100644 index 000000000000..f0bbde9be078 --- /dev/null +++ b/samples/bluetooth/radio_notification_cb/sample.yaml @@ -0,0 +1,13 @@ +sample: + description: Connection event callback sample + name: Connection event callback +tests: + sample.bluetooth.conn_evt_cb: + sysbuild: true + build_only: true + integration_platforms: + - nrf52dk/nrf52832 + - nrf52840dk/nrf52840 + - nrf5340dk/nrf5340/cpunet + platform_allow: nrf52dk/nrf52832 nrf52840dk/nrf52840 nrf5340dk/nrf5340/cpunet + tags: bluetooth ci_build sysbuild diff --git a/samples/bluetooth/radio_notification_cb/src/central.c b/samples/bluetooth/radio_notification_cb/src/central.c new file mode 100644 index 000000000000..c767eebbf407 --- /dev/null +++ b/samples/bluetooth/radio_notification_cb/src/central.c @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +#define ADV_NAME_STR_MAX_LEN (sizeof(CONFIG_BT_DEVICE_NAME)) + +void scan_start(void); + +static bool adv_data_parse_cb(struct bt_data *data, void *user_data) +{ + char *name = user_data; + uint8_t len; + + switch (data->type) { + case BT_DATA_NAME_SHORTENED: + case BT_DATA_NAME_COMPLETE: + len = MIN(data->data_len, ADV_NAME_STR_MAX_LEN - 1); + memcpy(name, data->data, len); + name[len] = '\0'; + return false; + default: + return true; + } +} + +static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, + struct net_buf_simple *ad) +{ + char name_str[ADV_NAME_STR_MAX_LEN] = {0}; + char addr_str[BT_ADDR_LE_STR_LEN]; + int err; + + /* We're only interested in connectable events */ + if (type != BT_GAP_ADV_TYPE_ADV_IND && + type != BT_GAP_ADV_TYPE_ADV_DIRECT_IND) { + return; + } + + bt_data_parse(ad, adv_data_parse_cb, name_str); + + if (strncmp(name_str, CONFIG_BT_DEVICE_NAME, ADV_NAME_STR_MAX_LEN) != 0) { + return; + } + + bt_addr_le_to_str(addr, addr_str, sizeof(addr_str)); + printk("Device found: %s (RSSI %d)\n", addr_str, rssi); + + if (bt_le_scan_stop()) { + return; + } + + struct bt_conn *unused_conn; + + err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, + BT_LE_CONN_PARAM_DEFAULT, &unused_conn); + if (err) { + printk("Create conn to %s failed (%d)\n", addr_str, err); + scan_start(); + } + + bt_conn_unref(unused_conn); +} + +void scan_start(void) +{ + int err; + + err = bt_le_scan_start(BT_LE_SCAN_ACTIVE, device_found); + if (err) { + printk("Scanning failed to start (err %d)\n", err); + return; + } + + printk("Scanning started\n"); +} diff --git a/samples/bluetooth/radio_notification_cb/src/main.c b/samples/bluetooth/radio_notification_cb/src/main.c new file mode 100644 index 000000000000..5c110f0f9499 --- /dev/null +++ b/samples/bluetooth/radio_notification_cb/src/main.c @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static struct bt_latency latency; +static struct bt_latency_client latency_client; + +void scan_start(void); +void adv_start(void); + +static volatile bool latency_request_time_in_use; +static volatile bool discovery_completed; +static uint32_t latency_request_time; +static uint32_t conn_interval_us; +static char role; + +static void discovery_complete(struct bt_gatt_dm *dm, void *context) +{ + struct bt_latency_client *latency = context; + + printk("Service discovery completed\n"); + + bt_latency_handles_assign(dm, latency); + bt_gatt_dm_data_release(dm); + + discovery_completed = true; +} + +static void discovery_service_not_found(struct bt_conn *conn, void *context) +{ + printk("Service not found\n"); +} + +static void discovery_error(struct bt_conn *conn, int err, void *context) +{ + printk("Error while discovering GATT database: (err %d)\n", err); +} + +static const struct bt_gatt_dm_cb discovery_cb = { + .completed = discovery_complete, + .service_not_found = discovery_service_not_found, + .error_found = discovery_error, +}; + +static void connected(struct bt_conn *conn, uint8_t conn_err) +{ + int err; + char addr[BT_ADDR_LE_STR_LEN]; + struct bt_conn_info info; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + if (conn_err) { + printk("Failed to connect to %s (%u)\n", addr, conn_err); + if (role == 'c') { + scan_start(); + } else { + adv_start(); + } + return; + } + + printk("Connected: %s\n", addr); + + err = bt_conn_get_info(conn, &info); + if (err) { + printk("Failed getting conn info, %d\n", err); + return; + } + + conn_interval_us = BT_CONN_INTERVAL_TO_US(info.le.interval); + + /*Start service discovery when link is encrypted*/ + err = bt_gatt_dm_start(conn, BT_UUID_LATENCY, &discovery_cb, + &latency_client); + if (err) { + printk("Discover failed (err %d)\n", err); + } +} + +static void on_conn_param_updated(struct bt_conn *conn, + uint16_t interval, + uint16_t latency, + uint16_t timeout) +{ + ARG_UNUSED(conn); + ARG_UNUSED(latency); + ARG_UNUSED(timeout); + + conn_interval_us = BT_CONN_INTERVAL_TO_US(interval); +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + printk("Disconnected: %s (reason %u)\n", addr, reason); + + latency_request_time_in_use = false; + discovery_completed = false; + + if (role == 'c') { + scan_start(); + } else { + adv_start(); + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .connected = connected, + .le_param_updated = on_conn_param_updated, + .disconnected = disconnected, +}; + +static void latency_response_handler(const void *buf, uint16_t len) +{ + uint32_t latency_time; + + if (len == sizeof(latency_time)) { + /* compute how long the time spent */ + + latency_time = *((uint32_t *)buf); + uint32_t cycles_spent = k_cycle_get_32() - latency_time; + uint32_t total_latency_us = (uint32_t)k_cyc_to_us_floor64(cycles_spent); + + /* The latency service uses ATT Write Requests. + * The ATT Write Response is sent in the next connection event. + * The actual expected latency is therefore the total latency minus the connection + * interval. + */ + uint32_t latency_minus_conn_interval = total_latency_us - conn_interval_us; + + printk("Latency: %d us, round trip: %d us\n", + latency_minus_conn_interval, total_latency_us); + + latency_request_time_in_use = false; + } +} + +static const struct bt_latency_client_cb latency_client_cb = { + .latency_response = latency_response_handler +}; + +static void radio_notification_conn_cb(struct bt_conn *conn) +{ + int err; + + ARG_UNUSED(conn); + + if (!discovery_completed || latency_request_time_in_use) { + return; + } + + latency_request_time = k_cycle_get_32(); + err = bt_latency_request(&latency_client, + &latency_request_time, + sizeof(latency_request_time)); + if (err) { + printk("Latency request failed (err %d)\n", err); + } + latency_request_time_in_use = true; +} + +static const struct bt_radio_notification_conn_cb callbacks = { + .prepare = radio_notification_conn_cb, +}; + +int main(void) +{ + int err; + + printk("Starting radio notification callback sample.\n"); + + err = bt_enable(NULL); + if (err) { + printk("Bluetooth init failed (err %d)\n", err); + return 0; + } + + err = bt_radio_notification_conn_cb_register(&callbacks, + BT_RADIO_NOTIFICATION_CONN_CB_PREPARE_DISTANCE_US_RECOMMENDED); + if (err) { + printk("Failed registering radio notification callback (err %d)\n", err); + return 0; + } + + err = bt_latency_init(&latency, NULL); + if (err) { + printk("Latency service initialization failed (err %d)\n", err); + return 0; + } + + err = bt_latency_client_init(&latency_client, &latency_client_cb); + if (err) { + printk("Latency client initialization failed (err %d)\n", err); + return 0; + } + + console_init(); + do { + printk("Choose device role - type c (central) or p (peripheral): "); + + role = console_getchar(); + + switch (role) { + case 'p': + printk("\nPeripheral. Starting advertising\n"); + adv_start(); + break; + case 'c': + printk("\nCentral. Starting scanning\n"); + scan_start(); + break; + default: + printk("\n"); + break; + } + } while (role != 'c' && role != 'p'); +} diff --git a/samples/bluetooth/radio_notification_cb/src/peripheral.c b/samples/bluetooth/radio_notification_cb/src/peripheral.c new file mode 100644 index 000000000000..a4f4bc91c7ad --- /dev/null +++ b/samples/bluetooth/radio_notification_cb/src/peripheral.c @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +static const struct bt_data ad[] = { + BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), + BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1), +}; + +void adv_start(void) +{ + int err; + + err = bt_le_adv_start(BT_LE_ADV_CONN_ONE_TIME, ad, ARRAY_SIZE(ad), NULL, 0); + if (err) { + printk("Advertising failed to start (err %d)\n", err); + return; + } + + printk("Advertising started\n"); +} diff --git a/samples/bluetooth/radio_notification_cb/sysbuild.cmake b/samples/bluetooth/radio_notification_cb/sysbuild.cmake new file mode 100644 index 000000000000..ccc4f6e44224 --- /dev/null +++ b/samples/bluetooth/radio_notification_cb/sysbuild.cmake @@ -0,0 +1,13 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +if (${SB_CONFIG_BOARD_NRF5340DK_NRF5340_CPUNET}) + ExternalZephyrProject_Add( + APPLICATION empty_app_core + SOURCE_DIR ${ZEPHYR_NRF_MODULE_DIR}/samples/nrf5340/empty_app_core + BOARD nrf5340dk/nrf5340/cpuapp + ) +endif() diff --git a/samples/bluetooth/radio_notification_cb/sysbuild.conf b/samples/bluetooth/radio_notification_cb/sysbuild.conf new file mode 100644 index 000000000000..6408669a8474 --- /dev/null +++ b/samples/bluetooth/radio_notification_cb/sysbuild.conf @@ -0,0 +1 @@ +SB_CONFIG_PARTITION_MANAGER=n