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.
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
.
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:
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:
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
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.
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.
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.
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.
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:
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.
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:
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:
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.
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.
1static void SimplePeripheral_processCharValueChangeEvt(uint8_t paramId) 2{ 3 //... 4}