Panel For Example Panel For Example Panel For Example

Why Use an RTOS on a Microcontroller?

Author : Adrian September 18, 2025

Introduction

For engineers working with microcontrollers, especially those using the 8051 series, the question often comes up: "Why use an RTOS? Microcontrollers have limited resources; will an RTOS be efficient?" A useful counter-question is: "What is your goal in using the microcontroller? To program in C, to program in assembly, or to program in binary opcodes?" In the 1980s engineers programmed the Z80 in binary; very few do that now. Some still insist on assembly, but more engineers use C because it helps deliver projects on schedule and with required quality.

The priority is finishing the project on time and to spec. The choice of tools and methods is secondary. If cost is the overriding constraint, that is a different discussion, but development time still matters. A product can often tolerate a small additional microcontroller cost. Furthermore, when using 8051-family microcontrollers, resources are often not fully utilized and the CPU is frequently idle, which makes using an RTOS feasible. What are the benefits of an RTOS? The following example illustrates the differences.

Example: Serial Protocol Handling

Suppose we implement a serial communication protocol with these rules: packet length is NBYTE, start bytes are STARTBYTE1 and STARTBYTE2, the last byte is a checksum, and the bytes between cannot contain the sequence STARTBYTE1 followed by STARTBYTE2. Consider three methods of implementing the receiver.

Method 1: Handle the protocol inside the interrupt

unsigned char Buf[NBYTE-2];
bit GetRight = 0;
void comm(void) interrupt 4 // "serial port interrupt"
{
    static unsigned char Sum,Flag = 0,i;
    unsigned char temp;
    if(RI == 1)
    {
        RI = 0;
        temp = SBUF;
        switch(Flag)
        {
        case 0:
            if(temp == STARTBYTE1)
            {
                Flag = 1;
            }
            break;
        case 1:
            if(temp == STARTBYTE2)
            {
                Sum = STARTBYTE1 + STARTBYTE2;
                i = 0;
                Flag = 2;
                break;
            }
            if(temp == STARTBYTE1) break;
            Flag = 0;
            break;
        case 2:
            if(temp == STARTBYTE1)
            {
                Flag = 3;
                break;
            }
            Sum += temp;
            if((i <= (NBYTE-3)) && Sum == 0)
            {
                GetRight = 1;
                Flag = 0;
                break;
            }
            Buf[i++] = temp;
            break;
        case 3:
            if(temp == STARTBYTE2)
            {
                Sum = STARTBYTE1 + STARTBYTE2;
                Flag = 2;
                i = 0;
                break;
            }
            Sum += STARTBYTE1;
            if((i <= (NBYTE - 3)) && Sum == 0)
            {
                GetRight = 1;
                Flag = 0;
                break;
            }
            Buf[i++] = STARTBYTE1;
            if(temp == STARTBYTE1)
            {
                break;
            }
            Sum += temp;
            if((i <= (NBYTE-3) && Sum == 0)
            {
                GetRight = 1;
                Flag = 0;
                break;
            }
            Buf[i++] = temp;
            Flag = 2;
            break;
        }
    }
}
 

Method 2: Interrupt only enqueues received bytes

void comm(void) interrupt 4 // "serial port interrupt" { if(RI == 1) { RI = 0; enqueue(SBUF); } }

The main loop continuously calls a function that parses the queue:

unsigned char Buf[NBYTE-2];
unsigned char ReadSerial(unsigned char *cp)
{
    unsigned char i;
    unsigned char temp,Sum;
    temp = queue_count();
    if(temp < (NBYTE)) return 0;
    dequeue(temp);
    if(temp != STARTBYTE1) return 0;
    temp = queue_peek();
    if(temp != STARTBYTE2) return 0;
    dequeue(temp);
    Sum = STARTBYTE1 + STARTBYTE2;
    for(i = 0;i < NBYTE-3;i++)
    {
        temp = queue_peek();
        if(temp == STARTBYTE1)
        {
            temp = queue_peek_next();
            if(temp == STARTBYTE2) return 0;
        }
        dequeue(temp);
        *cp++ = temp;
        Sum += temp;
    }
    temp = queue_peek();
    Sum += temp;
    if(Sum != 0) return 0;
    dequeue(temp);
    return 1;
}

Method 3: Interrupt signals an RTOS task

void comm(void) interrupt 4 // "serial port interrupt"
{
    OS_INT_ENTER();
    if(RI == 1)
    {
        RI = 0;
        OSIntSendSignal(RECIVE_TASK_ID);
    }
    OSIntExit();
}

The task with ID RECIVE_TASK_ID runs the following:

void Recive(void)
{
    unsigned char temp,temp1,Sum,i;
    OSWait(K_SIG,0);
    temp = SBUF;
    while(1)
    {
        while(1)
        {
            OSWait(K_SIG,0);
            temp1 = SBUF;
            if((temp == STARTBYTE1)&&(temp1 == STARTBYTE2)) break;
            temp = temp1;
        }
        Sum = STARTBYTE1 + STARTBYTE2;
        OSWait(K_SIG,0);
        temp = SBUF;
        for(i = 0;i < NBYTE-3;i++)
        {
            OSWait(K_SIG,0);
            temp1 = SBUF;
            if((temp == STARTBYTE1)&&(temp1 == STARTBYTE2))
            {
                OSWait(K_SIG,0);
                temp = SBUF;
                i = -1;
                Sum = STARTBYTE1 + STARTBYTE2;
                continue;
            }
            Buf[i] = temp;
            Sum += temp;
            temp = temp1;
        }
        Sum += temp1;
        if(Sum == 0) OSSendSignal(COMMAND_PARSE_TASK_ID);
    }
}

Comparison

Readability and ease of programming: the third method is best (the code is simpler and more readable; using goto in the example would make it even simpler), the second is next (because you must implement a queue), and the first is relatively the worst. The difference becomes more apparent as the protocol complexity increases. Simpler, more readable code naturally reduces the chance of bugs.

RAM usage: the third method is relatively small, the second is the largest (queue consumes a lot of space), and the first is the smallest. Interrupt execution time: the third method is the longest, the second the shortest, and the first is long. Functionality: the third method is the most powerful; it can support timeout handling (not shown in the example), which the other methods cannot provide easily.

If data arrives too fast and the command handler cannot keep up, the three methods behave differently. Methods one and three are similar: older data may be dropped. The second method drops new incoming data once the queue is full. Also, the second method must wait for the command handler to complete before processing the next packet, whereas methods one and three allow the command handler to work in parallel with packet reception; they only need the command handler to fetch the data before the next packet can be processed. In other words, methods one and three enable parallel processing with the command handler while method two tends toward serial processing.

When an RTOS Makes Sense

In general, development efficiency is often prioritized over maximum execution efficiency. Sacrificing a small amount of runtime efficiency to gain a large improvement in development productivity is often acceptable. Moreover, high efficiency in a single module does not guarantee higher overall program efficiency. For example, if a program needs to wait for some period, a busy delay or a simple timer-based delay will leave the CPU idle. An RTOS allows the CPU to perform other tasks during wait periods, improving overall utilization.

Quoted from uC/OS-II documentation:

"A real-time kernel, also called a real-time operating system or RTOS, makes the design and extension of real-time applications easier. New features can be added without major changes. By dividing the application into several independent tasks, the RTOS simplifies the application design process. With a preemptible kernel, time-critical events are handled as quickly and efficiently as possible. Effective services such as semaphores, mailboxes, queues, delays, and timeouts enable better resource utilization. If an application can tolerate the additional requirements, a real-time kernel should be considered. These additional requirements include the kernel cost, extra ROM/RAM consumption, and a 2 to 4 percent CPU overhead."

In short, use the right tool for the job. Do not reject an RTOS when it is appropriate; in suitable cases it works well.