Operating System Abstraction Layer (OSAL)
The BLE protocol stack, the profiles, and all applications are all built around the Operating System Abstraction Layer (OSAL). The OSAL is not an actual operating system (OS) in the traditional sense, but rather a control loop that allows software to setup the execution of events. For each layer of software that requires this type of control, a task identifier (ID) must be created, a task initialization routine must be defined and added to the OSAL initialization, and an event processing routine must be defined.
Optionally, a message processing routine may be defined as well. Several layers of the BLE stack, for example, are OSAL tasks, with the LL being the highest priority (since it has very strict timing requirements).
In addition to task management, the OSAL provides additional services such as message passing, memory management, and timers. All OSAL code is provided as full source.
Note: The OSAL is capable of providing many more services than are covered in this guide, including message management, timer management, and more; however for many applications this level of depth is not required. This guide should serve as an introduction to the basic framework of the OSAL
Additional information on the OSAL can be found in :
I. Task Initialization
In order to use the OSAL, at the end of the main function there should be a call to osal_start_system. This is the OSAL routine that starts the system, and which will call the osalInitTasks function that is defined by the application. In the SimpleBLEPeripheral project, this function can be found in the file OSAL_SimpleBLEPeripheral.c.
Each layer of software that is using the OSAL must have an initialization routine that is called from the function osalInitTasks. Within this function, the initialization routine for every layer of software is called. As each task initialization routine is called, an 8-bit “task ID” value is assigned to the task. Note that when creating an application, it is very important that it be added to the end of the list, such that it has a higher task ID than the others. This is because the priority of tasks is determined by the task ID, with a lower value meaning higher priority. It is important that the protocol stack tasks have the highest priority in order to function properly. Such is the case with the SimpleBLEPeripheral application: its initialization function is SimpleBLEPeripheral_Init, and it has the highest task ID and therefore the lowest priority.
II. Task Events and Event Processing
After the OSAL completes initialization, it runs the executive loop checking for task events. This loop can be found in the function osal_start_system in the file OSAL.c. Task events are implemented as a 16-bit variable (one for each task) where each bit corresponds to a unique event. The definition and use of these event flags is completely up to the application.
For example, the SimpleBLEPeripheral application defines a flag in simpleBLEPeripheral.h: SBP_START_DEVICE_EVT (0x0001), which indicates that the initial start has completed, and the application should begin. The only flag value which is reserved and cannot be defined by the application is 0x8000, which corresponds to the event SYS_EVENT_MSG (this event is used for messaging between tasks, which is covered in section 3.1.3).
When the OSAL detects an event for a task, it will call that task’s event processing routine. The layer must add its event processing routine to the table formed by the array of function pointers called tasksArr (located in OSAL_SimpleBLEPeripheral.c in the example). You will notice that the order of the event processing routines in tasksArr is identical to the order of task ID’s in the osalInitTasks function. This is required in order for events to be processed by the correct software layer.
In the case of the SimpleBLEPeripheral application, the function is called
SimpleBLEPeripheral_ProcessEvent. Note that once the event is handled and if it is not removed from the event flag, the OSAL will continue to call the task’s process event handler. As can be seen in the SimpleBLEPeripheral application function
SimpleBLEPeripheral_ProcessEvent, after the START_DEVICE_EVT event occurs, it returns the 16-bit events variable with the SBP_START_DEVICE_EVT flag cleared.
It is possible for any layer of the software to set an OSAL event for any other layer, as well as for itself. The simplest way to set up an OSAL event is to use the osal_set_event function (prototype in OSAL.h), which immediately schedules a new event. With this function, you specify the task ID (of the task that will be processing the event) and the event flag as parameters.
Another way to set an OSAL event for any layer is to use the osal_start_timerEx function (prototype in OSAL_Timers.h). This function operates just like the osal_set_event function. You select task ID of the task that will be processing the event and the event flag as parameters; however for a third parameter in osal_start_timerEx you input a timeout value in milliseconds. The OSAL will set a timer, and the specified event will not get set until the timer expires.
III. Heap Manager
OSAL provides basic memory management functions. The osal_mem_alloc function serves as a basic memory allocation function similar to the standard C malloc function, taking a single parameter determining the number of bytes to allocate, and returning a void pointer. If no memory is available, a NULL pointer will be returned. Similarly, the osal_mem_free function works similar to the standard C free function, freeing up memory that was previously allocated using osal_mem_alloc.
The pre-processor define INT_HEAP_LEN is used to reserve memory for dynamic allocation.
To see how much memory you typically need, you can set the pre-processor define
OSALMEM_METRICS=TRUE in the project options. After a stress test of the application where you send as many messages, have as many clients as you will in the worst case, remembering to use bonding and encryption during the test if that’s applicable, you can look at the value of the variable memMax in OSAL_Memory.c to see how much memory was ever allocated at the same time. This figure could be used as a guideline for lowering INT_HEAP_LEN if necessary, but thorough testing is needed, as the heap is used by the BLE stack.
IV. OSAL Messages
OSAL also provides a system for different subsystems of the software to communicate with each other by sending or receiving messages. Messages can contain any type of data and can be any size. To send an OSAL message, first the memory must be allocated by calling the osal_msg_allocate function, passing in the length of the message as the only parameter. A pointer to a buffer containing the allocated space will be returned (you do not need to use osal_mem_alloc when using osal_msg_allocate). If no memory is available, a NULL pointer will be returned. You can then copy the data into the buffer. To send the message, the osal_msg_send should be called, with the destination task for the message indicated as a parameter.
The OSAL will then signal the receiving task that a message is arriving by setting the
SYS_EVENT_MSG flag for that task. This causes the receiving task’s event handler function to be called. The receiving task can then retrieve the data by calling osal_msg_receive, and can process accordingly based on the data received. It is recommended that every OSAL task have a local message processing function (the simpleBLEPeripheral application’s message processing function is simpleBLEPeripheral_ProcessOSALMsg) that decides what action to take based on the type of message received. Once the receiving task has completed processing the message, it must deallocate the memory using the function osal_msg_deallocate (you do not need to use osal_mem_free when using osal_msg_deallocate).