mirror of
https://github.com/espressif/esp-idf.git
synced 2025-07-29 18:27:20 +02:00
Merge branch 'fix/pawr_docs_v5.4' into 'release/v5.4'
feat(nimble):Added tutorials for PAwR examples (v5.4) See merge request espressif/esp-idf!38622
This commit is contained in:
@ -1,19 +1,24 @@
|
||||
| Supported Targets | ESP32-C6 |
|
||||
| ----------------- | -------- |
|
||||
|
||||
# BLE Periodic Advertiser Example
|
||||
|
||||
# Important Note
|
||||
*This example currently requires an external Bluetooth controller supporting PAwR functionality, as the ESP chips listed above do not have native controller support for PAwR features and under development phase*
|
||||
|
||||
# BLE Periodic Advertiser With Response (PAwR) Advertiser Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
BLE PAwR Advertiser Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example starts periodic advertising with non resolvable private address.
|
||||
This example starts PAwR advertising with configurable subevents and response slots.
|
||||
|
||||
It uses Bluetooth controller and NimBLE stack based BLE host.
|
||||
It uses external Bluetooth controller and NimBLE stack based BLE host.
|
||||
|
||||
This example aims at understanding periodic advertisement and related NimBLE APIs.
|
||||
|
||||
|
||||
To test this demo, any BLE Periodic Sync app can be used.
|
||||
This example aims at understanding PAwR advertisement and related NimBLE APIs.
|
||||
|
||||
To test this demo, any BLE scanner supporting PAwR can be used.(check /examples/ble_pawr_adv/ble_pawr_sync)
|
||||
|
||||
Note :
|
||||
|
||||
@ -49,26 +54,24 @@ Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
|
||||
See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
```
|
||||
I (504) NimBLE_BLE_PAwR: Device Address 40:4c:ca:46:1f:e2
|
||||
I (504) NimBLE: GAP procedure initiated: extended advertise; instance=0
|
||||
|
||||
I (514) NimBLE_BLE_PAwR: instance 0 started (periodic)
|
||||
|
||||
I (514) NimBLE_BLE_PAwR: [Request] data: 0, subevt start:0, subevt count:5
|
||||
I (534) main_task: Returned from app_main()
|
||||
I (664) NimBLE_BLE_PAwR: [Request] data: 5, subevt start:5, subevt count:2
|
||||
I (814) NimBLE_BLE_PAwR: [Request] data: 7, subevt start:7, subevt count:2
|
||||
I (964) NimBLE_BLE_PAwR: [Request] data: 9, subevt start:9, subevt count:2
|
||||
I (1114) NimBLE_BLE_PAwR: [Request] data: b, subevt start:1, subevt count:2
|
||||
|
||||
There is this console output when periodic_adv is started:
|
||||
I (1163054) NimBLE_BLE_PAwR: [Response] subevent:0, response_slot:114, data_length:1
|
||||
I (1163054) NimBLE_BLE_PAwR: data: 0x8c, 0x0
|
||||
I (1163164) NimBLE_BLE_PAwR: [Request] data: 91, subevt start:5, subevt count:2
|
||||
|
||||
```
|
||||
I (313) BTDM_INIT: BT controller compile version [2ee0168]
|
||||
I (313) phy_init: phy_version 912,d001756,Jun 2 2022,16:28:07
|
||||
I (353) system_api: Base MAC address is not set
|
||||
I (353) system_api: read default base MAC address from EFUSE
|
||||
I (353) BTDM_INIT: Bluetooth MAC: 84:f7:03:08:14:8e
|
||||
|
||||
I (363) NimBLE_BLE_PERIODIC_ADV: BLE Host Task Started
|
||||
I (373) NimBLE: Device Address:
|
||||
I (373) NimBLE: d0:42:3a:95:84:05
|
||||
I (373) NimBLE:
|
||||
|
||||
I (383) NimBLE: instance 1 started (periodic)
|
||||
```
|
||||
|
||||
## Note
|
||||
* Periodic sync transfer is not implemented for now.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
@ -0,0 +1,329 @@
|
||||
# BLE Periodic Advertisement with Response Advertiser Example Walkthrough
|
||||
|
||||
## Introduction
|
||||
|
||||
This tutorial examines the BLE Periodic Advertisement with Responses (PAwR) example code for ESP32 chipsets with BLE 5.0+ support. The code demonstrates how to implement PAwR functionality using NimBLE APIs, which enables bidirectional communication between advertiser and scanner devices in a power-efficient manner.
|
||||
|
||||
## Includes
|
||||
|
||||
This example is located in the examples folder of the ESP-IDF under the [ble_pawr_adv/main](../main). The [main.c](../main/main.c) file located in the main folder contains all the functionality that we are going to review. The header files contained in [main.c](../main/main.c) are:
|
||||
|
||||
```c
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
/* BLE */
|
||||
#include "nimble/nimble_port.h"
|
||||
#include "nimble/nimble_port_freertos.h"
|
||||
#include "host/ble_hs.h"
|
||||
```
|
||||
These includes provide:
|
||||
|
||||
- ESP32 logging functionality (esp_log.h)
|
||||
|
||||
- Non-volatile storage (nvs_flash.h)
|
||||
|
||||
- NimBLE stack porting and FreeRTOS integration
|
||||
|
||||
- BLE host stack functionality
|
||||
|
||||
## Main Entry Point
|
||||
|
||||
The program’s entry point is the app_main() function:
|
||||
```c
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
/* Initialize NVS — it is used to store PHY calibration data */
|
||||
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 ", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Initialize the NimBLE host configuration. */
|
||||
ble_hs_cfg.reset_cb = on_reset;
|
||||
ble_hs_cfg.sync_cb = on_sync;
|
||||
|
||||
nimble_port_freertos_init(pawr_host_task);
|
||||
}
|
||||
```
|
||||
The main function starts by initializing the non-volatile storage library. This library allows us to save the key-value pairs in flash memory.`nvs_flash_init()` stores the PHY calibration data. In a Bluetooth Low Energy (BLE) device, cryptographic keys used for encryption and authentication are often stored in Non-Volatile Storage (NVS).BLE stores the peer keys, CCCD keys, peer records, etc on NVS. By storing these keys in NVS, the BLE device can quickly retrieve them when needed, without the need for time-consuming key generations.
|
||||
```c
|
||||
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 );
|
||||
```
|
||||
|
||||
## BT Controller and Stack Initialization
|
||||
|
||||
The main function calls `nimble_port_init()` to initialize BT Controller and nimble stack. This function initializes the BT controller by first creating its configuration structure named `esp_bt_controller_config_t` with default settings generated by the `BT_CONTROLLER_INIT_CONFIG_DEFAULT()` macro. It implements the Host Controller Interface (HCI) on the controller side, the Link Layer (LL), and the Physical Layer (PHY). The BT Controller is invisible to the user applications and deals with the lower layers of the BLE stack. The controller configuration includes setting the BT controller stack size, priority, and HCI baud rate. With the settings created, the BT controller is initialized and enabled with the `esp_bt_controller_init()` and `esp_bt_controller_enable()` functions:
|
||||
|
||||
```c
|
||||
esp_bt_controller_config_t config_opts = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&config_opts);
|
||||
```
|
||||
Next, the controller is enabled in BLE Mode.
|
||||
|
||||
```c
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
```
|
||||
The controller should be enabled in `ESP_BT_MODE_BLE` if you want to use the BLE mode.
|
||||
|
||||
There are four Bluetooth modes supported:
|
||||
|
||||
1. `ESP_BT_MODE_IDLE`: Bluetooth not running
|
||||
2. `ESP_BT_MODE_BLE`: BLE mode
|
||||
3. `ESP_BT_MODE_CLASSIC_BT`: BT Classic mode
|
||||
4. `ESP_BT_MODE_BTDM`: Dual mode (BLE + BT Classic)
|
||||
|
||||
After the initialization of the BT controller, the nimble stack, which includes the common definitions and APIs for BLE, is initialized by using `esp_nimble_init()`:
|
||||
|
||||
```c
|
||||
esp_err_t esp_nimble_init(void)
|
||||
{
|
||||
|
||||
#if !SOC_ESP_NIMBLE_CONTROLLER
|
||||
/* Initialize the function pointers for OS porting */
|
||||
npl_freertos_funcs_init();
|
||||
|
||||
npl_freertos_mempool_init();
|
||||
|
||||
if(esp_nimble_hci_init() != ESP_OK) {
|
||||
ESP_LOGE(NIMBLE_PORT_LOG_TAG, "hci inits failed\n");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Initialize default event queue */
|
||||
ble_npl_eventq_init(&g_eventq_dflt);
|
||||
|
||||
os_msys_init();
|
||||
|
||||
void ble_store_ram_init(void);
|
||||
/* XXX Need to have a template for store */
|
||||
ble_store_ram_init();
|
||||
#endif
|
||||
|
||||
/* Initialize the host */
|
||||
ble_hs_init();
|
||||
return ESP_OK;
|
||||
}
|
||||
```
|
||||
## PAwR Configuration
|
||||
The example defines several PAwR parameters
|
||||
|
||||
```c
|
||||
#define BLE_PAWR_EVENT_INTERVAL (600)
|
||||
#define BLE_PAWR_NUM_SUBEVTS (10)
|
||||
#define BLE_PAWR_SUB_INTERVAL (44) /*!< Interval between subevents (N * 1.25 ms) */
|
||||
#define BLE_PAWR_RSP_SLOT_DELAY (20) /*!< The first response slot delay (N * 1.25 ms) */
|
||||
#define BLE_PAWR_RSP_SLOT_SPACING (32) /*!< Time between response slots (N * 0.125 ms) */
|
||||
#define BLE_PAWR_NUM_RSP_SLOTS (5) /*!< Number of subevent response slots */
|
||||
#define BLE_PAWR_SUB_DATA_LEN (20)
|
||||
```
|
||||
|
||||
These parameters control:
|
||||
|
||||
- The interval between periodic advertising events
|
||||
|
||||
- Number of subevents per periodic interval
|
||||
|
||||
- Timing of response slots
|
||||
|
||||
- Data length for subevent payloads
|
||||
|
||||
|
||||
## Periodic Advertising Configuration
|
||||
```c
|
||||
memset(&pparams, 0, sizeof(pparams));
|
||||
pparams.include_tx_power = 0;
|
||||
pparams.itvl_min = BLE_GAP_PERIODIC_ITVL_MS(3000);
|
||||
pparams.itvl_max = BLE_GAP_PERIODIC_ITVL_MS(3000);
|
||||
pparams.num_subevents = BLE_PAWR_NUM_SUBEVTS;
|
||||
pparams.subevent_interval = BLE_PAWR_SUB_INTERVAL;
|
||||
pparams.response_slot_delay = BLE_PAWR_RSP_SLOT_DELAY;
|
||||
pparams.response_slot_spacing = BLE_PAWR_RSP_SLOT_SPACING;
|
||||
pparams.num_response_slots = BLE_PAWR_NUM_RSP_SLOTS;
|
||||
|
||||
rc = ble_gap_periodic_adv_configure(instance, &pparams);
|
||||
assert(rc == 0);
|
||||
```
|
||||
## Key PAwR Parameters:
|
||||
|
||||
- num_subevents: Number of subevents per periodic interval (10)
|
||||
|
||||
- subevent_interval: Time between subevents (44 × 1.25ms = 55ms)
|
||||
|
||||
- response_slot_delay: First response slot delay (20 × 1.25ms = 25ms)
|
||||
|
||||
- response_slot_spacing: Time between slots (32 × 0.125ms = 4ms)
|
||||
|
||||
- num_response_slots: Number of response slots per subevent (5)
|
||||
|
||||
## PAwR Advertisement
|
||||
|
||||
The start_periodic_adv() function configures and starts PAwR:
|
||||
```c
|
||||
static void
|
||||
start_periodic_adv(void)
|
||||
{
|
||||
int rc;
|
||||
uint8_t addr[6];
|
||||
struct ble_gap_periodic_adv_params pparams;
|
||||
struct ble_gap_ext_adv_params params;
|
||||
struct ble_hs_adv_fields adv_fields;
|
||||
struct os_mbuf *data;
|
||||
uint8_t instance = 0;
|
||||
|
||||
#if MYNEWT_VAL(BLE_PERIODIC_ADV_ENH)
|
||||
struct ble_gap_periodic_adv_enable_params eparams;
|
||||
memset(&eparams, 0, sizeof(eparams));
|
||||
#endif
|
||||
|
||||
/* Get the local public address. */
|
||||
rc = ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, addr, NULL);
|
||||
assert (rc == 0);
|
||||
|
||||
ESP_LOGI(TAG, "Device Address %02x:%02x:%02x:%02x:%02x:%02x", addr[5], addr[4], addr[3],
|
||||
addr[2], addr[1], addr[0]);
|
||||
|
||||
/* For periodic we use instance with non-connectable advertising */
|
||||
memset (¶ms, 0, sizeof(params));
|
||||
params.own_addr_type = BLE_OWN_ADDR_PUBLIC;
|
||||
params.primary_phy = BLE_HCI_LE_PHY_CODED;
|
||||
params.secondary_phy = BLE_HCI_LE_PHY_1M;
|
||||
params.sid = 0;
|
||||
params.itvl_min = BLE_PAWR_EVENT_INTERVAL;
|
||||
params.itvl_max = BLE_PAWR_EVENT_INTERVAL;
|
||||
|
||||
rc = ble_gap_ext_adv_configure(instance, ¶ms, NULL, gap_event_cb, NULL);
|
||||
assert (rc == 0);
|
||||
|
||||
memset(&adv_fields, 0, sizeof(adv_fields));
|
||||
adv_fields.name = (const uint8_t *)"Nimble_PAwR";
|
||||
adv_fields.name_len = strlen((char *)adv_fields.name);
|
||||
|
||||
/* mbuf chain will be increased if needed */
|
||||
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 */
|
||||
memset(&pparams, 0, sizeof(pparams));
|
||||
pparams.include_tx_power = 0;
|
||||
pparams.itvl_min = BLE_GAP_PERIODIC_ITVL_MS(3000);
|
||||
pparams.itvl_max = BLE_GAP_PERIODIC_ITVL_MS(3000);
|
||||
/* Configure the parameters of PAwR. */
|
||||
pparams.num_subevents = BLE_PAWR_NUM_SUBEVTS;
|
||||
pparams.subevent_interval = BLE_PAWR_SUB_INTERVAL;
|
||||
pparams.response_slot_delay = BLE_PAWR_RSP_SLOT_DELAY;
|
||||
pparams.response_slot_spacing = BLE_PAWR_RSP_SLOT_SPACING;
|
||||
pparams.num_response_slots = BLE_PAWR_NUM_RSP_SLOTS;
|
||||
|
||||
rc = ble_gap_periodic_adv_configure(instance, &pparams);
|
||||
assert(rc == 0);
|
||||
|
||||
/* start periodic advertising */
|
||||
#if MYNEWT_VAL(BLE_PERIODIC_ADV_ENH)
|
||||
eparams.include_adi = 1;
|
||||
rc = ble_gap_periodic_adv_start(instance, &eparams);
|
||||
#else
|
||||
rc = ble_gap_periodic_adv_start(instance);
|
||||
#endif
|
||||
assert (rc == 0);
|
||||
|
||||
/* start advertising */
|
||||
rc = ble_gap_ext_adv_start(instance, 0, 0);
|
||||
assert (rc == 0);
|
||||
|
||||
ESP_LOGI(TAG, "instance %u started (periodic)\n", instance);
|
||||
}
|
||||
```
|
||||
Key steps:
|
||||
|
||||
- Configure extended advertising parameters
|
||||
|
||||
- Set up periodic advertising with subevent and response slot parameters
|
||||
|
||||
- Start both periodic and extended advertising
|
||||
|
||||
|
||||
## Need of Extended Advertisement in Periodic Advertisement
|
||||
|
||||
Non-connectable and non-scannable advertising events containing synchronization information about a periodic advertising train are necessary for the scanner device to sync with the periodic advertising train. The periodic advertising will utilize the same physical layer (PHY) as the auxiliary packet, which is part of the extended advertisement.
|
||||
|
||||
|
||||
## GAP Event Callback
|
||||
The gap_event_cb function handles PAwR events:
|
||||
|
||||
```c
|
||||
static int gap_event_cb(struct ble_gap_event *event, void *arg)
|
||||
{
|
||||
switch (event->type) {
|
||||
case BLE_GAP_EVENT_PER_SUBEV_DATA_REQ:
|
||||
/* Handle subevent data request */
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_PER_SUBEV_RESP:
|
||||
/* Handle scanner responses */
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
It processes two main events:
|
||||
|
||||
- BLE_GAP_EVENT_PER_SUBEV_DATA_REQ: Triggered when data needs to be prepared for subevents
|
||||
|
||||
- BLE_GAP_EVENT_PER_SUBEV_RESP: Triggered when responses are received from scanners
|
||||
|
||||
## Host Task
|
||||
|
||||
The pawr_host_task runs the NimBLE stack:
|
||||
```c
|
||||
void pawr_host_task(void *param)
|
||||
{
|
||||
ESP_LOGI(TAG, "BLE Host Task Started");
|
||||
nimble_port_run();
|
||||
nimble_port_freertos_deinit();
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
This PAwR example demonstrates:
|
||||
|
||||
1. Configuration of periodic advertising with subevents and response slots
|
||||
|
||||
2. Bidirectional communication between advertiser and scanners
|
||||
|
||||
3. Efficient power usage through scheduled communication windows
|
||||
|
||||
4. Use of extended advertising to announce PAwR capabilities
|
||||
|
||||
The implementation shows how to:
|
||||
|
||||
- Set up PAwR parameters (intervals, slots, etc.)
|
||||
|
||||
- Handle data requests for subevents
|
||||
|
||||
- Process responses from scanners
|
||||
|
||||
- Manage the advertising lifecycle
|
||||
|
||||
PAwR is particularly useful for applications requiring periodic, bidirectional communication with multiple devices while maintaining low power consumption.
|
@ -1,320 +0,0 @@
|
||||
# BLE Periodic Advertisement Example Walkthrough
|
||||
|
||||
## Introduction
|
||||
|
||||
In this tutorial, the ble_periodic_adv example code for the espressif chipsets with BLE5.0 support is reviewed. This example aims at understanding periodic advertisements and related NimBLE APIs.This code implements the periodic advertisement functionality along with extended advertisement by generating a non-resolvable private address.
|
||||
|
||||
## Includes
|
||||
|
||||
This example is located in the examples folder of the ESP-IDF under the [ble_periodic_adv/main](../main). The [main.c](../main/main.c) file located in the main folder contains all the functionality that we are going to review. The header files contained in [main.c](../main/main.c) are:
|
||||
|
||||
```c
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
/* BLE */
|
||||
#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 "patterns.h"
|
||||
```
|
||||
These `includes` are required for the FreeRTOS and underlying system components to run, including the logging functionality and a library to store data in non-volatile flash memory. We are interested in `“nimble_port.h”`, `“nimble_port_freertos.h”`, `"ble_hs.h"` and `“ble_svc_gap.h”`, `“periodic_adv.h”` which expose the BLE APIs required to implement this example.
|
||||
|
||||
* `nimble_port.h`: Includes the declaration of functions required for the initialization of the nimble stack.
|
||||
* `nimble_port_freertos.h`: Initializes and enables nimble host task.
|
||||
* `ble_hs.h`: Defines the functionalities to handle the host event
|
||||
* `ble_svc_gap.h`:Defines the macros for device name, and device appearance and declares the function to set them.
|
||||
* `periodic_adv.h`:It includes the code containing forward declarations of 2 structs `ble_hs_cfg` , and `ble_gatt_register_ctxt` based on weather macro `H_BLE_PERIODIC_ADV_` is defined.
|
||||
|
||||
## Main Entry Point
|
||||
|
||||
The program’s entry point is the app_main() function:
|
||||
```c
|
||||
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 ", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Initialize the NimBLE host configuration. */
|
||||
ble_hs_cfg.reset_cb = periodic_adv_on_reset;
|
||||
ble_hs_cfg.sync_cb = periodic_adv_on_sync;
|
||||
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
|
||||
/* Set the default device name. */
|
||||
rc = ble_svc_gap_device_name_set("nimble_periodic_adv");
|
||||
assert(rc == 0);
|
||||
|
||||
/* XXX Need to have a template for store */
|
||||
ble_store_config_init();
|
||||
|
||||
nimble_port_freertos_init(periodic_adv_host_task);
|
||||
}
|
||||
```
|
||||
The main function starts by initializing the non-volatile storage library. This library allows us to save the key-value pairs in flash memory.`nvs_flash_init()` stores the PHY calibration data. In a Bluetooth Low Energy (BLE) device, cryptographic keys used for encryption and authentication are often stored in Non-Volatile Storage (NVS).BLE stores the peer keys, CCCD keys, peer records, etc on NVS. By storing these keys in NVS, the BLE device can quickly retrieve them when needed, without the need for time-consuming key generations.
|
||||
```c
|
||||
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 );
|
||||
```
|
||||
|
||||
## BT Controller and Stack Initialization
|
||||
|
||||
The main function calls `nimble_port_init()` to initialize BT Controller and nimble stack. This function initializes the BT controller by first creating its configuration structure named `esp_bt_controller_config_t` with default settings generated by the `BT_CONTROLLER_INIT_CONFIG_DEFAULT()` macro. It implements the Host Controller Interface (HCI) on the controller side, the Link Layer (LL), and the Physical Layer (PHY). The BT Controller is invisible to the user applications and deals with the lower layers of the BLE stack. The controller configuration includes setting the BT controller stack size, priority, and HCI baud rate. With the settings created, the BT controller is initialized and enabled with the `esp_bt_controller_init()` and `esp_bt_controller_enable()` functions:
|
||||
|
||||
```c
|
||||
esp_bt_controller_config_t config_opts = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&config_opts);
|
||||
```
|
||||
Next, the controller is enabled in BLE Mode.
|
||||
|
||||
```c
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
```
|
||||
The controller should be enabled in `ESP_BT_MODE_BLE` if you want to use the BLE mode.
|
||||
|
||||
There are four Bluetooth modes supported:
|
||||
|
||||
1. `ESP_BT_MODE_IDLE`: Bluetooth not running
|
||||
2. `ESP_BT_MODE_BLE`: BLE mode
|
||||
3. `ESP_BT_MODE_CLASSIC_BT`: BT Classic mode
|
||||
4. `ESP_BT_MODE_BTDM`: Dual mode (BLE + BT Classic)
|
||||
|
||||
After the initialization of the BT controller, the nimble stack, which includes the common definitions and APIs for BLE, is initialized by using `esp_nimble_init()`:
|
||||
|
||||
```c
|
||||
esp_err_t esp_nimble_init(void)
|
||||
{
|
||||
|
||||
#if !SOC_ESP_NIMBLE_CONTROLLER
|
||||
/* Initialize the function pointers for OS porting */
|
||||
npl_freertos_funcs_init();
|
||||
|
||||
npl_freertos_mempool_init();
|
||||
|
||||
if(esp_nimble_hci_init() != ESP_OK) {
|
||||
ESP_LOGE(NIMBLE_PORT_LOG_TAG, "hci inits failed\n");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Initialize default event queue */
|
||||
ble_npl_eventq_init(&g_eventq_dflt);
|
||||
|
||||
os_msys_init();
|
||||
|
||||
void ble_store_ram_init(void);
|
||||
/* XXX Need to have a template for store */
|
||||
ble_store_ram_init();
|
||||
#endif
|
||||
|
||||
/* Initialize the host */
|
||||
ble_hs_init();
|
||||
return ESP_OK;
|
||||
}
|
||||
```
|
||||
|
||||
The host is configured by setting up the callbacks for Stack-reset, Stack-sync, and Storage status
|
||||
|
||||
```c
|
||||
ble_hs_cfg.reset_cb = periodic_adv_on_reset;
|
||||
ble_hs_cfg.sync_cb = periodic_adv_on_sync;
|
||||
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
|
||||
```
|
||||
|
||||
The main function calls `ble_svc_gap_device_name_set()` to set the default device name. 'blecent_phy' is passed as the default device name to this function.
|
||||
```c
|
||||
rc = ble_svc_gap_device_name_set("nimble_periodic_adv");
|
||||
```
|
||||
main function calls `ble_store_config_init()` to configure the host by setting up the storage callbacks which handle the read, write, and deletion of security material.
|
||||
```c
|
||||
/* XXX Need to have a template for store */
|
||||
ble_store_config_init();
|
||||
```
|
||||
|
||||
The main function ends by creating a task where nimble will run using `nimble_port_freertos_init()`. This enables the nimble stack by using `esp_nimble_enable()`.
|
||||
|
||||
```c
|
||||
nimble_port_freertos_init(periodic_adv_host_task);
|
||||
```
|
||||
`esp_nimble_enable()` create a task where the nimble host will run. It is not strictly necessary to have a separate task for the nimble host, but to handle the default queue, it is easier to create a separate task.
|
||||
|
||||
## Generation of non-resolvable private address
|
||||
|
||||
In Bluetooth Low Energy (BLE), a non-resolvable private address is a type of Bluetooth device address that is used for privacy purposes. It is a randomly generated address that changes periodically to prevent long-term tracking of a device. The API call to `ble_hs_id_gen_rnd()` is responsible for generating a non-resolvable private address. NRPA is a 48-bit address that is stored in `addr.val`.
|
||||
|
||||
```c
|
||||
#if CONFIG_EXAMPLE_RANDOM_ADDR
|
||||
static void
|
||||
periodic_adv_set_addr(void)
|
||||
{
|
||||
ble_addr_t addr;
|
||||
int rc;
|
||||
|
||||
/* generate new non-resolvable private address */
|
||||
rc = ble_hs_id_gen_rnd(0, &addr);
|
||||
assert(rc == 0);
|
||||
|
||||
/* set generated address */
|
||||
rc = ble_hs_id_set_rnd(addr.val);
|
||||
|
||||
assert(rc == 0);
|
||||
}
|
||||
#endif
|
||||
```
|
||||
|
||||
## Periodic Advertisement
|
||||
|
||||
Periodic advertisement start by creating instances of structures `ble_gap_periodic_adv_params`, `ble_gap_ext_adv_params`, `ble_hs_adv_fields`, and `os_mbuf`. Advertising parameters such as connecting modes, advertising intervals, peer address, advertising-filter policy, etc are defined in these structures for periodic and extended advertisements. `pparams` and `params` instances have parameters for periodic advertisement and extended advertisement respectively.
|
||||
Bluetooth device address is given by the structure ble_aadr_t which contains the fields for address type and address value.
|
||||
|
||||
## Need of Extended Advertisement in Periodic Advertisement
|
||||
|
||||
Non-connectable and non-scannable advertising events containing synchronization information about a periodic advertising train are necessary for the scanner device to sync with the periodic advertising train. The periodic advertising will utilize the same physical layer (PHY) as the auxiliary packet, which is part of the extended advertisement.
|
||||
|
||||
|
||||
Below is the implementation to start periodic advertisement.
|
||||
|
||||
```c
|
||||
static void
|
||||
start_periodic_adv(void)
|
||||
{
|
||||
int rc;
|
||||
struct ble_gap_periodic_adv_params pparams;
|
||||
struct ble_gap_ext_adv_params params;
|
||||
struct ble_hs_adv_fields adv_fields;
|
||||
struct os_mbuf *data;
|
||||
uint8_t instance = 1;
|
||||
ble_addr_t addr;
|
||||
|
||||
/* set random (NRPA) address for instance */
|
||||
rc = ble_hs_id_gen_rnd(1, &addr);
|
||||
assert (rc == 0);
|
||||
|
||||
MODLOG_DFLT(INFO, "Device Address: ");
|
||||
print_addr(addr.val);
|
||||
MODLOG_DFLT(INFO, "\n");
|
||||
|
||||
/* For periodic we use instance with non-connectable advertising */
|
||||
memset (¶ms, 0, sizeof(params));
|
||||
|
||||
/* advertise using random addr */
|
||||
params.own_addr_type = BLE_OWN_ADDR_RANDOM;
|
||||
params.primary_phy = BLE_HCI_LE_PHY_1M;
|
||||
params.secondary_phy = BLE_HCI_LE_PHY_2M;
|
||||
params.sid = 2;
|
||||
|
||||
/* configure instance 1 */
|
||||
rc = ble_gap_ext_adv_configure(instance, ¶ms, NULL, NULL, NULL);
|
||||
assert (rc == 0);
|
||||
|
||||
rc = ble_gap_ext_adv_set_addr(instance, &addr );
|
||||
assert (rc == 0);
|
||||
|
||||
memset(&adv_fields, 0, sizeof(adv_fields));
|
||||
adv_fields.name = (const uint8_t *)"Periodic ADV";
|
||||
adv_fields.name_len = strlen((char *)adv_fields.name);
|
||||
|
||||
/* Default to legacy PDUs size, mbuf chain will be increased if needed
|
||||
*/
|
||||
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 */
|
||||
memset(&pparams, 0, sizeof(pparams));
|
||||
pparams.include_tx_power = 0;
|
||||
pparams.itvl_min = BLE_GAP_ADV_ITVL_MS(120);
|
||||
pparams.itvl_max = BLE_GAP_ADV_ITVL_MS(240);
|
||||
|
||||
rc = ble_gap_periodic_adv_configure(instance, &pparams);
|
||||
assert(rc == 0);
|
||||
|
||||
data = os_msys_get_pkthdr(sizeof(periodic_adv_raw_data), 0);
|
||||
assert(data);
|
||||
|
||||
rc = os_mbuf_append(data, periodic_adv_raw_data, sizeof(periodic_adv_raw_data));
|
||||
assert(rc == 0);
|
||||
rc = ble_gap_periodic_adv_set_data(instance, data);
|
||||
assert (rc == 0);
|
||||
|
||||
/* start periodic advertising */
|
||||
assert (rc == 0 rc = ble_gap_periodic_adv_start(instance);
|
||||
);
|
||||
|
||||
/* start advertising */
|
||||
rc = ble_gap_ext_adv_start(instance, 0, 0);
|
||||
assert (rc == 0);
|
||||
|
||||
MODLOG_DFLT(INFO, "instance %u started (periodic)\n", instance);
|
||||
}
|
||||
```
|
||||
|
||||
The periodic advertisement uses a non-connectable advertising mode. `memset (¶ms, 0, sizeof(params))` initialises params to 0. This also sets `params.connectable` to 0.
|
||||
|
||||
## Parameter Configuration
|
||||
|
||||
The below snippets represent the parameter configuration for extended and periodic advertisement.
|
||||
|
||||
### For Extended Advertisement
|
||||
|
||||
```c
|
||||
params.own_addr_type = BLE_OWN_ADDR_RANDOM; //Own address type is set to Random
|
||||
params.primary_phy = BLE_HCI_LE_PHY_1M; // Primary advertising PHY is set to 1M
|
||||
params.secondary_phy = BLE_HCI_LE_PHY_2M; // Secondary advertising PHY is set to 2M
|
||||
params.sid = 2; // Advertising set Id is assigned with value 2.
|
||||
```
|
||||
|
||||
### For Periodic Advertisement
|
||||
|
||||
```c
|
||||
memset(&pparams, 0, sizeof(pparams));
|
||||
pparams.include_tx_power = 0; // Indicates that TX power is not included in advertising PDU
|
||||
pparams.itvl_min = BLE_GAP_ADV_ITVL_MS(120); // Minimum advertising interval of 240ms
|
||||
pparams.itvl_max = BLE_GAP_ADV_ITVL_MS(240); //Maximum advertising interval of 480ms
|
||||
```
|
||||
|
||||
Periodic advertisement is started for a particular advertisement instance by calling the API `ble_gap_periodic_adv_start(instance)`. This function takes instance-id as an input parameter. It defines the hci command by initializing the command parameters which are represented in the following lines.
|
||||
|
||||
```c
|
||||
struct ble_hci_le_set_periodic_adv_enable_cp cmd;
|
||||
cmd.enable = 0x01;
|
||||
cmd.adv_handle = instance;
|
||||
```
|
||||
|
||||
Extended advertising is invoked for a particular instance using the API call `ble_gap_ext_adv_start(instance, 0, 0)`.Instance-id, duration, and max_events are input parameters for this API call respectively.
|
||||
|
||||
Duration represents the time for which the adverteiment will take place. Upon expiration, the advertising procedure ends, and the BLE_GAP_EVENT_ADV_COMPLETE event is reported.0 value is used for no expiration.
|
||||
|
||||
max_events Number of advertising events that should be sent before advertising ends and a BLE_GAP_EVENT_ADV_COMPLETE event is reported.0 value is used for no limit.
|
||||
|
||||
## Conclusion
|
||||
|
||||
This Walkthrough covers the code explanation of the BLE_PERIODIC_ADV. The following points are concluded through this walkthrough.
|
||||
|
||||
1. Periodic advertising allows the scanner to sync with the advertiser so the scanner and advertiser would wake up at the same time.
|
||||
2. Periodic advertisement uses NRPA (Non Resolvable private address). It is a randomly generated address that changes periodically to prevent long-term tracking of a device.
|
||||
3. Extended advertising is used to indicate to the scanner that the advertiser is utilizing periodic advertising. Therefore, periodic advertising is started before extended advertising so that the scanner and advertiser can synchronize their actions and operate at the same time.
|
@ -1,19 +1,25 @@
|
||||
| Supported Targets | ESP32-C6 |
|
||||
| ----------------- | -------- |
|
||||
|
||||
# BLE Periodic Advertiser Example
|
||||
# Important Note
|
||||
*This example currently requires an external Bluetooth controller supporting PAwR functionality, as the ESP chips listed above do not have native controller support for PAwR features and under development phase*
|
||||
|
||||
# BLE Periodic Advertiser With Response (PAwR) Sync Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
BLE PAwR Advertiser Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example starts periodic advertising with non resolvable private address.
|
||||
This example demonstrates synchronization PAwR sync using the NimBLE stack and related APIS.
|
||||
|
||||
It uses Bluetooth controller and NimBLE stack based BLE host.
|
||||
|
||||
This example aims at understanding periodic advertisement and related NimBLE APIs.
|
||||
|
||||
|
||||
To test this demo, any BLE Periodic Sync app can be used.
|
||||
To test this demo, any BLE advertiser supporting PAwR can be used.(check /examples/ble_pawr_adv/ble_pawr_adv).
|
||||
|
||||
## Key Features
|
||||
- Synchronization with PAwR advertising trains
|
||||
- Configurable synchronization parameters (skip factor, timeout)
|
||||
- Detailed reporting of PAwR advertisement data
|
||||
- Handling of synchronization loss events
|
||||
|
||||
Note :
|
||||
|
||||
@ -49,26 +55,21 @@ Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
|
||||
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 (608) NimBLE: GAP procedure initiated: extended discovery;
|
||||
|
||||
I (618) main_task: Returned from app_main()
|
||||
I (678) NimBLE_BLE_PAwR: [Disc advertiser] addr 40:4c:ca:46:1f:e2, props: 0x0, rssi:-43
|
||||
I (688) NimBLE_BLE_PAwR: Create sync
|
||||
I (1308) NimBLE_BLE_PAwR: [Periodic Sync Established] sync handle:0, num_subevents:0xa
|
||||
I (1308) NimBLE_BLE_PAwR: subevent_interval:0x3c, slot_delay:0x6,slot_spacing:0x2
|
||||
I (1308) NimBLE_BLE_PAwR: [Subevent Sync OK] sync handle:0, sync_subevents:4
|
||||
I (1318) NimBLE_BLE_PAwR: [Periodic Adv Report] handle:0, event_counter(1521), subevent(0)
|
||||
I (2058) NimBLE_BLE_PAwR: [Periodic Adv Report] handle:0, event_counter(1522), subevent(0)
|
||||
I (2058) NimBLE_BLE_PAwR: [RSP Data Set] sync handle: 0, subev(0), rsp_slot(2), rc(0x0)
|
||||
I (2208) NimBLE_BLE_PAwR: [Periodic Adv Report] handle:0, event_counter(1522), subevent(2)
|
||||
|
||||
```
|
||||
I (313) BTDM_INIT: BT controller compile version [2ee0168]
|
||||
I (313) phy_init: phy_version 912,d001756,Jun 2 2022,16:28:07
|
||||
I (353) system_api: Base MAC address is not set
|
||||
I (353) system_api: read default base MAC address from EFUSE
|
||||
I (353) BTDM_INIT: Bluetooth MAC: 84:f7:03:08:14:8e
|
||||
|
||||
I (363) NimBLE_BLE_PERIODIC_ADV: BLE Host Task Started
|
||||
I (373) NimBLE: Device Address:
|
||||
I (373) NimBLE: d0:42:3a:95:84:05
|
||||
I (373) NimBLE:
|
||||
|
||||
I (383) NimBLE: instance 1 started (periodic)
|
||||
```
|
||||
|
||||
## Note
|
||||
* Periodic sync transfer is not implemented for now.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
@ -0,0 +1,273 @@
|
||||
# BLE Periodic Advertisement with Responses (PAwR) Sync Example Walkthrough
|
||||
|
||||
## Introduction
|
||||
|
||||
This tutorial examines the BLE Periodic Advertisement with Responses (PAwR) Sync example code for ESP32 chipsets with BLE 5.0+ support. The code demonstrates how to implement a scanner/synchronizer device that connects to a PAwR advertiser, establishes synchronization, and exchanges data through response slots.
|
||||
|
||||
## Includes
|
||||
|
||||
This example is located in the examples folder of the ESP-IDF under the [ble_pawr_sync/main](../main). The [main.c](../main/main.c) file located in the main folder contains all the functionality that we are going to review. The header files contained in [main.c](../main/main.c) are:
|
||||
|
||||
```c
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
/* BLE */
|
||||
#include "nimble/nimble_port.h"
|
||||
#include "nimble/nimble_port_freertos.h"
|
||||
#include "host/ble_hs.h"
|
||||
#include "host/util/util.h"
|
||||
```
|
||||
These includes provide:
|
||||
|
||||
- ESP32 logging functionality (esp_log.h)
|
||||
|
||||
- Non-volatile storage (nvs_flash.h)
|
||||
|
||||
- NimBLE stack porting and FreeRTOS integration
|
||||
|
||||
- BLE host stack functionality
|
||||
|
||||
- Utility functions for BLE operations
|
||||
|
||||
## Main Entry Point
|
||||
|
||||
The program’s entry point is the app_main() function:
|
||||
```c
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
/* Initialize NVS — it is used to store PHY calibration data */
|
||||
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 ", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Initialize the NimBLE host configuration. */
|
||||
ble_hs_cfg.reset_cb = on_reset;
|
||||
ble_hs_cfg.sync_cb = on_sync;
|
||||
|
||||
nimble_port_freertos_init(pawr_host_task);
|
||||
}
|
||||
```
|
||||
|
||||
Key steps:
|
||||
|
||||
- Initialize NVS flash storage for PHY calibration data
|
||||
|
||||
- Initialize the NimBLE stack
|
||||
|
||||
- Set up reset and sync callbacks
|
||||
|
||||
Start the NimBLE host task
|
||||
The main function starts by initializing the non-volatile storage library. This library allows us to save the key-value pairs in flash memory.`nvs_flash_init()` stores the PHY calibration data. In a Bluetooth Low Energy (BLE) device, cryptographic keys used for encryption and authentication are often stored in Non-Volatile Storage (NVS).BLE stores the peer keys, CCCD keys, peer records, etc on NVS. By storing these keys in NVS, the BLE device can quickly retrieve them when needed, without the need for time-consuming key generations.
|
||||
```c
|
||||
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 );
|
||||
```
|
||||
|
||||
## BT Controller and Stack Initialization
|
||||
|
||||
The main function calls `nimble_port_init()` to initialize BT Controller and nimble stack. This function initializes the BT controller by first creating its configuration structure named `esp_bt_controller_config_t` with default settings generated by the `BT_CONTROLLER_INIT_CONFIG_DEFAULT()` macro. It implements the Host Controller Interface (HCI) on the controller side, the Link Layer (LL), and the Physical Layer (PHY). The BT Controller is invisible to the user applications and deals with the lower layers of the BLE stack. The controller configuration includes setting the BT controller stack size, priority, and HCI baud rate. With the settings created, the BT controller is initialized and enabled with the `esp_bt_controller_init()` and `esp_bt_controller_enable()` functions:
|
||||
|
||||
```c
|
||||
esp_bt_controller_config_t config_opts = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&config_opts);
|
||||
```
|
||||
Next, the controller is enabled in BLE Mode.
|
||||
|
||||
```c
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
```
|
||||
The controller should be enabled in `ESP_BT_MODE_BLE` if you want to use the BLE mode.
|
||||
|
||||
There are four Bluetooth modes supported:
|
||||
|
||||
1. `ESP_BT_MODE_IDLE`: Bluetooth not running
|
||||
2. `ESP_BT_MODE_BLE`: BLE mode
|
||||
3. `ESP_BT_MODE_CLASSIC_BT`: BT Classic mode
|
||||
4. `ESP_BT_MODE_BTDM`: Dual mode (BLE + BT Classic)
|
||||
|
||||
After the initialization of the BT controller, the nimble stack, which includes the common definitions and APIs for BLE, is initialized by using `esp_nimble_init()`:
|
||||
|
||||
```c
|
||||
esp_err_t esp_nimble_init(void)
|
||||
{
|
||||
|
||||
#if !SOC_ESP_NIMBLE_CONTROLLER
|
||||
/* Initialize the function pointers for OS porting */
|
||||
npl_freertos_funcs_init();
|
||||
|
||||
npl_freertos_mempool_init();
|
||||
|
||||
if(esp_nimble_hci_init() != ESP_OK) {
|
||||
ESP_LOGE(NIMBLE_PORT_LOG_TAG, "hci inits failed\n");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Initialize default event queue */
|
||||
ble_npl_eventq_init(&g_eventq_dflt);
|
||||
|
||||
os_msys_init();
|
||||
|
||||
void ble_store_ram_init(void);
|
||||
/* XXX Need to have a template for store */
|
||||
ble_store_ram_init();
|
||||
#endif
|
||||
|
||||
/* Initialize the host */
|
||||
ble_hs_init();
|
||||
return ESP_OK;
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Parameters
|
||||
|
||||
The example defines several key parameters:
|
||||
|
||||
```c
|
||||
#define TAG "NimBLE_BLE_PAwR"
|
||||
#define TARGET_NAME "Nimble_PAwR"
|
||||
#define BLE_PAWR_RSP_DATA_LEN (16)
|
||||
static uint8_t sub_data_pattern[BLE_PAWR_RSP_DATA_LEN] = {0};
|
||||
```
|
||||
These parameters control:
|
||||
|
||||
- Target advertiser name to sync with
|
||||
|
||||
- Response data length
|
||||
|
||||
- Data pattern for responses
|
||||
|
||||
|
||||
## GAP Event Callback
|
||||
The gap_event_cb function handles PAwR sync events:
|
||||
```c
|
||||
static int gap_event_cb(struct ble_gap_event *event, void *arg)
|
||||
{
|
||||
switch (event->type) {
|
||||
case BLE_GAP_EVENT_EXT_DISC:
|
||||
/* Handle advertiser discovery */
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_PERIODIC_REPORT:
|
||||
/* Handle periodic reports and send responses */
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_PERIODIC_SYNC:
|
||||
/* Handle sync establishment */
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_PERIODIC_SYNC_LOST:
|
||||
/* Handle sync loss */
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## Scanning and Synchronization
|
||||
1. Starting the Scan
|
||||
|
||||
```c
|
||||
static void start_scan(void)
|
||||
{
|
||||
struct ble_gap_ext_disc_params disc_params = {
|
||||
.itvl = BLE_GAP_SCAN_ITVL_MS(600),
|
||||
.window = BLE_GAP_SCAN_ITVL_MS(300),
|
||||
.passive = 1
|
||||
};
|
||||
|
||||
ble_gap_ext_disc(BLE_OWN_ADDR_PUBLIC, 0, 0, 1, 0, 0, NULL,
|
||||
&disc_params, gap_event_cb, NULL);
|
||||
}
|
||||
```
|
||||
Key parameters:
|
||||
|
||||
- Scan interval: 600ms
|
||||
|
||||
- Scan window: 300ms
|
||||
|
||||
- Passive scanning mode
|
||||
|
||||
2. Creating Periodic Sync
|
||||
|
||||
```c
|
||||
static int create_periodic_sync(struct ble_gap_ext_disc_desc *disc)
|
||||
{
|
||||
struct ble_gap_periodic_sync_params params = {
|
||||
.skip = 0,
|
||||
.sync_timeout = 4000, // 4 seconds timeout
|
||||
.reports_disabled = 0
|
||||
};
|
||||
|
||||
#if CONFIG_EXAMPLE_PERIODIC_ADV_ENH
|
||||
params.filter_duplicates = 1;
|
||||
#endif
|
||||
|
||||
return ble_gap_periodic_adv_sync_create(&disc->addr, disc->sid,
|
||||
¶ms, gap_event_cb, NULL);
|
||||
}
|
||||
```
|
||||
|
||||
## Response Handling
|
||||
When receiving periodic reports:
|
||||
|
||||
```c
|
||||
case BLE_GAP_EVENT_PERIODIC_REPORT:
|
||||
// Prepare response parameters
|
||||
struct ble_gap_periodic_adv_response_params param = {
|
||||
.request_event = event->periodic_report.event_counter,
|
||||
.request_subevent = event->periodic_report.subevent,
|
||||
.response_subevent = event->periodic_report.subevent,
|
||||
.response_slot = rsp_slot_idx
|
||||
};
|
||||
|
||||
// Prepare response data
|
||||
sub_data_pattern[0] = event->periodic_report.data[0];
|
||||
memset(sub_data_pattern + 1, event->periodic_report.subevent,
|
||||
BLE_PAWR_RSP_DATA_LEN - 1);
|
||||
|
||||
// Set response data
|
||||
rc = ble_gap_periodic_adv_set_response_data(
|
||||
event->periodic_report.sync_handle, ¶m, data);
|
||||
break;
|
||||
```
|
||||
## Subevent Configuration
|
||||
|
||||
After sync establishment:
|
||||
|
||||
```c
|
||||
// Choose subevents to listen to
|
||||
uint8_t subevents[] = {0, 1, 2, 3, 4};
|
||||
int result = ble_gap_periodic_adv_sync_subev(
|
||||
event->periodic_sync.sync_handle, 0, sizeof(subevents), subevents);
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
When sync is lost:
|
||||
```c
|
||||
case BLE_GAP_EVENT_PERIODIC_SYNC_LOST:
|
||||
ESP_LOGE(TAG, "[Periodic Sync Lost] handle:%d, Reason = 0x%x",
|
||||
event->periodic_sync_lost.sync_handle,
|
||||
event->periodic_sync_lost.reason);
|
||||
synced = false;
|
||||
start_scan();
|
||||
return 0;
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
This implementation demonstrates a complete PAwR synchronization solution, showcasing advertiser discovery via extended scanning, periodic sync establishment with configurable subevents (0-4), and efficient bidirectional communication through managed response slots. The robust architecture handles sync loss recovery while maintaining low-power operation, making it ideal for IoT applications requiring scheduled, bidirectional communication with multiple endpoints. The solution leverages BLE 5.0's PAwR features to optimize power efficiency and reliability in dense RF environments.
|
@ -1,320 +0,0 @@
|
||||
# BLE Periodic Advertisement Example Walkthrough
|
||||
|
||||
## Introduction
|
||||
|
||||
In this tutorial, the ble_periodic_adv example code for the espressif chipsets with BLE5.0 support is reviewed. This example aims at understanding periodic advertisements and related NimBLE APIs.This code implements the periodic advertisement functionality along with extended advertisement by generating a non-resolvable private address.
|
||||
|
||||
## Includes
|
||||
|
||||
This example is located in the examples folder of the ESP-IDF under the [ble_periodic_adv/main](../main). The [main.c](../main/main.c) file located in the main folder contains all the functionality that we are going to review. The header files contained in [main.c](../main/main.c) are:
|
||||
|
||||
```c
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
/* BLE */
|
||||
#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 "patterns.h"
|
||||
```
|
||||
These `includes` are required for the FreeRTOS and underlying system components to run, including the logging functionality and a library to store data in non-volatile flash memory. We are interested in `“nimble_port.h”`, `“nimble_port_freertos.h”`, `"ble_hs.h"` and `“ble_svc_gap.h”`, `“periodic_adv.h”` which expose the BLE APIs required to implement this example.
|
||||
|
||||
* `nimble_port.h`: Includes the declaration of functions required for the initialization of the nimble stack.
|
||||
* `nimble_port_freertos.h`: Initializes and enables nimble host task.
|
||||
* `ble_hs.h`: Defines the functionalities to handle the host event
|
||||
* `ble_svc_gap.h`:Defines the macros for device name, and device appearance and declares the function to set them.
|
||||
* `periodic_adv.h`:It includes the code containing forward declarations of 2 structs `ble_hs_cfg` , and `ble_gatt_register_ctxt` based on weather macro `H_BLE_PERIODIC_ADV_` is defined.
|
||||
|
||||
## Main Entry Point
|
||||
|
||||
The program’s entry point is the app_main() function:
|
||||
```c
|
||||
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 ", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Initialize the NimBLE host configuration. */
|
||||
ble_hs_cfg.reset_cb = periodic_adv_on_reset;
|
||||
ble_hs_cfg.sync_cb = periodic_adv_on_sync;
|
||||
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
|
||||
/* Set the default device name. */
|
||||
rc = ble_svc_gap_device_name_set("nimble_periodic_adv");
|
||||
assert(rc == 0);
|
||||
|
||||
/* XXX Need to have a template for store */
|
||||
ble_store_config_init();
|
||||
|
||||
nimble_port_freertos_init(periodic_adv_host_task);
|
||||
}
|
||||
```
|
||||
The main function starts by initializing the non-volatile storage library. This library allows us to save the key-value pairs in flash memory.`nvs_flash_init()` stores the PHY calibration data. In a Bluetooth Low Energy (BLE) device, cryptographic keys used for encryption and authentication are often stored in Non-Volatile Storage (NVS).BLE stores the peer keys, CCCD keys, peer records, etc on NVS. By storing these keys in NVS, the BLE device can quickly retrieve them when needed, without the need for time-consuming key generations.
|
||||
```c
|
||||
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 );
|
||||
```
|
||||
|
||||
## BT Controller and Stack Initialization
|
||||
|
||||
The main function calls `nimble_port_init()` to initialize BT Controller and nimble stack. This function initializes the BT controller by first creating its configuration structure named `esp_bt_controller_config_t` with default settings generated by the `BT_CONTROLLER_INIT_CONFIG_DEFAULT()` macro. It implements the Host Controller Interface (HCI) on the controller side, the Link Layer (LL), and the Physical Layer (PHY). The BT Controller is invisible to the user applications and deals with the lower layers of the BLE stack. The controller configuration includes setting the BT controller stack size, priority, and HCI baud rate. With the settings created, the BT controller is initialized and enabled with the `esp_bt_controller_init()` and `esp_bt_controller_enable()` functions:
|
||||
|
||||
```c
|
||||
esp_bt_controller_config_t config_opts = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&config_opts);
|
||||
```
|
||||
Next, the controller is enabled in BLE Mode.
|
||||
|
||||
```c
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
```
|
||||
The controller should be enabled in `ESP_BT_MODE_BLE` if you want to use the BLE mode.
|
||||
|
||||
There are four Bluetooth modes supported:
|
||||
|
||||
1. `ESP_BT_MODE_IDLE`: Bluetooth not running
|
||||
2. `ESP_BT_MODE_BLE`: BLE mode
|
||||
3. `ESP_BT_MODE_CLASSIC_BT`: BT Classic mode
|
||||
4. `ESP_BT_MODE_BTDM`: Dual mode (BLE + BT Classic)
|
||||
|
||||
After the initialization of the BT controller, the nimble stack, which includes the common definitions and APIs for BLE, is initialized by using `esp_nimble_init()`:
|
||||
|
||||
```c
|
||||
esp_err_t esp_nimble_init(void)
|
||||
{
|
||||
|
||||
#if !SOC_ESP_NIMBLE_CONTROLLER
|
||||
/* Initialize the function pointers for OS porting */
|
||||
npl_freertos_funcs_init();
|
||||
|
||||
npl_freertos_mempool_init();
|
||||
|
||||
if(esp_nimble_hci_init() != ESP_OK) {
|
||||
ESP_LOGE(NIMBLE_PORT_LOG_TAG, "hci inits failed\n");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Initialize default event queue */
|
||||
ble_npl_eventq_init(&g_eventq_dflt);
|
||||
|
||||
os_msys_init();
|
||||
|
||||
void ble_store_ram_init(void);
|
||||
/* XXX Need to have a template for store */
|
||||
ble_store_ram_init();
|
||||
#endif
|
||||
|
||||
/* Initialize the host */
|
||||
ble_hs_init();
|
||||
return ESP_OK;
|
||||
}
|
||||
```
|
||||
|
||||
The host is configured by setting up the callbacks for Stack-reset, Stack-sync, and Storage status
|
||||
|
||||
```c
|
||||
ble_hs_cfg.reset_cb = periodic_adv_on_reset;
|
||||
ble_hs_cfg.sync_cb = periodic_adv_on_sync;
|
||||
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
|
||||
```
|
||||
|
||||
The main function calls `ble_svc_gap_device_name_set()` to set the default device name. 'blecent_phy' is passed as the default device name to this function.
|
||||
```c
|
||||
rc = ble_svc_gap_device_name_set("nimble_periodic_adv");
|
||||
```
|
||||
main function calls `ble_store_config_init()` to configure the host by setting up the storage callbacks which handle the read, write, and deletion of security material.
|
||||
```c
|
||||
/* XXX Need to have a template for store */
|
||||
ble_store_config_init();
|
||||
```
|
||||
|
||||
The main function ends by creating a task where nimble will run using `nimble_port_freertos_init()`. This enables the nimble stack by using `esp_nimble_enable()`.
|
||||
|
||||
```c
|
||||
nimble_port_freertos_init(periodic_adv_host_task);
|
||||
```
|
||||
`esp_nimble_enable()` create a task where the nimble host will run. It is not strictly necessary to have a separate task for the nimble host, but to handle the default queue, it is easier to create a separate task.
|
||||
|
||||
## Generation of non-resolvable private address
|
||||
|
||||
In Bluetooth Low Energy (BLE), a non-resolvable private address is a type of Bluetooth device address that is used for privacy purposes. It is a randomly generated address that changes periodically to prevent long-term tracking of a device. The API call to `ble_hs_id_gen_rnd()` is responsible for generating a non-resolvable private address. NRPA is a 48-bit address that is stored in `addr.val`.
|
||||
|
||||
```c
|
||||
#if CONFIG_EXAMPLE_RANDOM_ADDR
|
||||
static void
|
||||
periodic_adv_set_addr(void)
|
||||
{
|
||||
ble_addr_t addr;
|
||||
int rc;
|
||||
|
||||
/* generate new non-resolvable private address */
|
||||
rc = ble_hs_id_gen_rnd(0, &addr);
|
||||
assert(rc == 0);
|
||||
|
||||
/* set generated address */
|
||||
rc = ble_hs_id_set_rnd(addr.val);
|
||||
|
||||
assert(rc == 0);
|
||||
}
|
||||
#endif
|
||||
```
|
||||
|
||||
## Periodic Advertisement
|
||||
|
||||
Periodic advertisement start by creating instances of structures `ble_gap_periodic_adv_params`, `ble_gap_ext_adv_params`, `ble_hs_adv_fields`, and `os_mbuf`. Advertising parameters such as connecting modes, advertising intervals, peer address, advertising-filter policy, etc are defined in these structures for periodic and extended advertisements. `pparams` and `params` instances have parameters for periodic advertisement and extended advertisement respectively.
|
||||
Bluetooth device address is given by the structure ble_aadr_t which contains the fields for address type and address value.
|
||||
|
||||
## Need of Extended Advertisement in Periodic Advertisement
|
||||
|
||||
Non-connectable and non-scannable advertising events containing synchronization information about a periodic advertising train are necessary for the scanner device to sync with the periodic advertising train. The periodic advertising will utilize the same physical layer (PHY) as the auxiliary packet, which is part of the extended advertisement.
|
||||
|
||||
|
||||
Below is the implementation to start periodic advertisement.
|
||||
|
||||
```c
|
||||
static void
|
||||
start_periodic_adv(void)
|
||||
{
|
||||
int rc;
|
||||
struct ble_gap_periodic_adv_params pparams;
|
||||
struct ble_gap_ext_adv_params params;
|
||||
struct ble_hs_adv_fields adv_fields;
|
||||
struct os_mbuf *data;
|
||||
uint8_t instance = 1;
|
||||
ble_addr_t addr;
|
||||
|
||||
/* set random (NRPA) address for instance */
|
||||
rc = ble_hs_id_gen_rnd(1, &addr);
|
||||
assert (rc == 0);
|
||||
|
||||
MODLOG_DFLT(INFO, "Device Address: ");
|
||||
print_addr(addr.val);
|
||||
MODLOG_DFLT(INFO, "\n");
|
||||
|
||||
/* For periodic we use instance with non-connectable advertising */
|
||||
memset (¶ms, 0, sizeof(params));
|
||||
|
||||
/* advertise using random addr */
|
||||
params.own_addr_type = BLE_OWN_ADDR_RANDOM;
|
||||
params.primary_phy = BLE_HCI_LE_PHY_1M;
|
||||
params.secondary_phy = BLE_HCI_LE_PHY_2M;
|
||||
params.sid = 2;
|
||||
|
||||
/* configure instance 1 */
|
||||
rc = ble_gap_ext_adv_configure(instance, ¶ms, NULL, NULL, NULL);
|
||||
assert (rc == 0);
|
||||
|
||||
rc = ble_gap_ext_adv_set_addr(instance, &addr );
|
||||
assert (rc == 0);
|
||||
|
||||
memset(&adv_fields, 0, sizeof(adv_fields));
|
||||
adv_fields.name = (const uint8_t *)"Periodic ADV";
|
||||
adv_fields.name_len = strlen((char *)adv_fields.name);
|
||||
|
||||
/* Default to legacy PDUs size, mbuf chain will be increased if needed
|
||||
*/
|
||||
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 */
|
||||
memset(&pparams, 0, sizeof(pparams));
|
||||
pparams.include_tx_power = 0;
|
||||
pparams.itvl_min = BLE_GAP_ADV_ITVL_MS(120);
|
||||
pparams.itvl_max = BLE_GAP_ADV_ITVL_MS(240);
|
||||
|
||||
rc = ble_gap_periodic_adv_configure(instance, &pparams);
|
||||
assert(rc == 0);
|
||||
|
||||
data = os_msys_get_pkthdr(sizeof(periodic_adv_raw_data), 0);
|
||||
assert(data);
|
||||
|
||||
rc = os_mbuf_append(data, periodic_adv_raw_data, sizeof(periodic_adv_raw_data));
|
||||
assert(rc == 0);
|
||||
rc = ble_gap_periodic_adv_set_data(instance, data);
|
||||
assert (rc == 0);
|
||||
|
||||
/* start periodic advertising */
|
||||
assert (rc == 0 rc = ble_gap_periodic_adv_start(instance);
|
||||
);
|
||||
|
||||
/* start advertising */
|
||||
rc = ble_gap_ext_adv_start(instance, 0, 0);
|
||||
assert (rc == 0);
|
||||
|
||||
MODLOG_DFLT(INFO, "instance %u started (periodic)\n", instance);
|
||||
}
|
||||
```
|
||||
|
||||
The periodic advertisement uses a non-connectable advertising mode. `memset (¶ms, 0, sizeof(params))` initialises params to 0. This also sets `params.connectable` to 0.
|
||||
|
||||
## Parameter Configuration
|
||||
|
||||
The below snippets represent the parameter configuration for extended and periodic advertisement.
|
||||
|
||||
### For Extended Advertisement
|
||||
|
||||
```c
|
||||
params.own_addr_type = BLE_OWN_ADDR_RANDOM; //Own address type is set to Random
|
||||
params.primary_phy = BLE_HCI_LE_PHY_1M; // Primary advertising PHY is set to 1M
|
||||
params.secondary_phy = BLE_HCI_LE_PHY_2M; // Secondary advertising PHY is set to 2M
|
||||
params.sid = 2; // Advertising set Id is assigned with value 2.
|
||||
```
|
||||
|
||||
### For Periodic Advertisement
|
||||
|
||||
```c
|
||||
memset(&pparams, 0, sizeof(pparams));
|
||||
pparams.include_tx_power = 0; // Indicates that TX power is not included in advertising PDU
|
||||
pparams.itvl_min = BLE_GAP_ADV_ITVL_MS(120); // Minimum advertising interval of 240ms
|
||||
pparams.itvl_max = BLE_GAP_ADV_ITVL_MS(240); //Maximum advertising interval of 480ms
|
||||
```
|
||||
|
||||
Periodic advertisement is started for a particular advertisement instance by calling the API `ble_gap_periodic_adv_start(instance)`. This function takes instance-id as an input parameter. It defines the hci command by initializing the command parameters which are represented in the following lines.
|
||||
|
||||
```c
|
||||
struct ble_hci_le_set_periodic_adv_enable_cp cmd;
|
||||
cmd.enable = 0x01;
|
||||
cmd.adv_handle = instance;
|
||||
```
|
||||
|
||||
Extended advertising is invoked for a particular instance using the API call `ble_gap_ext_adv_start(instance, 0, 0)`.Instance-id, duration, and max_events are input parameters for this API call respectively.
|
||||
|
||||
Duration represents the time for which the adverteiment will take place. Upon expiration, the advertising procedure ends, and the BLE_GAP_EVENT_ADV_COMPLETE event is reported.0 value is used for no expiration.
|
||||
|
||||
max_events Number of advertising events that should be sent before advertising ends and a BLE_GAP_EVENT_ADV_COMPLETE event is reported.0 value is used for no limit.
|
||||
|
||||
## Conclusion
|
||||
|
||||
This Walkthrough covers the code explanation of the BLE_PERIODIC_ADV. The following points are concluded through this walkthrough.
|
||||
|
||||
1. Periodic advertising allows the scanner to sync with the advertiser so the scanner and advertiser would wake up at the same time.
|
||||
2. Periodic advertisement uses NRPA (Non Resolvable private address). It is a randomly generated address that changes periodically to prevent long-term tracking of a device.
|
||||
3. Extended advertising is used to indicate to the scanner that the advertiser is utilizing periodic advertising. Therefore, periodic advertising is started before extended advertising so that the scanner and advertiser can synchronize their actions and operate at the same time.
|
@ -1,19 +1,17 @@
|
||||
| Supported Targets | ESP32-C6 |
|
||||
| ----------------- | -------- |
|
||||
|
||||
# BLE Periodic Advertiser Example
|
||||
# Important Note
|
||||
*This example currently requires an external Bluetooth controller supporting PAwR functionality, as the ESP chips listed above do not have native controller support for PAwR features and under development phase*
|
||||
|
||||
# BLE Periodic Advertiser With Response (PAwR) Advertiser Connection Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
BLE PAwR Advertiser Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example starts periodic advertising with non resolvable private address.
|
||||
|
||||
It uses Bluetooth controller and NimBLE stack based BLE host.
|
||||
|
||||
This example aims at understanding periodic advertisement and related NimBLE APIs.
|
||||
|
||||
|
||||
To test this demo, any BLE Periodic Sync app can be used.
|
||||
|
||||
This example demonstrates PAwR connection functionality as PAwR advertiser.
|
||||
|
||||
Note :
|
||||
|
||||
@ -49,26 +47,27 @@ Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
|
||||
See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
```
|
||||
I (562) NimBLE_BLE_PAwR: instance 0 started (periodic)
|
||||
|
||||
There is this console output when periodic_adv is started:
|
||||
I (572) NimBLE_BLE_PAwR: [Request] data: 0, subevt start:0, subevt count:5
|
||||
I (1842) NimBLE_BLE_PAwR: [Request] data: 17, subevt start:3, subevt count:2
|
||||
I (1872) NimBLE_BLE_PAwR: [Response] subevent:0, response_slot:2, data_length:10
|
||||
I (1872) NimBLE_BLE_PAwR: data: 0x00, 0x02, 0x5e, 0x02, 0xf6, 0xf9, 0x55, 0x60, 0x00, 0x00
|
||||
I (1882) NimBLE: GAP procedure initiated: extended connect;
|
||||
|
||||
I (1892) NimBLE_BLE_PAwR: Connection create sent, adv handle = 0, subevent = 5
|
||||
W (2192) NimBLE_BLE_PAwR: [Connection established], conn_handle = 0x00, Adv handle = 0x0, status = 0x0
|
||||
|
||||
I (2192) NimBLE_BLE_PAwR: handle=0 our_ota_addr_type=0 our_ota_addr=40:4c:ca:46:1f:e2
|
||||
I (2202) NimBLE_BLE_PAwR: our_id_addr_type=0 our_id_addr=40:4c:ca:46:1f:e2
|
||||
I (2212) NimBLE_BLE_PAwR: peer_ota_addr_type=0 peer_ota_addr=60:55:f9:f6:02:5e
|
||||
I (2212) NimBLE_BLE_PAwR: peer_id_addr_type=0 peer_id_addr=60:55:f9:f6:02:5e
|
||||
I (2222) NimBLE_BLE_PAwR: conn_itvl=40 conn_latency=0 supervision_timeout=256 encrypted=0 authenticated=0 bonded=0
|
||||
|
||||
I (2232) NimBLE_BLE_PAwR: [Request] data: 1d, subevt start:9, subevt count:2
|
||||
|
||||
```
|
||||
I (313) BTDM_INIT: BT controller compile version [2ee0168]
|
||||
I (313) phy_init: phy_version 912,d001756,Jun 2 2022,16:28:07
|
||||
I (353) system_api: Base MAC address is not set
|
||||
I (353) system_api: read default base MAC address from EFUSE
|
||||
I (353) BTDM_INIT: Bluetooth MAC: 84:f7:03:08:14:8e
|
||||
|
||||
I (363) NimBLE_BLE_PERIODIC_ADV: BLE Host Task Started
|
||||
I (373) NimBLE: Device Address:
|
||||
I (373) NimBLE: d0:42:3a:95:84:05
|
||||
I (373) NimBLE:
|
||||
|
||||
I (383) NimBLE: instance 1 started (periodic)
|
||||
```
|
||||
|
||||
## Note
|
||||
* Periodic sync transfer is not implemented for now.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
@ -0,0 +1,256 @@
|
||||
# BLE Periodic Advertisement with Response (PAwR) Advertiser Connection Example Walkthrough
|
||||
|
||||
## Introduction
|
||||
|
||||
This tutorial examines the BLE Periodic Advertisement with Responses (PAwR) example code for ESP32 chipsets with BLE 5.0+ support. The code demonstrates how to implement PAwR connection functionality using NimBLE APIs, which enables bidirectional communication between advertiser and scanner devices in a power-efficient manner. This version additionally supports connections initiated from responses.
|
||||
|
||||
## Includes
|
||||
|
||||
This example is located in the examples folder of the ESP-IDF under the [ble_pawr_adv_conn/main](../main). The [main.c](../main/main.c) file located in the main folder contains all the functionality that we are going to review. The header files contained in [main.c](../main/main.c) are:
|
||||
|
||||
```c
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
/* BLE */
|
||||
#include "nimble/nimble_port.h"
|
||||
#include "nimble/nimble_port_freertos.h"
|
||||
#include "host/ble_hs.h
|
||||
```
|
||||
These includes provide:
|
||||
|
||||
- ESP32 logging functionality (esp_log.h)
|
||||
|
||||
- Non-volatile storage (nvs_flash.h)
|
||||
|
||||
- NimBLE stack porting and FreeRTOS integration
|
||||
|
||||
- BLE host stack functionality
|
||||
|
||||
## Main Entry Point
|
||||
|
||||
The program’s entry point is the app_main() function:
|
||||
```c
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
/* Initialize NVS — it is used to store PHY calibration data */
|
||||
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 ", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Initialize the NimBLE host configuration. */
|
||||
ble_hs_cfg.reset_cb = on_reset;
|
||||
ble_hs_cfg.sync_cb = on_sync;
|
||||
|
||||
nimble_port_freertos_init(pawr_host_task);
|
||||
}
|
||||
```
|
||||
The main function starts by initializing the non-volatile storage library. This library allows us to save the key-value pairs in flash memory.`nvs_flash_init()` stores the PHY calibration data. In a Bluetooth Low Energy (BLE) device, cryptographic keys used for encryption and authentication are often stored in Non-Volatile Storage (NVS).BLE stores the peer keys, CCCD keys, peer records, etc on NVS. By storing these keys in NVS, the BLE device can quickly retrieve them when needed, without the need for time-consuming key generations.
|
||||
```c
|
||||
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 );
|
||||
```
|
||||
|
||||
## BT Controller and Stack Initialization
|
||||
|
||||
The main function calls `nimble_port_init()` to initialize BT Controller and nimble stack. This function initializes the BT controller by first creating its configuration structure named `esp_bt_controller_config_t` with default settings generated by the `BT_CONTROLLER_INIT_CONFIG_DEFAULT()` macro. It implements the Host Controller Interface (HCI) on the controller side, the Link Layer (LL), and the Physical Layer (PHY). The BT Controller is invisible to the user applications and deals with the lower layers of the BLE stack. The controller configuration includes setting the BT controller stack size, priority, and HCI baud rate. With the settings created, the BT controller is initialized and enabled with the `esp_bt_controller_init()` and `esp_bt_controller_enable()` functions:
|
||||
|
||||
```c
|
||||
esp_bt_controller_config_t config_opts = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&config_opts);
|
||||
```
|
||||
Next, the controller is enabled in BLE Mode.
|
||||
|
||||
```c
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
```
|
||||
The controller should be enabled in `ESP_BT_MODE_BLE` if you want to use the BLE mode.
|
||||
|
||||
There are four Bluetooth modes supported:
|
||||
|
||||
1. `ESP_BT_MODE_IDLE`: Bluetooth not running
|
||||
2. `ESP_BT_MODE_BLE`: BLE mode
|
||||
3. `ESP_BT_MODE_CLASSIC_BT`: BT Classic mode
|
||||
4. `ESP_BT_MODE_BTDM`: Dual mode (BLE + BT Classic)
|
||||
|
||||
After the initialization of the BT controller, the nimble stack, which includes the common definitions and APIs for BLE, is initialized by using `esp_nimble_init()`:
|
||||
|
||||
```c
|
||||
esp_err_t esp_nimble_init(void)
|
||||
{
|
||||
|
||||
#if !SOC_ESP_NIMBLE_CONTROLLER
|
||||
/* Initialize the function pointers for OS porting */
|
||||
npl_freertos_funcs_init();
|
||||
|
||||
npl_freertos_mempool_init();
|
||||
|
||||
if(esp_nimble_hci_init() != ESP_OK) {
|
||||
ESP_LOGE(NIMBLE_PORT_LOG_TAG, "hci inits failed\n");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Initialize default event queue */
|
||||
ble_npl_eventq_init(&g_eventq_dflt);
|
||||
|
||||
os_msys_init();
|
||||
|
||||
void ble_store_ram_init(void);
|
||||
/* XXX Need to have a template for store */
|
||||
ble_store_ram_init();
|
||||
#endif
|
||||
|
||||
/* Initialize the host */
|
||||
ble_hs_init();
|
||||
return ESP_OK;
|
||||
}
|
||||
```
|
||||
|
||||
## PAwR Configuration
|
||||
```c
|
||||
#define BLE_PAWR_EVENT_INTERVAL (520)
|
||||
#define BLE_PAWR_NUM_SUBEVTS (10)
|
||||
#define BLE_PAWR_SUB_INTERVAL (52)
|
||||
#define BLE_PAWR_RSP_SLOT_DELAY (5)
|
||||
#define BLE_PAWR_RSP_SLOT_SPACING (10)
|
||||
#define BLE_PAWR_NUM_RSP_SLOTS (25)
|
||||
#define BLE_PAWR_SUB_DATA_LEN (20)
|
||||
```
|
||||
These parameters configure PAwR interval, subevents, response slot timing, and payload length.
|
||||
|
||||
## Periodic Advertising Configuration
|
||||
|
||||
```c
|
||||
memset(&pparams, 0, sizeof(pparams));
|
||||
pparams.include_tx_power = 0;
|
||||
pparams.itvl_min = BLE_GAP_PERIODIC_ITVL_MS(3000);
|
||||
pparams.itvl_max = BLE_GAP_PERIODIC_ITVL_MS(3000);
|
||||
pparams.num_subevents = BLE_PAWR_NUM_SUBEVTS;
|
||||
pparams.subevent_interval = BLE_PAWR_SUB_INTERVAL;
|
||||
pparams.response_slot_delay = BLE_PAWR_RSP_SLOT_DELAY;
|
||||
pparams.response_slot_spacing = BLE_PAWR_RSP_SLOT_SPACING;
|
||||
pparams.num_response_slots = BLE_PAWR_NUM_RSP_SLOTS;
|
||||
```
|
||||
|
||||
These values are passed to ble_gap_periodic_adv_configure() to start PAwR.
|
||||
|
||||
## PAwR Advertisement
|
||||
The start_periodic_adv() function:
|
||||
- Configures extended advertising parameters
|
||||
- Sets up periodic advertising using subevent and slot parameters
|
||||
- Starts extended + periodic advertising
|
||||
|
||||
## Need of Extended Advertisement in Periodic Advertisement
|
||||
|
||||
Extended advertisements contain synchronization info that lets scanners align with periodic advertising. This enables precise subevent-based communicati
|
||||
|
||||
## GAP Event Callback
|
||||
|
||||
The function gap_event_cb() handles multiple events:
|
||||
```c
|
||||
|
||||
case BLE_GAP_EVENT_PER_SUBEV_DATA_REQ:
|
||||
// Send subevent data
|
||||
case BLE_GAP_EVENT_PER_SUBEV_RESP:
|
||||
// Receive response and initiate connection
|
||||
case BLE_GAP_EVENT_CONNECT:
|
||||
// Connection complete
|
||||
case BLE_GAP_EVENT_DISCONNECT:
|
||||
// Disconnection complete
|
||||
```
|
||||
|
||||
## Using ble_gap_connect_with_synced()
|
||||
|
||||
The API ble_gap_connect_with_synced() is a NimBLE API used by a PAwR Advertiser to initiate a BLE connection with a synced scanner. This allows the advertiser to transition from scheduled subevent-based communication to a higher-throughput, lower-latency connection with a specific scanner.
|
||||
|
||||
This is especially useful in use cases where on-demand, peer-to-peer data exchange is needed.
|
||||
```c
|
||||
ble_gap_connect_with_synced(
|
||||
BLE_OWN_ADDR_PUBLIC,
|
||||
adv_handle,
|
||||
subevent,
|
||||
&peer_addr,
|
||||
30000,
|
||||
BLE_GAP_LE_PHY_1M_MASK,
|
||||
NULL, NULL, NULL,
|
||||
gap_event_cb, NULL);
|
||||
```
|
||||
|
||||
Highlights:
|
||||
|
||||
- Uses subevent + handle to target synced scanner
|
||||
- Avoids scanning (direct connection)
|
||||
- Enables faster, deterministic connection
|
||||
|
||||
📌 Tip: Choose connection interval as a multiple of subevent interval for optimal scheduling.
|
||||
|
||||
## Host Task
|
||||
```c
|
||||
void pawr_host_task(void *param)
|
||||
{
|
||||
ESP_LOGI(TAG, "BLE Host Task Started");
|
||||
nimble_port_run();
|
||||
nimble_port_freertos_deinit();
|
||||
}
|
||||
```
|
||||
## Parameter Configuration
|
||||
|
||||
The below snippets represent the parameter configuration for extended and periodic advertisement.
|
||||
|
||||
### For Extended Advertisement
|
||||
|
||||
```c
|
||||
params.own_addr_type = BLE_OWN_ADDR_RANDOM; //Own address type is set to Random
|
||||
params.primary_phy = BLE_HCI_LE_PHY_1M; // Primary advertising PHY is set to 1M
|
||||
params.secondary_phy = BLE_HCI_LE_PHY_2M; // Secondary advertising PHY is set to 2M
|
||||
params.sid = 2; // Advertising set Id is assigned with value 2.
|
||||
```
|
||||
|
||||
### For Periodic Advertisement
|
||||
|
||||
```c
|
||||
memset(&pparams, 0, sizeof(pparams));
|
||||
pparams.include_tx_power = 0; // Indicates that TX power is not included in advertising PDU
|
||||
pparams.itvl_min = BLE_GAP_ADV_ITVL_MS(120); // Minimum advertising interval of 240ms
|
||||
pparams.itvl_max = BLE_GAP_ADV_ITVL_MS(240); //Maximum advertising interval of 480ms
|
||||
```
|
||||
|
||||
Periodic advertisement is started for a particular advertisement instance by calling the API `ble_gap_periodic_adv_start(instance)`. This function takes instance-id as an input parameter. It defines the hci command by initializing the command parameters which are represented in the following lines.
|
||||
|
||||
```c
|
||||
struct ble_hci_le_set_periodic_adv_enable_cp cmd;
|
||||
cmd.enable = 0x01;
|
||||
cmd.adv_handle = instance;
|
||||
```
|
||||
|
||||
Extended advertising is invoked for a particular instance using the API call `ble_gap_ext_adv_start(instance, 0, 0)`.Instance-id, duration, and max_events are input parameters for this API call respectively.
|
||||
|
||||
Duration represents the time for which the adverteiment will take place. Upon expiration, the advertising procedure ends, and the BLE_GAP_EVENT_ADV_COMPLETE event is reported.0 value is used for no expiration.
|
||||
|
||||
max_events Number of advertising events that should be sent before advertising ends and a BLE_GAP_EVENT_ADV_COMPLETE event is reported.0 value is used for no limit.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Conclusion
|
||||
|
||||
This PAwR with connection example demonstrates:
|
||||
|
||||
- Periodic advertising with subevents and response slots
|
||||
- Dynamic connection initiation based on scanner responses
|
||||
- Use of extended advertisement for synchronization
|
||||
- Efficient, scalable, low-power bidirectional communication
|
||||
|
@ -1,320 +0,0 @@
|
||||
# BLE Periodic Advertisement Example Walkthrough
|
||||
|
||||
## Introduction
|
||||
|
||||
In this tutorial, the ble_periodic_adv example code for the espressif chipsets with BLE5.0 support is reviewed. This example aims at understanding periodic advertisements and related NimBLE APIs.This code implements the periodic advertisement functionality along with extended advertisement by generating a non-resolvable private address.
|
||||
|
||||
## Includes
|
||||
|
||||
This example is located in the examples folder of the ESP-IDF under the [ble_periodic_adv/main](../main). The [main.c](../main/main.c) file located in the main folder contains all the functionality that we are going to review. The header files contained in [main.c](../main/main.c) are:
|
||||
|
||||
```c
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
/* BLE */
|
||||
#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 "patterns.h"
|
||||
```
|
||||
These `includes` are required for the FreeRTOS and underlying system components to run, including the logging functionality and a library to store data in non-volatile flash memory. We are interested in `“nimble_port.h”`, `“nimble_port_freertos.h”`, `"ble_hs.h"` and `“ble_svc_gap.h”`, `“periodic_adv.h”` which expose the BLE APIs required to implement this example.
|
||||
|
||||
* `nimble_port.h`: Includes the declaration of functions required for the initialization of the nimble stack.
|
||||
* `nimble_port_freertos.h`: Initializes and enables nimble host task.
|
||||
* `ble_hs.h`: Defines the functionalities to handle the host event
|
||||
* `ble_svc_gap.h`:Defines the macros for device name, and device appearance and declares the function to set them.
|
||||
* `periodic_adv.h`:It includes the code containing forward declarations of 2 structs `ble_hs_cfg` , and `ble_gatt_register_ctxt` based on weather macro `H_BLE_PERIODIC_ADV_` is defined.
|
||||
|
||||
## Main Entry Point
|
||||
|
||||
The program’s entry point is the app_main() function:
|
||||
```c
|
||||
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 ", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Initialize the NimBLE host configuration. */
|
||||
ble_hs_cfg.reset_cb = periodic_adv_on_reset;
|
||||
ble_hs_cfg.sync_cb = periodic_adv_on_sync;
|
||||
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
|
||||
/* Set the default device name. */
|
||||
rc = ble_svc_gap_device_name_set("nimble_periodic_adv");
|
||||
assert(rc == 0);
|
||||
|
||||
/* XXX Need to have a template for store */
|
||||
ble_store_config_init();
|
||||
|
||||
nimble_port_freertos_init(periodic_adv_host_task);
|
||||
}
|
||||
```
|
||||
The main function starts by initializing the non-volatile storage library. This library allows us to save the key-value pairs in flash memory.`nvs_flash_init()` stores the PHY calibration data. In a Bluetooth Low Energy (BLE) device, cryptographic keys used for encryption and authentication are often stored in Non-Volatile Storage (NVS).BLE stores the peer keys, CCCD keys, peer records, etc on NVS. By storing these keys in NVS, the BLE device can quickly retrieve them when needed, without the need for time-consuming key generations.
|
||||
```c
|
||||
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 );
|
||||
```
|
||||
|
||||
## BT Controller and Stack Initialization
|
||||
|
||||
The main function calls `nimble_port_init()` to initialize BT Controller and nimble stack. This function initializes the BT controller by first creating its configuration structure named `esp_bt_controller_config_t` with default settings generated by the `BT_CONTROLLER_INIT_CONFIG_DEFAULT()` macro. It implements the Host Controller Interface (HCI) on the controller side, the Link Layer (LL), and the Physical Layer (PHY). The BT Controller is invisible to the user applications and deals with the lower layers of the BLE stack. The controller configuration includes setting the BT controller stack size, priority, and HCI baud rate. With the settings created, the BT controller is initialized and enabled with the `esp_bt_controller_init()` and `esp_bt_controller_enable()` functions:
|
||||
|
||||
```c
|
||||
esp_bt_controller_config_t config_opts = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&config_opts);
|
||||
```
|
||||
Next, the controller is enabled in BLE Mode.
|
||||
|
||||
```c
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
```
|
||||
The controller should be enabled in `ESP_BT_MODE_BLE` if you want to use the BLE mode.
|
||||
|
||||
There are four Bluetooth modes supported:
|
||||
|
||||
1. `ESP_BT_MODE_IDLE`: Bluetooth not running
|
||||
2. `ESP_BT_MODE_BLE`: BLE mode
|
||||
3. `ESP_BT_MODE_CLASSIC_BT`: BT Classic mode
|
||||
4. `ESP_BT_MODE_BTDM`: Dual mode (BLE + BT Classic)
|
||||
|
||||
After the initialization of the BT controller, the nimble stack, which includes the common definitions and APIs for BLE, is initialized by using `esp_nimble_init()`:
|
||||
|
||||
```c
|
||||
esp_err_t esp_nimble_init(void)
|
||||
{
|
||||
|
||||
#if !SOC_ESP_NIMBLE_CONTROLLER
|
||||
/* Initialize the function pointers for OS porting */
|
||||
npl_freertos_funcs_init();
|
||||
|
||||
npl_freertos_mempool_init();
|
||||
|
||||
if(esp_nimble_hci_init() != ESP_OK) {
|
||||
ESP_LOGE(NIMBLE_PORT_LOG_TAG, "hci inits failed\n");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Initialize default event queue */
|
||||
ble_npl_eventq_init(&g_eventq_dflt);
|
||||
|
||||
os_msys_init();
|
||||
|
||||
void ble_store_ram_init(void);
|
||||
/* XXX Need to have a template for store */
|
||||
ble_store_ram_init();
|
||||
#endif
|
||||
|
||||
/* Initialize the host */
|
||||
ble_hs_init();
|
||||
return ESP_OK;
|
||||
}
|
||||
```
|
||||
|
||||
The host is configured by setting up the callbacks for Stack-reset, Stack-sync, and Storage status
|
||||
|
||||
```c
|
||||
ble_hs_cfg.reset_cb = periodic_adv_on_reset;
|
||||
ble_hs_cfg.sync_cb = periodic_adv_on_sync;
|
||||
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
|
||||
```
|
||||
|
||||
The main function calls `ble_svc_gap_device_name_set()` to set the default device name. 'blecent_phy' is passed as the default device name to this function.
|
||||
```c
|
||||
rc = ble_svc_gap_device_name_set("nimble_periodic_adv");
|
||||
```
|
||||
main function calls `ble_store_config_init()` to configure the host by setting up the storage callbacks which handle the read, write, and deletion of security material.
|
||||
```c
|
||||
/* XXX Need to have a template for store */
|
||||
ble_store_config_init();
|
||||
```
|
||||
|
||||
The main function ends by creating a task where nimble will run using `nimble_port_freertos_init()`. This enables the nimble stack by using `esp_nimble_enable()`.
|
||||
|
||||
```c
|
||||
nimble_port_freertos_init(periodic_adv_host_task);
|
||||
```
|
||||
`esp_nimble_enable()` create a task where the nimble host will run. It is not strictly necessary to have a separate task for the nimble host, but to handle the default queue, it is easier to create a separate task.
|
||||
|
||||
## Generation of non-resolvable private address
|
||||
|
||||
In Bluetooth Low Energy (BLE), a non-resolvable private address is a type of Bluetooth device address that is used for privacy purposes. It is a randomly generated address that changes periodically to prevent long-term tracking of a device. The API call to `ble_hs_id_gen_rnd()` is responsible for generating a non-resolvable private address. NRPA is a 48-bit address that is stored in `addr.val`.
|
||||
|
||||
```c
|
||||
#if CONFIG_EXAMPLE_RANDOM_ADDR
|
||||
static void
|
||||
periodic_adv_set_addr(void)
|
||||
{
|
||||
ble_addr_t addr;
|
||||
int rc;
|
||||
|
||||
/* generate new non-resolvable private address */
|
||||
rc = ble_hs_id_gen_rnd(0, &addr);
|
||||
assert(rc == 0);
|
||||
|
||||
/* set generated address */
|
||||
rc = ble_hs_id_set_rnd(addr.val);
|
||||
|
||||
assert(rc == 0);
|
||||
}
|
||||
#endif
|
||||
```
|
||||
|
||||
## Periodic Advertisement
|
||||
|
||||
Periodic advertisement start by creating instances of structures `ble_gap_periodic_adv_params`, `ble_gap_ext_adv_params`, `ble_hs_adv_fields`, and `os_mbuf`. Advertising parameters such as connecting modes, advertising intervals, peer address, advertising-filter policy, etc are defined in these structures for periodic and extended advertisements. `pparams` and `params` instances have parameters for periodic advertisement and extended advertisement respectively.
|
||||
Bluetooth device address is given by the structure ble_aadr_t which contains the fields for address type and address value.
|
||||
|
||||
## Need of Extended Advertisement in Periodic Advertisement
|
||||
|
||||
Non-connectable and non-scannable advertising events containing synchronization information about a periodic advertising train are necessary for the scanner device to sync with the periodic advertising train. The periodic advertising will utilize the same physical layer (PHY) as the auxiliary packet, which is part of the extended advertisement.
|
||||
|
||||
|
||||
Below is the implementation to start periodic advertisement.
|
||||
|
||||
```c
|
||||
static void
|
||||
start_periodic_adv(void)
|
||||
{
|
||||
int rc;
|
||||
struct ble_gap_periodic_adv_params pparams;
|
||||
struct ble_gap_ext_adv_params params;
|
||||
struct ble_hs_adv_fields adv_fields;
|
||||
struct os_mbuf *data;
|
||||
uint8_t instance = 1;
|
||||
ble_addr_t addr;
|
||||
|
||||
/* set random (NRPA) address for instance */
|
||||
rc = ble_hs_id_gen_rnd(1, &addr);
|
||||
assert (rc == 0);
|
||||
|
||||
MODLOG_DFLT(INFO, "Device Address: ");
|
||||
print_addr(addr.val);
|
||||
MODLOG_DFLT(INFO, "\n");
|
||||
|
||||
/* For periodic we use instance with non-connectable advertising */
|
||||
memset (¶ms, 0, sizeof(params));
|
||||
|
||||
/* advertise using random addr */
|
||||
params.own_addr_type = BLE_OWN_ADDR_RANDOM;
|
||||
params.primary_phy = BLE_HCI_LE_PHY_1M;
|
||||
params.secondary_phy = BLE_HCI_LE_PHY_2M;
|
||||
params.sid = 2;
|
||||
|
||||
/* configure instance 1 */
|
||||
rc = ble_gap_ext_adv_configure(instance, ¶ms, NULL, NULL, NULL);
|
||||
assert (rc == 0);
|
||||
|
||||
rc = ble_gap_ext_adv_set_addr(instance, &addr );
|
||||
assert (rc == 0);
|
||||
|
||||
memset(&adv_fields, 0, sizeof(adv_fields));
|
||||
adv_fields.name = (const uint8_t *)"Periodic ADV";
|
||||
adv_fields.name_len = strlen((char *)adv_fields.name);
|
||||
|
||||
/* Default to legacy PDUs size, mbuf chain will be increased if needed
|
||||
*/
|
||||
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 */
|
||||
memset(&pparams, 0, sizeof(pparams));
|
||||
pparams.include_tx_power = 0;
|
||||
pparams.itvl_min = BLE_GAP_ADV_ITVL_MS(120);
|
||||
pparams.itvl_max = BLE_GAP_ADV_ITVL_MS(240);
|
||||
|
||||
rc = ble_gap_periodic_adv_configure(instance, &pparams);
|
||||
assert(rc == 0);
|
||||
|
||||
data = os_msys_get_pkthdr(sizeof(periodic_adv_raw_data), 0);
|
||||
assert(data);
|
||||
|
||||
rc = os_mbuf_append(data, periodic_adv_raw_data, sizeof(periodic_adv_raw_data));
|
||||
assert(rc == 0);
|
||||
rc = ble_gap_periodic_adv_set_data(instance, data);
|
||||
assert (rc == 0);
|
||||
|
||||
/* start periodic advertising */
|
||||
assert (rc == 0 rc = ble_gap_periodic_adv_start(instance);
|
||||
);
|
||||
|
||||
/* start advertising */
|
||||
rc = ble_gap_ext_adv_start(instance, 0, 0);
|
||||
assert (rc == 0);
|
||||
|
||||
MODLOG_DFLT(INFO, "instance %u started (periodic)\n", instance);
|
||||
}
|
||||
```
|
||||
|
||||
The periodic advertisement uses a non-connectable advertising mode. `memset (¶ms, 0, sizeof(params))` initialises params to 0. This also sets `params.connectable` to 0.
|
||||
|
||||
## Parameter Configuration
|
||||
|
||||
The below snippets represent the parameter configuration for extended and periodic advertisement.
|
||||
|
||||
### For Extended Advertisement
|
||||
|
||||
```c
|
||||
params.own_addr_type = BLE_OWN_ADDR_RANDOM; //Own address type is set to Random
|
||||
params.primary_phy = BLE_HCI_LE_PHY_1M; // Primary advertising PHY is set to 1M
|
||||
params.secondary_phy = BLE_HCI_LE_PHY_2M; // Secondary advertising PHY is set to 2M
|
||||
params.sid = 2; // Advertising set Id is assigned with value 2.
|
||||
```
|
||||
|
||||
### For Periodic Advertisement
|
||||
|
||||
```c
|
||||
memset(&pparams, 0, sizeof(pparams));
|
||||
pparams.include_tx_power = 0; // Indicates that TX power is not included in advertising PDU
|
||||
pparams.itvl_min = BLE_GAP_ADV_ITVL_MS(120); // Minimum advertising interval of 240ms
|
||||
pparams.itvl_max = BLE_GAP_ADV_ITVL_MS(240); //Maximum advertising interval of 480ms
|
||||
```
|
||||
|
||||
Periodic advertisement is started for a particular advertisement instance by calling the API `ble_gap_periodic_adv_start(instance)`. This function takes instance-id as an input parameter. It defines the hci command by initializing the command parameters which are represented in the following lines.
|
||||
|
||||
```c
|
||||
struct ble_hci_le_set_periodic_adv_enable_cp cmd;
|
||||
cmd.enable = 0x01;
|
||||
cmd.adv_handle = instance;
|
||||
```
|
||||
|
||||
Extended advertising is invoked for a particular instance using the API call `ble_gap_ext_adv_start(instance, 0, 0)`.Instance-id, duration, and max_events are input parameters for this API call respectively.
|
||||
|
||||
Duration represents the time for which the adverteiment will take place. Upon expiration, the advertising procedure ends, and the BLE_GAP_EVENT_ADV_COMPLETE event is reported.0 value is used for no expiration.
|
||||
|
||||
max_events Number of advertising events that should be sent before advertising ends and a BLE_GAP_EVENT_ADV_COMPLETE event is reported.0 value is used for no limit.
|
||||
|
||||
## Conclusion
|
||||
|
||||
This Walkthrough covers the code explanation of the BLE_PERIODIC_ADV. The following points are concluded through this walkthrough.
|
||||
|
||||
1. Periodic advertising allows the scanner to sync with the advertiser so the scanner and advertiser would wake up at the same time.
|
||||
2. Periodic advertisement uses NRPA (Non Resolvable private address). It is a randomly generated address that changes periodically to prevent long-term tracking of a device.
|
||||
3. Extended advertising is used to indicate to the scanner that the advertiser is utilizing periodic advertising. Therefore, periodic advertising is started before extended advertising so that the scanner and advertiser can synchronize their actions and operate at the same time.
|
@ -1,19 +1,17 @@
|
||||
| Supported Targets | ESP32-C6 |
|
||||
| ----------------- | -------- |
|
||||
|
||||
# BLE Periodic Advertiser Example
|
||||
## Important Note
|
||||
*This example currently requires an external Bluetooth controller supporting PAwR functionality, as the ESP chips listed above do not have native controller support for PAwR features and under development phase*
|
||||
|
||||
# BLE Periodic Advertiser With Response (PAwR) Sync Connection Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
BLE PAwR Advertiser Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example starts periodic advertising with non resolvable private address.
|
||||
|
||||
It uses Bluetooth controller and NimBLE stack based BLE host.
|
||||
|
||||
This example aims at understanding periodic advertisement and related NimBLE APIs.
|
||||
|
||||
|
||||
To test this demo, any BLE Periodic Sync app can be used.
|
||||
|
||||
This example demonstrates PAwR connection functionality as PAwR Sync.
|
||||
|
||||
Note :
|
||||
|
||||
@ -49,26 +47,26 @@ Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
|
||||
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 (535) NimBLE: GAP procedure initiated: extended discovery;
|
||||
|
||||
I (545) main_task: Returned from app_main()
|
||||
I (6595) NimBLE_BLE_PAwR: [Disc advertiser] addr 40:4c:ca:46:1f:e2, props: 0x0, rssi:-45
|
||||
I (6595) NimBLE_BLE_PAwR: Create sync
|
||||
I (6975) NimBLE_BLE_PAwR: [Periodic Sync Established] sync handle:0, num_subevents:0xa
|
||||
I (6975) NimBLE_BLE_PAwR: subevent_interval:0x34, slot_delay:0x5,slot_spacing:0xa
|
||||
I (6985) NimBLE_BLE_PAwR: [Subevent Sync OK] sync handle:0, sync_subevents:10
|
||||
I (7885) NimBLE_BLE_PAwR: [Periodic Adv Report] handle:0, event_counter(2), subevent(4)
|
||||
W (7885) NimBLE_BLE_PAwR: [RSP Data Set] sync handle: 0, subev(4), rsp_slot(2), rc(0x0)
|
||||
W (7955) NimBLE_BLE_PAwR: [Connection established], conn_handle = 0x00, sync handle = 0x0, status = 0x0
|
||||
|
||||
I (7955) NimBLE_BLE_PAwR: handle=0 our_ota_addr_type=0 our_ota_addr=60:55:f9:f6:02:5e
|
||||
I (7965) NimBLE_BLE_PAwR: our_id_addr_type=0 our_id_addr=60:55:f9:f6:02:5e
|
||||
I (7965) NimBLE_BLE_PAwR: peer_ota_addr_type=0 peer_ota_addr=40:4c:ca:46:1f:e2
|
||||
I (7975) NimBLE_BLE_PAwR: peer_id_addr_type=0 peer_id_addr=40:4c:ca:46:1f:e2
|
||||
I (7985) NimBLE_BLE_PAwR: conn_itvl=40 conn_latency=0 supervision_timeout=256 encrypted=0 authenticated=0 bonded=0
|
||||
|
||||
```
|
||||
I (313) BTDM_INIT: BT controller compile version [2ee0168]
|
||||
I (313) phy_init: phy_version 912,d001756,Jun 2 2022,16:28:07
|
||||
I (353) system_api: Base MAC address is not set
|
||||
I (353) system_api: read default base MAC address from EFUSE
|
||||
I (353) BTDM_INIT: Bluetooth MAC: 84:f7:03:08:14:8e
|
||||
|
||||
I (363) NimBLE_BLE_PERIODIC_ADV: BLE Host Task Started
|
||||
I (373) NimBLE: Device Address:
|
||||
I (373) NimBLE: d0:42:3a:95:84:05
|
||||
I (373) NimBLE:
|
||||
|
||||
I (383) NimBLE: instance 1 started (periodic)
|
||||
```
|
||||
|
||||
## Note
|
||||
* Periodic sync transfer is not implemented for now.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
@ -0,0 +1,271 @@
|
||||
# BLE Periodic Advertisement with Response Scanner Example Walkthrough (PAwR Sync + Conn)
|
||||
|
||||
## Introduction
|
||||
|
||||
This tutorial reviews the PAwR Synchronizer with Connection example for ESP32 chipsets using NimBLE. It shows how a scanner:
|
||||
|
||||
- Discovers periodic advertisers.
|
||||
|
||||
- Synchronizes to scheduled subevents.
|
||||
|
||||
- Sends response data in specific slots.
|
||||
|
||||
- Accepts connections initiated by the advertiser
|
||||
|
||||
## Includes
|
||||
|
||||
This example is located in the examples folder of the ESP-IDF under the [ble_pawr_sync_conn/main](../main). The [main.c](../main/main.c) file located in the main folder contains all the functionality that we are going to review. The header files contained in [main.c](../main/main.c) are:
|
||||
|
||||
```c
|
||||
#include "esp_log.h" // ESP32 logging
|
||||
#include "nvs_flash.h" // Non-volatile storage for PHY calibration
|
||||
#include "nimble/nimble_port.h" // NimBLE core initialization
|
||||
#include "nimble/nimble_port_freertos.h" // NimBLE FreeRTOS support
|
||||
#include "host/ble_hs.h" // BLE host stack APIs
|
||||
#include "host/util/util.h" // BLE utilities (e.g., address helpers)
|
||||
```
|
||||
|
||||
|
||||
## Main Entry Point
|
||||
|
||||
The program’s entry point is the app_main() function:
|
||||
```c
|
||||
void app_main(void) {
|
||||
nvs_flash_init();
|
||||
. // Prepare flash for storing calibration data
|
||||
.
|
||||
.
|
||||
nimble_port_init(); // Initialize the BLE controller and host
|
||||
.
|
||||
.
|
||||
ble_hs_cfg.reset_cb = on_reset; // Register reset callback
|
||||
ble_hs_cfg.sync_cb = on_sync; // Register sync callback
|
||||
.
|
||||
.
|
||||
nimble_port_freertos_init(pawr_host_task); // Launch BLE host task
|
||||
}
|
||||
```
|
||||
- nvs_flash_init: Ensures persistent storage is ready for BLE keys and PHY data.
|
||||
|
||||
- nimble_port_init: Brings up the BT controller (radio + LL) and NimBLE host.
|
||||
|
||||
- Callbacks: on_reset handles controller resets; on_sync starts scanning when BLE is ready.
|
||||
|
||||
- pawr_host_task: Runs the BLE event loop on a FreeRTOS task.
|
||||
|
||||
## BT Controller and Stack Initialization
|
||||
|
||||
The main function calls `nimble_port_init()` to initialize BT Controller and nimble stack. This function initializes the BT controller by first creating its configuration structure named `esp_bt_controller_config_t` with default settings generated by the `BT_CONTROLLER_INIT_CONFIG_DEFAULT()` macro. It implements the Host Controller Interface (HCI) on the controller side, the Link Layer (LL), and the Physical Layer (PHY). The BT Controller is invisible to the user applications and deals with the lower layers of the BLE stack. The controller configuration includes setting the BT controller stack size, priority, and HCI baud rate. With the settings created, the BT controller is initialized and enabled with the `esp_bt_controller_init()` and `esp_bt_controller_enable()` functions:
|
||||
|
||||
```c
|
||||
esp_bt_controller_config_t config_opts = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&config_opts);
|
||||
```
|
||||
Next, the controller is enabled in BLE Mode.
|
||||
|
||||
```c
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
```
|
||||
The controller should be enabled in `ESP_BT_MODE_BLE` if you want to use the BLE mode.
|
||||
|
||||
There are four Bluetooth modes supported:
|
||||
|
||||
1. `ESP_BT_MODE_IDLE`: Bluetooth not running
|
||||
2. `ESP_BT_MODE_BLE`: BLE mode
|
||||
3. `ESP_BT_MODE_CLASSIC_BT`: BT Classic mode
|
||||
4. `ESP_BT_MODE_BTDM`: Dual mode (BLE + BT Classic)
|
||||
|
||||
After the initialization of the BT controller, the nimble stack, which includes the common definitions and APIs for BLE, is initialized by using `esp_nimble_init()`:
|
||||
|
||||
```c
|
||||
esp_err_t esp_nimble_init(void)
|
||||
{
|
||||
|
||||
#if !SOC_ESP_NIMBLE_CONTROLLER
|
||||
/* Initialize the function pointers for OS porting */
|
||||
npl_freertos_funcs_init();
|
||||
|
||||
npl_freertos_mempool_init();
|
||||
|
||||
if(esp_nimble_hci_init() != ESP_OK) {
|
||||
ESP_LOGE(NIMBLE_PORT_LOG_TAG, "hci inits failed\n");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Initialize default event queue */
|
||||
ble_npl_eventq_init(&g_eventq_dflt);
|
||||
|
||||
os_msys_init();
|
||||
|
||||
void ble_store_ram_init(void);
|
||||
/* XXX Need to have a template for store */
|
||||
ble_store_ram_init();
|
||||
#endif
|
||||
|
||||
/* Initialize the host */
|
||||
ble_hs_init();
|
||||
return ESP_OK;
|
||||
}
|
||||
```
|
||||
|
||||
## PAwR Synchronization
|
||||
|
||||
### Start Scanning
|
||||
|
||||
Configures a passive extended scan to detect periodic advertisers:
|
||||
```c
|
||||
static void start_scan(void) {
|
||||
struct ble_gap_ext_disc_params d = {
|
||||
.itvl = BLE_GAP_SCAN_ITVL_MS(600), // Scan every 600ms
|
||||
.window = BLE_GAP_SCAN_ITVL_MS(300), // Listen for 300ms
|
||||
.passive= 1 // Do not send scan requests
|
||||
};
|
||||
// Start discovery; gap_event_cb handles each advertisement
|
||||
ble_gap_ext_disc(BLE_OWN_ADDR_PUBLIC, 0, 0, 1, 0, 0,
|
||||
NULL, &d, gap_event_cb, NULL);
|
||||
}
|
||||
```
|
||||
|
||||
- BLE_OWN_ADDR_PUBLIC: Use the device’s public address.
|
||||
|
||||
- gap_event_cb: Processes discovery events (EXT_DISC) to find our target.`
|
||||
|
||||
## Create Periodic Sync
|
||||
|
||||
When a periodic advertiser is found, request synchronization:
|
||||
```c
|
||||
static int create_periodic_sync(struct ble_gap_ext_disc_desc *disc) {
|
||||
struct ble_gap_periodic_sync_params p = {
|
||||
.skip = 0, // Do not skip any events
|
||||
.sync_timeout = 4000, // Give 4000ms to establish sync
|
||||
.reports_disabled= 0, // Keep reports enabled
|
||||
#if CONFIG_EXAMPLE_PERIODIC_ADV_ENH
|
||||
.filter_duplicates = 1, // Only receive when data-id changes
|
||||
#endif
|
||||
};
|
||||
// Initiate sync; callback will receive PERIODIC_SYNC
|
||||
return ble_gap_periodic_adv_sync_create(
|
||||
&disc->addr, disc->sid, &p,
|
||||
gap_event_cb, NULL);
|
||||
}
|
||||
```
|
||||
- disc->addr / sid: Address and Sync ID identify the PAwR train.
|
||||
|
||||
- ble_gap_periodic_adv_sync_create: Starts low-power sync to periodic events.
|
||||
|
||||
## Sending Response Data
|
||||
|
||||
Once synchronized, respond during periodic reports:
|
||||
|
||||
```c
|
||||
|
||||
case BLE_GAPCreate Periodic Sync
|
||||
|
||||
When a periodic advertiser is found, request synchronization:
|
||||
|
||||
static int create_periodic_sync(struct ble_gap_ext_disc_desc *disc) {
|
||||
struct ble_gap_periodic_sync_params p = {
|
||||
.skip = 0, // Do not skip any events
|
||||
.sync_timeout = 4000, // Give 4000ms to establish sync
|
||||
.reports_disabled= 0, // Keep reports enabled
|
||||
#if CONFIG_EXAMPLE_PERIODIC_ADV_ENH
|
||||
.filter_duplicates = 1, // Only receive when data-id changes
|
||||
#endif
|
||||
};
|
||||
// Initiate sync; callback will receive PERIODIC_SYNC
|
||||
return ble_gap_periodic_adv_sync_create(
|
||||
&disc->addr, disc->sid, &p,
|
||||
gap_event_cb, NULL);
|
||||
}
|
||||
|
||||
disc->addr / sid: Address and Sync ID identify the PAwR train.
|
||||
|
||||
ble_gap_periodic_adv_sync_create: Starts low-power sync to periodic events.
|
||||
|
||||
_EVENT_PERIODIC_REPORT: {
|
||||
struct ble_gap_periodic_adv_response_params r = {
|
||||
.request_event = event->periodic_report.event_counter,
|
||||
.request_subevent = event->periodic_report.subevent,
|
||||
.response_subevent= event->periodic_report.subevent,
|
||||
.response_slot = 2, // Always use slot 2
|
||||
};
|
||||
// Allocate buffer for response payload
|
||||
struct os_mbuf *m = os_msys_get_pkthdr(BLE_PAWR_RSP_DATA_LEN, 0);
|
||||
// First byte: subevent index
|
||||
sub_data_pattern[0] = event->periodic_report.subevent;
|
||||
// Next 6 bytes: our public address
|
||||
ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, device_addr, NULL);
|
||||
memcpy(&sub_data_pattern[1], device_addr, BLE_DEV_ADDR_LEN);
|
||||
// Fill remaining bytes with slot index
|
||||
sub_data_pattern[7] = r.response_slot;
|
||||
os_mbuf_append(m, sub_data_pattern, BLE_PAWR_RSP_DATA_LEN);
|
||||
// Send response data back to advertiser
|
||||
ble_gap_periodic_adv_set_response_data(
|
||||
event->periodic_report.sync_handle,
|
||||
&r, m);
|
||||
break;
|
||||
}
|
||||
```
|
||||
- os_msys_get_pkthdr: Allocates memory for the response.
|
||||
|
||||
- Payload layout: [subevent, 6-byte address, slot index].
|
||||
|
||||
- ble_gap_periodic_adv_set_response_data: Transmits response in the next slot.
|
||||
|
||||
|
||||
## Accepting Connections
|
||||
|
||||
The advertiser may connect after receiving our response:
|
||||
|
||||
```c
|
||||
case BLE_GAP_EVENT_CONNECT:
|
||||
if (event->connect.status == 0) {
|
||||
ble_gap_conn_find(event->connect.conn_handle, &desc);
|
||||
print_conn_desc(&desc); // Log connection parameters and addresses
|
||||
}
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_DISCONNECT:
|
||||
// Handle cleanup when connection ends
|
||||
break;
|
||||
```
|
||||
- BLE_GAP_EVENT_CONNECT: Triggered when connection completes.
|
||||
|
||||
- print_conn_desc: Displays connection handle, addresses, intervals.
|
||||
|
||||
## GAP Event Callback Summary
|
||||
|
||||
gap_event_cb() covers:
|
||||
|
||||
- EXT_DISC → Identify periodic advertiser and call sync.
|
||||
|
||||
- PERIODIC_SYNC → Confirm sync and choose subevents.
|
||||
|
||||
- PERIODIC_REPORT → Send response data.
|
||||
|
||||
- PERIODIC_SYNC_LOST → Restart scanning.
|
||||
|
||||
- CONNECT/DISCONNECT → Handle connection lifecycle.
|
||||
|
||||
|
||||
## Host Task
|
||||
```c
|
||||
void pawr_host_task(void *param) {
|
||||
nimble_port_run(); // Process BLE events indefinitely
|
||||
nimble_port_freertos_deinit();
|
||||
}
|
||||
```
|
||||
This function runs the NimBLE host event loop until the stack is stopped.
|
||||
|
||||
## Conclusion
|
||||
|
||||
This PAwR Sync + Conn example demonstrates:
|
||||
|
||||
- Passive discovery of periodic advertisers.
|
||||
|
||||
- Low-power synchronization to scheduled subevents.
|
||||
|
||||
- Slot-based responses with custom payload.
|
||||
|
||||
- Connection handling initiated by the advertiser.
|
||||
|
@ -1,320 +0,0 @@
|
||||
# BLE Periodic Advertisement Example Walkthrough
|
||||
|
||||
## Introduction
|
||||
|
||||
In this tutorial, the ble_periodic_adv example code for the espressif chipsets with BLE5.0 support is reviewed. This example aims at understanding periodic advertisements and related NimBLE APIs.This code implements the periodic advertisement functionality along with extended advertisement by generating a non-resolvable private address.
|
||||
|
||||
## Includes
|
||||
|
||||
This example is located in the examples folder of the ESP-IDF under the [ble_periodic_adv/main](../main). The [main.c](../main/main.c) file located in the main folder contains all the functionality that we are going to review. The header files contained in [main.c](../main/main.c) are:
|
||||
|
||||
```c
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
/* BLE */
|
||||
#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 "patterns.h"
|
||||
```
|
||||
These `includes` are required for the FreeRTOS and underlying system components to run, including the logging functionality and a library to store data in non-volatile flash memory. We are interested in `“nimble_port.h”`, `“nimble_port_freertos.h”`, `"ble_hs.h"` and `“ble_svc_gap.h”`, `“periodic_adv.h”` which expose the BLE APIs required to implement this example.
|
||||
|
||||
* `nimble_port.h`: Includes the declaration of functions required for the initialization of the nimble stack.
|
||||
* `nimble_port_freertos.h`: Initializes and enables nimble host task.
|
||||
* `ble_hs.h`: Defines the functionalities to handle the host event
|
||||
* `ble_svc_gap.h`:Defines the macros for device name, and device appearance and declares the function to set them.
|
||||
* `periodic_adv.h`:It includes the code containing forward declarations of 2 structs `ble_hs_cfg` , and `ble_gatt_register_ctxt` based on weather macro `H_BLE_PERIODIC_ADV_` is defined.
|
||||
|
||||
## Main Entry Point
|
||||
|
||||
The program’s entry point is the app_main() function:
|
||||
```c
|
||||
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 ", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Initialize the NimBLE host configuration. */
|
||||
ble_hs_cfg.reset_cb = periodic_adv_on_reset;
|
||||
ble_hs_cfg.sync_cb = periodic_adv_on_sync;
|
||||
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
|
||||
/* Set the default device name. */
|
||||
rc = ble_svc_gap_device_name_set("nimble_periodic_adv");
|
||||
assert(rc == 0);
|
||||
|
||||
/* XXX Need to have a template for store */
|
||||
ble_store_config_init();
|
||||
|
||||
nimble_port_freertos_init(periodic_adv_host_task);
|
||||
}
|
||||
```
|
||||
The main function starts by initializing the non-volatile storage library. This library allows us to save the key-value pairs in flash memory.`nvs_flash_init()` stores the PHY calibration data. In a Bluetooth Low Energy (BLE) device, cryptographic keys used for encryption and authentication are often stored in Non-Volatile Storage (NVS).BLE stores the peer keys, CCCD keys, peer records, etc on NVS. By storing these keys in NVS, the BLE device can quickly retrieve them when needed, without the need for time-consuming key generations.
|
||||
```c
|
||||
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 );
|
||||
```
|
||||
|
||||
## BT Controller and Stack Initialization
|
||||
|
||||
The main function calls `nimble_port_init()` to initialize BT Controller and nimble stack. This function initializes the BT controller by first creating its configuration structure named `esp_bt_controller_config_t` with default settings generated by the `BT_CONTROLLER_INIT_CONFIG_DEFAULT()` macro. It implements the Host Controller Interface (HCI) on the controller side, the Link Layer (LL), and the Physical Layer (PHY). The BT Controller is invisible to the user applications and deals with the lower layers of the BLE stack. The controller configuration includes setting the BT controller stack size, priority, and HCI baud rate. With the settings created, the BT controller is initialized and enabled with the `esp_bt_controller_init()` and `esp_bt_controller_enable()` functions:
|
||||
|
||||
```c
|
||||
esp_bt_controller_config_t config_opts = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&config_opts);
|
||||
```
|
||||
Next, the controller is enabled in BLE Mode.
|
||||
|
||||
```c
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
```
|
||||
The controller should be enabled in `ESP_BT_MODE_BLE` if you want to use the BLE mode.
|
||||
|
||||
There are four Bluetooth modes supported:
|
||||
|
||||
1. `ESP_BT_MODE_IDLE`: Bluetooth not running
|
||||
2. `ESP_BT_MODE_BLE`: BLE mode
|
||||
3. `ESP_BT_MODE_CLASSIC_BT`: BT Classic mode
|
||||
4. `ESP_BT_MODE_BTDM`: Dual mode (BLE + BT Classic)
|
||||
|
||||
After the initialization of the BT controller, the nimble stack, which includes the common definitions and APIs for BLE, is initialized by using `esp_nimble_init()`:
|
||||
|
||||
```c
|
||||
esp_err_t esp_nimble_init(void)
|
||||
{
|
||||
|
||||
#if !SOC_ESP_NIMBLE_CONTROLLER
|
||||
/* Initialize the function pointers for OS porting */
|
||||
npl_freertos_funcs_init();
|
||||
|
||||
npl_freertos_mempool_init();
|
||||
|
||||
if(esp_nimble_hci_init() != ESP_OK) {
|
||||
ESP_LOGE(NIMBLE_PORT_LOG_TAG, "hci inits failed\n");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Initialize default event queue */
|
||||
ble_npl_eventq_init(&g_eventq_dflt);
|
||||
|
||||
os_msys_init();
|
||||
|
||||
void ble_store_ram_init(void);
|
||||
/* XXX Need to have a template for store */
|
||||
ble_store_ram_init();
|
||||
#endif
|
||||
|
||||
/* Initialize the host */
|
||||
ble_hs_init();
|
||||
return ESP_OK;
|
||||
}
|
||||
```
|
||||
|
||||
The host is configured by setting up the callbacks for Stack-reset, Stack-sync, and Storage status
|
||||
|
||||
```c
|
||||
ble_hs_cfg.reset_cb = periodic_adv_on_reset;
|
||||
ble_hs_cfg.sync_cb = periodic_adv_on_sync;
|
||||
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
|
||||
```
|
||||
|
||||
The main function calls `ble_svc_gap_device_name_set()` to set the default device name. 'blecent_phy' is passed as the default device name to this function.
|
||||
```c
|
||||
rc = ble_svc_gap_device_name_set("nimble_periodic_adv");
|
||||
```
|
||||
main function calls `ble_store_config_init()` to configure the host by setting up the storage callbacks which handle the read, write, and deletion of security material.
|
||||
```c
|
||||
/* XXX Need to have a template for store */
|
||||
ble_store_config_init();
|
||||
```
|
||||
|
||||
The main function ends by creating a task where nimble will run using `nimble_port_freertos_init()`. This enables the nimble stack by using `esp_nimble_enable()`.
|
||||
|
||||
```c
|
||||
nimble_port_freertos_init(periodic_adv_host_task);
|
||||
```
|
||||
`esp_nimble_enable()` create a task where the nimble host will run. It is not strictly necessary to have a separate task for the nimble host, but to handle the default queue, it is easier to create a separate task.
|
||||
|
||||
## Generation of non-resolvable private address
|
||||
|
||||
In Bluetooth Low Energy (BLE), a non-resolvable private address is a type of Bluetooth device address that is used for privacy purposes. It is a randomly generated address that changes periodically to prevent long-term tracking of a device. The API call to `ble_hs_id_gen_rnd()` is responsible for generating a non-resolvable private address. NRPA is a 48-bit address that is stored in `addr.val`.
|
||||
|
||||
```c
|
||||
#if CONFIG_EXAMPLE_RANDOM_ADDR
|
||||
static void
|
||||
periodic_adv_set_addr(void)
|
||||
{
|
||||
ble_addr_t addr;
|
||||
int rc;
|
||||
|
||||
/* generate new non-resolvable private address */
|
||||
rc = ble_hs_id_gen_rnd(0, &addr);
|
||||
assert(rc == 0);
|
||||
|
||||
/* set generated address */
|
||||
rc = ble_hs_id_set_rnd(addr.val);
|
||||
|
||||
assert(rc == 0);
|
||||
}
|
||||
#endif
|
||||
```
|
||||
|
||||
## Periodic Advertisement
|
||||
|
||||
Periodic advertisement start by creating instances of structures `ble_gap_periodic_adv_params`, `ble_gap_ext_adv_params`, `ble_hs_adv_fields`, and `os_mbuf`. Advertising parameters such as connecting modes, advertising intervals, peer address, advertising-filter policy, etc are defined in these structures for periodic and extended advertisements. `pparams` and `params` instances have parameters for periodic advertisement and extended advertisement respectively.
|
||||
Bluetooth device address is given by the structure ble_aadr_t which contains the fields for address type and address value.
|
||||
|
||||
## Need of Extended Advertisement in Periodic Advertisement
|
||||
|
||||
Non-connectable and non-scannable advertising events containing synchronization information about a periodic advertising train are necessary for the scanner device to sync with the periodic advertising train. The periodic advertising will utilize the same physical layer (PHY) as the auxiliary packet, which is part of the extended advertisement.
|
||||
|
||||
|
||||
Below is the implementation to start periodic advertisement.
|
||||
|
||||
```c
|
||||
static void
|
||||
start_periodic_adv(void)
|
||||
{
|
||||
int rc;
|
||||
struct ble_gap_periodic_adv_params pparams;
|
||||
struct ble_gap_ext_adv_params params;
|
||||
struct ble_hs_adv_fields adv_fields;
|
||||
struct os_mbuf *data;
|
||||
uint8_t instance = 1;
|
||||
ble_addr_t addr;
|
||||
|
||||
/* set random (NRPA) address for instance */
|
||||
rc = ble_hs_id_gen_rnd(1, &addr);
|
||||
assert (rc == 0);
|
||||
|
||||
MODLOG_DFLT(INFO, "Device Address: ");
|
||||
print_addr(addr.val);
|
||||
MODLOG_DFLT(INFO, "\n");
|
||||
|
||||
/* For periodic we use instance with non-connectable advertising */
|
||||
memset (¶ms, 0, sizeof(params));
|
||||
|
||||
/* advertise using random addr */
|
||||
params.own_addr_type = BLE_OWN_ADDR_RANDOM;
|
||||
params.primary_phy = BLE_HCI_LE_PHY_1M;
|
||||
params.secondary_phy = BLE_HCI_LE_PHY_2M;
|
||||
params.sid = 2;
|
||||
|
||||
/* configure instance 1 */
|
||||
rc = ble_gap_ext_adv_configure(instance, ¶ms, NULL, NULL, NULL);
|
||||
assert (rc == 0);
|
||||
|
||||
rc = ble_gap_ext_adv_set_addr(instance, &addr );
|
||||
assert (rc == 0);
|
||||
|
||||
memset(&adv_fields, 0, sizeof(adv_fields));
|
||||
adv_fields.name = (const uint8_t *)"Periodic ADV";
|
||||
adv_fields.name_len = strlen((char *)adv_fields.name);
|
||||
|
||||
/* Default to legacy PDUs size, mbuf chain will be increased if needed
|
||||
*/
|
||||
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 */
|
||||
memset(&pparams, 0, sizeof(pparams));
|
||||
pparams.include_tx_power = 0;
|
||||
pparams.itvl_min = BLE_GAP_ADV_ITVL_MS(120);
|
||||
pparams.itvl_max = BLE_GAP_ADV_ITVL_MS(240);
|
||||
|
||||
rc = ble_gap_periodic_adv_configure(instance, &pparams);
|
||||
assert(rc == 0);
|
||||
|
||||
data = os_msys_get_pkthdr(sizeof(periodic_adv_raw_data), 0);
|
||||
assert(data);
|
||||
|
||||
rc = os_mbuf_append(data, periodic_adv_raw_data, sizeof(periodic_adv_raw_data));
|
||||
assert(rc == 0);
|
||||
rc = ble_gap_periodic_adv_set_data(instance, data);
|
||||
assert (rc == 0);
|
||||
|
||||
/* start periodic advertising */
|
||||
assert (rc == 0 rc = ble_gap_periodic_adv_start(instance);
|
||||
);
|
||||
|
||||
/* start advertising */
|
||||
rc = ble_gap_ext_adv_start(instance, 0, 0);
|
||||
assert (rc == 0);
|
||||
|
||||
MODLOG_DFLT(INFO, "instance %u started (periodic)\n", instance);
|
||||
}
|
||||
```
|
||||
|
||||
The periodic advertisement uses a non-connectable advertising mode. `memset (¶ms, 0, sizeof(params))` initialises params to 0. This also sets `params.connectable` to 0.
|
||||
|
||||
## Parameter Configuration
|
||||
|
||||
The below snippets represent the parameter configuration for extended and periodic advertisement.
|
||||
|
||||
### For Extended Advertisement
|
||||
|
||||
```c
|
||||
params.own_addr_type = BLE_OWN_ADDR_RANDOM; //Own address type is set to Random
|
||||
params.primary_phy = BLE_HCI_LE_PHY_1M; // Primary advertising PHY is set to 1M
|
||||
params.secondary_phy = BLE_HCI_LE_PHY_2M; // Secondary advertising PHY is set to 2M
|
||||
params.sid = 2; // Advertising set Id is assigned with value 2.
|
||||
```
|
||||
|
||||
### For Periodic Advertisement
|
||||
|
||||
```c
|
||||
memset(&pparams, 0, sizeof(pparams));
|
||||
pparams.include_tx_power = 0; // Indicates that TX power is not included in advertising PDU
|
||||
pparams.itvl_min = BLE_GAP_ADV_ITVL_MS(120); // Minimum advertising interval of 240ms
|
||||
pparams.itvl_max = BLE_GAP_ADV_ITVL_MS(240); //Maximum advertising interval of 480ms
|
||||
```
|
||||
|
||||
Periodic advertisement is started for a particular advertisement instance by calling the API `ble_gap_periodic_adv_start(instance)`. This function takes instance-id as an input parameter. It defines the hci command by initializing the command parameters which are represented in the following lines.
|
||||
|
||||
```c
|
||||
struct ble_hci_le_set_periodic_adv_enable_cp cmd;
|
||||
cmd.enable = 0x01;
|
||||
cmd.adv_handle = instance;
|
||||
```
|
||||
|
||||
Extended advertising is invoked for a particular instance using the API call `ble_gap_ext_adv_start(instance, 0, 0)`.Instance-id, duration, and max_events are input parameters for this API call respectively.
|
||||
|
||||
Duration represents the time for which the adverteiment will take place. Upon expiration, the advertising procedure ends, and the BLE_GAP_EVENT_ADV_COMPLETE event is reported.0 value is used for no expiration.
|
||||
|
||||
max_events Number of advertising events that should be sent before advertising ends and a BLE_GAP_EVENT_ADV_COMPLETE event is reported.0 value is used for no limit.
|
||||
|
||||
## Conclusion
|
||||
|
||||
This Walkthrough covers the code explanation of the BLE_PERIODIC_ADV. The following points are concluded through this walkthrough.
|
||||
|
||||
1. Periodic advertising allows the scanner to sync with the advertiser so the scanner and advertiser would wake up at the same time.
|
||||
2. Periodic advertisement uses NRPA (Non Resolvable private address). It is a randomly generated address that changes periodically to prevent long-term tracking of a device.
|
||||
3. Extended advertising is used to indicate to the scanner that the advertiser is utilizing periodic advertising. Therefore, periodic advertising is started before extended advertising so that the scanner and advertiser can synchronize their actions and operate at the same time.
|
Reference in New Issue
Block a user