cancel
Showing results for 
Search instead for 
Did you mean: 

Was anybody able to implement a custom BLE profile for the STM32WB55?

VVakh.1
Associate III

Was anybody able to implement a custom BLE profile for the STM32WB55 using STM32CubeIDE v1.7 (or around) and the firmware package 1.11.1 (or around)? I would like to recreate and make publicly available this workshop. It is out-of-date.

2 REPLIES 2
SRyan.1
Associate

I was able to recreate the project outlined in the MOOC STM32WB Workshop - 4 How to modify BLE Profile using STMCubeIDE v 1.7 and firmware STM32Cube_FW_WB_V1.12.1. But to do this I started with MOOC STM32WB Workshop - 3 How to add BLE functionality, which is now also out of date, and then modified that project to include a notification stream like that in Workshop - 4, while maintaining the push button notification and LED control of Workshop - 3. I'll paste in my lengthy notes below for the Workshop - 3 below, and for Workshop - 4 in a separate answer.

In terms of client apps, I could not get the ST apps (STBLESensor, STBLEProfiles) running on iOs to work with these two projects. The app LightBlue by PunchThrough seemed to work well for both.

Project BLE_BasicP2P, replicates the functionality outlined in Workshop for the P-Nucleo-WB55 board:

In STM32CubeMx (using 6.3.0):

1. Use Board selector to find: P-NUCLEO-WB55-Nucleo. Start project, accept "Set peripherals to defaults".

2. Configure board for BLE:

  • Check setting for High Speed External, need 32 MHz

System Core > RCC > HSE > Crystal/Ceramic Resonator

(default), note this enables two pins RCC_OSC_IN, and RCC_OSCOUT

  • Check setting for Low Speed Clock (could also use LSI2):

System Core > RCC > LSE > Crystal/Ceramic Resonator

(default), two pins RCC_OSC32_IN and _OUT

  • Activate hardware semaphore

System Core > HSEM > check Activated

  • Enable RF

Connectivity > RF > Activate RF1

RF_RF1 pin enabled

  • Enable RTC, used by middleware to manage wakeup, software timers (used for followup BLE_CustomP2P project) 

Timers > RTC > Activate Clock Source

 WakeUp > Internal WakeUp, 

Wakeup >Configuration> NVIC Interrupt Table > RTC wakeup interrupt

  • Enable BLE middleware. 

Middleware > STM32_WPAN > check BLE

  • In BLE Applications and Services tab check settings (default).

BLE Application Type : Server profile

Server Mode: Custom P2P Server Enabled (default)

Local Name: max chars = 7, default is P2PSRV1

3. Configure Clocks: Clock Configuration Tab

 RTC Source Mux (top left of clock map): set to LSE

System Clock Mux (just below RTC): set to HSE_SYS (default),

both CPUs should have 32 MHz (HCLK1, HCLK2)

RFWKP Clock Mux (lower right corner): set to LSE

4. Project Manager 

 Project tab: Set or check project name, location, STM32CubeIDE,

check Generate Under Root.

This project uses Firmware Package: STM32Cube FW_WB V1.12.1

Advanced tab: Make sure STM32_WPAN is LAST in the list of Generated Function Calls (default).

5. Generate code, accept open project in IDE if working in standalone CubeMX.

6. Add HSE tuning, in Core/Src/stm32wbxx_hal_msp.c. See AN5042 for details on how to generate the underlying otp data.

  • in /*USER CODE BEGIN Includes (~line 25) add:
#include "otp.h"

  • in /*USER CODE BEGIN MspInit_0 (~line 67) add (note this is valid only for P-NUCLEO-WB55 boards, should be re-implemented for other boards):
OTP_ID0_t * p_otp;
 // Read HSE_Tuning from OTP
p_otp = (OTP_ID0_t *) OTP_Read(0);
if (p_otp){
	LL_RCC_HSE_SetCapacitorTuning(p_otp->hse_tuning);
}

7. Add STM32_WPAN ISRs in Core/Src/STM32wbxx_it.c. in /*USER CODE BEGIN 1 (~line 245)

void IPCC_C1_RX_IRQHandler(void){
	// This function handles IPCC RX occupied interrupt
	HW_IPCC_Rx_Handler();
}
 
void IPCC_C1_TX_IRQHandler(void){
	// This function handles IPCC TX free interrupt.
	HW_IPCC_Tx_Handler();
}
 

8. Add a blinking led to mark radio activity event in STM32_WPAN/app_ble.c (~line 679) at /* USER CODE BEGIN RADIO_ACTIVITY_EVENT*/

    

// 5 ms green led after(?) every Radio Activity Event finishes
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
HAL_Delay(5);
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
 

9. Test build, debug, resume, run BLE phone app . At this point I could see the board on the phone app, could connect, and see default characteristics of the P2PServer: one read/write without response, one notify. Radio activity LED (LD2) on the Nucleo blinks slowly when disconnected, more rapidly when connected. If you don't connect within 60 sec, board goes into low power mode and you will have to reset the board. Note: LightBlue does not appear to update the primary service name until after you connect.  

10. Turn red LED (LD3) on or off when GATT client writes 1 or 0, respectively, to P2P_WRITE characteristic. In STM32_WPAN/App/p2p_server_app.c P2PS_STM_App_Notification(), case P2PS_STM_WRITE_EVT, check data payload of notification. Note it seems like the payload structure has recently changed to not include the length (which is now DataTransfered.Length) so you can just dereference the pointer. At /* USER CODE BEGIN P2PS_STM_WRITE_EVT */ ~ line 85

if(*pNotification->DataTransfered.pPayload == 0x01) {
	HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_SET);
}
else {
	HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_RESET);
}

11. Build and debug. Connect with LightBlue app and write 1 and then 0 and check for LD3 response. 

12. When switch SW1 on Nucleo board is pressed, modify P2P_NOTIFY characteristic value, and send notification to client.  

  • Modify the characteristic, in STM32_WPAN/App/p2p_server_app.c at /* USER CODE BEGIN PFP */ ~line 56
static void P2PS_Send_Notification(void);

and at /* USER CODE BEGIN FD */ ~line 149

static void P2PS_Send_Notification(void){
	// Update P2P_NOTIFY characteristic
	P2PS_STM_App_Update_Char(P2P_NOTIFY_CHAR_UUID, 0x00);
	return;
}

  • Register this function with sequencer, in In STM32_WPAN/App/p2p_server_app P2P_APP_Init(). Note the 0 in UTIL_SEQ_RegTask is a flag reserved for future use, also sequencer requires bitshift on ID unlike original in MOOC. At /* USER CODE BEGIN P2PS_APP_Init */ ~line 143
UTIL_SEQ_RegTask(1 << CFG_TASK_SW1_BUTTON_PUSHED_ID, 0, P2PS_Send_Notification);

  • Make sure to save all files here, so that when you generate code with CubeMx the previous changes won't disappear.

  •  Set up SW1 pin for external interrupt, using MX (in IDE or CubeMx standalone) modify the .ioc file.

 i. In Pinout tab / Pinout view, left click pin for PC4, select GPIO_EXT14, then right click on the pin and rename the pin to SW1

ii. System Core > GPIO > PC4 > GPIO mode > Ext interrupt: Falling edge detection. GPIO Pull-up/Pull-down > Pull-up

iii. System Core > NVIC > EXTI line4 interrupt (check box far to the right, often hidden) > enable

 iv. generate code, open project in IDE

  • Override the weak HAL_GPIO_EXTI_Callback, put this in Core/Src/app_entry.c. Note the call chain on the interrupt: ext intrpt line 4 > EXTI4_IRQHandler > HAL_GPIO_EXTI_IRQHandler > HAL_GPIO_EXTI_Callback. At /* USER CODE BEGIN FD */ ~line 158
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){
	switch (GPIO_Pin){
		case SW1_Pin:
			HAL_GPIO_TogglePin(LD1_GPIO_Port, LD1_Pin); //optional blue led
			UTIL_SEQ_SetTask(1<<CFG_TASK_SW1_BUTTON_PUSHED_ID, CFG_SCH_PRIO_0); 
			break;
		default:
			break;
	}
	return;
}

  • Add CFG_TASK_SW1_BUTTON_PUSHED_ID to enum in Core/Inc/app_conf.h. In CFG_Task_Id_With_HCI_Cmd_t at /* USER CODE BEGIN CFG_Task_Id_With_HCI_Cmd_t */ ~line 579:
CFG_TASK_SW1_BUTTON_PUSHED_ID,

  • Since the call chain from main.c main: > app_entry.c MX_APPE_Init() > System_Init( ) > Init_Exti( ) > LL_EXTI_DisableIT_0_31(~0) disables external interrupts, including the interrupt initiated by SW1, I added a line in Core/Src/app_entry.c in Mx_APPE_Init() to re-enable EXTI Line 4. At /* USER CODE BEGIN APPE_Init_1 */ ~line 115.

LL_EXTI_EnableIT_0_31(1<<4); // re-enable of exti line 4

  • Build, debug. Using LightBlue, connect, open notify characteristic, turn on Listen for Notifications, press SW1, check for the notification with timestamp in LightBlue.
SRyan.1
Associate

BLE_CustomP2P project. The goal of this project was to begin with a copy of the above project and then add a custom notification characteristic that would push a sawtooth stream of 4 byte numbers from the Nucleo-STM32WB55 Nucleo board out to the client, recreating the functionality shown in the MOOC Workshop-4.

Duplicate and rename a project in the workspace

  • Open the project to be copied in IDE
  • Rt-click project in Project Explorer > Copy
  • Rt-clk Paste and rename
  • Close the original project
  • Rename the .ioc file to the new name, check update references
  • Clean the project
  • Delete the <old project name> Debug.launch file, can also delete the Debug/<old project name>.map file
  • Regenerate code from the .ioc file: double-click the .ioc file to open in IDE's MX then Project Menu > Generate Code
  • Build. Debug As > STM32 Cortex-M app

Add custom template. Using Mx (IDE or standalone version) modify the .ioc. In Pinout & Configuration > Middleware>STM32_WPAN >BLE Applications and Services

  • BLE Application Type > Server profile (default from previous project)
  • Server Mode > Custom P2P > Enabled (default from previous project)
  • Server Mode >Custom Template > Enabled

This brings up new tabs to be configured. A lot of confusion here. I thought I should be using AD_TYPE_MANUFACTURER_SPECIFIC_DATA element and that this would be 13 bytes with predefined fields as outlined in AN5289 section 7.3 and UM2496 section 1, but it seemed to be limited to 4 bytes. The UUID for P2P Service shown in AN5289 section 7.4.1 seems to be a nibble short: 

00 00 FE 40 CC 7A 48 2A 98 4A 7F ED 5B 3E 58 F

So in the end I just used the UUID's from the MOOC Workshop #4 in the AD_TYPE_128_BIT_SERV_UUID_CMPLT_LIST and Characteristic1 UUID even though this is likely not correct.

BLE Advertising tab

Advertising configuration

  • Peripheral: Advertise and connectable > Yes
  • CFG_GAP_DEVICE_NAME NUMSTRM

Advertising Elements

  • Include AD_TYPE_128_BIT_SERV_UUID_CMPLT_LIST > Yes
  • AD_TYPE_128_BIT_SERV_UUID_CMPLT_LIST > 00 00 00 00 00 01 11 E1 9A B4 00 02 A5 D5 C5 1B

BLE GATT tab

  • Number of services > 1
  • Service long name > NumberStreamSvc
  • Service short name > NUMSTRSVC

NumberStreamSvc tab

  • Characteristic1 general
  • Characteristic long name NumberStreamChar
  • Characteristic short name NUMSTRCHR
  • UUID 128 input type > full
  • UUID 00 04 00 00 00 01 11 E1 AC 36 00 02 A5 D5 C5 1B
  • Value length 4

Characteristic1 properties

  • CHAR_PROP_NOTIFY > Yes

Build, Debug, Test previous functions in LightBlue: write to LD3 and SW1 notify. Note LightBlue doesn't seem to update Service Name until after connecting. The primary service name changes to NUMSTRM.

Create a structure for number stream data. Since in STM32_WPAN/App/custom_app.c, the user code section for private typedefs is below the Mx generated Custom_App_Context_t, I put the typedef in custom_app.h at /* USER CODE BEGIN ET */ ~line 49

typedef struct {
 uint16_t Timestamp;
 uint16_t Value;
} Numstrchar_Data_t;

Add Numstrchar_Data_t to Custom_App_Context_t in STM32_WPAN/App/custom_app.c at /* USER CODE BEGIN CUSTOM_APP_Context_t */ ~ line 41

Numstrchar_Data_t	Numstr_Data;

Initialize Numstr_data. In STM32_WPAN/App/custom_app.c, Custom_APP_Init() at /* USER CODE BEGIN CUSTOM_APP_Init */ ~ line 166

Custom_App_Context.Numstr_Data.Timestamp = 0;
Custom_App_Context.Numstr_Data.Value = 0;

Implement number updates for number stream

  • Define some parameters for updating the number to be sent. In STM32_WPAN/App/custom_app.c /* USER CODE BEGIN PD */ ~line 53
#define NUM_CHANGE_STEP 10
 #define NUM_CHANGE_PERIOD  \
  (0.1*1000*1000/CFG_TS_TICK_VAL)
 #define NUM_VALUE_MAX 500
 #define NUM_VALUE_MIN 10

  • Add Change_Step to Custom_App_Context_t, in STM32_WPAN/App/custom_app.c /* USER CODE BEGIN CUSTOM_APP_Context_t */ ~line 42
uint16_t Change_Step;

  • Initialize Change_Step. In STM32_WPAN/App/custom_app.c /* USER CODE BEGIN CUSTOM_APP_Init */ ~line 172
Custom_App_Context.Change_Step = NUM_CHANGE_STEP;

  • Implement a function to modify the timestamp and value and call Custom_Numstrchr_Update_Char() in /* USER CODE BEGIN FD */ ~line 183
static void Numstr_Modify_Number(void){
 HAL_GPIO_TogglePin(LD1_GPIO_Port, LD1_Pin); //optional blue led
 Custom_App_Context.Numstr_Data.Value += Custom_App_Context.Change_Step;
 Custom_App_Context.Numstr_Data.Timestamp += NUM_CHANGE_STEP;
 if (Custom_App_Context.Numstr_Data.Value > NUM_VALUE_MAX){
 Custom_App_Context.Change_Step = -NUM_CHANGE_STEP;
 }
 else if (Custom_App_Context.Numstr_Data.Value < NUM_VALUE_MIN){
 Custom_App_Context.Change_Step = NUM_CHANGE_STEP;
 }
 // Update the send buffer by bytes. LightBlue app seems to expect bigendian
 
 UpdateCharData[0] = Custom_App_Context.Numstr_Data.Timestamp >> 8;
 UpdateCharData[1] = Custom_App_Context.Numstr_Data.Timestamp & 0x00ff;
 UpdateCharData[2] = Custom_App_Context.Numstr_Data.Value >> 8;
 UpdateCharData[3] = Custom_App_Context.Numstr_Data.Value & 0x00ff;
 Custom_Numstrchr_Update_Char();
 }

  • Provide the prototype in /* USER CODE BEGIN PFP */ ~line 90
static void Numstr_Modify_Number(void);

Create a task for triggering Numstr_Modify_Number. In Core/Inc/app_conf.h, /* USER CODE BEGIN CFG_Task_Id_With_HCI_Cmd_t */ ~ line 583 (I put this below CFG_TASK_SW1_BUTTON_PUSHED_ID, previously added for SW1 task)

CFG_TASK_NUMSTR_NOTIFY,

Register the task during app init. In STM32_WPAN/App/custom_app.c, in /* USER CODE BEGIN CUSTOM_APP_Init */ ~line 172

UTIL_SEQ_RegTask(1<<CFG_TASK_NUMSTR_NOTIFY, 0, Numstr_Modify_Number);

Set up a virtual timer to trigger send notification at 10Hz. All code goes in STM32_WPAN/App/custom_app.c

  • Create a variable for timer ID in Custom_App_Context_t. In  /* USER CODE BEGIN CUSTOM_APP_Context_t */ ~line 43
uint8_t Update_Timer_Id;

  • Create a callback prototype for the timer at /* USER CODE BEGIN PFP */ ~line 92

static void Numstr_Timer_Callback(void);

  • Create the callback function at /* USER CODE BEGIN FD */ ~line 197
static void Numstr_Timer_Callback(void){
 UTIL_SEQ_SetTask(1 << CFG_TASK_NUMSTR_NOTIFY, CFG_SCH_PRIO_0);
 }

  • Create the timer. In /* USER CODE BEGIN CUSTOM_APP_Init */ ~line 175

HW_TS_Create(CFG_TIM_PROC_ID_ISR, &Custom_App_Context.Update_Timer_Id, \
 hw_ts_Repeated, Numstr_Timer_Callback);

Manage timer start. In STM32_WPAN/App/custom_app.c /* USER CODE BEGIN CUSTOM_STM_NUMSTRCHR_NOTIFY_ENABLED_EVT */ ~line 110

HW_TS_Start(Custom_App_Context.Update_Timer_Id, NUM_CHANGE_PERIOD);

Manage timer stop, in /* USER CODE BEGIN CUSTOM_STM_NUMSTRCHR_NOTIFY_DISABLED_EVT */ ~line 116

HW_TS_Stop(Custom_App_Context.Update_Timer_Id);

Build, debug. In LightBlue, test previous write and notify characteristics. In the new notify characteristic: Listen for Notifications. Blue LED (LD1) on Nucleo board flashes at about 5Hz (10Hz toggle). LightBlue app shows stream of 4-byte numbers. High 2 bytes are the timestamp which just steadily increments, low 2 bytes are the value, which swings between 0x000A and 0x01f4.

Hope that helps fill the void.