Merge branch 'feature/walkthrough_for_nimble_examples' into 'master'

doc (nimble): Added Walkthrough tutorial for phy_cent example

Closes IDFGH-9638

See merge request espressif/esp-idf!22811
This commit is contained in:
Rahul Tank 2023-07-06 20:09:45 +08:00
commit 5222ba7312

View File

@ -0,0 +1,360 @@
# BLE Central PHY Example Walkthrough
## Introduction
In this tutorial, the ble_phy central example code for the espressif chipsets with BLE5.0 support is reviewed. This example aims at understanding how to establish connections on preferred PHY and changing LE PHY once the connection is established. The code implements a BLE Central PHY, which establishes a connection on LE 1M PHY and switches to LE 2M PHY once the connection is established. The Central then performs GATT read operation against a specified peer and disconnects once this is completed.
## Includes
This example is located in the examples folder of the ESP-IDF under the [ble_phy/phy_cent/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 "phy_cent.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”`, `“phy_cent.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 ,device apperance and declare the function to set them.
* `phy_cent.h`: Defines the macro name `LE_PHY_UUID16` and `LE_PHY_CHR_UUID16`.
## Main Entry Point
The programs 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);
nimble_port_init();
/* Configure the host. */
ble_hs_cfg.reset_cb = blecent_on_reset;
ble_hs_cfg.sync_cb = blecent_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
/* Initialize data structures to track connected peers. */
rc = peer_init(MYNEWT_VAL(BLE_MAX_CONNECTIONS), 64, 64, 64);
assert(rc == 0);
/* Set the default device name. */
rc = ble_svc_gap_device_name_set("blecent-phy");
assert(rc == 0);
/* XXX Need to have template for store */
ble_store_config_init();
nimble_port_freertos_init(blecent_host_task);
}
```
The main function starts by initializing the non-volatile storage library. This library allows 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 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 = blecent_on_reset;
ble_hs_cfg.sync_cb = blecent_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
```
Further Data Structures are created and initialized to track connected peers using `peer_init()`. This function creates memory buffers to generate the memory pools like `peer_pool`, `peer_svc_pool`, `peer_chr_pool`, and `peer_dsc_pool`.
```c
rc = peer_init(MYNEWT_VAL(BLE_MAX_CONNECTIONS), 64, 64, 64);
```
## Structure of Peer
The structure of a peer includes fields such as its connection handle, a pointer to the next peer, a list of discovered gatt services, tracking parameters for the service discovery process, and the callbacks that get executed when service discovery completes.
```c
struct peer {
SLIST_ENTRY(peer) next;
uint16_t conn_handle;
struct peer_svc_list svcs;
uint16_t disc_prev_chr_val;
struct peer_svc *cur_svc;
peer_disc_fn *disc_cb;
void *disc_cb_arg;
};
```
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("blecent-phy");
```
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(blecent_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 since something needs to handle the default queue, it is easier to create a separate task.
## Intializaion of LE PHY to Default 1M PHY .
1M PHY is the default PHY for BLE devices which enables it to provide a data rate of 1 Mbps. It is used while establishing the connection between devices and maintains backward compatibility with all those devices that don't have BLE5.0 support.`set_default_le_phy_before_conn()` function set default LE PHY before establishing a connection.
```c
void set_default_le_phy_before_conn(uint8_t tx_phys_mask, uint8_t rx_phys_mask)
{
int rc = ble_gap_set_prefered_default_le_phy(tx_phys_mask, rx_phys_mask);
if (rc == 0)
{
MODLOG_DFLT(INFO, "Default LE PHY set successfully; tx_phy = %d, rx_phy = %d",
tx_phys_mask, rx_phys_mask);
} else {
MODLOG_DFLT(ERROR, "Failed to set default LE PHY");
}
}
```
## Setting LE PHY to preferred LE PHY.
2M PHY is introduced in BLE5.0 to increase the symbol rate at the physical layer. It provides a symbol rate of 2 Mega symbols per second where each symbol corresponds to a single bit. This allows the user to double the number of bits sent over the air during a given period, or conversely reduce energy consumption for a given amount of data by having the necessary transmit time.
The following lines change the default LE PHY to 2M PHY.
` tx_phys_mask = BLE_HCI_LE_PHY_2M_PREF_MASK`
` rx_phys_mask = BLE_HCI_LE_PHY_2M_PREF_MASK`
```c
void set_prefered_le_phy_after_conn(uint16_t conn_handle)
{
uint8_t tx_phys_mask = 0, rx_phys_mask = 0;
tx_phys_mask = BLE_HCI_LE_PHY_2M_PREF_MASK;
rx_phys_mask = BLE_HCI_LE_PHY_2M_PREF_MASK;
int rc = ble_gap_set_prefered_le_phy(conn_handle, tx_phys_mask, rx_phys_mask,0);
if (rc == 0) {
MODLOG_DFLT(INFO, "Prefered LE PHY set to LE_PHY_2M successfully");
} else {
MODLOG_DFLT(ERROR, "Failed to set prefered LE_PHY_2M");
}
}
```
## Read Operation
`blecent_read` reads the supported LE PHY characteristic.If the peer does not support a required service, characteristic, or descriptor OR if a GATT procedure fails , then this function immediately terminates the connection.
```c
blecent_read(const struct peer *peer)
{
const struct peer_chr *chr;
int rc;
/* Read the supported-new-alert-category characteristic. */
chr = peer_chr_find_uuid(peer,
BLE_UUID16_DECLARE(LE_PHY_UUID16),
BLE_UUID16_DECLARE(LE_PHY_CHR_UUID16));
if (chr == NULL) {
MODLOG_DFLT(ERROR, "Error: Peer doesn't support the Supported "
"LE PHY characteristic\n");
goto err;
}
rc = ble_gattc_read(peer->conn_handle, chr->chr.val_handle,
NULL, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Error: Failed to read characteristic; rc=%d\n",
rc);
goto err;
}
return;
err:
/* Terminate the connection. */
ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
}
```
## BLE GAP Connect Event
Once the connection is established `BLE_GAP_EVENT_CONNECT` event occurs. This event is handled by checking the current LE PHY equals 1M PHY and updating it to 2M PHY followed by performing the service discovery. If the connection attempt is failed then central start scanning again using the `blecent_scan` function.
```c
case BLE_GAP_EVENT_CONNECT:
/* A new connection was established or a connection attempt failed. */
if (event->connect.status == 0) {
/* Connection successfully established. */
MODLOG_DFLT(INFO, "Connection established ");
rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
assert(rc == 0);
print_conn_desc(&desc);
MODLOG_DFLT(INFO, "\n");
/* Remember peer. */
rc = peer_add(event->connect.conn_handle);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Failed to add peer; rc=%d\n", rc);
return 0;
}
if (s_current_phy == BLE_HCI_LE_PHY_1M_PREF_MASK) {
/* Update LE PHY from 1M to 2M */
set_prefered_le_phy_after_conn(event->connect.conn_handle);
}
/* Perform service discovery. */
rc = peer_disc_all(event->connect.conn_handle,
blecent_on_disc_complete, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Failed to discover services; rc=%d\n", rc);
return 0;
}
} else {
/* Connection attempt failed; resume scanning. */
MODLOG_DFLT(ERROR, "Error: Connection failed; status=%d\n",
event->connect.status);
blecent_scan();
}
return 0;
```
## BLE GAP Disconnect Event
The connection between Central and Peripheral is terminated when the service discovery is failed or the GATT procedure is completed. `ble_gap_terminate` function is used to terminate the connection which results in the event called `BLE_GAP_EVENT_DISCONNECT`. As the connection is terminated so the event is handled by setting up the default LE PHY to 1M followed by calling the `blecent_scan` function.
```c
case BLE_GAP_EVENT_DISCONNECT:
/* Connection terminated. */
MODLOG_DFLT(INFO, "disconnect; reason=%d ", event->disconnect.reason);
print_conn_desc(&event->disconnect.conn);
MODLOG_DFLT(INFO, "\n");
/* Forget about peer. */
peer_delete(event->disconnect.conn.conn_handle);
switch (s_current_phy) {
case BLE_HCI_LE_PHY_1M_PREF_MASK:
/* Setting current phy to create connection on 2M PHY */
s_current_phy = BLE_HCI_LE_PHY_2M_PREF_MASK;
break;
case BLE_HCI_LE_PHY_2M_PREF_MASK:
/* Setting current phy to create connection on CODED PHY */
s_current_phy = BLE_HCI_LE_PHY_CODED_PREF_MASK;
break;
case BLE_HCI_LE_PHY_CODED_PREF_MASK:
return 0;
}
set_default_le_phy_before_conn(s_current_phy, s_current_phy);
blecent_scan();
return 0;
case BLE_GAP_EVENT_DISC_COMPLETE:
MODLOG_DFLT(INFO, "discovery complete; reason=%d\n",
event->disc_complete.reason);
return 0;
```
## Conclusion
This Walkthrough covers the code explanation of the BLE_PHY_CENTRAL example. The following points are concluded through this walkthrough.
1. As the nimble stack is initialized default LE PHY is set to 1M in the `blecent_on_sync` function.
2. Once the connection is established default LE PHY is updated to 2M PHY and service discovery is performed.
3. Once the Service discovery completes or fails, a connection is terminated.
4. Once the connection is terminated, depending on the value current LE PHY it is updated to the preferred LE PHY.