Constructing a real-time operating system for 51 single-chip microcomputer

Publisher:Whisper123Latest update time:2011-03-14 Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere

Abstract : Starting from the memory space management method of Keil C51, this paper focuses on the reentry problem of the real-time operating system during task scheduling, and analyzes some basic ways and methods to solve reentry: analyzing the preemptiveness of task scheduling in the real-time operating system, and proposing that non-preemptive task scheduling is a scheduling method that is more suitable for Keil C51. To this end, this real-time operating system is constructed, and the system's heap management method, task establishment, and task switching are specifically introduced.
Keywords : 51 single-chip microcomputer, real-time operating system, task re-scheduling

At present, most product development is based on some small-capacity single-chip microcomputers. The 51 series single-chip microcomputer is one of the most widely used single-chip microcomputer series in China. It has a very broad application environment and prospects. The accumulation of resources over the years has made the 51 series single-chip microcomputer still the first choice for many developers. In response to this situation, many expansion chips based on the 51 core have emerged in recent years, with more and more complete functions and faster speeds, which also shows the vitality of the 51 series single-chip microcomputer in China from one side.

For many years, we have been looking for a suitable real-time operating system as the basis for our development. According to development requirements, some commonly used embedded components are integrated to save development time and reduce development workload as much as possible; in addition, this real-time operating system is required to be very easily embedded in small-capacity chips. After all, large systems are a minority, while small applications are the majority and widespread. Obviously, μC/OS-II is not suitable for the above requirements, and the RTX Tiny brought by Keil C does not have source code and is not transparent, not to mention its FULL version.

1 Keil C51 and reentrancy
When it comes to real-time operating systems, we cannot ignore the reentrancy problem. For a large memory processor like a PC, this does not seem to be a very troublesome problem. To borrow the words of μC/OS-II RTOS, it is required to use local variables in the reentrant function. However, the stack space of the 5l series microcontroller is very small, limited to 256 bytes, and it is impossible to allocate a local stack space for each function. It is for this reason that Keil C51 uses the so-called overlay technology:
① Local variables are stored in the global RAM space (without considering the situation of expanding external memory);
② When compiling and linking, the local variables are already located;
③ If there is no direct or indirect calling relationship between the functions, their local variable space can be overwritten.

It is for the above reasons that in the Keil C51 environment, pure functions cannot be reentered without processing (such as adding a simulation stack). So how to make the function reusable in Keil C5l environment? The following is an analysis of the basic structure and mode of tasks under the real-time operating system:
vold TaskA(void*ptr){
UINT8 vaL_a;
//Other variable definitions
do{
//Actual user task processing code
}while(1);
}
void TaskB(void*ptr){
UINT8 vaLb;
//Other variable definitions
do{
Funcl();
//Other actual user task processing code
)while(1);
void Funcl(){
UlNT8 val_fa;
//Other variable definitions
//Function processing code
}

In the above code, TaskA and TaskB do not have a direct or indirect calling relationship, so their local variables val_a and val_b can be overwritten, that is, they may be located in the same RAM space. In this way, when TaskA runs for a period of time and changes val_a, TaskB may change val_b when it obtains CPU control and runs. Since they point to the same RAM space, when TaskA regains control of the CPU, the value of val-a has changed, causing the program to run incorrectly, and vice versa. On the other hand, Funcl() has a direct calling relationship with TaskB, so its local variables val_fa and val_b will not be overwritten by each other, but there is no guarantee that its local variable val_fa will not form an overwriting relationship with the local variables of TaskA or other tasks.

Defining local variables such as val_a, val_b and val_fa as static variables (with static indicators) can solve this problem. But the problem is that defining a large number of static type variables will lead to a large amount of RAM space occupied, which may directly lead to insufficient RAM space. Especially in some small-capacity microcontrollers, which generally only have 128 or 256 bytes, a large number of static variable definitions are obviously not suitable under such small RAM resources. This leads to another solution, as shown in the following code:
void TaskC(void){
UINT8 x, v;
whlk(1){
OS_ENTER_CRITICAL();
x=GetX(); (1)
y=GetY(); (2)
//Other codes of the task
OS_EXIT_CRITICAL(); (3)
0SSleep(100); (4)
}
}

In the above code TaskC uses the critical protection method to protect the code from being preempted by interrupts, which effectively solves the problem that the RAM space is too small and it is not suitable to define a large number of static variables. However, if each task adopts this structure, the interrupt will be turned off at the beginning of the task, which will make the real-time performance unable to be guaranteed. Facts have proved that this delay is quite considerable. To use an example to illustrate, if you want to use a dynamically refreshed LED display in the system, it is difficult to ensure the stability and continuity of the display, even if a separate timer is used in the system to do this work (after entering the critical section, EA=0). Secondly, this structure actually converts the preemptive task scheduling into non-preemptive task scheduling. In fact, if there is no interruption between (3) and (4) that causes a task scheduling, it can be understood that the task actively gives up the control of the CPU. If there is an interruption between (3) and (4) that causes a task scheduling, it is just an unnecessary task scheduling. And it is not expected that there will be two or more task schedulings after (3). I believe that readers also have this wish. In addition

, a feature of the task can be found: when the task restarts from (1), it does not matter what the values ​​of the local variables x and y are. That is, even if x and y change after (3), it is no longer important and will not affect the correctness of the program. In fact, this feature is also a common feature of most tasks, at least most of the local variables of most tasks - if the task will not (be preempted) give up the control of the CPU during the entire execution process, then most of its local variables do not need special protection, that is, their scope is only the current execution of the task, and for the above code, it is the code area within the critical protection zone.

2 Should real-time operating systems preempt?
From the above analysis, if you want to keep a function re-enterable, you have to use static variables, and the system's RAM resources will be a severe test; if you use critical sections to protect the operating environment, the real-time performance of the system cannot be guaranteed, and there is a risk of converting preemptive task scheduling to non-preemptive task scheduling. Obviously, using static variables is simple, but there are more inapplicability, and it is also an obstacle to future function adjustments, so it is generally not adopted. Then, we can only work hard on environmental protection, but can we really only sacrifice the real-time performance of the system by entering the critical section to ensure that the task is not preempted? Let's take a look at the basic idea of ​​the critical protection method:
① In a task, if local variables are not preempted in their scope, these variables will not affect the correct execution of the task after the task is deprived of CPU control, regardless of their values;
② Using critical section protection can meet the requirements mentioned above;
③ The resulting reduction in real-time performance and preemptive switching is acceptable. It can be seen that not being preempted is the key to task protection of local variables. In this case, why not abandon preemptive task scheduling? This is a good starting point. For Keil C51, non-preemptive task scheduling may be a better method to better coordinate the established resources of the 51 series microcontrollers. The following is a system that:
① Uses non-preemptive task scheduling;
② Can be used in small-capacity chips. The development goal is that even a small chip like 8051 can use this real-time operating system;
③ Supports priority scheduling to ensure its real-time performance as much as possible.

3 Implementation of real-time operating system
Based on the above analysis and purpose, this operating system was recently completed. The management method of RTx is used on the stack, that is, the current task uses all the heap space, as shown in Figure 1.

3.1 Stack initialization and task creation
Stack initialization actually initializes the 0STaskStackBotton array, and specifies the current task as an idle task, and the next running task as the highest priority task, that is, the task with a priority of zero. During initialization, the value of SP is stored in OSTaskStackBotton[O], the value of SP+2 is stored in OSTaskStacKBotton[1], and so on. The task is created by calling the 0STa-skCreate function. In fact, it just fills the address of the task (assuming it is task number n) into the corresponding position pointed to by OSTaskStackBotton[n], and moves SP backward by 2 bytes, as shown in Figure 2.


Why is it in this way and not in other ways? This is because after the task is established and before the task is scheduled, the stack of each task is actually its own address, so its stack depth is 2, which is directly filled in for the simplicity of the program.
void main(void){
OSInit(); /*Initialize OSTaskStackBcBotton queue*/
TMOD=(TMOD&0XFO)│ 0XOl;
TL0=0xBF;
TH0=0xFC;
TRO=1;
ETO=1;
TFO=0:
OSTaskCreate(TaskA,NULL,0);
OSTaskCreate(TaskB.NULL,1);
OSTaskCreate(TaskC,NULL,2);
OSStart();

In the above code, after all tasks are established, OSStart() is called to start task scheduling. OSStart() is a macro definition, as shown below:
#deflne OSStart() d0{\
OSTaskCreate(TaskIdle, NULL, OS_MAX_TASKS);\
EA=1:\
return;\
}while(O)

First, it creates an idle task and turns on interrupts, and then returns. Where does it return to? We know that the idle task is the lowest priority task. When OSTaskCreate is called to create it, its address will be filled in the SP position, and SP will be moved back 2 bytes (see Figure 2 and description). Therefore, the idle task Taslddle must be at the top of the stack at this time. This means that the return here will definitely return to the idle task. At this point, the system enters normal operation. 3.2 Task switching There are two cases for task switching. When the priority of the current task is lower than that of the next task to obtain CPU control, the contents between the top of the stack of the next task to obtain CPU control and the top of the stack of the current task are moved to the high end of the RAM space to free up all the RAM space for the next task's stack space. At the same time, the corresponding OSTaskStackBotton is updated to point to the bottom of the stack of the new correct task. If the priority of the current task is higher than that of the next task, the opposite movement is performed, as shown in Figures 3 and 4.


All tasks must actively call OSSleep to give up control of the CPU. After a task calls OSSleep, the highest priority ready task will be selected to run.

Conclusion
After the system is completed, the kernel code size is about 400 bytes, occupying 1 timer interrupt and a small amount of memory space. The system is set to have a capacity of 8 tasks, and the actual number of tasks available to users is 7, which can meet general needs and also meet the development requirements for application in small-capacity chips. Since preemptive task scheduling is not used, except for some local variables of individual tasks related to the entire process, other local variables no longer have an overwriting relationship. Since the task actively gives up the CPU control, it is also easy to process individual variables that need to be protected separately. In the system, there is no need to repeatedly switch interrupts throughout the process, and the real-time performance is also very good. Except for individual peripherals with strict timing requirements (such as DSl8820)

Reference address:Constructing a real-time operating system for 51 single-chip microcomputer

Previous article:In-depth analysis of Keil C51 bus peripheral operation problems
Next article:Design of automatic trumpet sound player based on AT89C51 single chip microcomputer

Latest Microcontroller Articles
  • Download from the Internet--ARM Getting Started Notes
    A brief introduction: From today on, the ARM notebook of the rookie is open, and it can be regarded as a place to store these notes. Why publish it? Maybe you are interested in it. In fact, the reason for these notes is ...
  • Learn ARM development(22)
    Turning off and on interrupts Interrupts are an efficient dialogue mechanism, but sometimes you don't want to interrupt the program while it is running. For example, when you are printing something, the program suddenly interrupts and another ...
  • Learn ARM development(21)
    First, declare the task pointer, because it will be used later. Task pointer volatile TASK_TCB* volatile g_pCurrentTask = NULL;volatile TASK_TCB* vol ...
  • Learn ARM development(20)
    With the previous Tick interrupt, the basic task switching conditions are ready. However, this "easterly" is also difficult to understand. Only through continuous practice can we understand it. ...
  • Learn ARM development(19)
    After many days of hard work, I finally got the interrupt working. But in order to allow RTOS to use timer interrupts, what kind of interrupts can be implemented in S3C44B0? There are two methods in S3C44B0. ...
  • Learn ARM development(14)
  • Learn ARM development(15)
  • Learn ARM development(16)
  • Learn ARM development(17)
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号