esp-idf/examples/bluetooth/nimble/blecsc/tutorial/blecsc_walkthrough.md

21 KiB
Raw Blame History

BLE CSC Example Walkthrough

Introduction

This Bluetooth Low Energy (BLE) Cycling Speed and Cadence (CSC) profile is designed to provide real-time measurements of a cyclist's speed and cadence. It allows cycling-related devices, such as bike sensors or fitness trackers, to communicate this data wirelessly to a receiving device, such as a smartphone or cycling computer.

Includes

This example is located in the examples folder of the ESP-IDF under the blecsc/main. The 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 are: 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”, “blecsc_sens.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.
  • blecsc_sens.h: This file defines the Cycling Speed and Cadence configuration, Device Information configuration, CSC Measurement flags, CSC feature flags, Sensor location enum CSC simulation configuration, etc.

Main Entry Point

int
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 -1;
     }

    /* Initialize the NimBLE host configuration */
    ble_hs_cfg.sync_cb = blecsc_on_sync;

    /* Initialize measurement and notification timer */
    ble_npl_callout_init(&blecsc_measure_timer, nimble_port_get_dflt_eventq(),
                    blecsc_measurement, NULL);
    rc = ble_npl_callout_reset(&blecsc_measure_timer, portTICK_PERIOD_MS * 100);
    assert(rc == 0);

    rc = gatt_svr_init(&csc_measurement_state);
    assert(rc == 0);

    /* Set the default device name */
    rc = ble_svc_gap_device_name_set(device_name);
    assert(rc == 0);

    nimble_port_freertos_init(blecsc_host_task);

    return 0;
}

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.

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:

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.

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():

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 callback that gets executed upon Stack-sync.

ble_hs_cfg.sync_cb = blecsc_on_sync;

Callout init and callout reset

  ble_npl_callout_init(&blecsc_measure_timer, nimble_port_get_dflt_eventq(),
                    blecsc_measurement, NULL);
    rc = ble_npl_callout_reset(&blecsc_measure_timer, portTICK_PERIOD_MS * 100);
    assert(rc == 0);

It initializes a timer for measuring and sending notifications. Let's break down the code step by step:

  1. ble_npl_callout_init(&blecsc_measure_timer, nimble_port_get_dflt_eventq(), blecsc_measurement, NULL);

    • This line initializes a timer named blecsc_measure_timer using a function or library called ble_npl_callout_init.
    • &blecsc_measure_timer is a pointer to the timer structure that will be initialized.
    • nimble_port_get_dflt_eventq() is a function call that retrieves the default event queue for a NimBLE (a Bluetooth stack) port. This event queue is likely used for scheduling events and tasks related to Bluetooth communication.
    • blecsc_measurement is a callback that will be called when the timer expires. It is explained in following parts.
    • NULL is passed as the last argument, which typically represents user-specific data or context to be passed to the callback function. In this case, it's not used, so it's set to NULL.
  2. rc = ble_npl_callout_reset(&blecsc_measure_timer, portTICK_PERIOD_MS * 100);

    • This line resets the blecsc_measure_timer timer with a new timeout value.
    • &blecsc_measure_timer is again a pointer to the timer structure to be reset.
    • portTICK_PERIOD_MS * 100 sets the timer's new expiration time. It appears to be based on the portTICK_PERIOD_MS value multiplied by 100, suggesting a timeout of 100 milliseconds.
    • The return value of ble_npl_callout_reset is stored in rc, indicating the result of the timer reset operation.

In summary, this code initializes a timer named blecsc_measure_timer, associates it with a specific event queue, and sets it to trigger the blecsc_measurement function after a delay of 100 milliseconds. If the timer reset operation is successful (indicated by rc == 0), the program continues; otherwise, it will halt if there's an error. This timer is used for periodic measurement and notification tasks in a BLECSC application.

GATT SERVER INIT

 rc = gatt_svr_init();
    assert(rc == 0);

The gatt_svr_init function is called during the initialization phase of a BLE application to set up the GATT server and define the services and characteristics it supports.

SETTING DEVICE NAME

The main function calls ble_svc_gap_device_name_set() to set the default device name. 'blecsc_sensor' is passed as the default device name to this function.

rc = ble_svc_gap_device_name_set(device_name);

THREAD MODEL

The main function creates a task where nimble will run using nimble_port_freertos_init(). This enables the nimble stack by using esp_nimble_enable().

nimble_port_freertos_init(blecsc_host_task);

esp_nimble_enable() creates a task where the nimble host will run. Nimble stack runs in a separate thread with its own context to handle async operations from the controller and post HCI commands to the controller.

BLE CSC ADVERTISEMENT

  1. CSC advertisement is used by many applications to start advertising from applications. The blecsc example provides this feature of advertisement. It uses the following function to start an advertisement.
  2. blecsc_advertise start by creating the instances of structures ble_gap_adv_params and ble_hs_adv_fields.
  3. Advertising parameters such as connecting modes, discoverable modes, advertising intervals, channel map advertising filter policy, and high duty cycle for directed advertising are defined in these structures.
  4. ble_hs_adv_fields provides a structure to store advertisement data in a Bluetooth Low Energy (BLE) application. It contains various data members, such as flags (indicating advertisement type and other general information), advertising transmit power, device name, and 16-bit service UUIDs (such as alert notifications), etc.

Below is the implementation to start the advertisement.

Configuration of Advertising data(ble_hs_adv_fields fields)

Setting advertising flags and transmitting power level

  memset(&fields, 0, sizeof fields);

    fields.flags = BLE_HS_ADV_F_DISC_GEN |
                   BLE_HS_ADV_F_BREDR_UNSUP;
    fields.tx_pwr_lvl_is_present = 1;
    fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;

All data members of fields are initialized to 0 before setting up using the memset() function. Discoverability for upcoming advertisements is set to general. Advertisement is set to BLE supported only. Assigning the tx_pwr_lvl field to BLE_HS_ADV_TX_PWR_LVL_AUTO prompts the stack to automatically fill in the transmit power level field.

Setting up the device name and appearance

fields.name = (uint8_t *)device_name;
fields.name_len = strlen(device_name);
fields.name_is_complete = 1;

fields.appearance = ble_svc_gap_device_appearance();
fields.appearance_is_present = 1;

The app main function calls the API ble_svc_gap_device_name_set("blecsc_sensor") which takes the user-defined device name as a parameter. This API sets the device name to the characters array ble_svc_gap_name defined in the file ble_svc_gap.c. The ble_svc_gap_device_name() API returns the same above-mentioned char array which was set from the main function.

ble_gap_adv_set_fields() sets the following advertising data. This API internally depends on other APIs to set the data.APIs can be checked here ble_hs_adv.c

  1. Adv flags
  2. 16-bit service class UUIDS
  3. 32-bit service class UUIDS
  4. 128-bit service class UUIDS
  5. Local Name
  6. Tx Power Level
  7. Slave connection interval range
  8. Service data - 16-bit UUID
  9. Public target address
  10. Appearance
  11. Advertising interval
  12. Service data - 32-bit UUID
  13. Service data - 128-bit UUID
  14. URI
  15. Manufacturer-specific data

Start Advertisement

 memset(&adv_params, 0, sizeof(adv_params));
    adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
    adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
    rc = ble_gap_adv_start(blecsc_addr_type, NULL, BLE_HS_FOREVER,
                           &adv_params, blecsc_gap_event, NULL);
    if (rc != 0) {
        MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc);
        return;
    }

Advertisement parameters are initialized to 0 using memset. Enables advertising with general discoverable mode and undirected connectable mode. The ble_gap_adv_start() function configures and starts the advertising procedure.

The description of the ble_gap_adv_start() method is as follows:

Return Value : 0 on success, error code on failure
Parameters

1. blecsc_addr_type:The type of address the stack should use for itself. Valid values are:
- BLE_OWN_ADDR_PUBLIC
- BLE_OWN_ADDR_RANDOM
- BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT
- BLE_OWN_ADDR_RPA_RANDOM_DEFAULT
2. direct_addr:The peers address for directed advertising. This parameter shall be non-NULL if directed advertising is being used. In this case, we have set it to NULL.
3. duration_ms: The duration of the advertisement procedure. On expiration, the procedure ends and a BLE_GAP_EVENT_ADV_COMPLETE event is reported. Units are milliseconds. Specify BLE_HS_FOREVER for no expiration.
4. adv_params: Additional arguments specifying the particulars of the advertising procedure.
5. cb: The callback to associate with this advertising procedure. If advertising ends, the event is reported through this callback. If advertising results in a connection, the connection inherits this callback as its event-reporting mechanism. The Callback, in this case, is `blecsc_gap_event`.
6. cb_arg: The optional argument to pass to the callback function.

BLECSC Simulate speed and cadance value.

This function, named blecsc_simulate_speed_and_cadence, is related to simulating speed and cadence data in the context of cycling. It is used in a Bluetooth Low Energy (BLE) Cycling Speed and Cadence (CSC) application. Following is the break down what this function is doing step by step:

  1. Function Description: This function updates simulated measurements for cycling speed and cadence. It calculates the time periods for wheel and crank revolutions based on the simulated speed (in kilometers per hour, kph) and cadence (in revolutions per minute, RPM). The time periods are expressed in 1/1024th of a second units.

  2. Constants: There are some constants defined, such as CSC_SIM_SPEED_KPH_MAX, CSC_SIM_SPEED_KPH_MIN, CSC_SIM_CRANK_RPM_MAX, and CSC_SIM_CRANK_RPM_MIN, which set the maximum and minimum values for simulated speed and cadence.

  3. Simulated Speed and Cadence Updates: The function increments the simulated cycling speed (csc_sim_speed_kph) and cadence (csc_sim_crank_rpm) by one. If these values reach their maximum limits, they are reset to their minimum values.

  4. Calculating Wheel and Crank Revolution Periods:

    • Wheel Revolution Period (wheel_rev_period): If the simulated speed is greater than zero, it calculates the time it takes for one wheel revolution based on the circumference of the wheel and the simulated speed. It increments the cumulative wheel revolution counter and updates the last wheel event time by adding the wheel revolution period.

    • Crank Revolution Period (crank_rev_period): If the simulated cadence is greater than zero, it calculates the time it takes for one crank revolution based on the cadence. It increments the cumulative crank revolution counter and updates the last crank event time by adding the crank revolution period.

Overall, this function is part of a simulated cycling environment, used to generate and update simulated speed and cadence measurements based on predefined parameters and equations. These simulated values can be used for testing and development of a cycling-related application, over a BLE connection.

Mathematical Calculations


                  60 * 1024
  crank_dt =    --------------
                 cadence[RPM]
 
 
                 circumference[mm] * 1024 * 60 * 60
  wheel_dt =    -------------------------------------
                          10^6 * speed [kph]
  

BLECSC Measurement

This function appears to be part of a program that's related to Bluetooth Low Energy (BLE) Cycling Speed and Cadence (CSC) measurements. It's called blecsc_measurement, and it seems to be responsible for running the CSC measurement simulation and notifying the simulated measurements to a client. Let's break down what this function is doing point by point:

  1. Function Description: This function is responsible for triggering the CSC measurement simulation and notifying the simulated measurements to a client over a BLE connection.

  2. ble_npl_callout_reset Call:

    • ble_npl_callout_reset is a function call used to schedule an event to run periodically. In this case, it schedules the blecsc_measure_timer event to run every 10 milliseconds (portTICK_PERIOD_MS * 10).
    • The return code is stored in the rc variable, and an assert statement is used to check if the return code is zero (indicating successful scheduling).
  3. blecsc_simulate_speed_and_cadence Call:

    • This function is called to simulate speed and cadence measurements. It updates the simulated values, as explained in the previous response.
  4. Notification to the Client:

    • There is a condition check if (notify_state) which seems to determine whether to notify the client about the simulated measurements.
    • If notify_state is true (non-zero), it proceeds to notify the client about the CSC measurement. It likely uses the function gatt_svr_chr_notify_csc_measurement to send this notification.
    • The return code of the notification function call is stored in the rc variable.

In summary, this function is part of a BLE CSC application and serves as a periodic task that simulates CSC measurements, schedules the next simulation, and notifies the client about these measurements if the notify_state condition is met. It includes error checking to ensure the success of scheduling and notification operations.

BLE CSC GapEvent

This function, blecsc_gap_event, appears to handle various events related to the Bluetooth Low Energy (BLE) connection in a Cycling Speed and Cadence (CSC) application. It contains a switch statement that handles different event types and takes appropriate actions for each event. Let's go through each case in this switch statement point by point:

  1. BLE_GAP_EVENT_CONNECT:

    • This case handles events related to the establishment of a new connection or the failure of a connection attempt.
    • It logs whether the connection was successfully established or if it failed along with the status code.
    • If the connection attempt fails (event->connect.status != 0), it resumes advertising and sets the connection handle (conn_handle) to 0.
    • If the connection was successfully established, it sets conn_handle to the value of event->connect.conn_handle.
  2. BLE_GAP_EVENT_DISCONNECT:

    • This case handles events related to a disconnection. It logs the reason for the disconnection.
    • It sets conn_handle to 0 and resumes advertising after a disconnection.
  3. BLE_GAP_EVENT_ADV_COMPLETE:

    • This case handles events indicating that advertising has been completed.
    • It logs that the advertising is complete.
  4. BLE_GAP_EVENT_SUBSCRIBE:

    • This case handles events related to subscriptions by a client to characteristics. It includes information about which attribute was subscribed and whether notifications or indications are enabled.
    • It logs the attribute handle and the current state of notification/indication for the subscribed attribute.
    • If the subscribed attribute is the CSC measurement attribute, it updates the notify_state.
    • If the subscribed attribute is the CSC control point attribute, it updates the indicate_state and sets it using the gatt_svr_set_cp_indicate function.
  5. BLE_GAP_EVENT_MTU:

    • This case handles events related to the Maximum Transmission Unit (MTU) update. The MTU represents the maximum data packet size that can be sent over the connection.
    • It logs the connection handle and the updated MTU value.

In summary, this function is a critical part of managing the BLE connection in a CSC application. It handles connection and disconnection events, tracks subscription states for specific attributes (such as CSC measurements and control points), and logs relevant information for debugging and monitoring purposes. Additionally, it resumes advertising when connections are terminated or when a connection attempt fails

Conclusion

  • This tutorial explains the CSC measurement values i.e how it is calculated and communicated over ble protocol
  • It also commented on ble_csc advertising which is similar to peripheral advertisement .
  • Various gap events related to this csc service is explained here.