Introduction

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

Main initialization

The main function is contained in source file main.c located in the IDE Start-up 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, TI-RTOS tasks created or constructed, and start the SYS/BIOS kernel scheduler with interrupts enabled. The main function does not return. Main.c exists in the application project; in other words main.c will be allocated within flash reserved for the application.

Listing 54. A basic main function.
 1int main()
 2{
 3    /* Register Application callback to trap asserts raised in the Stack */
 4    RegisterAssertCback(AssertHandler);
 5
 6    Board_initGeneral();
 7
 8    // Enable iCache prefetching
 9    VIMSConfigure(VIMS_BASE, TRUE, TRUE);
10    // Enable cache
11    VIMSModeSet(VIMS_BASE, VIMS_MODE_ENABLED);
12
13    #if !defined( POWER_SAVING )
14    /* Set constraints for Standby, powerdown and idle mode */
15    // PowerCC26XX_SB_DISALLOW may be redundant
16    Power_setConstraint(PowerCC26XX_SB_DISALLOW);
17    Power_setConstraint(PowerCC26XX_IDLE_PD_DISALLOW);
18    #endif // POWER_SAVING
19
20    /* Update User Configuration of the stack */
21    user0Cfg.appServiceInfo->timerTickPeriod = Clock_tickPeriod;
22    user0Cfg.appServiceInfo->timerMaxMillisecond  = ICall_getMaxMSecs();
23
24    /* Initialize ICall module */
25    ICall_init();
26
27    /* Start tasks of external images - Priority 5 */
28    ICall_createRemoteTasks();
29
30    SimplePeripheral_createTask();
31
32    /* enable interrupts and start SYS/BIOS */
33    BIOS_start();
34
35    return 0;
36}

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

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 TI-RTOS (for example, thread synchronization). ICall allows the application and protocol stack to operate efficiently, communicate, and share resources in a unified TI-RTOS 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-22506e72cb9d36aacac889e158653924a5ade334.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 TI-RTOS of the CC13xx or CC26xx.

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 routes (that is, 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 (see the relevant module in TI-RTOS (RTOS Kernel) Overview). The ICall primitive service is implemented as part of icall.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 TI-RTOS environment.

In ICall, messaging between two tasks occurs by sending a block of 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. See Dynamic Memory Allocation for more details on managing dynamic memory. 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 in in the snippet below in main() before starting the TI-RTOS kernel scheduler:

Listing 55. 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 56. 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 from SimplePeripheral_init in simple_peripheral.c

Listing 57. 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() returns are initialized. These objects are subsequently used by ICall to facilitate messaging between the application and server tasks. The syncEvent argument represents the Events Module handle for signaling and the selfEntity represents the destination message queue of the 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 TI-RTOS Events Module for thread synchronization instead of Semaphores.

To allow a client or a server thread to block until it receives a message, ICall provides the API functions which blocks until the semaphore associated with the caller TI-RTOS thread is posted.

Listing 58. ICall Blocking/Pending Calls
1UInt Event_pend(Event_Handle handle, UInt andMask, UInt orMask, UInt32 timeout);

handle is the constructed Event_Handle instance. andMask and orMask are for the user to select which Event flags to block/ pend on. timeout is a time-out period in milliseconds. If not already returned after this time-out period, the function returns with events consumed.

Event_pend blocks the current task until the desired Events are posted. Allowing an application or a server thread to block/yield the processor resource to other lower priority threads or conserve energy by shutting down power and/or clock domains when possible.

There are a total of 32 events. These can be defined for application specific purposes. Note that there is an event specifically for ICall Messages and Queues.

The Event associated with a TI-RTOS thread is signaled/posted by when Event_post is called with the desired flags.

Event_post is used so an application or a server can add its own event to unblock Event_pend and synchronize the thread with appropriate flags. Event_post constructed Event_Handle instance, as well as an eventFlag mask to select the desired flags.

Listing 59. ICall Signaling/Posting Call
1Void Event_post(Event_Handle handle, UInt eventMask);

The Event handle 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 the TI-RTOS Events Module, see Event.

Example ICall Usage

Figure 65. 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 SYS/BIOS 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.

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 is through ICall back to the application thread. An example diagram of this exchange can be seen in Figure 65.

@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 65. 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

Simple Peripheral Task

Simple Peripheral Task, or the application task, is the lowest priority task in the system. The code for this task is in simple_peripheral.c and simple_peripheral in the Application IDE folder.

Application Initialization Function

TI-RTOS (RTOS Kernel) Overview describes how a task is constructed. After the task is constructed and the SYS/BIOS kernel scheduler is started, the function that was passed during task construction is run when the task is ready (for example, SimplePeripheral_taskFxn). This function must first call an application initialization function.

Listing 60. simple_peripheral Task Function Pseudocode
 1static void SimplePeripheral_taskFxn(UArg a0, UArg a1)
 2{
 3  // Initialize application
 4  SimplePeripheral_init();
 5
 6  //Application main loop
 7  for (;;)
 8  {
 9
10  }
11}

This initialization function (SimplePeripheral_init) configures several services for the task and sets several hardware and software configuration settings and parameters. The following list contains some common examples:

  • Initializing the GATT client

  • Registering for callbacks in various profiles

  • Setting up the Bond Manager

  • Setting up the GAP layer

  • Configuring hardware modules such as LCD or SPI.

For more information on these examples, see their respective sections in this guide.

Note

In the application initialization function, ICall_registerApp() must be called before any stack API is called.

Event Processing in the Task Function

simple_peripheral implements an event driven task function. The task function enters an infinite loop so that it continuously processes as an independent task and does not run to completion. In this infinite loop, the task remains blocked and waits until proper events flags signal a new reason for processing:

Listing 61. ICall task remains blocked and waits until signaled for processing.
1// Waits for an event to be posted associated with the calling thread.
2// Note that an event associated with a thread is posted when a
3// message is queued to the message receive queue of the thread
4events = Event_pend(syncEvent, Event_Id_NONE, SBP_ALL_EVENTS,
5                    ICALL_TIMEOUT_FOREVER);

When an event or other stimulus occurs and is processed, the task waits for event flags and remains in a blocked state until there is another reason to process.

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 TI-RTOS Events Module

All BLE5-Stack 2.02.06.00 projects use the TI-RTOS Event module acquire ICall stack message event. Usage is described in ICall Thread Synchronization and more documentation on the Event module can be found in the TI-RTOS Kernel (SYS/BIOS) User’s Guide.

Processing Queued Application Messages

Application messages enqueued using the Util_enqueueMsg() function are dequeued for processing in the order in which they occurred. The application should dequeue and free messages when UTIL_QUEUE_EVENT_ID events are posted.

The code snippet below shows how simple_peripheral processes application messages.

Listing 62. Queued messages are processed in the order they occurred.
 1#define SBP_QUEUE_EVT   UTIL_QUEUE_EVENT_ID // Event_Id_30
 2
 3// If TI-RTOS queue is not empty, process app message.
 4if (events & SBP_QUEUE_EVT)
 5{
 6    while (!Queue_empty(appMsgQueue))
 7    {
 8        sbpEvt_t *pMsg = (sbpEvt_t *)Util_dequeueMsg(appMsgQueue);
 9        if (pMsg)
10        {
11            // Process message.
12            SimpleBLEPeripheral_processAppMsg(pMsg);
13
14            // Free the space from the message.
15            ICall_free(pMsg);
16        }
17    }
18}

Warning

Messages originating from the application message queue should be free’d using ICall_free().

Processing Stack Events from the ICall Message Queue

The 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”.

The application thread receives these messages as below:

Listing 63. App receives event from ICall message queue.
 1static void SimplePeripheral_taskFxn(UArg a0, UArg a1)
 2{
 3  // Initialize application
 4  SimplePeripheral_init();
 5
 6  // Application main loop
 7  for (;;)
 8  {
 9    uint32_t events;
10
11    // Waits for an event to be posted associated with the calling thread.
12    // Note that an event associated with a thread is posted when a
13    // message is queued to the message receive queue of the thread
14    events = Event_pend(syncEvent, Event_Id_NONE, SP_ALL_EVENTS,
15                        ICALL_TIMEOUT_FOREVER);
16
17    if (events)
18    {
19      ICall_EntityID dest;
20      ICall_ServiceEnum src;
21      ICall_HciExtEvt *pMsg = NULL;
22
23      // Fetch any available messages that might have been sent from the stack
24      if (ICall_fetchServiceMsg(&src, &dest,
25                                (void **)&pMsg) == ICALL_ERRNO_SUCCESS)
26      {
27        uint8 safeToDealloc = TRUE;
28
29        if ((src == ICALL_SERVICE_CLASS_BLE) && (dest == selfEntity))
30        {
31          ICall_Stack_Event *pEvt = (ICall_Stack_Event *)pMsg;
32
33          // Check for BLE stack events first
34          if (pEvt->signature != 0xffff)
35          {
36            // Process inter-task message
37            safeToDealloc = SimplePeripheral_processStackMsg((ICall_Hdr *)pMsg);
38          }
39        }
40      }
41  }
42  // ...
43}

This message is consumed by the application’s _processStackMsg(...) function. Ususally this involves building a case statement based on message type and processing based on that. An example is shown below:

Listing 64. Processing message from stack
 1static uint8_t SimplePeripheral_processStackMsg(ICall_Hdr *pMsg)
 2{
 3    switch (pMsg->event)
 4    {
 5        case GAP_MSG_EVENT:
 6        // Process GAP_MSG_EVENT
 7        break;
 8
 9        case GATT_MSG_EVENT:
10        // Process GATT message
11        break;
12
13        case HCI_GAP_EVENT_EVENT:
14        // Process HCI message
15        break;
16
17        default:
18        // do nothing
19        break;
20    }
21
22    // ...
23}

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 TI-RTOS 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 TI-RTOS 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 TI-RTOS 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 65. simple_peripheral char value change callback.
1static void SimplePeripheral_charValueChangeCB(uint8_t paramId)
2{
3  SimplePeripheral_enqueueMsg(SP_CHAR_CHANGE_EVT, pValue);
4}

The code snippet above shows the callback function that is sent to the application via SimplePeripheral_simpleProfileCBs and SimpleProfile_RegisterAppCBs. 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 66. simple_peripheral char value change event.
1static void SimplePeripheral_processCharValueChangeEvt(uint8_t paramId)
2{
3  //...
4}