The Application¶
This section describes the application portion of the simple_peripheral project, which includes the following:
Pre-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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 int main() { /* Register Application callback to trap asserts raised in the Stack */ RegisterAssertCback(AssertHandler); PIN_init(BoardGpioInitTable); // Enable iCache prefetching VIMSConfigure(VIMS_BASE, TRUE, TRUE); // Enable cache VIMSModeSet(VIMS_BASE, VIMS_MODE_ENABLED); #if !defined( POWER_SAVING ) /* Set constraints for Standby, powerdown and idle mode */ // PowerCC26XX_SB_DISALLOW may be redundant Power_setConstraint(PowerCC26XX_SB_DISALLOW); Power_setConstraint(PowerCC26XX_IDLE_PD_DISALLOW); #endif // POWER_SAVING /* Update User Configuration of the stack */ user0Cfg.appServiceInfo->timerTickPeriod = Clock_tickPeriod; user0Cfg.appServiceInfo->timerMaxMillisecond = ICall_getMaxMSecs(); /* Initialize ICall module */ ICall_init(); /* Start tasks of external images - Priority 5 */ ICall_createRemoteTasks(); SimplePeripheral_createTask(); /* enable interrupts and start SYS/BIOS */ BIOS_start(); return 0; }
See TI-RTOS 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 dispatcher, which facilitates the application program interface between the application and the BLE5-Stack task across the dual-image boundary as well as in Library configuration. 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.
ICall BLE5-Stack Protocol Service¶
As Figure 10. 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 CC26x2.
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 Overview).
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. The size of the ICall heap is configured with the
HEAPMGR_SIZE
preprocessor-defined symbol in the application
project. 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:
1 2 3 4 5 /* Initialize ICall module */ ICall_init(); /* Start tasks of external images - Priority 5 */ ICall_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:
1 2 /* Enroll the service that this stack represents */ ICall_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
1 2 3 // Register the current thread as an ICall dispatcher application // so that the application can send and receive messages. ICall_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.
1 UInt 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.
1 Void 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.
Improved ICall Architecture (ICall Lite)¶
Although there are no API changes to ICall or Stack API, in order for projects to realize the benefits of improved ICall the APIs need to be remapped.
The remapping is all done with icall_ble_api.h
, and by including
icall_api_lite.c
in the build. Effectively, icall_ble_api.h
redefines 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 new message format is designed to be compatible with the improved
ICall translation layer, 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.
Example ICall Usage¶
Figure 11. 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 11.
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 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.
1 2 3 4 5 6 7 8 9 10 11 static void SimplePeripheral_taskFxn(UArg a0, UArg a1) { // Initialize application SimplePeripheral_init(); //Application main loop for (;;) { } }
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:
1 2 3 4 5 // Waits for an event to be posted associated with the calling thread. // Note that an event associated with a thread is posted when a // message is queued to the message receive queue of the thread events = Event_pend(syncEvent, Event_Id_NONE, SBP_ALL_EVENTS, 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.
Task Events¶
Task events are set when the BLE5-Stack sets an event in the application task through the Events Module. An example of a task event is when the HCI_EXT_ConnEventNoticeCmd() is called to indicate the end of a connection event. An example of a task event that signals the end of a connection event is shown in the task function of the simple_peripheral:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 if (events) { ICall_EntityID dest; ICall_ServiceEnum src; ICall_HciExtEvt *pMsg = NULL; if (ICall_fetchServiceMsg(&src, &dest, (void **)&pMsg) == ICALL_ERRNO_SUCCESS) { uint8 safeToDealloc = TRUE; if ((src == ICALL_SERVICE_CLASS_BLE) && (dest == selfEntity)) { ICall_Stack_Event *pEvt = (ICall_Stack_Event *)pMsg; // Check for BLE stack events first if (pEvt->signature == 0xffff) { if (pEvt->event_flag & SBP_CONN_EVT_END_EVT) { // Try to retransmit pending ATT Response (if any) SimpleBLEPeripheral_sendAttRsp(); } } else { // Process inter-task message safeToDealloc = SimpleBLEPeripheral_processStackMsg((ICall_Hdr *)pMsg); } } if (pMsg && safeToDealloc) { ICall_freeMsg(pMsg); } } // Additional Event Processing }
Note
In the code, the pEvt->signature
is always equal to 0xFFFF
if the event is coming from the BLE5-Stack.
When selecting an event value for an intertask event, the value must
be unique for the given task and must be a power of 2 (so only 1 bit
is set). Because the pEvt->event
variable is initialized as
uint16_t
, this initialization allows for a maximum of 16 events.
The only event values that cannot be used are those already used for
BLE5-Stack OSAL global events (stated in bcomdef.h):
1 2 3 4 5 /************************************************************ * BLE OSAL GAP GLOBAL Events */ #define GAP_EVENT_SIGN_COUNTER_CHANGED 0x4000 //!< The device level sign counter changed
Note
These intertask events are a different set of events than the intratask events mentioned in Requesting and Processing Stack Events.
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)
Task Events an example from the main task loop of the simple_peripheral.
Using TI-RTOS Events Module¶
All BLE5-Stack 1.01.01.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 User 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #define SBP_QUEUE_EVT UTIL_QUEUE_EVENT_ID // Event_Id_30 // If TI-RTOS queue is not empty, process app message. if (events & SBP_QUEUE_EVT) { while (!Queue_empty(appMsgQueue)) { sbpEvt_t *pMsg = (sbpEvt_t *)Util_dequeueMsg(appMsgQueue); if (pMsg) { // Process message. SimpleBLEPeripheral_processAppMsg(pMsg); // Free the space from the message. ICall_free(pMsg); } } }
Requesting and Processing Stack Events¶
Some APIs have the option to notify the application when specific events occur in
the BLE5-Stack. The API which enabled the notification of such events will
contain a taskEvent
argument. This taskEvent
must be unique for a given
ICall-aware task. The application can process the requested stack events by
checking if the taskEvent
is contained in the uint16_t event_flag
variable of the ICall_Stack_Event
data structure.
Note
The event_flag
is not to be confused with events posted by the TI-RTOS
Event module.
The snippet below shows how simple_peripheral requests stack event flags.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // Application specific event ID for HCI Connection Event End Events #define SBP_HCI_CONN_EVT_END_EVT 0x0001 static uint8_t SimpleBLEPeripheral_processGATTMsg(gattMsgEvent_t *pMsg) { // See if GATT server was unable to transmit an ATT response if (pMsg->hdr.status == blePending) { // No HCI buffer was available. Let's try to retransmit the response // on the next connection event. if (HCI_EXT_ConnEventNoticeCmd(pMsg->connHandle, selfEntity, SBP_HCI_CONN_EVT_END_EVT) == SUCCESS) { // First free any pending response SimpleBLEPeripheral_freeAttRsp(FAILURE); // Hold on to the response message for retransmission pAttRsp = pMsg; //... } //... } //... }
The snippet below shows how simple_peripheral processes stack event flags.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 // Application specific event ID for HCI Connection Event End Events #define SBP_HCI_CONN_EVT_END_EVT 0x0001 static void SimplePeripheral_taskFxn(UArg a0, UArg a1) { // Application main loop for (;;) { uint32_t events; // Waits for an event to be posted associated with the calling thread. // Note that an event associated with a thread is posted when a // message is queued to the message receive queue of the thread events = Event_pend(syncEvent, Event_Id_NONE, SBP_ALL_EVENTS, ICALL_TIMEOUT_FOREVER); if (events) { ICall_EntityID dest; ICall_ServiceEnum src; ICall_HciExtEvt *pMsg = NULL; if (ICall_fetchServiceMsg(&src, &dest, (void **)&pMsg) == ICALL_ERRNO_SUCCESS) { uint8 safeToDealloc = TRUE; if ((src == ICALL_SERVICE_CLASS_BLE) && (dest == selfEntity)) { ICall_Stack_Event *pEvt = (ICall_Stack_Event *)pMsg; // Check for BLE stack events first if (pEvt->signature == 0xffff) { if (pEvt->event_flag & SBP_HCI_CONN_EVT_END_EVT) { // Try to retransmit pending ATT Response (if any) SimpleBLEPeripheral_sendAttRsp(); } } else { // Process inter-task message safeToDealloc = SimpleBLEPeripheral_processStackMsg((ICall_Hdr *)pMsg); } } } } } }
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 call back 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.
1 2 3 4 | static void SimplePeripheral_charValueChangeCB(uint8_t paramId)
{
SimplePeripheral_enqueueMsg(SP_CHAR_CHANGE_EVT, pValue);
}
|
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.
1 2 3 4 static void SimplePeripheral_processCharValueChangeEvt(uint8_t paramId) { //... }