mirror of
https://github.com/espressif/esp-idf.git
synced 2025-07-31 11:17:20 +02:00
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:
@ -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:
|
||||
|
@ -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)
|
@ -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.
|
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "main.c"
|
||||
INCLUDE_DIRS ".")
|
@ -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
|
@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
cte_config:
|
||||
path: ${IDF_PATH}/examples/bluetooth/nimble/ble_cte/common_components/cte_config
|
@ -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);
|
||||
}
|
@ -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
|
@ -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
|
@ -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
|
@ -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)
|
@ -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.
|
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "main.c"
|
||||
INCLUDE_DIRS ".")
|
@ -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
|
@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
cte_config:
|
||||
path: ${IDF_PATH}/examples/bluetooth/nimble/ble_cte/common_components/cte_config
|
@ -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");
|
||||
}
|
||||
}
|
@ -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
|
@ -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
|
@ -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
|
@ -0,0 +1,4 @@
|
||||
idf_component_register(SRCS "cte_config.c"
|
||||
INCLUDE_DIRS .
|
||||
PRIV_REQUIRES driver bt
|
||||
)
|
@ -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;
|
||||
}
|
@ -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);
|
Reference in New Issue
Block a user