sl_main Baremetal to RTOS Application Transition#
Table of Contents#
Overview#
This document provides a guide to help developers transition from baremetal to RTOS-based applications using the sl_main software module. It covers the differences between these application types, explains the initialization flow variations, outlines the step-by-step migration process, and addresses common issues that might arise during the transition.
The guide is intended for developers who need to:
Understand the structural differences between baremetal and RTOS applications in sl_main
Convert an existing baremetal application to use an RTOS
Implement proper initialization sequences in an RTOS environment
Configure and optimize RTOS applications using sl_main
By following this migration guide, developers will be able to successfully transition their applications while maintaining proper system initialization and operation.
Key Differences Between sl_main baremetal and RTOS Based Applications#
Feature | Baremetal | RTOS |
---|---|---|
Main loop | Explicit super-loop in | No super-loop; task-based execution |
Execution model | Sequential processing | Concurrent task execution |
Initialization flow | All initialization occurs in sequence | Two-stage initialization with kernel start between stages |
Event processing | Called explicitly in main loop | Handled in separate tasks |
RTOS Structures and the Main Function#
In baremetal applications, the main
function contains the infinite loop that drives the application. With RTOS-based applications using sl_main:
The
main
function is called from an initial RTOS task called the start task, which is created by sl_main. By default, this task has the highest possible priority. The priority of the start task can be changed, See Step 4: Configure start task for more details.This task runs all the initialization countained in (
sl_main_second_stage_init
andapp_init
)The application execution is driven by the RTOS scheduler and application-specific tasks
Application Architecture#
Baremetal Architecture#
Baremetal applications using sl_main have a sequential architecture:
main() → sl_main_init() → app_init() → while(1) loop:
├─ sl_main_process_action()
├─ app_process_action()
└─ power management/sleep
All code executes sequentially in a single execution context
Processing is driven by the super-loop in
main
Each component gets processing time in a predetermined order
The system sleeps when no processing is required
RTOS Architecture#
RTOS applications using sl_main have a concurrent, task-based architecture:
sl_main_init() → RTOS kernel start → Start task:
├─ sl_main_second_stage_init()
└─ app_init() → Create application tasks
[Optional: Start task deletes itself]
Task 1 ─┐
Task 2 ─┼─ Scheduled by the RTOS
Task N ─┘
Multiple tasks execute concurrently based on priority and state
Processing is driven by the RTOS scheduler
Components can have dedicated tasks or shared tasks
The system sleeps when no tasks are ready to run
Application Flow Differences#
Baremetal Application Flow#
In a baremetal application, the initialization flow is straightforward:
sl_main_init
- System initialization:Memory management initialization
Chip initialization routine for revision errata workarounds. (
CHIP_Init
)User pre-clock initialization (
app_init_pre_clock
)Interrupt management system setup
Infrastructure initialization (oscillators, clocks, DCDC, powermanager, and Sleeptimer)
User early initialization (
app_init_early
)System permanent memory allocations (platform components and stacks)
sl_main_second_stage_init()
sl_platform_init
- Platform initializationsl_driver_init
- Driver initializationsl_service_init
- Service initializationsl_stack_init
- Protocol stack initializationsl_internal_app_init
- Internal application initialization
app_init() - Application-specific initialization
Main loop
sl_main_process_action
- System component processingapp_process_action
- Application processingPower management / sleep
RTOS Application Flow#
In an RTOS application, the initialization flow is split between pre-RTOS and post-RTOS contexts:
sl_main_init
- Pre-kernel initialization:Memory management initialization
Chip initialization routine for revision errata workarounds.
CHIP_Init
User pre-clock initialization (
app_init_pre_clock
)Interrupt management system setup
Infrastructure initialization (oscillators, clocks, DCDC, powermanager and Sleeptimer)
User early initialization (
app_init_early
)System permanent memory allocations (platform components and stacks)
RTOS kernel structures initialization (
osKernelInitialize
)Start task creation with highest priority
RTOS kernel starts
The scheduler begins running the start task
In the start task context:
sl_main_second_stage_init
sl_platform_init
- Platform initializationsl_driver_init
- Driver initializationsl_service_init
- Service initializationsl_stack_init
- Protocol stack initializationsl_internal_app_init
- Internal application initialization
app_init
- Application-specific initialization, including task creation
RTOS scheduler
Application tasks run according to their priorities and states
Start task either terminates or continues based on configuration
Migration Process#
Migrate from a Baremetal to an RTOS Application#
Step 1: Save previous code and delete main.c file#
Before migrating, save your application code but prepare to replace the main.c file:
Save your main.c content for reference, especially:
Your
app_init
implementationYour
app_process_action
implementationAny custom code in
main
Make a backup of any custom initialization you've implemented
Delete your main.c file so that it's replaced by the new template
Step 2: Add RTOS of choice and regenerate project#
Modify your project configuration to include an RTOS:
In Simplicity Studio, add either "FreeRTOS Kernel" or "Micrium OS Kernel" (Series 2 only) components to your project
If using the command line, modify your .slcp file to include the desired RTOS components
Regenerate your project:
In Simplicity Studio, clicking on the install button will trigger a project regeneration event
With the CLI, run
slc generate
to regenerate project filesKeep in mind that a new main.c file will be added at the root of your project
Step 3: Update application code structure#
Implement or update your
app_init
function:Write task creation code here
Create required RTOS primitives (semaphores, queues, etc.)
Initialize application-specific resources
void app_init(void) { // Initialize application resources initialize_app_resources(); // Create application tasks xTaskCreate(app_task_function, "App Task", APP_TASK_STACK_SIZE, NULL, APP_TASK_PRIORITY, NULL); // Create RTOS primitives app_semaphore = xSemaphoreCreateBinary(); app_queue = xQueueCreate(10, sizeof(message_t)); }
Replace app_process_action() with task functions:
Move code from app_process_action() to appropriate task functions
Use RTOS primitives for synchronization and communication between tasks
void app_task_function(void *p_arg) { (void)p_arg; while (1) { // Code moved from app_process_action() process_application_logic(); // Use RTOS primitives for synchronization xSemaphoreTake(app_semaphore, portMAX_DELAY); // Yield or delay as appropriate vTaskDelay(pdMS_TO_TICKS(10)); } }
Optionally implement
app_init_early
,app_init_pre_clock
orapp_permanent_memory_alloc
for respectively pre-RTOS initialization, pre clock initialization and permanent memory allocation:void app_init_early(void) { // Pre-RTOS initialization if needed // This runs before the kernel is started } void app_init_pre_clock(void) { // Before essential infrastructure components are initialized // like (clock, oscillators, interrupt management, etc.). } void app_permanent_memory_alloc(void) { // Calls to memory manager permanent memory allocation. }
Step 4: Configure start task#
If you are in Simplicity Studio, please open the sl_main component. Otherwise you can configure the start task properties in sl_main_start_task_config.h:
Adjust the start task stack size if needed:
#define SL_MAIN_START_TASK_STACK_SIZE_BYTES 4096
If you decide to keep the start task running, you may change its priority by setting the following configurations:
#define SL_MAIN_ENABLE_START_TASK_PRIORITY_CHANGE 1 #define SL_MAIN_START_TASK_PRIORITY osPriorityNormal
In the file of your choice:
Optionally, keep the start task running, implement
sl_main_start_task_should_continue
:bool sl_main_start_task_should_continue(void) { return true; // Return true to keep the start task running }
Create Baremetal-Like Application Using RTOS with Event System#
The publish/subscribe inter-process communication (IPC) design using the Event System provides an elegant transition path for developers moving from baremetal to RTOS-based applications on Series 3 platforms, where RTOS usage is mandatory. This approach creates an environment that feels familiar to baremetal developers by maintaining a similar programming model.
Instead of the traditional super-loop architecture, the application runs as a single RTOS task that waits for events from multiple system tasks through the Event System. The Event System enables inter-process communication using a publish/subscribe mechanism. This design allows developers to maintain a recognizable code structure while adapting to the RTOS requirements.
The key difference from traditional baremetal applications is in how commands are issued to protocol stacks. Rather than direct function calls, the application must use the Event System to communicate with system tasks. While this introduces some context-switching latency compared to true baremetal applications, it provides a simplified migration path that minimizes the learning curve.
When generating an RTOS sample application, a start task is created by default during system initialization. The start task calls the main function, which in turn calls the app_process_action()
function. This function is where user code will be executed. It is important to note that the weak function sl_main_start_task_should_continue()
must be reimplemented to return true.
Important Note: The supervisor mode must be enabled by adding the component Event System Supervisor Mode
to the project in Simplicity Studio.
To demonstrate Baremetal-Like application with Event System, we will reimplement the weak function app_process_action()
to process all system events and reimplement the weak function sl_main_start_task_should_continue()
to return true to keep the start task running, which emulates the super loop of a baremetal application. The IRQ event system support is initialized during application initialization with sl_event_irq_publisher_init()
. The sleeptimer callback publishes the IRQ event with sl_event_irq_publish()
that will be decoded and processed in the switch case in the app_process_action()
function.
In this RTOS implementation, the event system captures and routes system events to your application. The implementation of app_process_action()
provides users with placeholders to add code for processing different events.
In more complex applications, you can extend this pattern by having multiple tasks contribute events for processing, while maintaining the central event processing loop in your main application task.
main.c file:
#include "sl_main_init.h"
#include "sl_main_kernel.h"
int main(void)
{
// Initialize Silicon Labs device, system, service(s) and protocol stack(s).
sl_main_second_stage_init();
// Initialize application
app_init();
// Main processing loop - similar to baremetal super-loop
while (sl_main_start_task_should_continue()) {
app_process_action();
}
}
Updated app.c file:
// Periodic timer handler
static sl_sleeptimer_timer_handle_t periodic_timer;
// Periodic timer callback
static void on_periodic_timeout(sl_sleeptimer_timer_handle_t *handle,
void *data)
{
(void)&handle;
(void)&data;
// Toggle a LED every time the timer expires.
sl_led_toggle(&YOUR_LED_INSTANCE);
// Publish IRQ event
sl_status_t status = sl_event_irq_publish(sleeptimer_irqn);
if (status != SL_STATUS_OK) {
// Handle the error
}
// sleeptimer_irqn depends on the hardware timer IRQn used (RTCC, SYSRTC)
}
/***************************************************************************//**
* Initialize application.
******************************************************************************/
void app_init(void)
{
sl_status_t status;
// Initialize event system IRQ events publisher
status = sl_event_irq_publisher_init();
if ( status != SL_STATUS_OK ) {
// Handle the error.
}
// Other application-specific initialization code goes here
// Initialize a periodic timer that triggers an IRQ every 1 second
// Create timer for waking up the system periodically.
sl_sleeptimer_start_periodic_timer_ms(&periodic_timer,
1000,
on_periodic_timeout, NULL,
0,
SL_SLEEPTIMER_NO_HIGH_PRECISION_HF_CLOCKS_REQUIRED_FLAG);
}
/******************************************************************************
* @brief User-defined function to determine if the start task should continue.
*
* @note By default the start task should not continue, but this function can be
* re-implemented to force the start task to continue running.
*****************************************************************************/
bool sl_main_start_task_should_continue(void)
{
return true;
}
/******************************************************************************
* @brief User-defined function to process events in a baremetal-like manner.
*
* @details This function processes events from the event system in a loop,
* similar to how a baremetal application would handle events.
* This function will be called continuously in the main loop.
*****************************************************************************/
void app_process_action(void)
{
sl_event_t* event_ptr;
sl_status_t status;
// Process events one by one in a loop
while (SL_STATUS_OK == sl_event_supervisor_queue_get(&event_ptr)) {
switch (event_ptr->event_class) {
case SL_EVENT_CLASS_BLUETOOTH:
// Add event handling here
break;
case SL_EVENT_CLASS_IRQ:
// Decode IRQ event
uint32_t irqn = sl_event_irq_decode(event_ptr);
if ( irqn != 0xFFFFFFFFUL ) {
// Add event handling here
}
break;
// Handle additional event classes as needed
// case SL_EVENT_CLASS_YOUR_EVENT:
// Add event handling here
// break;
}
// Process the event (frees resources)
status = sl_event_process(&event_ptr);
if (status != SL_STATUS_OK) {
// Handle error if needed
}
}
// Add other periodic processing code here
// This is similar to what you would do in a baremetal super-loop
// ...
}
Troubleshooting Common Issues#
Kernel Structure Initialization and Use#
Problem: RTOS structures (semaphores, mutexes, etc.) are used before being properly initialized.
Solution:
Ensure RTOS structures are created in
app_init
or within tasks, not inapp_init_early
Double-check that any system component that requires RTOS primitives is initialized after the RTOS kernel starts
Review initialization order to ensure dependencies are satisfied
Start Task Priority and Configuration#
Problem: The start task priority causes issues with initialization sequence or other tasks preempt initialization.
Solution:
Remember that the start task runs at maximum priority (osPriorityRealtime7) during sl_main_init() and sl_main_second_stage_init() but not during app_init if you enabled
SL_MAIN_ENABLE_START_TASK_PRIORITY_CHANGE
.If keeping the start task running (sl_main_start_task_should_continue() is redefined to return true), make sure SL_MAIN_ENABLE_START_TASK_PRIORITY_CHANGE is enabled and choose an appropriate priority in SL_MAIN_START_TASK_PRIORITY
If initialization is being preempted, check that other tasks aren't starting too early
Check for priority inversions if using RTOS mutexes
Memory Allocation Considerations#
Problem: Stack overflow on start task process.
Solution:
Adjust SL_MAIN_START_TASK_STACK_SIZE_BYTES if the default 4096 bytes is insufficient
Process Action Handling#
Problem: Functionality that worked in baremetal's process_action is not working in RTOS tasks.
Solution:
When migrating from baremetal, the
sl_main_process_action
call in the super-loop must be replaced by appropriate task structuresCreate dedicated tasks for components that were previously serviced by process_action calls
Use RTOS primitives (semaphores, queues, notifications) to trigger processing instead of relying on the main loop
Ensure periodic processing is handled by task delays (vTaskDelay) rather than polling
Components that expected to be called regularly from the main loop may need redesigning to work in a task-based environment