diff --git a/examples/bluetooth/.build-test-rules.yml b/examples/bluetooth/.build-test-rules.yml index c39ceb356a..f30e7b5206 100644 --- a/examples/bluetooth/.build-test-rules.yml +++ b/examples/bluetooth/.build-test-rules.yml @@ -178,7 +178,6 @@ examples/bluetooth/hci/controller_vhci_ble_adv: depends_filepatterns: - examples/bluetooth/hci/hci_common_component/**/* - # config BT_NIMBLE_ENABLED does not depends on any soc cap examples/bluetooth/nimble/ble_ancs: <<: *bt_default_depends @@ -189,6 +188,13 @@ examples/bluetooth/nimble/ble_ancs: depends_filepatterns: - examples/bluetooth/nimble/common/**/* +examples/bluetooth/nimble/ble_cte: + <<: *bt_default_depends + enable: + - if: SOC_BLE_CTE_SUPPORTED == 1 and IDF_TARGET != "esp32c61" + depends_filepatterns: + - examples/bluetooth/nimble/ble_cte/common_components/* + examples/bluetooth/nimble/ble_enc_adv_data: <<: *bt_default_depends enable: diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/CMakeLists.txt b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/CMakeLists.txt new file mode 100644 index 0000000000..12819ac6d5 --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) +project(ble_periodic_adv_cte) diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/README.md b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/README.md new file mode 100644 index 0000000000..e678fef522 --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/README.md @@ -0,0 +1,91 @@ +| Supported Targets | ESP32-H2 | +| ----------------- | -------- | + +# Bluetooth LE Direction Finding Example (Periodic Advertiser With CTE) + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example starts periodic advertising with a non-resolvable private address and includes Constant Tone Extension (CTE) signals. + +It uses Bluetooth controller and NimBLE stack based BLE host. + +This example aims at understanding how to use direction finding features use periodic advertisement and related NimBLE APIs. + +To test this demo, BLE Periodic Sync With CTE app can be used as a locator. + +## How to Use Example + +Before project configuration and build, be sure to set the correct chip target using: + +```bash +idf.py set-target +``` + +### Configure the project (Optional) + +If you need to change the Direction Finding mode or configure antenna control GPIOs, run the configuration menu: + +```bash +idf.py menuconfig +``` + +Navigate to the `Example Configuration` menu where you can: + +* Change the `Direction Finding Mode` if needed. +* Set the GPIO pin configuration for antenna switching when using the `AoD (Antenna Switching with GPIO Encoding)` mode. + +> Note: These configurations are optional. Only modify them if you need to adjust the default behavior. + + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +There is this console output when periodic_adv is started: + +``` +I (364) main_task: Started on CPU0 +I (364) main_task: Calling app_main() +I (374) BLE_INIT: Using main XTAL as clock source +I (374) BLE_INIT: ble controller commit:[c223b2b] +I (384) BLE_INIT: Bluetooth MAC: 74:4d:bd:60:1b:bb +I (394) phy: phy_version: 322,2, 823e7f8, Mar 3 2025, 16:09:11 +I (414) phy: libbtbb version: e9c8b26, Mar 3 2025, 16:09:24 +I (414) CTE_ADV_EXAMPLE: +███████╗███████╗██████╗ ██████╗ ██╗ ███████╗ +██╔════╝██╔════╝██╔══██╗ ██╔══██╗██║ ██╔════╝ +█████╗ ███████╗██████╔╝ ██████╔╝██║ █████╗ +██╔══╝ ╚════██║██╔═══╝ ██╔══██╗██║ ██╔══╝ +███████╗███████║██║ ██████╔╝███████╗███████╗ +╚══════╝╚══════╝╚═╝ ╚═════╝ ╚══════╝╚══════╝ + +██████╗ ██╗██████╗ ███████╗ ██████╗████████╗██╗ ██████╗ ███╗ ██╗ ███████╗██╗███╗ ██╗██████╗ ██╗███╗ ██╗ ██████╗ +██╔══██╗██║██╔══██╗██╔════╝██╔════╝╚══██╔══╝██║██╔═══██╗████╗ ██║ ██╔════╝██║████╗ ██║██╔══██╗██║████╗ ██║██╔════╝ +██║ ██║██║██████╔╝█████╗ ██║ ██║ ██║██║ ██║██╔██╗ ██║ █████╗ ██║██╔██╗ ██║██║ ██║██║██╔██╗ ██║██║ ███╗ +██║ ██║██║██╔══██╗██╔══╝ ██║ ██║ ██║██║ ██║██║╚██╗██║ ██╔══╝ ██║██║╚██╗██║██║ ██║██║██║╚██╗██║██║ ██║ +██████╔╝██║██║ ██║███████╗╚██████╗ ██║ ██║╚██████╔╝██║ ╚████║ ██║ ██║██║ ╚████║██████╔╝██║██║ ╚████║╚██████╔╝ +╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═════╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ + + +I (704) CTE_ADV_EXAMPLE: DIRECTION_FINDING Example Periodic Adv AOA Mode +I (714) CTE_ADV_EXAMPLE: BLE Host Task Started +I (714) NimBLE: Failed to restore IRKs from store; status=8 + +I (724) CTE_ADV_EXAMPLE: Device Address: +I (724) CTE_ADV_EXAMPLE: 10:21:34:8d:5a:c4 +I (734) NimBLE: GAP procedure initiated: extended advertise; instance=1 + +I (734) CTE_ADV_EXAMPLE: Instance 1 started (periodic) +I (744) main_task: Returned from app_main() + +``` + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/CMakeLists.txt b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/CMakeLists.txt new file mode 100644 index 0000000000..cf2c455cb5 --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "main.c" + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/Kconfig.projbuild b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/Kconfig.projbuild new file mode 100644 index 0000000000..be0efca6de --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/Kconfig.projbuild @@ -0,0 +1,80 @@ +menu "Example Configuration" + + choice + prompt "Direction Finding Mode" + default EXAMPLE_ADV_DIRECTION_FINDING_AOA + help + Select the direction finding technology for periodic advertising. + + config EXAMPLE_ADV_DIRECTION_FINDING_AOA + bool "AoA (Constant Tone Only)" + help + Select this option for Angle of Arrival (AoA) mode. + The advertiser will send CTE with constant tone only, + no antenna switching required on the advertiser side. + + config EXAMPLE_ADV_DIRECTION_FINDING_AOD + bool "AoD (Antenna Switching with GPIO Encoding)" + help + Select this option for Angle of Departure (AoD) mode. + The advertiser will send CTE with antenna switching pattern. + GPIOs are used as encoded bits to control multiple antennas. + For example: 2 GPIOs can control 4 antennas (00, 01, 10, 11). + + endchoice + + if EXAMPLE_ADV_DIRECTION_FINDING_AOD + + config EXAMPLE_ANT_GPIO_BIT_COUNT + int "Number of GPIO bits for antenna encoding" + range 1 4 + default 2 + help + Select the number of GPIO pins used as encoded bits for antenna switching. + Each additional GPIO bit doubles the number of controllable antennas. + Example: + 1 bit -> 2 antennas (0, 1) + 2 bits -> 4 antennas (00, 01, 10, 11) + 3 bits -> 8 antennas + 4 bits -> 16 antennas + + config EXAMPLE_ANT_GPIO_0 + int "GPIO Bit 0 (LSB)" + range 0 39 + default 0 + help + GPIO pin number for the least significant bit (LSB) of antenna encoding. + + config EXAMPLE_ANT_GPIO_1 + int "GPIO Bit 1" + range 0 39 + default 1 + depends on EXAMPLE_ANT_GPIO_BIT_COUNT > 1 + help + GPIO pin number for bit 1 of antenna encoding. + + config EXAMPLE_ANT_GPIO_2 + int "GPIO Bit 2" + range 0 39 + default 2 + depends on EXAMPLE_ANT_GPIO_BIT_COUNT > 2 + help + GPIO pin number for bit 2 of antenna encoding. + + config EXAMPLE_ANT_GPIO_3 + int "GPIO Bit 3 (MSB)" + range 0 39 + default 3 + depends on EXAMPLE_ANT_GPIO_BIT_COUNT > 3 + help + GPIO pin number for the most significant bit (MSB) of antenna encoding. + + endif # EXAMPLE_DIRECTION_FINDING_CTE + + config EXAMPLE_RANDOM_ADDR + bool + prompt "Advertise RANDOM Address" + help + Use this option to advertise a random address instead of public address + +endmenu diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/idf_component.yml b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/idf_component.yml new file mode 100644 index 0000000000..2f8e51806b --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/idf_component.yml @@ -0,0 +1,3 @@ +dependencies: + cte_config: + path: ${IDF_PATH}/examples/bluetooth/nimble/ble_cte/common_components/cte_config diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/main.c b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/main.c new file mode 100644 index 0000000000..ef897d132e --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/main.c @@ -0,0 +1,229 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include "esp_log.h" +#include "nvs_flash.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#include "host/util/util.h" +#include "console/console.h" +#include "services/gap/ble_svc_gap.h" +#include "periodic_adv.h" +#include "host/ble_gap.h" +#include "host/ble_hs_adv.h" +#include "cte_config.h" + +/* Global constants */ +static const char *TAG = "CTE_ADV_EXAMPLE"; +static uint8_t s_periodic_adv_raw_data[] = {0x0D, 0x09, 'C','T','E',' ','P','e','r','i','o','d','i','c'}; + +/* Configuration based on Kconfig settings */ +#if CONFIG_EXAMPLE_RANDOM_ADDR +static uint8_t s_own_addr_type = BLE_OWN_ADDR_RANDOM; +#else +static uint8_t s_own_addr_type; +#endif + + +/** + * @brief Configure and start periodic advertising with CTE + */ +static void start_periodic_adv_cte(void) +{ + int rc; + uint8_t instance = 1; + ble_addr_t addr; + + /* Generate random address for instance */ + rc = ble_hs_id_gen_rnd(1, &addr); + assert(rc == 0); + + ESP_LOGI(TAG, "Device Address: "); + ESP_LOGI(TAG, "%02x:%02x:%02x:%02x:%02x:%02x", + addr.val[5], addr.val[4], addr.val[3], addr.val[2], addr.val[1], addr.val[0]); + + /* Configure extended advertising parameters */ + struct ble_gap_ext_adv_params ext_adv_params = { + .own_addr_type = BLE_OWN_ADDR_RANDOM, + .primary_phy = BLE_HCI_LE_PHY_1M, + .secondary_phy = BLE_HCI_LE_PHY_1M, + .sid = 2, + .tx_power = 0 + }; + + rc = ble_gap_ext_adv_configure(instance, &ext_adv_params, NULL, NULL, NULL); + assert(rc == 0); + + rc = ble_gap_ext_adv_set_addr(instance, &addr); + assert(rc == 0); + + /* Configure advertising data */ + struct ble_hs_adv_fields adv_fields = { + .name = (const uint8_t *)"CTE_Periodic_Adv", + .name_len = strlen((char *)adv_fields.name) + }; + + struct os_mbuf *data = os_msys_get_pkthdr(BLE_HCI_MAX_ADV_DATA_LEN, 0); + assert(data); + + rc = ble_hs_adv_set_fields_mbuf(&adv_fields, data); + assert(rc == 0); + + rc = ble_gap_ext_adv_set_data(instance, data); + assert(rc == 0); + + /* Configure periodic advertising parameters */ + struct ble_gap_periodic_adv_params pparams = { + .include_tx_power = 0, + .itvl_min = BLE_GAP_ADV_ITVL_MS(200), + .itvl_max = BLE_GAP_ADV_ITVL_MS(400) + }; + + rc = ble_gap_periodic_adv_configure(instance, &pparams); + assert(rc == 0); + + /* Set periodic advertising data */ + data = os_msys_get_pkthdr(sizeof(s_periodic_adv_raw_data), 0); + assert(data); + + rc = os_mbuf_append(data, s_periodic_adv_raw_data, sizeof(s_periodic_adv_raw_data)); + assert(rc == 0); + + rc = ble_gap_periodic_adv_set_data(instance, data); + assert(rc == 0); + + /* Configure CTE parameters */ +#if defined(CONFIG_EXAMPLE_ADV_DIRECTION_FINDING_AOA) + /* Configure CTE parameters */ + struct ble_gap_periodic_adv_cte_params cte_params = { + .cte_length = 0x14, + .cte_type = BLE_CTE_TYPE_AOA, + .cte_count = 1, + }; +#elif defined(CONFIG_EXAMPLE_ADV_DIRECTION_FINDING_AOD) + struct ble_gap_periodic_adv_cte_params cte_params = { + .cte_length = 0x14, + .cte_type = BLE_CTE_TYPE_AOD_2US, + .cte_count = 1, + .switching_pattern_length = 4, + .antenna_ids = (uint8_t[]){0x00, 0x01, 0x02, 0x03} + }; +#endif + + rc = ble_gap_set_connless_cte_transmit_params(instance, &cte_params); + if (rc != 0) { + ESP_LOGE(TAG, "CTE params config failed (rc=0x%x)", rc); + assert(rc == 0); + } + + /* Start advertising instances */ + rc = ble_gap_periodic_adv_start(instance); + assert(rc == 0); + + rc = ble_gap_ext_adv_start(instance, 0, 0); + assert(rc == 0); + + /* Enable CTE transmission */ + rc = ble_gap_set_connless_cte_transmit_enable(instance, 1); + if (rc != 0) { + ESP_LOGE(TAG, "CTE enable failed (rc=0x%x)", rc); + assert(rc == 0); + } + + ESP_LOGI(TAG, "Instance %u started (periodic)", instance); +} + +/** + * @brief Reset handler for BLE stack + */ +static void periodic_adv_on_reset(int reason) +{ + ESP_LOGE(TAG, "Resetting state; reason=%d", reason); +} + +/** + * @brief Synchronization callback for BLE stack + */ +static void periodic_sync_cb(void) +{ + int rc; +#if CONFIG_EXAMPLE_RANDOM_ADDR + ble_addr_t addr; + if (ble_hs_id_gen_rnd(0, &addr) == 0) { + ble_hs_id_set_rnd(addr.val); + } + /* Ensure proper identity address */ + rc = ble_hs_util_ensure_addr(1); +#else + rc = ble_hs_util_ensure_addr(0); +#endif + assert(rc == 0); + + /* Infer address type */ + rc = ble_hs_id_infer_auto(0, &s_own_addr_type); + if (rc != 0) { + ESP_LOGE(TAG, "Failed to infer address type (rc=%d)", rc); + return; + } + + /* Start advertising */ + start_periodic_adv_cte(); +} + +/** + * @brief BLE host task + */ +void periodic_adv_host_task(void *param) +{ + ESP_LOGI(TAG, "BLE Host Task Started"); + /* Run the BLE stack event loop */ + nimble_port_run(); + nimble_port_freertos_deinit(); +} + +/** + * @brief Main application entry point + */ +void app_main(void) +{ + int rc; + /* Initialize NVS for PHY calibration data */ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + /* Initialize NimBLE stack */ + ret = nimble_port_init(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Nimble init failed (%d)", ret); + return; + } + +#if MYNEWT_VAL(BLE_AOA_AOD) + ESP_LOGI(TAG, "%s", direction_finding_logo); +#if defined(CONFIG_EXAMPLE_ADV_DIRECTION_FINDING_AOD) + ESP_LOGI(TAG, "DIRECTION_FINDING Example Periodic Adv AOD Mode"); + ble_direction_finding_antenna_init(antenna_use_gpio,CONFIG_EXAMPLE_AOD_GPIO_BIT_COUNT); +#elif defined(CONFIG_EXAMPLE_ADV_DIRECTION_FINDING_AOA) + ESP_LOGI(TAG, "DIRECTION_FINDING Example Periodic Adv AOA Mode"); +#endif +#endif + + /* Configure BLE stack */ + ble_hs_cfg.reset_cb = periodic_adv_on_reset; + ble_hs_cfg.sync_cb = periodic_sync_cb; + + /* Set device name */ + rc = ble_svc_gap_device_name_set("Periodic ADV with CTE"); + assert(rc == 0); + + /* Start BLE host task */ + nimble_port_freertos_init(periodic_adv_host_task); +} diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/periodic_adv.h b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/periodic_adv.h new file mode 100644 index 0000000000..1359ce1050 --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/periodic_adv.h @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#ifndef H_BLE_PERIODIC_ADV_ +#define H_BLE_PERIODIC_ADV_ + +#include +#include "nimble/ble.h" +#include "modlog/modlog.h" +#ifdef __cplusplus +extern "C" { +#endif + +struct ble_hs_cfg; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults new file mode 100644 index 0000000000..4e27f1576f --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults @@ -0,0 +1,7 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) 5.5.0 Project Minimal Configuration +# +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_AOA_AOD=y +CONFIG_BT_NIMBLE_EXT_ADV=y diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults.esp32h2 b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults.esp32h2 new file mode 100644 index 0000000000..9543898142 --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults.esp32h2 @@ -0,0 +1,5 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) 5.5.0 Project Minimal Configuration +# +CONFIG_IDF_TARGET="esp32h2" +CONFIG_BT_NIMBLE_SECURITY_ENABLE=n diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/CMakeLists.txt b/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/CMakeLists.txt new file mode 100644 index 0000000000..1124620805 --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) +project(ble_periodic_sync_cte) diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/README.md b/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/README.md new file mode 100644 index 0000000000..e055c9ec5f --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/README.md @@ -0,0 +1,139 @@ +| Supported Targets | ESP32-H2 | +| ----------------- | -------- | + +# Bluetooth LE Direction Finding Example (Periodic Sync with CTE) + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example performs passive scanning on non-connectable, non-scannable extended advertisements, then establishes periodic synchronization with a specified advertiser containing CTE information. It listens to its periodic advertisements and prints the CTE IQ data. + +It uses the Bluetooth controller and the Bluetooth LE host based on the NimBLE stack. + +This example aims to understand how to enable CTE reception in BLE periodic synchronization advertisements and how to obtain CTE IQ reports. + +To test this demonstration, use the specified extended advertisement named "Periodic ADV with CTE" as the periodic advertiser. + + +## How to Use Example + +Before project configuration and build, be sure to set the correct chip target using: + +```bash +idf.py set-target +``` + +### Hardware Required + +* A CTE antenna array development board with ESP32_CTE_Antenna_Array_Board (e.g., ESP32-H2_BLE_AOA&AOD_Antenna_Array_Board V1.0). +* A USB cable for Power supply and programming + +See [Development Boards](https://www.espressif.com/en/products/devkits) for more information about it. + +### Configure the Project (Optional) + +If needed, you can customize the synchronization matching method and antenna control GPIO settings through the configuration menu: + +```bash +idf.py menuconfig +``` + +Navigate to the `Example Configuration` menu and do the following if required : + +* Change the Periodic Sync Matching Method option. +* Configure the GPIO pin settings for antenna control. + +> Note: These configurations are optional. Only modify them if you need to adjust the default behavior. + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +This is the console output on successful periodic sync: + +``` +I (372) main_task: Started on CPU0 +I (372) main_task: Calling app_main() +I (382) BLE_INIT: Using main XTAL as clock source +I (382) BLE_INIT: ble controller commit:[c223b2b] +I (392) BLE_INIT: Bluetooth MAC: 60:55:f9:f7:44:a5 +I (402) phy: phy_version: 322,2, 823e7f8, Mar 3 2025, 16:09:11 +I (422) phy: libbtbb version: e9c8b26, Mar 3 2025, 16:09:24 +I (422) CTE_SYNC_EXAMPLE: + +███████╗███████╗██████╗ ██████╗ ██╗ ███████╗ +██╔════╝██╔════╝██╔══██╗ ██╔══██╗██║ ██╔════╝ +█████╗ ███████╗██████╔╝ ██████╔╝██║ █████╗ +██╔══╝ ╚════██║██╔═══╝ ██╔══██╗██║ ██╔══╝ +███████╗███████║██║ ██████╔╝███████╗███████╗ +╚══════╝╚══════╝╚═╝ ╚═════╝ ╚══════╝╚══════╝ + +██████╗ ██╗██████╗ ███████╗ ██████╗████████╗██╗ ██████╗ ███╗ ██╗ ███████╗██╗███╗ ██╗██████╗ ██╗███╗ ██╗ ██████╗ +██╔══██╗██║██╔══██╗██╔════╝██╔════╝╚══██╔══╝██║██╔═══██╗████╗ ██║ ██╔════╝██║████╗ ██║██╔══██╗██║████╗ ██║██╔════╝ +██║ ██║██║██████╔╝█████╗ ██║ ██║ ██║██║ ██║██╔██╗ ██║ █████╗ ██║██╔██╗ ██║██║ ██║██║██╔██╗ ██║██║ ███╗ +██║ ██║██║██╔══██╗██╔══╝ ██║ ██║ ██║██║ ██║██║╚██╗██║ ██╔══╝ ██║██║╚██╗██║██║ ██║██║██║╚██╗██║██║ ██║ +██████╔╝██║██║ ██║███████╗╚██████╗ ██║ ██║╚██████╔╝██║ ╚████║ ██║ ██║██║ ╚████║██████╔╝██║██║ ╚████║╚██████╔╝ +╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═════╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ + + +I (712) DF: bind gpio 1 to signal 57 + +I (712) DF: bind gpio 0 to signal 58 + +I (722) DF: bind gpio 2 to signal 59 + +I (722) DF: bind gpio 25 to signal 60 + +I (722) CTE_SYNC_EXAMPLE: BLE Host Task Started +I (732) NimBLE: Failed to restore IRKs from store; status=8 + +I (732) NimBLE: GAP procedure initiated: extended discovery; + +I (742) CTE_SYNC_EXAMPLE: Receive CTE Antenna Pattern Info: +I (752) CTE_SYNC_EXAMPLE: aoa slot:2 +I (752) CTE_SYNC_EXAMPLE: pattern_len: 4 +I (752) CTE_SYNC_EXAMPLE: pattern: +I (762) CTE_SYNC_EXAMPLE: 00 04 08 0c +I (762) CTE_SYNC_EXAMPLE: Local CTE Antenna Info: +I (762) CTE_SYNC_EXAMPLE: switch_sampling_rates: 7 +I (772) CTE_SYNC_EXAMPLE: num_antennae: 4 +I (772) CTE_SYNC_EXAMPLE: max_switch_pattern_len: 16 +I (782) CTE_SYNC_EXAMPLE: max_cte_len: 20 +I (782) main_task: Returned from app_main() +I (812) CTE_SYNC_EXAMPLE: Started periodic sync with device + +I (1722) CTE_SYNC_EXAMPLE: Periodic Sync Established +I (1722) CTE_SYNC_EXAMPLE: Periodic Sync Event: +I (1722) CTE_SYNC_EXAMPLE: Status: 0 +I (1722) CTE_SYNC_EXAMPLE: Sync Handle: 0 +I (1722) CTE_SYNC_EXAMPLE: SID: 2 +I (1732) CTE_SYNC_EXAMPLE: Adv PHY: 1M +I (1732) CTE_SYNC_EXAMPLE: Interval: 640 +I (1742) CTE_SYNC_EXAMPLE: Clock Accuracy: 4 +I (1742) CTE_SYNC_EXAMPLE: Adv Addr: 10:21:34:8D:5A:C4 +I (2522) CTE_SYNC_EXAMPLE: IQ Report | Sync Handle: 0 IQ num: 45 RSSI: -65 cte_type: 0 channel: 28 +I (2522) CTE_SYNC_EXAMPLE: I: -48,-76,36,83,-18,-87,-2,85,-45,-37,-69,-104,-46,28,-16,25,34,35,69,100,45,-39,11,-35,-44,-32,-68,-97,-46,32,-7,29,41,32,70,93,40,-35,5,-35,-51,-34,-72,-101,48, +I (2532) CTE_SYNC_EXAMPLE: Q: -67,42,82,-26,-83,9,87,5,52,-28,16,-16,-41,-29,-66,-94,-45,34,-3,29,41,34,71,107,46,-29,13,-26,-45,-29,-70,-90,-47,37,-1,40,51,36,71,100,43,-32,-1,-27,-37, +I (3322) CTE_SYNC_EXAMPLE: IQ Report | Sync Handle: 0 IQ num: 45 RSSI: -62 cte_type: 0 channel: 5 +I (3322) CTE_SYNC_EXAMPLE: I: 76,-15,-83,6,81,9,-81,-24,-36,-1,54,40,30,22,39,34,37,-1,-53,-43,-26,-24,-41,-38,-38,-2,48,42,24,23,42,36,38,1,-50,-39,-24,-24,-43,-36,-39,-5,46,39,46, +I (3332) CTE_SYNC_EXAMPLE: Q: -24,-81,11,82,1,-81,-15,79,-32,-25,-37,-39,-37,-2,51,40,26,22,39,32,39,-2,-52,-41,-29,-24,-43,-36,-42,-1,49,36,26,22,42,35,38,-1,-49,-41,-28,-25,-45,-38,17, +I (4122) CTE_SYNC_EXAMPLE: IQ Report | Sync Handle: 0 IQ num: 45 RSSI: -67 cte_type: 0 channel: 11 +I (4122) CTE_SYNC_EXAMPLE: I: 75,6,-75,-19,71,38,-64,-44,-6,53,64,79,49,45,87,73,14,-47,-64,-74,-49,-39,-89,-67,-9,52,61,86,47,44,87,72,18,-45,-59,-77,-46,-39,-90,-72,-12,50,57,86,23, +I (4132) CTE_SYNC_EXAMPLE: Q: 1,-73,-11,72,30,-67,-41,59,-47,-41,-87,-77,-15,46,64,76,45,39,87,72,16,-51,-60,-84,-49,-43,-90,-72,-13,51,63,79,47,41,92,70,15,-50,-59,-79,-50,-44,-92,-69,42, +I (4922) CTE_SYNC_EXAMPLE: IQ Report | Sync Handle: 0 IQ num: 45 RSSI: -67 cte_type: 0 channel: 13 +I (4922) CTE_SYNC_EXAMPLE: I: -27,62,44,-49,-52,41,63,-28,57,24,62,53,-14,-55,-59,-80,-44,-21,-63,-51,8,68,50,93,59,26,56,53,-2,-52,-54,-87,-53,-13,-64,-45,21,67,56,87,48,22,63,50,-41, +I (4932) CTE_SYNC_EXAMPLE: Q: 65,35,-57,-49,47,62,-35,-63,19,63,58,90,56,23,60,50,0,-57,-56,-85,-44,-14,-58,-47,17,56,61,79,43,17,68,48,-12,-66,-56,-90,-56,-19,-57,-56,7,51,60,87,-12, +I (5722) CTE_SYNC_EXAMPLE: IQ Report | Sync Handle: 0 IQ num: 45 RSSI: -70 cte_type: 0 channel: 20 + + + +``` + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/main/CMakeLists.txt b/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/main/CMakeLists.txt new file mode 100644 index 0000000000..cf2c455cb5 --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "main.c" + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/main/Kconfig.projbuild b/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/main/Kconfig.projbuild new file mode 100644 index 0000000000..bb9e6f38af --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/main/Kconfig.projbuild @@ -0,0 +1,70 @@ +menu "Example Configuration" + + choice + prompt "Periodic Sync Matching Method" + default EXAMPLE_SYNC_BY_SID + + config EXAMPLE_SYNC_BY_SID + bool "Sync by Advertising SID" + + config EXAMPLE_SYNC_BY_NAME + bool "Sync by Device Name" + + endchoice + + config EXAMPLE_SYNC_TARGET_SID + int "Target SID for periodic sync" + depends on EXAMPLE_SYNC_BY_SID + default 2 + + config EXAMPLE_SYNC_TARGET_DEVNAME + string "Target Device Name for sync" + depends on EXAMPLE_SYNC_BY_NAME + default "CTE_Periodic_Adv" + + + config EXAMPLE_ANT_GPIO_BIT_COUNT + int "Number of GPIO bits for antenna encoding" + range 1 4 + default 2 + help + Select the number of GPIO pins used as encoded bits for antenna switching. + Each additional GPIO bit doubles the number of controllable antennas. + Example: + 1 bit -> 2 antennas (0, 1) + 2 bits -> 4 antennas (00, 01, 10, 11) + 3 bits -> 8 antennas + 4 bits -> 16 antennas + + config EXAMPLE_ANT_GPIO_0 + int "GPIO Bit 0 (LSB)" + range 0 39 + default 0 + help + GPIO pin number for the least significant bit (LSB) of antenna encoding. + + config EXAMPLE_ANT_GPIO_1 + int "GPIO Bit 1" + range 0 39 + default 1 + depends on EXAMPLE_ANT_GPIO_BIT_COUNT > 1 + help + GPIO pin number for bit 1 of antenna encoding. + + config EXAMPLE_ANT_GPIO_2 + int "GPIO Bit 2" + range 0 39 + default 2 + depends on EXAMPLE_ANT_GPIO_BIT_COUNT > 2 + help + GPIO pin number for bit 2 of antenna encoding. + + config EXAMPLE_ANT_GPIO_3 + int "GPIO Bit 3 (MSB)" + range 0 39 + default 3 + depends on EXAMPLE_ANT_GPIO_BIT_COUNT > 3 + help + GPIO pin number for the most significant bit (MSB) of antenna encoding. + +endmenu diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/main/idf_component.yml b/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/main/idf_component.yml new file mode 100644 index 0000000000..2f8e51806b --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/main/idf_component.yml @@ -0,0 +1,3 @@ +dependencies: + cte_config: + path: ${IDF_PATH}/examples/bluetooth/nimble/ble_cte/common_components/cte_config diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/main/main.c b/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/main/main.c new file mode 100644 index 0000000000..c94ba0a2a5 --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/main/main.c @@ -0,0 +1,329 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include "esp_log.h" +#include "nvs_flash.h" +#include "esp_check.h" + +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#include "host/util/util.h" +#include "services/gap/ble_svc_gap.h" +#include "console/console.h" + +#include "periodic_sync.h" +#include "cte_config.h" + +static const char *TAG = "CTE_SYNC_EXAMPLE"; +static int is_synced = 0; + +static int periodic_sync_gap_event(struct ble_gap_event *event, void *arg); + +#if MYNEWT_VAL(BLE_AOA_AOD) +static uint8_t cte_pattern[] = {0, 4, 8, 12}; +static struct ble_gap_cte_sampling_params sync_cte_sampling_params = { + .slot_durations = 0x2, + .switching_pattern_length = sizeof(cte_pattern), + .antenna_ids = cte_pattern, +}; +#endif + + +static void periodic_sync_scan(void) +{ + uint8_t own_addr_type; + struct ble_gap_disc_params disc_params = {0}; + int rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + ESP_LOGE(TAG, "Failed to determine address type; rc=%d", rc); + return; + } + + /** + * Perform a passive scan. I.e., don't send follow-up scan requests to + * each advertiser. + */ + disc_params.passive = 1; + rc = ble_gap_disc(own_addr_type, BLE_HS_FOREVER, &disc_params, + periodic_sync_gap_event, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "GAP discovery failed; rc=%d", rc); + } +} + +static void print_periodic_sync_data(const struct ble_gap_event *event) +{ + ESP_LOGI(TAG, "Periodic Sync Event:"); + ESP_LOGI(TAG, " Status: %d", event->periodic_sync.status); + ESP_LOGI(TAG, " Sync Handle: %d", event->periodic_sync.sync_handle); + ESP_LOGI(TAG, " SID: %d", event->periodic_sync.sid); + ESP_LOGI(TAG, " Adv PHY: %s", + event->periodic_sync.adv_phy == 1 ? "1M" : + (event->periodic_sync.adv_phy == 2 ? "2M" : "Coded")); + ESP_LOGI(TAG, " Interval: %d", event->periodic_sync.per_adv_ival); + ESP_LOGI(TAG, " Clock Accuracy: %d", event->periodic_sync.adv_clk_accuracy); + ESP_LOGI(TAG, " Adv Addr: %02X:%02X:%02X:%02X:%02X:%02X", + event->periodic_sync.adv_addr.val[5], + event->periodic_sync.adv_addr.val[4], + event->periodic_sync.adv_addr.val[3], + event->periodic_sync.adv_addr.val[2], + event->periodic_sync.adv_addr.val[1], + event->periodic_sync.adv_addr.val[0]); +} + +#if MYNEWT_VAL(BLE_AOA_AOD) +static void print_iq_report(const struct ble_gap_event *event) +{ + char buffer[512] = {0}; + int len = 0; + + ESP_LOGI(TAG, "IQ Report | Sync Handle: %d IQ num: %d RSSI: %d cte_type: %d channel: %d", + event->connless_iq_report.sync_handle, + event->connless_iq_report.sample_count, + event->connless_iq_report.rssi / 10, + event->connless_iq_report.cte_type, + event->connless_iq_report.channel_index); + + len += snprintf(buffer + len, sizeof(buffer) - len, "I: "); + for (int i = 0; i < event->connless_iq_report.sample_count; i++) { + len += snprintf(buffer + len, sizeof(buffer) - len, "%d,", (int8_t)event->connless_iq_report.i_samples[i]); + if (len >= sizeof(buffer)) break; + } + ESP_LOGI(TAG, "%s", buffer); + + len = 0; + memset(buffer, 0, sizeof(buffer)); + len += snprintf(buffer + len, sizeof(buffer) - len, "Q: "); + for (int i = 0; i < event->connless_iq_report.sample_count; i++) { + len += snprintf(buffer + len, sizeof(buffer) - len, "%d,", (int8_t)event->connless_iq_report.q_samples[i]); + if (len >= sizeof(buffer)) break; + } + ESP_LOGI(TAG, "%s", buffer); +} +#endif + +static void print_periodic_adv_data(const struct ble_gap_event *event) +{ + ESP_LOGD(TAG, "Periodic Report:"); + ESP_LOGD(TAG, " Sync Handle: %d", event->periodic_report.sync_handle); + ESP_LOGD(TAG, " RSSI: %d", event->periodic_report.rssi); + ESP_LOGD(TAG, " TX Power: %d", event->periodic_report.tx_power); + ESP_LOGD(TAG, " Data Status: %d", event->periodic_report.data_status); + ESP_LOGD(TAG, " Data Length: %d", event->periodic_report.data_length); + + ESP_LOGD(TAG, " Data :"); + ESP_LOG_BUFFER_HEX_LEVEL(TAG, event->periodic_report.data, event->periodic_report.data_length, ESP_LOG_DEBUG); + +} + +static void print_sync_lost(const struct ble_gap_event *event) +{ + ESP_LOGW(TAG, "Sync Lost (Handle: %d), Reason: %s", + event->periodic_sync_lost.sync_handle, + event->periodic_sync_lost.reason == 13 ? "Timeout" : + (event->periodic_sync_lost.reason == 14 ? "Terminated Locally" : "Unknown")); +} + +/** + * The nimble host executes this callback when a GAP event occurs. The + * application associates a GAP event callback with each connection that is + * established. periodic_sync uses the same callback for all connections. + * + * @param event The event being signalled. + * @param arg Application-specified argument; unused by + * periodic_sync. + * + * @return 0 if the application successfully handled the + * event; nonzero on failure. The semantics + * of the return code is specific to the + * particular GAP event being signalled. + */ +static int periodic_sync_gap_event(struct ble_gap_event *event, void *arg) +{ + switch (event->type) { + case BLE_GAP_EVENT_EXT_DISC: { + const struct ble_gap_ext_disc_desc *disc = ((struct ble_gap_ext_disc_desc *)(&event->disc)); + + if (is_synced) { + return 0; + } + + bool should_sync = false; + +#if CONFIG_EXAMPLE_SYNC_BY_SID + if (disc->sid == CONFIG_EXAMPLE_SYNC_TARGET_SID) { + should_sync = true; + } +#elif CONFIG_EXAMPLE_SYNC_BY_NAME + char dev_name[32] = {0}; + const uint8_t *adv_data = disc->data; + int adv_data_len = disc->length_data; + + int index = 0; + while (index < adv_data_len) { + uint8_t len = adv_data[index]; + if (len == 0 || index + len >= adv_data_len) { + break; + } + + uint8_t type = adv_data[index + 1]; + if (type == 0x09 || type == 0x08) { // Complete Local Name or Shortened + int name_len = len - 1; + if (name_len >= sizeof(dev_name)) { + name_len = sizeof(dev_name) - 1; + } + + memcpy(dev_name, &adv_data[index + 2], name_len); + dev_name[name_len] = '\0'; + + if (strcmp(dev_name, CONFIG_SYNC_TARGET_DEVNAME) == 0) { + should_sync = true; + break; + } + } + + index += len + 1; + } +#else + #error "Please select EXAMPLE_SYNC_BY_SID or EXAMPLE_SYNC_BY_NAME in menuconfig" +#endif + if (should_sync) { + ble_addr_t addr; + memcpy(&addr, &disc->addr, sizeof(ble_addr_t)); + + struct ble_gap_periodic_sync_params params = { + .skip = 0, + .sync_timeout = 1000, + .sync_cte_type = 0x0, + }; + + int rc = ble_gap_periodic_adv_sync_create(&addr, disc->sid, ¶ms, + periodic_sync_gap_event, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "Failed to create periodic sync, rc=%d\n", rc); + return 0; + } + + is_synced = 1; + ESP_LOGI(TAG, "Started periodic sync with device\n"); + } + + return 0; + } + + case BLE_GAP_EVENT_PERIODIC_SYNC: + ESP_LOGI(TAG, "Periodic Sync Established"); + print_periodic_sync_data(event); + ble_gap_disc_cancel(); + +#if MYNEWT_VAL(BLE_AOA_AOD) + int rc = ble_gap_set_connless_iq_sampling_enable( + event->periodic_sync.sync_handle, 1, 0, &sync_cte_sampling_params); + assert(rc == 0); +#endif + return 0; + + case BLE_GAP_EVENT_PERIODIC_REPORT: + print_periodic_adv_data(event); + return 0; + + case BLE_GAP_EVENT_PERIODIC_SYNC_LOST: + print_sync_lost(event); + is_synced = 0; + periodic_sync_scan(); + return 0; + +#if MYNEWT_VAL(BLE_AOA_AOD) + case BLE_GAP_EVENT_CONNLESS_IQ_REPORT: + print_iq_report(event); + return 0; +#endif + + default: + return 0; + } +} + +static void periodic_sync_on_reset(int reason) +{ + ESP_LOGE(TAG, "Resetting state; reason=%d", reason); +} + +static void periodic_sync_on_sync(void) +{ + int rc; + /* Make sure we have proper identity address set (public preferred) */ + rc = ble_hs_util_ensure_addr(0); + assert(rc == 0); + + /* Begin scanning for a peripheral to connect to. */ + periodic_sync_scan(); +} + +void periodic_sync_host_task(void *param) +{ + ESP_LOGI(TAG, "BLE Host Task Started"); + + /* This function will return only when nimble_port_stop() is executed */ + nimble_port_run(); + + nimble_port_freertos_deinit(); +} + +void app_main(void) +{ + int rc; + /* Initialize NVS — it is used to store PHY calibration data */ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ret = nimble_port_init(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to init nimble %d \n", ret); + return; + } + + /* Configure the host. */ + ble_hs_cfg.reset_cb = periodic_sync_on_reset; + ble_hs_cfg.sync_cb = periodic_sync_on_sync; + + /* Set the default device name. */ + rc = ble_svc_gap_device_name_set("periodic_sync_CTE"); + assert(rc == 0); + + +#if MYNEWT_VAL(BLE_AOA_AOD) + ESP_LOGI(TAG, "\n%s", direction_finding_logo); + ble_direction_finding_antenna_init(antenna_use_gpio, CONFIG_EXAMPLE_ANT_GPIO_BIT_COUNT); +#endif + + nimble_port_freertos_init(periodic_sync_host_task); + + ESP_LOGI(TAG, "Receive CTE Antenna Pattern Info:"); + ESP_LOGI(TAG, "aoa slot:%d",sync_cte_sampling_params.slot_durations); + ESP_LOGI(TAG, "pattern_len: %d", sizeof(cte_pattern)); + ESP_LOGI(TAG, "pattern:"); + ESP_LOG_BUFFER_HEX_LEVEL(TAG, cte_pattern, sizeof(cte_pattern), ESP_LOG_INFO); + + + uint8_t switch_sampling_rates, num_antennae, max_switch_pattern_len, max_cte_len; + rc = ble_gap_read_antenna_information(&switch_sampling_rates, &num_antennae, + &max_switch_pattern_len, &max_cte_len); + if (rc == 0) { + ESP_LOGI(TAG, "Local CTE Antenna Info:"); + ESP_LOGI(TAG, " switch_sampling_rates: %d", switch_sampling_rates); + ESP_LOGI(TAG, " num_antennae: %d", num_antennae); + ESP_LOGI(TAG, " max_switch_pattern_len: %d", max_switch_pattern_len); + ESP_LOGI(TAG, " max_cte_len: %d", max_cte_len); + } else { + ESP_LOGW(TAG, "Failed to read antenna information"); + } +} diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/main/periodic_sync.h b/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/main/periodic_sync.h new file mode 100644 index 0000000000..db11337a3b --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/main/periodic_sync.h @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#ifndef H_BLE_PERIODIC_SYNC_ +#define H_BLE_PERIODIC_SYNC_ + +#include "modlog/modlog.h" +#ifdef __cplusplus +extern "C" { +#endif + +struct ble_hs_adv_fields; +struct ble_hs_cfg; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/sdkconfig.defaults b/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/sdkconfig.defaults new file mode 100644 index 0000000000..269316f1b2 --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/sdkconfig.defaults @@ -0,0 +1,7 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) 5.3.0 Project Minimal Configuration +# +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_AOA_AOD=y +CONFIG_BT_NIMBLE_EXT_ADV=y diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/sdkconfig.defaults.esp32h2 b/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/sdkconfig.defaults.esp32h2 new file mode 100644 index 0000000000..9543898142 --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte/sdkconfig.defaults.esp32h2 @@ -0,0 +1,5 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) 5.5.0 Project Minimal Configuration +# +CONFIG_IDF_TARGET="esp32h2" +CONFIG_BT_NIMBLE_SECURITY_ENABLE=n diff --git a/examples/bluetooth/nimble/ble_cte/common_components/cte_config/CMakeLists.txt b/examples/bluetooth/nimble/ble_cte/common_components/cte_config/CMakeLists.txt new file mode 100644 index 0000000000..add8078385 --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/common_components/cte_config/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "cte_config.c" + INCLUDE_DIRS . + PRIV_REQUIRES driver bt + ) diff --git a/examples/bluetooth/nimble/ble_cte/common_components/cte_config/cte_config.c b/examples/bluetooth/nimble/ble_cte/common_components/cte_config/cte_config.c new file mode 100644 index 0000000000..c8755c87f8 --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/common_components/cte_config/cte_config.c @@ -0,0 +1,105 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include +#include "sdkconfig.h" +#include "cte_config.h" +#include "esp_log.h" +#include "driver/gpio.h" +#include "soc/gpio_sig_map.h" +#include "esp_rom_gpio.h" + +const char direction_finding_logo[] = { +"\n\ +███████╗███████╗██████╗ ██████╗ ██╗ ███████╗ \n\ +██╔════╝██╔════╝██╔══██╗ ██╔══██╗██║ ██╔════╝ \n\ +█████╗ ███████╗██████╔╝ ██████╔╝██║ █████╗ \n\ +██╔══╝ ╚════██║██╔═══╝ ██╔══██╗██║ ██╔══╝ \n\ +███████╗███████║██║ ██████╔╝███████╗███████╗ \n\ +╚══════╝╚══════╝╚═╝ ╚═════╝ ╚══════╝╚══════╝ \n\ + \n\ +██████╗ ██╗██████╗ ███████╗ ██████╗████████╗██╗ ██████╗ ███╗ ██╗ ███████╗██╗███╗ ██╗██████╗ ██╗███╗ ██╗ ██████╗ \n\ +██╔══██╗██║██╔══██╗██╔════╝██╔════╝╚══██╔══╝██║██╔═══██╗████╗ ██║ ██╔════╝██║████╗ ██║██╔══██╗██║████╗ ██║██╔════╝ \n\ +██║ ██║██║██████╔╝█████╗ ██║ ██║ ██║██║ ██║██╔██╗ ██║ █████╗ ██║██╔██╗ ██║██║ ██║██║██╔██╗ ██║██║ ███╗ \n\ +██║ ██║██║██╔══██╗██╔══╝ ██║ ██║ ██║██║ ██║██║╚██╗██║ ██╔══╝ ██║██║╚██╗██║██║ ██║██║██║╚██╗██║██║ ██║ \n\ +██████╔╝██║██║ ██║███████╗╚██████╗ ██║ ██║╚██████╔╝██║ ╚████║ ██║ ██║██║ ╚████║██████╔╝██║██║ ╚████║╚██████╔╝ \n\ +╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═════╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ \n\ + \n\ +" +}; + +struct ble_gatt_register_ctxt; + +#ifdef CONFIG_EXAMPLE_ANT_GPIO_BIT_COUNT +uint8_t antenna_use_gpio[CONFIG_EXAMPLE_ANT_GPIO_BIT_COUNT] = { +#if CONFIG_EXAMPLE_ANT_GPIO_BIT_COUNT > 0 + CONFIG_EXAMPLE_ANT_GPIO_0, +#endif +#if CONFIG_EXAMPLE_ANT_GPIO_BIT_COUNT > 1 + CONFIG_EXAMPLE_ANT_GPIO_1, +#endif +#if CONFIG_EXAMPLE_ANT_GPIO_BIT_COUNT > 2 + CONFIG_EXAMPLE_ANT_GPIO_2, +#endif +#if CONFIG_EXAMPLE_ANT_GPIO_BIT_COUNT > 3 + CONFIG_EXAMPLE_ANT_GPIO_3 +#endif +}; +#endif + +#ifndef CTE_ANT0_IDX +#ifdef ANT_SEL0_IDX +#define CTE_ANT0_IDX ANT_SEL0_IDX +#define CTE_ANT1_IDX ANT_SEL1_IDX +#define CTE_ANT2_IDX ANT_SEL2_IDX +#define CTE_ANT3_IDX ANT_SEL3_IDX +#ifdef ANT_SEL4_IDX +#define CTE_ANT4_IDX ANT_SEL4_IDX +#define CTE_ANT5_IDX ANT_SEL5_IDX +#define CTE_ANT6_IDX ANT_SEL6_IDX +#define CTE_ANT7_IDX ANT_SEL7_IDX +#define CTE_ANT8_IDX ANT_SEL8_IDX +#define CTE_ANT9_IDX ANT_SEL9_IDX +#define CTE_ANT10_IDX ANT_SEL10_IDX +#define CTE_ANT11_IDX ANT_SEL11_IDX +#define CTE_ANT12_IDX ANT_SEL12_IDX +#define CTE_ANT13_IDX ANT_SEL13_IDX +#define CTE_ANT14_IDX ANT_SEL14_IDX +#define CTE_ANT15_IDX ANT_SEL15_IDX +#endif +#else +#error "CTE Antenna not support." +#endif +#endif + + +uint8_t antenna_signal_index[4] = {CTE_ANT0_IDX, CTE_ANT1_IDX, CTE_ANT2_IDX, CTE_ANT3_IDX}; + +int ble_direction_finding_antenna_init(uint8_t* gpio_array,uint8_t gpio_array_len){ + int rc; + // GPIO configuration + uint32_t gpio_pin_maks = 0; + for (int i = 0; i < gpio_array_len; i++){ + gpio_pin_maks |= (1ULL << gpio_array[i]); + } + gpio_config_t gpio_conf = { + .intr_type = GPIO_INTR_DISABLE, + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = gpio_pin_maks, + .pull_down_en = false, + .pull_up_en = true, + }; + rc = gpio_config(&gpio_conf); + if(rc != 0) { + ESP_LOGE("DF","config fault GPIO failed"); + } + // gpio bind signal + for (int i = 0; i < gpio_array_len; i++){ + ESP_LOGI("DF","bind gpio %d to signal %d\n",gpio_array[i],antenna_signal_index[i]); + esp_rom_gpio_connect_out_signal(gpio_array[i],antenna_signal_index[i],0,0); + } + return 0; +} diff --git a/examples/bluetooth/nimble/ble_cte/common_components/cte_config/cte_config.h b/examples/bluetooth/nimble/ble_cte/common_components/cte_config/cte_config.h new file mode 100644 index 0000000000..754bfd11e7 --- /dev/null +++ b/examples/bluetooth/nimble/ble_cte/common_components/cte_config/cte_config.h @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include +#include + +/* --- cte type --- */ +#define BLE_CTE_TYPE_AOA (0) +#define BLE_CTE_TYPE_AOD_1US (1) +#define BLE_CTE_TYPE_AOD_2US (2) + +extern uint8_t antenna_use_gpio[]; +extern const char direction_finding_logo[]; + +int ble_direction_finding_antenna_init(uint8_t* gpio_array,uint8_t gpio_array_len);