Introduction

This section describes the application portion of the basic_ble project, which includes the following:

Main initialization

The main function is contained in source file main_freertos.c located in the IDE Start-up or root folder. This function is the starting point at run time. The purpose of main is to bring up the target with interrupts disabled, drivers initialized, power management on, FreeRTOS tasks created, and start the FreeRTOS kernel scheduler with interrupts enabled. The main function does not return. Because the main_freertos.c file exists in the application project, main_freertos.c will be allocated within the flash memory reserved for the application.

Listing 14. A basic main function.
 1int main()
 2{
 3  /* Register Application callback to trap asserts raised in the Stack */
 4  halAssertCback = AssertHandler;
 5  RegisterAssertCback(AssertHandler);
 6
 7  Board_init();
 8
 9  /* Update User Configuration of the stack */
10  user0Cfg.appServiceInfo->timerTickPeriod = ICall_getTickPeriod();
11  user0Cfg.appServiceInfo->timerMaxMillisecond  = ICall_getMaxMSecs();
12
13  /* Initialize all applications tasks */
14  appMain();
15
16  /* Start the FreeRTOS scheduler */
17  vTaskStartScheduler();
18
19  return 0;
20}

See FreeRTOS (RTOS Kernel) Overview for how the application is constructed through FreeRTOS.

Note

The ICall module must be initialized by ICall_init() and the stack task is created via ICall_createRemoteTasks().

ICall

Introduction

Indirect Call Framework (ICall) is a module that provides a mechanism for the application to interface with the Bluetooth low energy protocol stack services (that is, BLE5-Stack APIs) as well as certain primitive services provided by FreeRTOS (for example, thread synchronization). ICall allows the application and protocol stack to operate efficiently, communicate, and share resources in a unified FreeRTOS environment.

The central component of the ICall architecture is the API translation, which facilitates the application program interface between the application and the BLE5-Stack. Although most ICall interactions are abstracted within the BLE5-Stack APIs (for example, GAP, HCI, and so forth), the application developer must understand the underlying architecture for the BLE5-Stack to operate properly in the multithreaded RTOS environment.

The ICall module source code is provided in the ICall and ICall BLE IDE folders in the application project. The figure below shows an example of the application calling the stack API GATT_Notification.

../_images/ditaa-48620577815735507afd6c65f21a17aaa3a3b38f.png

ICall BLE5-Stack Protocol Service

As the figure above shows, the ICall core use case involves messaging between a server entity (that is, the BLE5-Stack task) and a client entity (for example, the application task).

Note

The ICall framework is not the GATT server and client architecture, as defined by the Bluetooth Low Energy protocol.

The reasoning for this architecture is as follows:

  • To enable independent updating of the application and Bluetooth Low Energy protocol stack

  • To maintain API consistency as software is ported from legacy platforms (that is, OSAL for the CC254x) to FreeRTOS of the CC23xx.

The ICall BLE5-Stack service serves as the application interface to BLE5-Stack APIs. When a BLE5-Stack API is called by the application internally, the ICall module dispatches the command to the BLE5-Stack and routes messages from the BLE5-Stack to the application when appropriate.

Because the ICall module is part of the application project, the application task can access ICall with direct function calls. Because the BLE5-Stack executes at the highest priority, the application task blocks until the response is received. Certain protocol stack APIs may respond immediately, but the application thread blocks as the API is dispatched to the BLE5-Stack through ICall. Other BLE5-Stack APIs may also respond asynchronously to the application through ICall (for example, event updates) with the response sent to the event handler of the application task.

ICall Primitive Service

ICall includes a primitive service that abstracts various operating system-related functions. Due to shared resources and to maintain interprocess communication, the application must use the following ICall primitive service functions:

  • Messaging and Thread Synchronization

  • Heap Allocation and Management

Some of these are abstracted to Util functions (such as Util_enqueueMsg and Util_dequeueMsg). The ICall primitive service is implemented as part of icall_POSIX.c.

Messaging and Thread Synchronization

The Messaging and Thread Synchronization functions provided by ICall enable an application to communicate with the BLE5-Stack in the multithreaded FreeRTOS environment.

In ICall, messaging between two tasks occurs by sending a message from one thread to the other through a message queue. The sender allocates a memory block, writes the content of the message into the memory block, and then sends (that is, enqueues) the memory block to the recipient. Notification of message delivery occurs using an event flag. The receiver wakes up on the event flag post, copies the message memory block (or blocks), processes the message, and returns (frees) the memory block to the heap.

The stack uses ICall for notifying and sending messages to the application. ICall delivers these service messages, the application task receives them, and the messages are processed in the context of the application.

Heap Allocation and Management

ICall provides the application with global heap APIs for dynamic memory allocation. ICall uses this heap for all protocol stack messaging and to obtain memory for other ICall services. TI recommends that the application uses these ICall APIs to allocate dynamic memory.

ICall Initialization and Registration

To instantiate and initialize the ICall service, the application must call the functions shown in the snippet below before starting the FreeRTOS scheduler:

Listing 15. Required code to utilize ICall.
1/* Initialize ICall module */
2ICall_init();
3
4/* Start tasks of external images - Priority 5 */
5ICall_createRemoteTasks();

Calling ICall_init() initializes the ICall primitive service (for example, heap manager) and framework. Calling ICall_createRemoteTasks() creates but does not start the BLE5-Stack task. Before using ICall protocol services, the server and client must enroll and register with ICall. The server enrolls a service, which is defined at build time. Service function handler registration uses a globally defined unique identifier for each service. For example, Bluetooth Low Energy uses ICALL_SERVICE_CLASS_BLE for receiving BLE5-Stack task messages through ICall.

To enroll the BLE5-Stack service (server) with ICall in osal_icall_ble.c, see the snippet below:

Listing 16. ICall Enrollment
1/* Enroll the service that this stack represents */
2ICall_enrollService(ICALL_SERVICE_CLASS_BLE, NULL, &entity, &syncHandle);

The registration mechanism is used by the client to send and/or receive messages through the ICall dispatcher.

For a client (for example, application task) to use the BLE5-Stack APIs, the client must first register its task with ICall. This registration usually occurs in the task initialization function of the application. The snippet below is an example on how to register the current thread with ICall.

Listing 17. ICall Registration
1// Register the current thread as an ICall dispatcher application
2// so that the application can send and receive messages.
3ICall_registerApp(&selfEntity, &syncEvent);

The application supplies the selfEntity and syncEvent inputs. These inputs are initialized for the task of the client (for example, application) when the ICall_registerApp() is called. These objects are subsequently used by ICall to facilitate messaging between the client (e.g. the application) and server (e.g. the BLE stack) tasks. The syncEvent argument represents the POSIX message queue descriptor for signaling and the selfEntity represents the destination identifier of the client task. Each task registering with ICall has unique syncEvent and selfEntity identifiers.

Note

BLE5-Stack APIs defined in icall_ble_api.h and other ICall primitive services are not available before ICall registration.

ICall Thread Synchronization

The ICall module uses POSIX message queues (MQ) for thread synchronization.

To allow a client or a server thread to block until it receives a message, ICall provides the POSIX mq_receive API function to block until a message from the sender FreeRTOS thread is enqueued.

Listing 18. ICall Blocking/Pending Calls
1ssize_t mq_receive(mqd_t mqdes,
2                   char *msg_ptr,
3                   size_t msg_len,
4                   unsigned int *msg_prio);

mqdes is the message queue descriptor. If the function is successful, the selected message will be removed from the queue and copied into the buffer pointed to by``msg_ptr``. msg_len is the length of the buffer pointed by msg_ptr. If msg_prio is not NULL (i.e a valid RAM address), the message priority of the selected message will be stored in the location referenced by msg_prio.

mq_receive blocks the current task until a message is enqueued, allowing an client or a server thread to block and yield the processor resource to other lower priority threads or conserve energy by shutting down power and clock domains when possible.

A message is delivered to another thread through the message queue when mq_send is called. This will unblock a thread that is waiting with mq_receive.

Listing 19. ICall Signaling/Posting Call
1int mq_send(mqd_t mqdes,
2            const char *msg_ptr,
3            size_t msg_len,
4            unsigned int msg_prio);

mqdes is the message queue descriptor. msg_ptr points to the data buffer (i.e. the message) that will be added by the message queue. msg_len defines the lenght of the message, in bytes. msg_prio defines the message priority; that is, where in the queue the message will be inserted: before other messages with lower msg_prio and after other messages with the same or higher msg_prio.

The message queue descriptor associated with the thread is obtained through either ICall_enrollService() call or ICall_registerApp() call.

Warning

Do not call an ICall function from a stack callback. This action can cause ICall to abort (with ICall_abort()) and break the system.

For more information on POSIX, see the TI-POSIX Users Guide included in your SDK and the official specification.

Example ICall Usage

Figure 40. shows an example command being sent from the application to the BLE5-Stack through the ICall framework with a corresponding return value passed back to the application.

ICall_init() initializes the ICall module instance and ICall_createRemoteTasks() creates a task per external image with an entry function at a known address.

After initializing ICall, the application task registers with ICall through ICall_registerApp().

After the FreeRTOS scheduler starts and the application task runs, the application sends a protocol command defined in ble_dispatch_JT.c such as GAP_GetParamValue().

Warning

Although the protocol command is declared in gap.h and defined on the BLE5-Stack side via ble_dispatch_JT.c, the declaration MUST be overridden by icall_api.h.

It is crucial to note that the protocol command is not executed in the thread of the application but is encapsulated in an ICall message and routed to the BLE5-Stack task via the ICall framework. This command is sent to the ICall dispatcher where it is dispatched and executed in the BLE5-Stack context. The application thread meanwhile blocks until the corresponding command status message is received. The BLE5-Stack finishes executing the command, then sends a command status message response through ICall back to the application thread. An sequence diagram of this exchange can be seen below in Figure 40.

@startuml
participant App
participant "ICall"
participant "BLE Stack"
  App -> "ICall" : ICall_Init
  App -> "ICall" : ICall_createRemoteTasks
  App -> "ICall" : ICall_registerApp
  App -> "ICall" : GAP_GetParamValue
  ICall -> ICall : icall_directAPI(...)

  ICall -> "BLE Stack" : ICall_sendServiceMsg\n(BLE Primitive Service)

rnote over "BLE Stack"
    Stack Executes
    GAP_GetParamValue
end note

activate App

rnote over App
   App Task Blocks
end note

  "BLE Stack" -> "ICall" : osal_service_complete(sendGapCmdStatus)

deactivate "App"

  ICall -> App : return

@enduml


ICall Messaging Example

Figure 40. ICall Messaging Example

ICall Translation and Include

Effectively, icall_ble_api.h defines all the ICall/Stack API while keeping their original function prototypes. This redefinition is done to utilize a different message format for the dispatcher to handle.

In order for the redefinition to take effect correctly, icall_ble_api.h MUST be the last file to be included in the source file. This ensures that the redefinition correctly occurs. If icall_ble_api.h is not the last file to be included, it’s possible that original definitions could be used due to gapbondmgr.h, gapgattserver.h, or any other ICall/Stack API being included in another header file.

Warning

For any source file utilizing ICall/Stack API, #include "icall_ble_api.h" must be the last include statement. Erratic runtime behavior or link time errors may result.

The ICall translation layer is where the stack parses API calls from the app, defined in icall_lite_translation.c in the BLE5-Stack project. All messages will be processed with icall_liteTranslation in the BLE-Stack context.

Warning

Only Tasks/Threads registered with ICall via ICall_registerApp() should use ICall/Stack API.

Application will abort if an unknown task uses ICall/Stack API.

APIs that are not in ``icall_ble_api.h`` should not be called by the application. This file should be treated as the high level stack header definition

Intertask Messages

These messages are passed from another task (such as the BLE5-Stack Service) through ICall to the application task.

Some possible examples are as follows:

  • A confirmation sent from the protocol stack in acknowledgment of a successful over-the-air indication

  • An event corresponding to an HCI command (see BLE Stack API Reference for HCI command documentation and corresponding events)

  • A response to a GATT client operation (see Using the GATT Layer Directly)

Using FreeRTOS Events Module

All BLE5-Stack 3.02.04.00 projects use the FreeRTOS Event module acquire ICall stack message event. Usage is described in ICall Thread Synchronization and more documentation on thread synchronization can be found in the FreeRTOS Kernel Overview.

Processing Queued Messages in BLEAppUtil framework

Messages enqueued using the BLEAppUtil_enqueueMsg function are dequeued for processing in the order in which they occurred. The BLEAppUtil framework dequeues and frees messages when new events are posted.

The code snippet below shows how BLEAppUtil framework processes events & messages.

Listing 20. Queued messages are processed in the order they occurred.
 1// wait until receive queue message
 2if (mq_receive(BLEAppUtil_theardEntity.queueHandle, (char*)&pAppEvt, sizeof(pAppEvt), NULL) > 0)
 3{
 4    BLEAppUtil_msgHdr_t *pMsgData = (BLEAppUtil_msgHdr_t *)pAppEvt.pData;
 5    bool freeMsg = FALSE;
 6
 7    switch (pAppEvt.event){
 8        if (pMsg)
 9        {
10          case BLEAPPUTIL_EVT_STACK_CALLBACK:
11          {
12            // ..
13          }
14          case BLEAPPUTIL_EVT_ADV_CB_EVENT:
15          {
16              BLEAppUtil_processAdvEventMsg(pMsgData);
17              if (((BLEAppUtil_AdvEventData_t *)pMsgData)->event != BLEAPPUTIL_ADV_INSUFFICIENT_MEMORY &&
18                  ((BLEAppUtil_AdvEventData_t *)pMsgData)->pBuf)
19              {
20                  BLEAppUtil_free(((BLEAppUtil_AdvEventData_t *)pMsgData)->pBuf);
21              }
22              break;
23          }
24    }
25}

Warning

Messages originating from the message queue should be free’d using ICall_free() (BLEAppUtil_free() is a wrapper function for ICall_free()).

Processing Stack Events from the ICall Message Queue

The stack will often asynchronously signal the application by placing a message the application’s ICall message queue. This includes event types defined in bcomdef.h under “BLE OSAL GAP GLOBAL Events”. Any incoming stack messages are caught by the BLEAPPUTIL_EVT_STACK_CALLBACK case in the switch-case block.

The BLEAppUtil framework receives these messages as below:

Listing 21. BLEAppUtil receives event from ICall message queue.
 1void *BLEAppUtil_Task(void *arg)
 2{
 3  // Register to the stack and create queue and event
 4  BLEAppUtil_stackRegister();
 5
 6  // Init the ble stack
 7  BLEAppUtil_stackInit();
 8
 9  // Application main loop
10  for (;;)
11  {
12      BLEAppUtil_appEvt_t pAppEvt;
13
14      // wait until receive queue message
15      if (mq_receive(BLEAppUtil_theardEntity.queueHandle, (char*)&pAppEvt, sizeof(pAppEvt), NULL) > 0)
16      {
17          BLEAppUtil_msgHdr_t *pMsgData = (BLEAppUtil_msgHdr_t *)pAppEvt.pData;
18          bool freeMsg = FALSE;
19
20          switch (pAppEvt.event)
21          {
22            case BLEAPPUTIL_EVT_STACK_CALLBACK:
23            {
24                // Set the flag to true to indicate that BLEAppUtil_freeMsg
25                // should be used to free the msg
26                freeMsg = TRUE;
27
28                switch (pMsgData->event)
29                {
30                    case GAP_MSG_EVENT:
31                        BLEAppUtil_processGAPEvents(pMsgData);
32                        break;
33
34                    case GATT_MSG_EVENT:
35                        BLEAppUtil_processGATTEvents(pMsgData);
36                        break;
37
38                    case L2CAP_DATA_EVENT:
39                        BLEAppUtil_processL2CAPDataMsg(pMsgData);
40                        break;
41
42                    case L2CAP_SIGNAL_EVENT:
43                        BLEAppUtil_processL2CAPSignalEvents(pMsgData);
44                        break;
45
46                    case HCI_GAP_EVENT_EVENT:
47                        BLEAppUtil_processHCIGAPEvents(pMsgData);
48                        break;
49
50                    case HCI_DATA_EVENT:
51                        BLEAppUtil_processHCIDataEvents(pMsgData);
52                        break;
53
54                    case HCI_SMP_EVENT_EVENT:
55                        BLEAppUtil_processHCISMPEvents(pMsgData);
56                        break;
57
58                    case HCI_SMP_META_EVENT_EVENT:
59                        BLEAppUtil_processHCISMPMetaEvents(pMsgData);
60                        break;
61                // ..
62                }
63              }
64            }
65          }
66        }
67      }
68    }
69  }

Messages that originate from the ICall Message Queue should be free’d using ICall_freeMsg(). Messages coming from the ICall message queue are received using ICall_fetchServiceMsg.

Warning

Using ICall_freeMsg() on messages that are not from the ICall message queue can cause memory corruption. Only use ICall_freeMsg() on messages that have be fetched using ICall_fetchServiceMsg.

Callbacks

The application code also includes various callbacks to protocol stack layers, profiles, and FreeRTOS modules. To ensure thread safety, processing must be minimized in the actual callback and the bulk of the processing should occur in the application context. Two functions are defined per callback. One is the callback itself, which is called upon by another module or task. The second is the function to handle the event generated by the callback in the application context. Consider the characteristic change callback, which is called when a characteristic change occurs.

Warning

No blocking FreeRTOS function calls or protocol stack APIs should be performed in a callback function. Such function calls may result in an abort or undefined behavior. Always perform protocol stack and FreeRTOS blocking calls from the application task context.

Note

All callbacks are called in the context of the calling task or module (for example, the stack task). To minimize processing in the calling context, this function should enqueue an event that the application pends on.

Listing 22. BLEAppUtil framework connection event callback
1void BLEAppUtil_connEventCB(Gap_ConnEventRpt_t *pReport)
2{
3    // Enqueue the event msg
4    if ( BLEAppUtil_enqueueMsg(BLEAPPUTIL_EVT_CONN_EVENT_CB, pReport) != SUCCESS)
5    {
6        BLEAppUtil_free(pReport);
7    }
8}

The callback simply places a message in the queue to signal the application to wake up. Once the callback’s context returns and its parent task goes to sleep, the application wakes up due to the enqueue from the callback. The code snippet below is called when the event is popped from the application queue and processed.

Listing 23. BLEAppUtil Framework connection event processor.
1void BLEAppUtil_processConnEventMsg(BLEAppUtil_msgHdr_t *pMsg)
2{
3  //...
4}