21 KiB
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 eventble_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:
ESP_BT_MODE_IDLE
: Bluetooth not runningESP_BT_MODE_BLE
: BLE modeESP_BT_MODE_CLASSIC_BT
: BT Classic modeESP_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:
-
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 calledble_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 toNULL
.
- This line initializes a timer named
-
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 theportTICK_PERIOD_MS
value multiplied by 100, suggesting a timeout of 100 milliseconds.- The return value of
ble_npl_callout_reset
is stored inrc
, indicating the result of the timer reset operation.
- This line resets the
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
- 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. blecsc_advertise
start by creating the instances of structuresble_gap_adv_params
andble_hs_adv_fields
.- 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.
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
- Adv flags
- 16-bit service class UUIDS
- 32-bit service class UUIDS
- 128-bit service class UUIDS
- Local Name
- Tx Power Level
- Slave connection interval range
- Service data - 16-bit UUID
- Public target address
- Appearance
- Advertising interval
- Service data - 32-bit UUID
- Service data - 128-bit UUID
- URI
- 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 peer’s 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:
-
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.
-
Constants: There are some constants defined, such as
CSC_SIM_SPEED_KPH_MAX
,CSC_SIM_SPEED_KPH_MIN
,CSC_SIM_CRANK_RPM_MAX
, andCSC_SIM_CRANK_RPM_MIN
, which set the maximum and minimum values for simulated speed and cadence. -
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. -
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:
-
Function Description: This function is responsible for triggering the CSC measurement simulation and notifying the simulated measurements to a client over a BLE connection.
-
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 theblecsc_measure_timer
event to run every 10 milliseconds (portTICK_PERIOD_MS * 10
).- The return code is stored in the
rc
variable, and anassert
statement is used to check if the return code is zero (indicating successful scheduling).
-
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.
-
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 functiongatt_svr_chr_notify_csc_measurement
to send this notification. - The return code of the notification function call is stored in the
rc
variable.
- There is a condition check
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:
-
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 ofevent->connect.conn_handle
.
-
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.
-
BLE_GAP_EVENT_ADV_COMPLETE:
- This case handles events indicating that advertising has been completed.
- It logs that the advertising is complete.
-
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 thegatt_svr_set_cp_indicate
function.
-
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.