Merge branch 'feat/add_cte_iq_report_example_v5.4' into 'release/v5.4'

Add Bluetooth LE CTE connless example. (v5.4)

See merge request espressif/esp-idf!39782
This commit is contained in:
Island
2025-07-23 20:50:21 +08:00
22 changed files with 1167 additions and 1 deletions

View File

@ -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:

View File

@ -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)

View File

@ -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 <chip_name>
```
### 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.

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")

View File

@ -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

View File

@ -0,0 +1,3 @@
dependencies:
cte_config:
path: ${IDF_PATH}/examples/bluetooth/nimble/ble_cte/common_components/cte_config

View File

@ -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);
}

View File

@ -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 <stdbool.h>
#include "nimble/ble.h"
#include "modlog/modlog.h"
#ifdef __cplusplus
extern "C" {
#endif
struct ble_hs_cfg;
#ifdef __cplusplus
}
#endif
#endif

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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 <chip_name>
```
### 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.

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")

View File

@ -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

View File

@ -0,0 +1,3 @@
dependencies:
cte_config:
path: ${IDF_PATH}/examples/bluetooth/nimble/ble_cte/common_components/cte_config

View File

@ -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, &params,
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");
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,4 @@
idf_component_register(SRCS "cte_config.c"
INCLUDE_DIRS .
PRIV_REQUIRES driver bt
)

View File

@ -0,0 +1,105 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#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;
}

View File

@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdint.h>
#include <stdbool.h>
/* --- 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);