STM32WB0 Bluetooth® LE - STM32CubeMX application conception


Click here for Bluetooth® LE overview.
Click here for an introduction to Bluetooth® LE with STM32.

1. Introduction

STM32CubeMX tool is a graphical tool that helps you generate an application targeting the STMicroelectronics MCU of your choice, with initialization code based on the configuration you specify. In the tools interface, you can select your STMicroelectronics MCU, configure pins, clocks, peripherals, and middleware, and generate the code project based on your setup.

This wiki page demonstrates how to generate a Bluetooth® LE server application for an STM32WB0 series MCU using STM32CubeMX.
It is illustrated with examples using the NUCLEO-WB09KE Nucleo board, which features the STM32WB09KE microcontroller.

2. Build a Bluetooth® LE application on STM32WB0 series with STM32CubeMX

The STM32WB0 series Nucleo board, programmed with the application generated by STM32CubeMX, can send data to a smartphone (using the ST BLE ToolBox application or ST BLE Sensor application), and receive commands from it through Bluetooth® LE.

System presentation
Connectivity STM32WB0 STM32CubeMX system presentation


A generic attribute (GATT) server application is used to expose some user services. Note that the generic access profile (GAP) and GATT services are automatically added to the list of user services. For our peer-to-peer (P2P) server application, we use two user services:

  • The first service exposes two characteristics.
  • The second service exposes three characteristics.

The first service (P2P_Server) looks like a P2P server (STM proprietary) with two characteristics:

  • Characteristic 1 has write property.
  • Characteristic 2 has notify property.

Writing to characteristic 1 toggles the blue LED.
Pressing button 1 on the Nucleo board sends data to the phone.

Custom service
Connectivity STM32WB0 STM32CubeMX custom service


The following steps are recommended to build this application example:


Service and characteristic configurations are given in the following table:

Service Long Name P2P_Server
Service Short Name P2P_Server
UUID Type 128 bits
UUID 0x8FE5B3D52E7F4A982A487ACC40FE0000
Characteristic Long Name LED_C SWITCH_C
Characteristic Short Name LED_C SWITCH_C
UUID Type 128 bits 128 bits
UUID 0x19ED82AEED214C9D4145228E41FE0000 0x19ED82AEED214C9D4145228E41FE0000
Char Properties Read + Write w/o response Notify
Char Permissions None None
Char GATT Events GATT_NOTIFY_ATTRIBUTE_WRITE|

GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP| GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP

GATT_NOTIFY_ATTRIBUTE_WRITE|

GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP| GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP

3. Tools

3.1. Software tools

To make and use this application project, you need the following software tools:

  • STM32CubeMX[1] software
  • STM32CubeWB0 MCU Package[2]
  • IDE: STM32CubeIDE[3] or IAR Embedded Workbench®[4]
  • Your preferred serial terminal (such as Tera Term)
  • ST BLE ToolBox smartphone application
  • ST BLE Sensor smartphone application

The following demonstration is based on:

  • STM32CubeMX v6.13.0
  • STM32CubeWB0 MCU Package v1.1.0

3.2. Hardware tools

You also need an STM32WB0 series Nucleo board and a USB Type-C® to Type-A cable.

Hardware presentation
Connectivity WB0 CMX Hardware Presentation.jpg

4. STM32CubeMX initialization for STM32WB0 series Nucleo board

With the STM32CubeMX software tool, it is possible to start your project at different levels: directly from the chip, from boards with the hardware support already configured, or directly from an embedded application developed by STMicroelectronics.
In this example, we start with the STM32WB09KEVx chip to detail all the configuration steps to enable Bluetooth® LE and create an application. In this example, it is a P2P server application.

STM32WB0 series application design with STM32CubeMX for a P2P server application (firmware stack version must be updated to 5.4)
Connectivity STM32WB09 features.png

4.1. STM32CubeMX initial setup

Open STM32CubeMX and start a new project by clicking on the Access to MCU selector button:

New Project menu
Connectivity wiki photo 03.png


Select the correct MCU and start the new project:

Project interface
Connectivity WB0 CMX Project Interface.jpg

4.2. Pinout and peripheral configuration

To configure the system, enable all the required system resources and peripherals by clicking on the A->Z tab to display all peripherals.

Pinout and configuration interface
Connectivity 800px-Connectivity WB0 CMX pinout Config Interface.jpg

STM32WB0 Bluetooth® LE middleware STM32_BLE is reachable once the following system resources and peripherals are set to "enabled".
Mandatory:

  • RCC: HSE and LSE on crystal/ceramic resonator
  • RNG: activated
  • PKA: activated
  • RADIO_TIMER: activated
  • RADIO: activated
  • NVIC: verify NVIC settings
  • USART1: set mode to asynchronous


Peripheral list
Connectivity WB0 CMX IP List.jpg

4.2.1. RCC

Reset and Clock Control (RCC) manages the different kinds of reset and generates all clocks for the bus and peripherals.

RCC activation and setup
Connectivity WB0 CMX RCC Configuration.jpg

4.2.2. RNG

The Random Number Generator (RNG) provides the application with full entropy outputs as 32-bit samples. It is necessary to activate it, because the link layer regularly requests RNG.

RNG activation
Connectivity 1200px-Connectivity WB0 CMX RNG Configuration.jpg

4.2.3. PKA

The public key accelerator (PKA) is intended for the computation of cryptographic public key primitives, specifically those related to RSA, Diffie-Hellmann or ECC (elliptic curve cryptography) over GF(p) (Galois fields). To achieve high performance at a reasonable cost, these operations are executed in the Montgomery domain. All needed computations are performed within the accelerator, so no further hardware/software elaboration is needed to process the inputs or the outputs.

PKA activation
Connectivity WB0 CMX PKA Configuration.jpg

4.2.4. RADIO_TIMER

The RADIO_TIMER is used to wake up the device during low-power mode phases. It allows the application to program an event can be related to a wake-up of the device, a user timeout or a preconfigured radio transaction to be triggered.

RADIO_TIMER configuration
Connectivity WB0 CMX RADIO TIMER Configuration.jpg

4.2.5. RADIO

The RADIO peripheral embedded in the STM32WB09xE product, radio controller MR_BLE, manages the LE protocol with hardware add on to support some new Bluetooth® specification features, and controls the RF analog module.

RADIO configuration
Connectivity WB0 CMX RADIO Configuration.jpg

4.2.6. USART1

USART1 is enabled to allow the display of traces on a terminal.

USART1 configuration (1/2)
Connectivity WB0 CMX USART1 1 Configuration.jpg
USART1 configuration (2/2)
Connectivity WB0 CMX USART1 2 Configuration.jpg

4.2.7. NVIC

The Nested Vector Interrupt Controller (NVIC) and the processor core interface are closely coupled, enabling low-latency interrupt processing and efficient processing of late-arriving interrupts. The NVIC manages all interrupts including core exceptions.

NVIC configuration
Connectivity WB0 CMX NVIC Configuration.jpg

4.3. Clock configuration

In the Clock Configuration tab, run the automatic clock issue solver (if requested).

The following configuration is defined for the initial startup phase. At runtime, the clocks are managed dynamically by the System Clock Manager (SCM) module.

Connectivity WBA CMX Clock Configuration 12.jpg

Configure the Clock as the following figure:

Clock configuration
Connectivity Figure 16.png

5. STM32_BLE Bluetooth® LE GAP/GATT Custom application configuration

Now, we can start configuring and defining the STM32_BLE Middleware application.

  • The Bluetooth® LE Peripheral Custom Template GATT Server Application can be defined:
    • on STM32WB0.
    • with STM32CubeMX (starting from version 6.13.0).
  • GAP peripheral: advertising configuration
  • GATT server configuration:
    • 25 services maximum.
    • 25 characteristics maximum per service.
  • ACI commands:
    • AN6142: STM32WB0 Bluetooth® LE wireless interface[5]

5.1. Enabling Bluetooth® LE

To configure your application:

STM32_BLE mode and configuration (1/2)
Connectivity WB0 CMX STM32 BLE Configuration TAB1.jpg
STM32_BLE mode and configuration (2/2)
Connectivity WB0 CMX STM32 BLE Configuration.jpg

5.2. Application configuration

All application configuration topics are reachable by:

Application configuration
Connectivity WB0 CMX STM32 Application Configuration 1-12.jpg

5.2.1. Application parameters

Pairing is done to support a secure connection method.
All parameters defined in this chapter are located in the app_conf.h file.

Application configuration - Application parameters
Connectivity WB0 CMX STM32 BLE Configuration ApplicationParameters.jpg

5.2.2. BLE stack

Application configuration - BLE stack
Connectivity WB0 CMX STM32 BLE Configuration BLEStackParameters.jpg

5.2.3. Modular Options

Application configuration - Modular Options
Connectivity WB0 CMX STM32 BLE Configuration ModularOptions.jpg

5.2.4. Low Power

Application configuration - Low Power
Connectivity WB0 CMX STM32 BLE Configuration LowPower.jpg

5.2.5. Traces

Application configuration - Traces
Connectivity WB0 CMX STM32 BLE Configuration TraceParameters.jpg

5.2.6. Debug

Application configuration - Debug
Connectivity WB0 CMX STM32 BLE Configuration DebugParameters.jpg

5.3. Advertising configuration


STM32_BLE interface: Bluetooth® LE advertising configuration
Connectivity WB0 CMX STM32 ADV Configuration Tab.jpg


This array is defined in the app_ble.c file with the following code:

static const char a_GapDeviceName[] = {  'P', 'e', 'e', 'r', ' ', 't', 'o', ' ', 'P', 'e', 'e', 'r', ' ', 'S', 'e', 'r', 'v', 'e', 'r' }; /* Gap Device Name */

In the generated code, these parameters are defined in the app_conf.h file:

#define ADV_INTERVAL_MIN                  (0x0080)
#define ADV_INTERVAL_MAX                  (0x00A0)
#define ADV_LP_INTERVAL_MIN               (0x0640)
#define ADV_LP_INTERVAL_MAX               (0x0FA0)
#define ADV_TYPE                          HCI_ADV_EVENT_PROP_LEGACY|HCI_ADV_EVENT_PROP_CONNECTABLE|HCI_ADV_EVENT_PROP_SCANNABLE)
#define ADV_FILTER                        HCI_ADV_FILTER_NONE
STM32_BLE interface: BLE Advertising tab
Connectivity WB0 CMX STM32 BLE ADV TAB.jpg


Open Advertising elements, included in the advertising packet payload, and:

STM32_BLE interface: advertising elements
Connectivity WB0 CMX STM32 ADV Configuration 34.jpg


The generated code corresponding to the advertising packet elements is located in the app_ble.c file.

/**
 * Advertising Data
 */
uint8_t a_AdvData[] =
{
  2, AD_TYPE_FLAGS, FLAG_BIT_LE_GENERAL_DISCOVERABLE_MODE|FLAG_BIT_BR_EDR_NOT_SUPPORTED,
  8, AD_TYPE_COMPLETE_LOCAL_NAME, 'p', '2', 'p', 'S', '_', 'X', 'X',  /* Complete name */
  15, AD_TYPE_MANUFACTURER_SPECIFIC_DATA, 0x30, 0x00, 0x00 /*  */, 0x00 /*  */, 0x00 /*  */, 0x00 /*  */, 0x00 /*  */, 0x00 /*  */, 0x00 /*  */, 0x00 /*  */, 0x00 /*  */, 0x00 /*  */, 0x00 /*  */, 0x00 /*  */,
};

In this example, we configure 2 advertising elements (advertising packet structure length = 25 bytes):

  • Complete local name
  • Manufacturer-specific data

The advertising information is represented by the advertising data elements, which are standardized on the Bluetooth SIG.

The manufacturer-specific data are updated at runtime (specific function in the app_ble.c file).


5.4. Service and characteristic definition

All created services and characteristics are managed in an associated xxx.c file (one file per service; xxx = service short name, defined in the SERVICEx panel). Each service and characteristic is added with the XXX_Init(void) function (XXX = service short name (capitalized), defined in the SERVICEx panel), using :

  • aci_gatt_srv_add_service(…) for services and characteristics.

5.4.1. BLE Applications and Services tab

To start defining the Bluetooth® LE application:

BLE Applications and Services tab
Connectivity WB0 CMX STM32 Applications and ServicesTab.jpg

5.4.2. Service definition

Each service has to be configured in a dedicated tab (named SERVICE1 to SERVICE25).

  • Up to 25 characteristics per service
  • UUID type can be defined as 16 or 128 bits, full or reduced
  • UUID definition
  • Type value is primary or secondary service

Once the number of services has been defined (in this example, we defined only one service with two characteristics), update the service information:

Service definition
Connectivity WB0 CMX STM32 Service Definition.jpg

5.4.3. Characteristic definition

For each characteristic, the following parameters must be defined:

  • General
  • Properties
  • Permissions
  • GATT events

In our example, the service we defined has the following 2 characteristics:

Characteristic 1 Characteristic 2
UUID type 128 bits UUID (0x02) 128 bits UUID (0x02)
UUID 128 Input type Full Full
UUID 19 ED 82 AE ED 21 4C 9D 41 45 22 8E 41 FE 00 00 19 ED 82 AE ED 21 4C 9D 41 45 22 8E 41 FE 00 00
Characteristic type Buffered Buffered
Characteristic long name LED_C Switch_C
Characteristic Short Name LED_C SWITCH_C
Value length 2 2
Length characteristic Constant Connstant
Encryption key size 0x10 0x10
Char Properties READ WRITE_WITHOUT_RESP NOTIFY
GATT events GATT_NOTIFY_ATTRIBUTE_WRITE

GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP

GATT_NOTIFY_ATTRIBUTE_WRITE

GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP


These characteristics have to be defined as shown in the image below:

SERVICE1 characteristic definition
Connectivity WB0 CMX STM32 BLE Characteristic Definition.jpg


5.5. Platform settings

To change the BSP settings:

Platform settings
Connectivity WB0 CMX STM32 BLE PlatformSettings.jpg


6. Project configuration

The Project Manager tab, which is used for the configuration of the project, has three main sections:

  • Project
  • Code Generator
  • Advanced Settings

To configure your project, proceed as follows:

Depending on your work organization, you can choose not to use the default firmware location. In this case:

Project configuration - Project
Connectivity WB0 CMX STM32 ProjectConfigurationTab.jpg


The Code Generator section lets you manage files, packages, and embedded software packs.

Project configuration - Code Generator
Connectivity WB0 CMX STM32 CodeGeneratorConfiguration.jpg


In the Advanced Settings section, rearrange the order of the functions:

Project configuration - Advanced Settings
Connectivity WB0 CMX STM32 AdvancedSettinsConfiguration.jpg


7. P2P server notification and write management

To use the P2P server notification and write characteristics, several elements must be configured:

  • The user button and the related interrupt, to notify the P2P client.
  • The blue LED, to receive a P2P client write operation.
  • The custom application code, to manage B1 and write operations on the P2P server service.
STM32WB0 series Nucleo board
Connectivity WBA CMX STM32WBA Nucleo Board.jpg


7.1. User button and LED pinout

Nucleo board pinout configuration:

  • Blue LED: PB0
  • Red LED: PB2
  • Green LED: PB4

Switch (user button) pinout configuration:

  • B1: PA0
  • B2: PB5
  • B3: PB14

When used:

  • The switch pins are declared as GPIO_INPUT.
  • The LED pins are declared as GPIO_OUTPUT.

7.2. LED and button setup

To configure the buttons and LEDs in this example, the BSP files are used in the project at the end of this wikipage.

8. Code generation

When you configure your application, you can use STM32CubeMX to generate your code:

Code generation
Connectivity WB0 CMX STM32 CodeGeneration.jpg


9. Code modification: user sections

Once your button and LED GPIOs have been configured, generate your project by following the steps described in the Code Generation chapter.

The generated source code contains several sections called user sections, where users can add custom application code parts.
These sections are not erased or modified during the project regeneration by STM32CubeMX.

To manage notify and write operations on the P2P server service, some code parts must be added to the user sections of the following files and their respective header files:

Files to update
Connectivity WB0 CMX STM32 UpdateFiles.jpg


  • app_conf.h:

In app_conf.h, define the tasks for buttons, notifications, and advertising by adding code to the CFG_Task_Id_t user code section:

  /* USER CODE BEGIN CFG_Task_Id_t */
  TASK_BUTTON_1,
  TASK_BUTTON_2,
  TASK_BUTTON_3,
  CFG_TASK_ADV_CANCEL_ID,
  CFG_TASK_SEND_NOTIF_ID,
  /* USER CODE END CFG_Task_Id_t */

You must also update the user code defines to support the LEDs, buttons, and debug:

/* USER CODE BEGIN Defines */
/**
 * User interaction
 * When CFG_LED_SUPPORTED is set, LEDS are activated if requested
 * When CFG_BUTTON_SUPPORTED is set, the push-buttons are activated if requested
 */

#define CFG_LED_SUPPORTED                       (1)
#define CFG_BUTTON_SUPPORTED                    (1)

/**
 * If CFG_FULL_LOW_POWER is requested, make sure LED and debugger are disabled
 */
#if (CFG_FULL_LOW_POWER == 1)
  #undef  CFG_LED_SUPPORTED
  #define CFG_LED_SUPPORTED      (0)
#endif /* CFG_FULL_LOW_POWER */

/* USER CODE END Defines */
  • stm32wb0x_it.c:
/* USER CODE BEGIN 1 */
void GPIOA_IRQHandler(void)
{
  BSP_PB_IRQHandler(B1_GPIO_PORT, B1_PIN);
}

void GPIOB_IRQHandler(void)
{
  BSP_PB_IRQHandler(B2_GPIO_PORT, B2_PIN);
  BSP_PB_IRQHandler(B3_GPIO_PORT, B3_PIN);
}
/* USER CODE END 1 */


  • app_entry.h:
/* USER CODE BEGIN EF */
#if (CFG_BUTTON_SUPPORTED == 1)
uint8_t APPE_ButtonIsLongPressed(uint16_t btnIdx);
void APPE_Button1Action(void);
void APPE_Button2Action(void);
void APPE_Button3Action(void);
#endif

/* USER CODE END EF */


  • app_entry.c:
/* USER CODE BEGIN PTD */
#if (CFG_BUTTON_SUPPORTED == 1)
typedef struct
{
  Button_TypeDef      button;
  VTIMER_HandleType   longTimerId;
  uint8_t             longPressed;
} ButtonDesc_t;
#endif /* (CFG_BUTTON_SUPPORTED == 1) */
/* USER CODE END PTD */
/* USER CODE BEGIN PD */
#if (CFG_BUTTON_SUPPORTED == 1)
#define BUTTON_LONG_PRESS_THRESHOLD_MS   (500u)
#define BUTTON_NB_MAX                    (B3 + 1u)
#endif

/* USER CODE END PD */
/* USER CODE BEGIN PV */
#if (CFG_BUTTON_SUPPORTED == 1)
/* Button management */
static ButtonDesc_t buttonDesc[BUTTON_NB_MAX];
#endif
/* USER CODE BEGIN PFP */
#if (CFG_LED_SUPPORTED == 1)
static void Led_Init(void);
#endif
#if (CFG_BUTTON_SUPPORTED == 1)
static void Button_Init(void);
static void Button_TriggerActions(void *arg);
#endif
/* USER CODE END PFP */
/* USER CODE BEGIN FD */
#if (CFG_BUTTON_SUPPORTED == 1)
/**
 * @brief   Indicate if the selected button was pressedn during a 'long time' or not.
 *
 * @param   btnIdx    Button to test, listed in enum Button_TypeDef
 * @return  '1' if pressed during a 'long time', else '0'.
 */
uint8_t APPE_ButtonIsLongPressed(uint16_t btnIdx)
{
  uint8_t pressStatus;

  if ( btnIdx < BUTTON_NB_MAX )
  {
    pressStatus = buttonDesc[btnIdx].longPressed;
  }
  else
  {
    pressStatus = 0;
  }

  return pressStatus;
}

/**
 * @brief  Action of button 1 when pressed, to be implemented by user.
 * @param  None
 * @retval None
 */
__WEAK void APPE_Button1Action(void)
{
}

/**
 * @brief  Action of button 2 when pressed, to be implemented by user.
 * @param  None
 * @retval None
 */
__WEAK void APPE_Button2Action(void)
{
}

/**
 * @brief  Action of button 3 when pressed, to be implemented by user.
 * @param  None
 * @retval None
 */
__WEAK void APPE_Button3Action(void)
{
}
#endif

/* USER CODE END FD */
/* USER CODE BEGIN PFP */
#if (CFG_LED_SUPPORTED == 1)
static void Led_Init(void);
#endif
#if (CFG_BUTTON_SUPPORTED == 1)
static void Button_Init(void);
static void Button_TriggerActions(void *arg);
#endif

/* USER CODE END PFP */
/* USER CODE BEGIN FD_LOCAL_FUNCTIONS */
#if (CFG_LED_SUPPORTED == 1)
static void Led_Init( void )
{
  /* Leds Initialization */
  BSP_LED_Init(LED_BLUE);
  BSP_LED_Init(LED_GREEN);
  BSP_LED_Init(LED_RED);

  BSP_LED_On(LED_GREEN);

  return;
}
#endif

#if (CFG_BUTTON_SUPPORTED == 1)
static void Button_Init( void )
{
  /* Button Initialization */
  buttonDesc[B1].button = B1;
  buttonDesc[B2].button = B2;
  buttonDesc[B3].button = B3;
  BSP_PB_Init(B1, BUTTON_MODE_EXTI);
  BSP_PB_Init(B2, BUTTON_MODE_EXTI);
  BSP_PB_Init(B3, BUTTON_MODE_EXTI);
  
#if (CFG_LPM_SUPPORTED == 1)
  HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PA0, PWR_WUP_FALLEDG);
  HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PB5, PWR_WUP_FALLEDG);
#if defined(STM32WB06) || defined(STM32WB07)
  HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PB9, PWR_WUP_FALLEDG);
#else
  HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PB14, PWR_WUP_FALLEDG);
#endif  
#endif  
  
  /* Register tasks associated to buttons */
  UTIL_SEQ_RegTask(1U << TASK_BUTTON_1, UTIL_SEQ_RFU, APPE_Button1Action);
  UTIL_SEQ_RegTask(1U << TASK_BUTTON_2, UTIL_SEQ_RFU, APPE_Button2Action);
  UTIL_SEQ_RegTask(1U << TASK_BUTTON_3, UTIL_SEQ_RFU, APPE_Button3Action);

  /* Create timers to detect button long press (one for each button) */
  Button_TypeDef buttonIndex;
  for ( buttonIndex = B1; buttonIndex < BUTTON_NB_MAX; buttonIndex++ )
  {
    buttonDesc[buttonIndex].longTimerId.callback = Button_TriggerActions;
    buttonDesc[buttonIndex].longTimerId.userData = &buttonDesc[buttonIndex];
  }
  
  return;
}

static void Button_TriggerActions(void *arg)
{
  ButtonDesc_t *p_buttonDesc = ((VTIMER_HandleType *)arg)->userData;

  p_buttonDesc->longPressed = BSP_PB_GetState(p_buttonDesc->button);

  APP_DBG_MSG("Button %d pressed\n", (p_buttonDesc->button + 1));
  switch (p_buttonDesc->button)
  {
    case B1:
      UTIL_SEQ_SetTask(1U << TASK_BUTTON_1, CFG_SEQ_PRIO_0);
      break;
    case B2:
      UTIL_SEQ_SetTask(1U << TASK_BUTTON_2, CFG_SEQ_PRIO_0);
      break;
    case B3:
      UTIL_SEQ_SetTask(1U << TASK_BUTTON_3, CFG_SEQ_PRIO_0);
      break;
    default:
      break;
  }

  return;
}
#endif

/* USER CODE END FD_LOCAL_FUNCTIONS */
/* USER CODE BEGIN FD_WRAP_FUNCTIONS */
#if (CFG_BUTTON_SUPPORTED == 1)
void BSP_PB_Callback(Button_TypeDef Button)
{
  buttonDesc[Button].longPressed = 0;
  HAL_RADIO_TIMER_StartVirtualTimer(&buttonDesc[Button].longTimerId, BUTTON_LONG_PRESS_THRESHOLD_MS);

  return;
}

#if (CFG_LPM_SUPPORTED == 1)
void HAL_PWR_WKUPx_Callback(uint32_t wakeupIOs)
{
  if (wakeupIOs & PWR_WAKEUP_PA0)
  {
    BSP_PB_Callback(B1);
  }
  if (wakeupIOs & PWR_WAKEUP_PB5)
  {
    BSP_PB_Callback(B2);
  }

#if defined(STM32WB06) || defined(STM32WB07)
  if (wakeupIOs & PWR_WAKEUP_PB9)
  {
    BSP_PB_Callback(B3);
  }
#else
  if (wakeupIOs & PWR_WAKEUP_PB14)
  {
    BSP_PB_Callback(B3);
  }
#endif

}
#endif

void HAL_GPIO_EXTI_Callback(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
  if (GPIO_Pin == B1_PIN)
  {
    BSP_PB_Callback(B1);
  }
  else if (GPIO_Pin == B2_PIN)
  {
    BSP_PB_Callback(B2);
  }
  else if (GPIO_Pin == B3_PIN)
  {
    BSP_PB_Callback(B3);
  }

  return;
}

#endif /* (CFG_BUTTON_SUPPORTED == 1) */

/* USER CODE END FD_WRAP_FUNCTIONS */


  • app_ble.h:
/* USER CODE BEGIN EC */
/** 
  * ST Manufacturer ID (2 bytes: least significant and most significant bytes).
  */
#define ST_MANUF_ID_LSB         0x30
#define ST_MANUF_ID_MSB         0x00

/** 
  * BlueSTSDK Version
  */
#define  BLUESTSDK_V1           0x01
#define  BLUESTSDK_V2           0x02

/** 
  * BOARD ID 
  */
#define  BOARD_ID_NUCLEO_WB0    0x8D

/** 
  * FIRMWARE ID 
  */
#define  FW_ID_P2P_SERVER       0x83
#define  FW_ID_P2P_ROUTER       0x85
#define  FW_ID_COC_PERIPH       0x87
#define  FW_ID_DT_SERVER        0x88
#define  FW_ID_HEART_RATE       0x89
#define  FW_ID_HEALTH_THERMO    0x8A
/* USER CODE END EC */
  • app_ble.c:
/* USER CODE BEGIN PD */
#define LED_ON_TIMEOUT_MS              (5)
#define ADV_TIMEOUT_MS                 (60 * 1000)
/* USER CODE END PD */
/* USER CODE BEGIN PFP */
static void Adv_Cancel_Req(void *arg);
static void Adv_Cancel(void);
static void Switch_OFF_GPIO(void *arg);
static void fill_advData(uint8_t *p_adv_data, uint8_t tab_size, const uint8_t*p_bd_addr);
/* USER CODE END PFP */
/* USER CODE BEGIN APP_BLE_Init_4 */
  UTIL_SEQ_RegTask(1<<CFG_TASK_ADV_CANCEL_ID, UTIL_SEQ_RFU, Adv_Cancel);
  
  /* Create timer to handle the Advertising Stop */
  bleAppContext.Advertising_mgr_timer_Id.callback = Adv_Cancel_Req;    
  
  /* Create timer to handle the Led Switch OFF */
  bleAppContext.SwitchOffGPIO_timer_Id.callback = Switch_OFF_GPIO;
  
  /* USER CODE END APP_BLE_Init_4 */
  /* USER CODE BEGIN APP_BLE_Init_3 */
  
  ret = aci_hal_set_radio_activity_mask(0x0006);
  if (ret != BLE_STATUS_SUCCESS)
  {
    APP_DBG_MSG("  Fail   : aci_hal_set_radio_activity_mask command, result: 0x%2X\n", ret);
  }
  else
  {
    APP_DBG_MSG("  Success: aci_hal_set_radio_activity_mask command\n\r");
  }
  
  /* Start to Advertise to accept a connection */
  APP_BLE_Procedure_Gap_Peripheral(PROC_GAP_PERIPH_ADVERTISE_START_FAST);
  
  /* Start a timer to stop advertising after a while */
  HAL_RADIO_TIMER_StartVirtualTimer(&bleAppContext.Advertising_mgr_timer_Id, ADV_TIMEOUT_MS);
  
  /* USER CODE END APP_BLE_Init_3 */
/* USER CODE BEGIN APP_BLE_Init_2 */
bleAppContext.connIntervalFlag = 0;
/* USER CODE END APP_BLE_Init_2 */
/* USER CODE BEGIN EVT_DISCONN_COMPLETE */
APP_BLE_Procedure_Gap_Peripheral(PROC_GAP_PERIPH_ADVERTISE_START_FAST);
HAL_RADIO_TIMER_StartVirtualTimer(&bleAppContext.Advertising_mgr_timer_Id, ADV_TIMEOUT_MS);
/* USER CODE END EVT_DISCONN_COMPLETE */
/* USER CODE BEGIN HCI_EVT_LE_CONN_COMPLETE */
/* The connection is done, there is no need anymore to schedule the LP ADV */
HAL_RADIO_TIMER_StopVirtualTimer(&(bleAppContext.Advertising_mgr_timer_Id));
/* USER CODE END HCI_EVT_LE_CONN_COMPLETE */
/* USER CODE BEGIN HCI_EVT_LE_CONN_COMPLETE */
/* The connection is done, there is no need anymore to schedule the LP ADV */
HAL_RADIO_TIMER_StopVirtualTimer(&(bleAppContext.Advertising_mgr_timer_Id));
/* USER CODE END HCI_EVT_LE_CONN_COMPLETE */
/* USER CODE BEGIN RADIO_ACTIVITY_EVENT*/
BSP_LED_On(LED_GREEN);
HAL_RADIO_TIMER_StopVirtualTimer(&bleAppContext.SwitchOffGPIO_timer_Id);
HAL_RADIO_TIMER_StartVirtualTimer(&bleAppContext.SwitchOffGPIO_timer_Id, LED_ON_TIMEOUT_MS);
/* USER CODE END RADIO_ACTIVITY_EVENT*/
/* USER CODE BEGIN CONN_PARAM_UPDATE */
if (bleAppContext.connIntervalFlag != 0)
{
    bleAppContext.connIntervalFlag = 0;
    paramA = CONN_INT_MS(50);
    paramB = CONN_INT_MS(50);
}
else
{
    bleAppContext.connIntervalFlag = 1;
    paramA = CONN_INT_MS(1000);
    paramB = CONN_INT_MS(1000);
}
/* USER CODE END CONN_PARAM_UPDATE */
/* USER CODE BEGIN Ble_Hci_Gap_Gatt_Init_1*/
fill_advData(&a_AdvData[0], sizeof(a_AdvData), bd_address);
/* USER CODE END Ble_Hci_Gap_Gatt_Init_1*/
/* USER CODE BEGIN FD_LOCAL_FUNCTION */

static void Adv_Cancel_Req(void *arg)
{
  APP_DBG_MSG("Adv_Cancel_Req\n");
  UTIL_SEQ_SetTask(1 << CFG_TASK_ADV_CANCEL_ID, CFG_SEQ_PRIO_0);
  return;
}

static void Switch_OFF_GPIO(void *arg)
{
  BSP_LED_Off(LED_GREEN);
  return;
}

static void Adv_Cancel(void)
{
  BSP_LED_Off(LED_GREEN);

  APP_BLE_Procedure_Gap_Peripheral(PROC_GAP_PERIPH_ADVERTISE_STOP);
  bleAppContext.Device_Connection_Status = APP_BLE_IDLE;

  return;
}

static void fill_advData(uint8_t *p_adv_data, uint8_t tab_size, const uint8_t* p_bd_addr)
{
  uint16_t i =0;
  uint8_t bd_addr_1, bd_addr_0;
  uint8_t ad_length, ad_type;  
  
  while(i < tab_size)
  {
    ad_length = p_adv_data[i];
    ad_type = p_adv_data[i + 1];
      
    switch (ad_type)
    {
    case AD_TYPE_FLAGS:
      break;
    case AD_TYPE_TX_POWER_LEVEL:
      break;
    case AD_TYPE_COMPLETE_LOCAL_NAME:
      {
        if((p_adv_data[i + ad_length] == 'X') && (p_adv_data[i + ad_length - 1] == 'X'))
        {
          bd_addr_1 = ((p_bd_addr[0] & 0xF0)>>4);
          bd_addr_0 = (p_bd_addr[0] & 0xF);
          
          /* Convert hex value into ascii */
          if(bd_addr_1 > 0x09)
          {
            p_adv_data[i + ad_length - 1] = bd_addr_1 + '7';
          }
          else
          {
            p_adv_data[i + ad_length - 1] = bd_addr_1 + '0';
          }
          
          if(bd_addr_0 > 0x09)
          {
            p_adv_data[i + ad_length] = bd_addr_0 + '7';
          }
          else
          {
            p_adv_data[i + ad_length] = bd_addr_0 + '0';
          }
        }
        break;
      }
    case AD_TYPE_MANUFACTURER_SPECIFIC_DATA:
      {
        p_adv_data[i+2] = ST_MANUF_ID_LSB;
        p_adv_data[i+3] = ST_MANUF_ID_MSB;
        p_adv_data[i+4] = BLUESTSDK_V2; /* blueST SDK version */
        p_adv_data[i+5] = BOARD_ID_NUCLEO_WB0; /* Board ID */
        p_adv_data[i+6] = FW_ID_P2P_SERVER; /* FW ID */
        p_adv_data[i+7] = 0x00; /* FW data 1 */
        p_adv_data[i+8] = 0x00; /* FW data 2 */
        p_adv_data[i+9] = 0x00; /* FW data 3 */
        p_adv_data[i+10] = p_bd_addr[5]; /* MSB BD address */
        p_adv_data[i+11] = p_bd_addr[4];
        p_adv_data[i+12] = p_bd_addr[3];
        p_adv_data[i+13] = p_bd_addr[2];
        p_adv_data[i+14] = p_bd_addr[1];
        p_adv_data[i+15] = p_bd_addr[0]; /* LSB BD address */
        break;
      }
    default:
      break;
    }
    i += ad_length + 1; /* increment the iterator to go on next element*/
  }
}
/* USER CODE END FD_LOCAL_FUNCTION */
/* USER CODE BEGIN FD_WRAP_FUNCTIONS */
#if (CFG_BUTTON_SUPPORTED == 1)
void APPE_Button1Action(void)
{    
  if (bleAppContext.Device_Connection_Status == APP_BLE_IDLE)
  {
    /* Relaunch advertising */
    APP_BLE_Procedure_Gap_Peripheral(PROC_GAP_PERIPH_ADVERTISE_START_FAST);
    HAL_RADIO_TIMER_StartVirtualTimer(&bleAppContext.Advertising_mgr_timer_Id, ADV_TIMEOUT_MS);
  }
  else if (bleAppContext.Device_Connection_Status == APP_BLE_CONNECTED_SERVER)
  {
    UTIL_SEQ_SetTask( 1<<CFG_TASK_SEND_NOTIF_ID, CFG_SEQ_PRIO_0);
  }
  
  return;
}

void APPE_Button2Action(void)
{
  tBleStatus ret = BLE_STATUS_INVALID_PARAMS;
  
  if (bleAppContext.Device_Connection_Status != APP_BLE_CONNECTED_SERVER)
  {
    ret = aci_gap_clear_security_db();
    if (ret != BLE_STATUS_SUCCESS)
    {
      APP_DBG_MSG("==>> aci_gap_clear_security_db - Fail, result: 0x%02X\n", ret);
    }
    else
    {
      APP_DBG_MSG("==>> aci_gap_clear_security_db - Success\n");
    }
  }
  else
  {
    /* Launch advertising for multi connection */
    APP_BLE_Procedure_Gap_Peripheral(PROC_GAP_PERIPH_ADVERTISE_START_FAST);
    HAL_RADIO_TIMER_StartVirtualTimer(&bleAppContext.Advertising_mgr_timer_Id, ADV_TIMEOUT_MS);
  }

  return;
}

void APPE_Button3Action(void)
{
  if (bleAppContext.Device_Connection_Status != APP_BLE_CONNECTED_SERVER)
  {

  }
  else
  {
    APP_BLE_Procedure_Gap_Peripheral(PROC_GAP_PERIPH_CONN_PARAM_UPDATE);
  }

  return;
}
#endif /* (CFG_BUTTON_SUPPORTED == 1) */
/* USER CODE END FD_WRAP_FUNCTIONS */


  • p2p_server.c:
/* USER CODE BEGIN Service1_Char_1_ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE */
APP_DBG_MSG("-- GATT : LED CONFIGURATION RECEIVED\n");
notification.DataTransfered.Length = p_attribute_modified->Attr_Data_Length;
notification.DataTransfered.p_Payload = p_attribute_modified->Attr_Data;
/* USER CODE END Service1_Char_1_ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE */
/* USER CODE BEGIN BLECORE_EVT */
/* Manage ACI_GATT_INDICATION_VSEVT_CODE occurring on Android 12 */   
case ACI_GATT_CLT_INDICATION_VSEVT_CODE:
{
  tBleStatus status = BLE_STATUS_FAILED;
  aci_gatt_clt_indication_event_rp0 *pr = (void*)p_evt->data;
  status = aci_gatt_clt_confirm_indication(pr->Connection_Handle, BLE_GATT_UNENHANCED_ATT_L2CAP_CID);
  if (status != BLE_STATUS_SUCCESS)
  {
    APP_DBG_MSG("  Fail   : aci_gatt_confirm_indication command, result: 0x%x \n", status);
  }
  else
  {
    APP_DBG_MSG("  Success: aci_gatt_confirm_indication command\n");
  }   
}
break; /* end ACI_GATT_NOTIFICATION_VSEVT_CODE */
/* USER CODE END BLECORE_EVT */
default:
/* USER CODE BEGIN EVT_DEFAULT */


  • p2p_server_app.c:
/* USER CODE BEGIN PTD */
typedef struct{
  uint8_t             Device_Led_Selection;
  uint8_t             Led1;
}P2P_LedCharValue_t;
 
typedef struct{
  uint8_t             Device_Button_Selection;
  uint8_t             ButtonStatus;
}P2P_ButtonCharValue_t;
/* USER CODE END PTD */
/* USER CODE BEGIN Service1_APP_Context_t */
P2P_LedCharValue_t              LedControl;
P2P_ButtonCharValue_t          ButtonControl;
/* USER CODE END Service1_APP_Context_t */
/* USER CODE BEGIN PFP */
static void P2P_SERVER_APP_LED_BUTTON_context_Init(void);
/* USER CODE END PFP */
/* USER CODE BEGIN Service1Char1_WRITE_NO_RESP_EVT */
if(p_Notification->DataTransfered.p_Payload[1] == 0x01)
{
BSP_LED_On(LED_BLUE);
APP_DBG_MSG("-- P2P APPLICATION SERVER : LED1 ON\n"); 
P2P_SERVER_APP_Context.LedControl.Led1 = 0x01; /* LED1 ON */
}
if(p_Notification->DataTransfered.p_Payload[1] == 0x00)
{
BSP_LED_Off(LED_BLUE);
APP_DBG_MSG("-- P2P APPLICATION SERVER : LED1 OFF\n"); 
P2P_SERVER_APP_Context.LedControl.Led1 = 0x00; /* LED1 OFF */
}
      /* USER CODE END Service1Char1_WRITE_NO_RESP_EVT */
/* USER CODE BEGIN Service1Char2_NOTIFY_ENABLED_EVT */
P2P_SERVER_APP_Context.Switch_c_Notification_Status = Switch_c_NOTIFICATION_ON;
APP_DBG_MSG("-- P2P APPLICATION SERVER : NOTIFICATION ENABLED\n"); 
APP_DBG_MSG(" \n\r");
/* USER CODE END Service1Char2_NOTIFY_ENABLED_EVT */
/* USER CODE BEGIN Service1Char2_NOTIFY_DISABLED_EVT */
P2P_SERVER_APP_Context.Switch_c_Notification_Status = Switch_c_NOTIFICATION_OFF;
APP_DBG_MSG("-- P2P APPLICATION SERVER : NOTIFICATION DISABLED\n"); 
APP_DBG_MSG(" \n\r");
/* USER CODE END Service1Char2_NOTIFY_DISABLED_EVT */
/* USER CODE BEGIN Service1_APP_DISCON_HANDLE_EVT */
P2P_SERVER_APP_LED_BUTTON_context_Init();
/* USER CODE END Service1_APP_DISCON_HANDLE_EVT */
/* USER CODE BEGIN Service1_APP_Init */
UTIL_SEQ_RegTask( 1U << CFG_TASK_SEND_NOTIF_ID, UTIL_SEQ_RFU, P2P_SERVER_Switch_c_SendNotification);

/**
* Initialize LedButton Service
*/
P2P_SERVER_APP_Context.Switch_c_Notification_Status= Switch_c_NOTIFICATION_OFF;
P2P_SERVER_APP_LED_BUTTON_context_Init();
/* USER CODE END Service1_APP_Init */
/* USER CODE BEGIN FD */
void P2P_SERVER_APP_LED_BUTTON_context_Init(void)
{  
  BSP_LED_Off(LED_BLUE);
  P2P_SERVER_APP_Context.LedControl.Device_Led_Selection=0x01;   /* Device1 */
  P2P_SERVER_APP_Context.LedControl.Led1=0x00; /* led OFF */
  P2P_SERVER_APP_Context.ButtonControl.Device_Button_Selection=0x01;/* Device1 */
  P2P_SERVER_APP_Context.ButtonControl.ButtonStatus=0x00;
 
  return;
}
/* USER CODE END FD */
/* USER CODE BEGIN Service1Char2_NS_1*/

if(P2P_SERVER_APP_Context.ButtonControl.ButtonStatus == 0x00)
{
P2P_SERVER_APP_Context.ButtonControl.ButtonStatus = 0x01;
} 
else
{
P2P_SERVER_APP_Context.ButtonControl.ButtonStatus = 0x00;
}
a_P2P_SERVER_UpdateCharData[0] = 0x01; /* Device Led selection */
a_P2P_SERVER_UpdateCharData[1] = P2P_SERVER_APP_Context.ButtonControl.ButtonStatus;
/* Update notification data length */
p2p_server_notification_data.Length = (p2p_server_notification_data.Length) + 2; 

if(P2P_SERVER_APP_Context.Switch_c_Notification_Status == Switch_c_NOTIFICATION_ON)
{ 
APP_DBG_MSG("-- P2P APPLICATION SERVER : INFORM CLIENT BUTTON 1 PUSHED\n");
notification_on_off = Switch_c_NOTIFICATION_ON;
} 
else
{
APP_DBG_MSG("-- P2P APPLICATION SERVER : CAN'T INFORM CLIENT - NOTIFICATION DISABLED\n"); 
}
/* USER CODE END Service1Char2_NS_1*/

10. Inserting external files

To complete the creation of our P2P server project, we add the following external BSP files:

  • stm32wb0x_nucleo.c
  • stm32wb0x_nucleo.h
  • stm32wb0x_nucleo_conf.h

These files provide a set of firmware functions to manage the LEDs and push-buttons available on the STM32WB0x Nucleo board.

Add the stm32wb0x_nucleo.c and stm32wb0x_nucleo.h files to your project folder (STM32Cube_FW_WB0_Vx.y.z\Drivers\BSP\STM32WB0x_Nucleo) and respectively in the folders BLE_p2pServer\Core\Src and BLE_p2pServer\Core\Core\Inc.

Then rename the stm32wb0x_nucleo_conf_template.h file to stm32wb0x_nucleo_conf.h.

Now, add stm32wb0x_nucleo_conf.h to your project folder (BLE_p2pServer\Core\Inc).

To add external code files, we recommend using the .extSettings file. For more details, refer to the section 6.4 of the STM32CubeMX user manual[6].

The .extsettings file allows you to add additional settings, which can be used when external tools call STM32CubeMX to generate the project and require specific project settings.

.extSettings file
Connectivity WB0 CMX STM32 extSettings file.jpg


Your .extSettings file contains the following:

[ProjectFiles]
HeaderPath=../../../../../../Drivers/BSP/STM32WB0x-nucleo;
[Others]
Define=NUCLEO_WB09KE

[Groups]
Doc=../README.md;
Drivers/BSP/NUCLEO-WB09KE=../../../../../../Drivers/BSP/STM32WB0x-nucleo/stm32wb0x_nucleo.c;

The path in HeaderPath indicates where the stm32wb0x_nucleo.h file is stored in your project. In Groups, you need to specify where you want the code file to appear in your workspace, and where it is stored.

After this, you can click Generate again in STM32CubeMX.
Your workspace now looks like this:

Connectivity WB0 CMX STM32 Workspace file.jpg


You have just finished creating a p2pServer application with STM32CubeMX starting from the STM32WB09KEV6TR chip.

You can find more information about peer-to-peer applications on related ReadMe file available under the BLE_p2pServer folder.

11. References