Building Your Own Efficient Time-Sharing Operating System for Microcontrollers

Step-by-step guide to building an efficient time-sharing OS for microcontrollers! Here’s how to solve the challenges of multitasking concurrency.

In microcontroller development, have you ever faced the dilemma of needing to handle urgent tasks like real-time data collection and device control, while also managing regular operations such as data analysis and command responses that can be processed less frequently, like once a minute or hour? This kind of multitasking with varying priorities often leads to response delays and resource conflicts. In fact, an efficient time-sharing operating system can easily solve this problem. Today, we will take you through the principles and practical implementation of building a time-sharing OS for microcontrollers, which even beginners can directly apply!

Building Your Own Efficient Time-Sharing Operating System for Microcontrollers

1. Core Logic of Time-Sharing OS: Frontend and Backend + Dual-Driver Mode

The core of the microcontroller time-sharing operating system is to efficiently support multitasking with limited hardware resources. The key lies in two major designs: “task prioritization” and “precise triggering”.

Building Your Own Efficient Time-Sharing Operating System for Microcontrollers

2. Practical Code: Building a Usable Time-Sharing OS from Scratch

After understanding the principles, let’s dive into a practical code framework! The following is designed based on the STM32 microcontroller, implementing five levels of time-sharing tasks at 10ms, 100ms, 1s, 1min, and 1h. Beginners can directly replace the “user program” section.

1. Main Function Framework: Background Task Loop Execution

The main function is responsible for initializing devices and then continuously calling different periodic background tasks, with a very simple structure:

int main(void) {
    SystemInit(); // System initialization
    DeviceInit(); // Device initialization (including timer configuration)
    while (1) // Background loop
    {
        TenMSTask(); // 10ms periodic task
        HundredMSTask(); // 100ms periodic task
        OneSTask(); // 1s periodic task
        OneMTask(); // 1min periodic task
    }
}

2. Device Initialization: Timer + Watchdog Configuration

The core of initialization is configuring a 10ms timer (for time-sharing timing) and a watchdog (to prevent the program from hanging):

void DeviceInit(void)
{
    __disable_irq(); // Disable interrupts for safe initialization
    Gpio_Init(); // GPIO initialization
    TIM3_10MS_Configuration(); // Configure 10ms timer
    IWDG_Config(); // Enable independent watchdog
    __enable_irq(); // Enable interrupts
    IWDG_ReloadCounter(); // Feed the watchdog to avoid reset during initialization
}

3. Timer Interrupt: The “Time Scale” for Time-Sharing Tasks

Through the 10ms timer interrupt, different periodic task flags are generated, effectively assigning an “execution schedule” to each task:

typedef struct
{
    volatile unsigned char  _1MS_Count;
    volatile unsigned char  _10MS_Count;
    volatile unsigned char  _100MS_Count;
    volatile unsigned char  _1S_Count;
    volatile unsigned char  _1MINUTE_Count;
    volatile unsigned char  _1MS_Flag;
    volatile unsigned char  _10MS_Flag;
    volatile unsigned char  _100MS_Flag;
    volatile unsigned char  _1S_Flag;
    volatile unsigned char  _1MINUTE_Flag;
    volatile unsigned char  _1HOUR_Flag;
} Timer0_Manager_t;
Timer0_Manager_t Timer0_Manager;

void TIM3_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM3,TIM_IT_Update))
    {
        Timer0_Manager._10MS_Flag = TRUE;
        Timer0_Manager._10MS_Count++; /*10MS timing variable accumulation*/
        if (Timer0_Manager._10MS_Count > 9) /*10MS*/
        {
            Timer0_Manager._10MS_Count = 0;
            Timer0_Manager._100MS_Flag = TRUE;
            Timer0_Manager._100MS_Count++; /*100MS timing variable accumulation*/
            if (Timer0_Manager._100MS_Count > 9) /*100MS */
            {
                Timer0_Manager._100MS_Count = 0;
                Timer0_Manager._1S_Flag = TRUE;
                Timer0_Manager._1S_Count++; /*1 second timing variable accumulation*/
                if(Timer0_Manager._1S_Count > 60 ) /*1S */
                {
                    Timer0_Manager._1S_Count = 0; /*1 second timing variable reset*/
                    Timer0_Manager._1MINUTE_Flag = TRUE; // Minute flag set
                    Timer0_Manager._1MINUTE_Count++; /*1 minute timing variable accumulation*/
                    if(Timer0_Manager._1MINUTE_Count > 60 ) /*1MINUTE */
                    {
                        Timer0_Manager._1MINUTE_Count = 0; /*1 minute timing variable reset*/
                        Timer0_Manager._1HOUR_Flag = TRUE; // Hour flag set
                    }
                }
            }
        }
    }
    TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
}

4. Time-Sharing Task Functions: Execute Corresponding Logic Based on Flags

Each task function checks the corresponding time flag, and when the flag is true, it executes the user program and resets the flag afterward:

void TenMSTask(void)
{
    if (Timer0_Manager._10MS_Flag == TRUE)
    {
        Timer0_Manager._10MS_Flag = FALSE;
        // User program
    }
}

void HundredMSTask(void)
{
    if (Timer0_Manager._100MS_Flag == TRUE)
    {
        Timer0_Manager._100MS_Flag = FALSE;
        // User program
    }
}

void OneSTask(void)
{
    if (Timer0_Manager._1S_Flag == TRUE)
    {
        Timer0_Manager._1S_Flag = FALSE;
        // User program
        IWDG_ReloadCounter(); /* Feed the watchdog */
    }
}

Building Your Own Efficient Time-Sharing Operating System for Microcontrollers

3. Core Advantages of This Time-Sharing OS

High resource utilization: No complex scheduling algorithms are needed; multitasking is achieved through time-sharing using timers, suitable for resource-limited microcontrollers;

Controllable real-time performance: By using interrupt priorities and time flags, high-priority tasks can respond promptly;

Extremely extensible: Adding new tasks only requires adding corresponding periodic flags and task functions without needing to restructure the framework;

Maximum stability: Integrated independent watchdog to prevent program crashes, suitable for harsh scenarios like industrial control.

This time-sharing OS framework has been validated in multiple microcontroller projects. What multitasking challenges have you encountered in your development? Feel free to leave a comment, and we will break down more practical optimization techniques in the future!

If you find this useful, remember to like + follow, and share it with your embedded development friends! Follow me for more microcontroller tutorials!

Leave a Comment