sl_main Baremetal to RTOS Application Transition#

Table of Contents#

  1. Overview

  2. Key Differences Between sl_main baremetal and RTOS 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#

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" 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
    }

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