cancel
Showing results for 
Search instead for 
Did you mean: 

How to use STM32WB's BLE event callback framework?

DK.6
Associate II

I'm trying to use the STM32WB firmware event callbacks defined in ble_events.h and ble_events.c but they don't appear to be working.

According to the documentation in PM0271 revision 2 section 4.4 I should be able to use the event callbacks framework as an alternative to the switch/case event handlers.

However when I implement the event, it never gets hit (tried both with and without the __WEAK attribute):

__WEAK void hci_le_connection_complete_event(
	uint8_t Status,
	uint16_t Connection_Handle,
	uint8_t Role,
	uint8_t Peer_Address_Type,
	const uint8_t* Peer_Address,
	uint16_t Conn_Interval,
	uint16_t Conn_Latency,
	uint16_t Supervision_Timeout,
	uint8_t Master_Clock_Accuracy
)
{
	Debug_print("hci_le_connection_complete_event called!");
}

But in my switch/case events I do get the EVT_LE_CONN_COMPLETE event triggering.

The version of the STM32WB firmware I'm using is 1.10.0

and the firmware loaded onto my P-NUCLEO-WB55 development board is V2J37M26

loaded with STM32CubeProgrammer v2.5.0.

I did a regex search for _event\s*\( in all STM32WB examples under STM32CubeWB/Projects/P-NUCLEO-WB55.Nucleo/ but there are no examples of any project using the BLE event callback framework.

Is there an additional setup step I'm missing to use the event callback framework?

Do I need to modify the assembly startup_stm32wb55rgvx.s file?

Is there a specific BLE firmware I need to load onto the coprocessor?

1 ACCEPTED SOLUTION

Accepted Solutions
CBuch.1
Associate III

I asked me the same question and there ist not really an satisfying answer.

The same question appears in https://github.com/STMicroelectronics/STM32CubeWB/issues/24, and now there is an Example for the Nucleo-WB55 -> BLE_Peripheral_Lite_EventCallbacks .

But in this Example the callbacks, e.g. hci_le_connection_complete_event(), are called within the Switch-Case Handler.

In PM0271 Page 38 are the event callbacks as an option the user can choose between, but that is not really the case in my understanding.

0693W00000NqXu3QAF.pngIf you want to use the callbacks, you have to use the Switch case event handler and implement the callbacks into the handler like it is done in the example:

static void BLE_UserEventReceivedCallback( void * pData )
{
  tHCI_UserEvtRxParam *pParam;
  hci_event_pckt *event_pckt;
  hci_uart_pckt *pUartPckt;
 
  pParam = (tHCI_UserEvtRxParam *)pData; 
 
  pUartPckt = (hci_uart_pckt *)&(pParam->pckt->evtserial);
  event_pckt = (hci_event_pckt*)(pUartPckt->data);
 
  switch (event_pckt->evt)
  {
    case HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE:
      {
        evt_blecore_aci *blecore_evt;
        
        blecore_evt = (evt_blecore_aci*) event_pckt->data;
 
        switch (blecore_evt->ecode)
        {
          case EVT_END_OF_RADIO_ACTIVITY:
            {
              BSP_LED_Toggle(LED_GREEN);
            }
            break; /* EVT_END_OF_RADIO_ACTIVITY */
            
          case ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE:
            {
              aci_gatt_attribute_modified_event_rp0 * attribute_modified;
              
              attribute_modified = (aci_gatt_attribute_modified_event_rp0*)blecore_evt->data;
              aci_gatt_attribute_modified_event(attribute_modified->Connection_Handle,
                                                attribute_modified->Attr_Handle,
                                                attribute_modified->Offset,
                                                attribute_modified->Attr_Data_Length,
                                                attribute_modified->Attr_Data);
            }
            break;
 
          default:
            break;
        }
      }
      break; /* HCI_HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE_SPECIFIC */
 
    case HCI_DISCONNECTION_COMPLETE_EVT_CODE:
      {
        hci_disconnection_complete_event_rp0 * disconnection_complete_event;
 
        disconnection_complete_event = (hci_disconnection_complete_event_rp0 *)event_pckt->data;
 
        hci_disconnection_complete_event(disconnection_complete_event->Status,
                                         disconnection_complete_event->Connection_Handle,
                                         disconnection_complete_event->Reason );
      }
      break; /* HCI_DISCONNECTION_COMPLETE_EVT_CODE */
}
}

I hope that im wrong and there is an better option, but in that case it is not really usefull. If there is please let me know 🙂

View solution in original post

4 REPLIES 4
CBuch.1
Associate III

I asked me the same question and there ist not really an satisfying answer.

The same question appears in https://github.com/STMicroelectronics/STM32CubeWB/issues/24, and now there is an Example for the Nucleo-WB55 -> BLE_Peripheral_Lite_EventCallbacks .

But in this Example the callbacks, e.g. hci_le_connection_complete_event(), are called within the Switch-Case Handler.

In PM0271 Page 38 are the event callbacks as an option the user can choose between, but that is not really the case in my understanding.

0693W00000NqXu3QAF.pngIf you want to use the callbacks, you have to use the Switch case event handler and implement the callbacks into the handler like it is done in the example:

static void BLE_UserEventReceivedCallback( void * pData )
{
  tHCI_UserEvtRxParam *pParam;
  hci_event_pckt *event_pckt;
  hci_uart_pckt *pUartPckt;
 
  pParam = (tHCI_UserEvtRxParam *)pData; 
 
  pUartPckt = (hci_uart_pckt *)&(pParam->pckt->evtserial);
  event_pckt = (hci_event_pckt*)(pUartPckt->data);
 
  switch (event_pckt->evt)
  {
    case HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE:
      {
        evt_blecore_aci *blecore_evt;
        
        blecore_evt = (evt_blecore_aci*) event_pckt->data;
 
        switch (blecore_evt->ecode)
        {
          case EVT_END_OF_RADIO_ACTIVITY:
            {
              BSP_LED_Toggle(LED_GREEN);
            }
            break; /* EVT_END_OF_RADIO_ACTIVITY */
            
          case ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE:
            {
              aci_gatt_attribute_modified_event_rp0 * attribute_modified;
              
              attribute_modified = (aci_gatt_attribute_modified_event_rp0*)blecore_evt->data;
              aci_gatt_attribute_modified_event(attribute_modified->Connection_Handle,
                                                attribute_modified->Attr_Handle,
                                                attribute_modified->Offset,
                                                attribute_modified->Attr_Data_Length,
                                                attribute_modified->Attr_Data);
            }
            break;
 
          default:
            break;
        }
      }
      break; /* HCI_HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE_SPECIFIC */
 
    case HCI_DISCONNECTION_COMPLETE_EVT_CODE:
      {
        hci_disconnection_complete_event_rp0 * disconnection_complete_event;
 
        disconnection_complete_event = (hci_disconnection_complete_event_rp0 *)event_pckt->data;
 
        hci_disconnection_complete_event(disconnection_complete_event->Status,
                                         disconnection_complete_event->Connection_Handle,
                                         disconnection_complete_event->Reason );
      }
      break; /* HCI_DISCONNECTION_COMPLETE_EVT_CODE */
}
}

I hope that im wrong and there is an better option, but in that case it is not really usefull. If there is please let me know 🙂

I ended up doing it the same way you did

CBuch.1
Associate III

For all that want to use the Event callbacks without the whole switch case loop, there is an quite easy version. I dont know why ST did it with the whole switch case loop in the EventCallback Example, but maybe somebody can it explain it to me:)

ST implemented in the ble_events.c an hci_le_event_table with all Event codes and the process Callbacks:

/* HCI LE event process functions table */
const hci_event_table_t hci_le_event_table[HCI_LE_EVENT_TABLE_SIZE] =
{
  { 0x0001U, hci_le_connection_complete_event_process },
  { 0x0002U, hci_le_advertising_report_event_process },
  { 0x0003U, hci_le_connection_update_complete_event_process },
  { 0x0004U, hci_le_read_remote_features_complete_event_process },
  { 0x0005U, hci_le_long_term_key_request_event_process },
  { 0x0007U, hci_le_data_length_change_event_process },
  { 0x0008U, hci_le_read_local_p256_public_key_complete_event_process },
  { 0x0009U, hci_le_generate_dhkey_complete_event_process },
  { 0x000AU, hci_le_enhanced_connection_complete_event_process },
  { 0x000BU, hci_le_direct_advertising_report_event_process },
  { 0x000CU, hci_le_phy_update_complete_event_process },
  { 0x000DU, hci_le_extended_advertising_report_event_process },
  { 0x0011U, hci_le_scan_timeout_event_process },
  { 0x0012U, hci_le_advertising_set_terminated_event_process },
  { 0x0013U, hci_le_scan_request_received_event_process },
  { 0x0014U, hci_le_channel_selection_algorithm_event_process },
};

So what i have done for the meta Events is to iterate over the Table and call the Callback with the event Data:

static void BLE_UserEvtRx( void * pPayload )
{
    tHCI_UserEvtRxParam *pParam;
    pParam = (tHCI_UserEvtRxParam *)pPayload;
    void *pckt = &(pParam->pckt->evtserial);
 
    hci_event_pckt *eventPackage;
    eventPackage = (hci_event_pckt*) ((hci_uart_pckt *) pckt)->data;
 
    if (eventPackage->evt == HCI_LE_META_EVT_CODE)
    {
        evt_le_meta_event *metaEvt;
        metaEvt = (evt_le_meta_event*) eventPackage->data;
 
        for(int i = 0; i < HCI_LE_EVENT_TABLE_SIZE; i ++)
        {
            if(hci_le_event_table[i].evt_code == metaEvt->subevent)
            {
                hci_le_event_table[i].process(metaEvt->data);
                return;
            }
        }
    }
}

So all LE Meta Callbacks from ble_events.h will be called. Am I missing something or this an more viable option ?

Thanks for the answer! The issue with this one is that you're potentially looping through each event type without needing to, making this O(n) efficient when it could be O(1). The benefit of using switch case is that it maps the event directly to its functionality which is O(1).

A similar but better approach would be to make your list of hci_event_table_t into a map (by means of an array) and then you can access the entry directly by looking up the metaEvt->subevent.

So something like this:

const hci_event_process hci_le_event_table[] =
{
  null, // 0x0000U
  hci_le_connection_complete_event_process, // 0x0001U
  hci_le_advertising_report_event_process, // 0x0002U
  hci_le_connection_update_complete_event_process, // 0x0003U
  hci_le_read_remote_features_complete_event_process, // 0x0004U
  hci_le_long_term_key_request_event_process, // 0x0005U
  null, // 0x0006U
  hci_le_data_length_change_event_process, // 0x0007U
  hci_le_read_local_p256_public_key_complete_event_process, // 0x0008U
  hci_le_generate_dhkey_complete_event_process, // 0x0009U
  hci_le_enhanced_connection_complete_event_process, // 0x000AU
  hci_le_direct_advertising_report_event_process, // 0x000BU
  hci_le_phy_update_complete_event_process, // 0x000CU
  hci_le_extended_advertising_report_event_process, // 0x000DU
  null, // 0x000EU
  null, // 0x000FU
  null, // 0x0010U
  hci_le_scan_timeout_event_process, // 0x0011U
  hci_le_advertising_set_terminated_event_process, // 0x0012U
  hci_le_scan_request_received_event_process, // 0x0013U
  hci_le_channel_selection_algorithm_event_process, // 0x0014U
};
 
// ...
 
static void BLE_UserEvtRx( void * pPayload )
{
  tHCI_UserEvtRxParam *pParam;
  pParam = (tHCI_UserEvtRxParam *)pPayload;
  void *pckt = &(pParam->pckt->evtserial);
 
  hci_event_pckt *eventPackage;
  eventPackage = (hci_event_pckt*) ((hci_uart_pckt *) pckt)->data;
     
  if (eventPackage->evt == HCI_LE_META_EVT_CODE)
  {
      evt_le_meta_event *metaEvt;
      metaEvt = (evt_le_meta_event*) eventPackage->data;
 
      hci_le_event_table[metaEvt->subevent].process(metaEvt->data);
  }
}

The drawback to the map method compared to using switch/case is that this uses heap memory instead of stack memory. And since heap is slower than stack it's still a better idea to use switch/case (at least, in this specific case because we want this code to run as fast as possible).

The reason I asked my original question (and actually the GitHub question was asked by me as well) is that I was hoping these callbacks to be triggered by hardware interrupts.

I went ahead and marked your first solution from a month ago as the best answer. However I will be continuing to use pure switch/case on my end.