This tutorial shows how to create a basic Bluetooth LE Peripheral using the STM32WB-Nucleo board. We will create a basic Heart Rate Service peripheral (emulating a heart rate measurement device) and will show the Bluetooth LE services, characteristics and descriptors involved in getting a reading from that device and the STM32 functions used to control them.

Before you begin, ensure you have the STM32WB-Nucleo kit (including the Nucleo board and the USB dongle) and install VisualGDB 5.4R10 or later.

Open Visual Studio and launch the VisualGDB Embedded Project Wizard: Specify your project name and location and then press “Create” to launch the VisualGDB’s wizard: On the first page of the wizard select “Create a new project -> Embedded Binary -> MSBuild“: Note that due to the excessive amounts of the include directories used by the STM32WB SDK, the GNU Make will not be able to build most STM32WB projects correctly. Hence, please ensure you use the MSBuild subsystem instead. On the next page select your STM32WB device. In this tutorial we will use the STM32WB-Nucleo board that features an STM32WB55RGV6 device: On the next page select “Heart Rate Service Demo” sample and proceed with the default configuration for Bluetooth LE services: On the last page of the wizard select the debug settings that match your configuration. If you are not sure, follow our basic STM32WB tutorial to get started: Press “Finish” to create the project, then build it via Build->Build Solution: Once the project is built, program the regular Bluetooth LE wireless stack into your device using the Wireless Stack Updater tool: Then connect the board via the ST-Link connector and press F5 to start debugging: The Bluetooth Low Energy devices output data in a structured way. Each device (a.k.a. peripheral) can define one or more services. Each service defines one or more characteristics. Each characteristic may define additional descriptors used to control it. The layout of services, characteristics and descriptors for common service types (such as Heart Rate Service) is documented on the Bluetooth SIG website. Specifically the most important components for the Heart Rate Service are shown below: Heart Rate Service (BLE Service, see this document for the layout) Heart Rate Measurement (BLE characteristic, see this document for the output value format) Client Characteristic Configuration Descriptor (a.k.a. CCCD, see this document for the format) Body sensor location (BLE characteristic, see this document for a list of values) Heart Rate Control Point (BLE characteristic, see this document for the description) We will now use the Bluetooth LE Explorer app to locate the Heart Rate Service and its main characteristics in the output from our STM32WB project. Install Bluetooth LE explorer on your computer (alternatively, see below for instructions for Raspberry Pi) and launch it. Start scanning and wait for the app to discover the HRSTM peripheral: Click on the HSTM device. Bluetooth LE Explorer will show a list of the services and characteristics exposed by the device. Note the Heart Rate service with characteristics matching the list above: Click on the Heart Rate Measurement characteristic and enable the “Notify” switch. The Bluetooth LE explorer will begin receiving measurements from the device and displaying them in the “Read Value” field: Now we will look through the STM32WB driver code to see the functions involved in managing the services and characteristics. First of all, find the aci_gatt_add_char() function and use CodeJumps to locate other functions calling it: The go to the HRS_Init() function, set a breakpoint there and reset your device: Once the breakpoint triggers, observe the call stack: If you track the call stack down to main(), you will notice that shci_user_evt_proc() was not called explicitly. Instead, it was invoked via a function pointer from SCH_Run(). If you look up the references to shci_user_evt_proc(), you will see that it’s registered as a task by calling SCH_RegTask(): The task mechanism is used by the STM32WB samples to minimize the power consumption. Instead of continuously checking for various events from the main() function, the code must register its tasks and BLE event handlers via the STM32 API and then let the scheduler handle them by calling SCH_Run() from main(). Once the scheduler finishes running all the pending tasks, it will call the SCH_Idle() function that will in turn enter one of the low-power states (depending on the configuration), dramatically decreasing the power usage: Note that unless the client (i.e. Bluetooth Explorer) has specifically subscribed to the changes of the Heart Rate Measurement characteristic, the STM32WB device will not spend any power updating the measurement. You can find it out by checking the functions accessing the HeartRatemeasurementCharHdle field in HRS_Context_t: Specifically, the following events will take place once the Bluetooth LE explorer subscribes to the heart rate measurement notifications: The Bluetooth LE stack determines that the CCCD descriptor for the Heart Rate Measurement Characteristic got changed, so it calls HearRate_Event_Handler() with the EVT_BLUE_GATT_ATTRIBUTE_MODIFIED. HearRate_Event_Handler() determines that the modified attribute is the CCCD descriptor for the Heart Rate Measurement Characteristic (see the HeartRatemeasurementCharHdle check) and calls HRS_Notification(). HRS_Notification() calls HW_TS_Start() to start a hardware-controller periodic timer created earlier via HW_TS_Create(). The timer is integrated with the power-aware scheduler and will call the HrMeas() function periodically, automatically bringing the device out of sleep. HrMeas() will in turn call SCH_SetTask() on the HRSAPP_Measurement() task, adding it to the task queue. When the HRSAPP_Measurement() task runs, it will read the real-time clock register and report a heart rate value based on it. You can step through most of the logic described above by setting a breakpoint in the HRS_Notification() function and subscribing to heart rate measurement notifications in Bluetooth LE explorer: VisualGDB provides a structured way of referencing various STM32WB libraries and frameworks via the Embedded Frameworks page of VisualGDB Project Properties. E.g. you can enable the code for the Health Thermometer Service (HTS) by enabling the corresponding checkbox: Note that you would still need to copy various callbacks (e.g. HTS_App_Notification()) required by the added services from another example project that uses those services. Finally, we will show how to interact with the Bluetooth LE devices using the bluepy library on Raspberry Pi. Install bluepy on your Raspberry Pi as shown on this page and run the blescan command to display the list of all nearby Bluetooth LE devices: Then use the Python script shown below to locate the CCCD descriptor of the Heart Rate Measurement characteristic and enable notifications on it: from bluepy import btle import pprint; class MyDelegate(btle.DefaultDelegate): def __init__(self): btle.DefaultDelegate.__init__(self) def handleNotification(self, cHandle, data): print "Notification: " + str(cHandle) + " = " + pprint.pformat(data); print "Connecting..." dev = btle.Peripheral("80:e1:26:00:66:38") # <=== Substitute your device address here dev.setDelegate(MyDelegate()) print "Enabling notifications..."; #Get the Heart Rate Service by its UUID service = dev.getServiceByUUID(btle.UUID("0000180d-0000-1000-8000-00805f9b34fb")) #Get the Heart Rate Measurement Characteristic rate_char = service.getCharacteristics("00002a37-0000-1000-8000-00805f9b34fb")[0] #Get the CCCD descriptor for the Heart Rate Measurement Characteristic rate_cccd = rate_char.getDescriptors(0x2902)[0] #Enable notifications for the Heart Rate Measurement Characteristic by setting the "Notifications Enabled" bit (bit #0) rate_cccd.write(b"\x01\x00", True) #Handle notifications until Ctrl-C is pressed while dev.waitForNotifications(5000): pass 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 from bluepy import btle import pprint ; class MyDelegate ( btle . DefaultDelegate ) : def __init__ ( self ) : btle . DefaultDelegate . __init__ ( self ) def handleNotification ( self , cHandle , data ) : print "Notification: " + str ( cHandle ) + " = " + pprint . pformat ( data ) ; print "Connecting..." dev = btle . Peripheral ( "80:e1:26:00:66:38" ) # <=== Substitute your device address here dev . setDelegate ( MyDelegate ( ) ) print "Enabling notifications..." ; #Get the Heart Rate Service by its UUID service = dev . getServiceByUUID ( btle . UUID ( "0000180d-0000-1000-8000-00805f9b34fb" ) ) #Get the Heart Rate Measurement Characteristic rate_char = service . getCharacteristics ( "00002a37-0000-1000-8000-00805f9b34fb" ) [ 0 ] #Get the CCCD descriptor for the Heart Rate Measurement Characteristic rate_cccd = rate_char . getDescriptors ( 0x2902 ) [ 0 ] #Enable notifications for the Heart Rate Measurement Characteristic by setting the "Notifications Enabled" bit (bit #0) rate_cccd . write ( b "\x01\x00" , True ) #Handle notifications until Ctrl-C is pressed while dev . waitForNotifications ( 5000 ) : pass This will have the same effect as when using the Bluetooth LE Explorer app on Windows – the STM32WB board will begin sending out notifications with the simulated heart rate measurements:

STM32WB devices can also take the role of a Bluetooth LE Central (a device communicating to one or more peripherals). Follow this tutorial for a detailed step-by-step example.