sl_main Baremetal to RTOS Application Transition#

Table of Contents#

  1. Overview

  2. Key Differences Between sl_main baremetal and RTOS Based Applications

  3. Application Architecture

  4. Application Flow Differences

  5. Migration Process

  6. Troubleshooting Common Issues

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 main

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 and app_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:

  1. 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)

  2. sl_main_second_stage_init()

    • sl_platform_init - Platform initialization

    • sl_driver_init - Driver initialization

    • sl_service_init - Service initialization

    • sl_stack_init - Protocol stack initialization

    • sl_internal_app_init - Internal application initialization

  3. app_init() - Application-specific initialization

  4. Main loop

    • sl_main_process_action - System component processing

    • app_process_action - Application processing

    • Power management / sleep

RTOS Application Flow#

In an RTOS application, the initialization flow is split between pre-RTOS and post-RTOS contexts:

  1. 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

  2. RTOS kernel starts

    • The scheduler begins running the start task

  3. In the start task context:

    • sl_main_second_stage_init

      • sl_platform_init - Platform initialization

      • sl_driver_init - Driver initialization

      • sl_service_init - Service initialization

      • sl_stack_init - Protocol stack initialization

      • sl_internal_app_init - Internal application initialization

    • app_init - Application-specific initialization, including task creation

  4. 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:

  1. Save your main.c content for reference, especially:

    • Your app_init implementation

    • Your app_process_action implementation

    • Any custom code in main

  2. Make a backup of any custom initialization you've implemented

  3. Delete your main.c file so that it's replaced by the new template

Step 2: Add RTOS of choice and regenerate project#

  1. 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

  2. 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 files

    • Keep in mind that a new main.c file will be added at the root of your project

Step 3: Update application code structure#

  1. 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));
    }
  2. 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));
    }
    }
  3. Optionally implement app_init_early, app_init_pre_clock or app_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:

  1. Adjust the start task stack size if needed:

    #define SL_MAIN_START_TASK_STACK_SIZE_BYTES  4096
  2. 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:

  3. 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 in app_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 structures

  • Create 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