Why Use RTOS with Limited Resources in Microcontrollers?

For engineers working with microcontrollers, especially those using the 8051 series, the question often arises when discussing RTOS: “Why use RTOS? With such limited resources in microcontrollers, can RTOS ensure efficiency?”

In response to this question, I would counter: “What is your purpose in using a microcontroller? Is it to program in C, assembly, or even binary instructions?” Back in the 1980s, engineers programmed the Z80 using binary instructions; who still does that today? While some still cling to assembly, an increasing number of engineers are using C programming (I initially used assembly as well). Why? Because our goal is to complete projects with quality and efficiency within limited or insufficient time! The tools and methods we use are secondary (unless your project prioritizes cost, in which case development time must also be considered). Time is money; a slight increase in cost for a product on a microcontroller is acceptable. Moreover, when using the 8051 series microcontrollers, resources are often abundant, and the CPU is typically idle, creating conditions favorable for using RTOS.

So, what are the benefits of using RTOS? Let me provide an example. Suppose we are writing a serial communication program with the following protocol:

The data packet length is NBYTE, the start bytes are STARTBYTE1 and STARTBYTE2, and the last byte is a checksum. The intermediate bytes cannot consecutively contain STARTBYTE1 or STARTBYTE2.

First method: handling the protocol in an 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))&∑==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))&∑==0){GetRight=1;Flag=0;break;}Buf[i++]=STARTBYTE1;if(temp==STARTBYTE1){break;}Sum+=temp;if((i>=(NBYTE-3))&∑==0){GetRight=1;Flag=0;break;}Buf[i++]=temp;Flag=2;break;}}}

Second method: using a queue in the interrupt function:

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

The main program continuously calls the function:

unsigned char Buf[NBYTE-2];unsigned char ReadSerial(unsigned char *cp){unsigned char i;unsigned char temp,Sum;temp=queue size;if(temp<(NBYTE)) return 0;dequeue temp;if(temp!=STARTBYTE1) return 0;temp=queue front;if(temp!=STARTBYTE2) return 0;dequeue temp;sum=STARTBYTE1+STARTBYTE2;for(i=0;i{temp=queue front;if(temp==STARTBYTE1){temp=queue second front;if(temp==STARTBYTE2) return 0;}dequeue temp;*cp++=temp;Sum+=temp;}temp=queue front;Sum+=temp;if(Sum!=0) return 0;dequeue temp;return 1;}

Third method: using RTOS interrupt function:

void comm(void) interrupt 4//"Serial port interrupt"{OS_INT_ENTER();if(RI==1){RI=0;OSIntSendSignal(RECIVE_TASK_ID);}OSIntExit();}Task with ID RECIVE_TASK_ID void Recuve(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{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 interpreter task ID);}}

Comparison of these methods:

In terms of readability and ease of programming, the third method is the best (if using goto statements is allowed, the program becomes even simpler and more readable), the second is next (as it requires writing a queue program), and the first is the worst. If the protocol is more complex, this difference becomes even more pronounced. A simple and readable program naturally reduces the chances of errors.

In terms of RAM usage, the third method uses the least, the second the most (due to the large space occupied by the queue), and the first the least.

In terms of interrupt execution time, the third method takes the longest, the second the shortest, and the first is longer.

From a functionality perspective, the third method is the strongest; it can also handle timeouts (although the example program does not), while the other methods cannot.

If data arrives too quickly for the command processing program to handle, the three methods differ in their handling: the first and third methods are similar: they discard previous data, while the second discards the later data. Moreover, the second method must wait for the command processing program to finish before processing the next data packet, while the first and third methods can process the next data packet as soon as the command processing program has received the data. In other words, the first and third methods process in parallel with the command processing program, while the second method processes serially.

Currently, development efficiency is prioritized over execution efficiency (including execution time and resource usage). In this case, sacrificing some efficiency for a significant increase in development efficiency is worthwhile. Furthermore, high execution efficiency of a single module does not equate to high execution efficiency of the entire program. For example, if the program needs to wait for a period, it typically uses program delay or timer delay. Regardless of the method, the CPU does not handle other tasks during this time, resulting in low efficiency. However, with RTOS, the CPU can handle other tasks while waiting, improving efficiency.

Excerpted from “uC/OS-II–The Open Source Real-Time Embedded Operating System”

“Real-time kernels are also known as real-time operating systems or RTOS. Using them makes the design and expansion of real-time applications easier. New functionalities can be added with minimal changes. By dividing applications into several independent tasks, RTOS greatly simplifies the application design process. With a preemptive kernel, all time-critical events are handled as quickly and efficiently as possible. Through effective services such as semaphores, mailboxes, queues, delays, and timeouts, RTOS allows for better resource utilization.

“If the application project can tolerate additional requirements, a real-time kernel should be considered. These additional requirements include: the cost of the kernel, extra ROM/RAM overhead, and an additional CPU load of 2 to 4 percent.

“Another factor not mentioned is the increased cost associated with using a real-time kernel. In some applications, price is everything, to the extent that they wouldn’t even consider using RTOS.”

In summary, what is suitable is the best; do not reject RTOS. When applicable, it works very well.

This article is reprinted from: Embedded ARM

Leave a Comment