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 my country. It has a very broad application environment and prospects. With the accumulation of resources over the years, the 51 series single-chip microcomputer is 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 aspect.
For many years, we have been looking for a suitable real-time operating system as the basis for our development. According to the development requirements, we need to integrate some commonly used embedded components to save development time and minimize the development workload; in addition, we need to require that this real-time operating system can be easily embedded in small-capacity chips. After all, large systems are few, while small applications are many and widespread. Obviously, μC/OS-II is not suitable for the above requirements, and the RTX Tiny provided by Keil C does not come with source code and is not transparent, not to mention its FULL version.
1 Keil C51 and reentrancy issues
When it comes to real-time operating systems, we cannot ignore the reentry 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 reentry 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 heap 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 case of extended external memory);
②When compiling and linking, the local variables have been located;
③If there is no direct or indirect calling relationship between functions, their local variable spaces can be overwritten.
It is for the above reasons that in the Keil C51 environment, pure functions cannot be re-implemented without processing (such as adding a simulation stack). So how to make the function re-implementable in the Keil C51 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;
//Some other variable definitions
do{
//Actual user task processing code
}while(1);
}
void TaskB(void*ptr){
UINT8 vaLb;
//Some other variable definitions
do{
Funcl();
//Other actual user task processing codes
)while(1);
void Funcl() {
UlNT8 v al_fa;
//Definition of other variables
//Function processing code
}
In the above code, there is no direct or indirect calling relationship between TaskA and TaskB, 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. Because they point to the same RAM space, when TaskA regains CPU control, 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 it cannot 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 (adding the static indicator) 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 being 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 resource conditions. Therefore, there is 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 for 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. It turns out 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) and a task scheduling does not happen, it can be understood that the task actively gives up the control of the CPU. If an interrupt happens to occur between (3) and (4) and a task scheduling occurs, it is just an unnecessary task scheduling. And it is not expected that two or more task scheduling will occur after (3). I believe that readers also have this wish.
In addition, we can find a feature of the task: when the task restarts from (1), it does not matter what the values of 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 local variables of most tasks - if the task will not give up CPU control (before being preempted) 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.
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 my country. It has a very broad application environment and prospects. With the accumulation of resources over the years, the 51 series single-chip microcomputer is 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 aspect.
For many years, we have been looking for a suitable real-time operating system as the basis for our development. According to the development requirements, we need to integrate some commonly used embedded components to save development time and minimize the development workload; in addition, we need to require that this real-time operating system can be easily embedded in small-capacity chips. After all, large systems are few, while small applications are many and widespread. Obviously, μC/OS-II is not suitable for the above requirements, and the RTX Tiny provided by Keil C does not come with source code and is not transparent, not to mention its FULL version.
1 Keil C51 and reentrancy issues
When it comes to real-time operating systems, we cannot ignore the reentry 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 reentry 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 heap 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 case of extended external memory);
②When compiling and linking, the local variables have been located;
③If there is no direct or indirect calling relationship between functions, their local variable spaces can be overwritten.
It is for the above reasons that in the Keil C51 environment, pure functions cannot be re-implemented without processing (such as adding a simulation stack). So how to make the function re-implementable in the Keil C51 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;
//Some other variable definitions
do{
//Actual user task processing code
}while(1);
}
void TaskB(void*ptr){
UINT8 vaLb;
//Some other variable definitions
do{
Funcl();
//Other actual user task processing codes
)while(1);
void Funcl() {
UlNT8 v al_fa;
//Definition of other variables
//Function processing code
}
In the above code, there is no direct or indirect calling relationship between TaskA and TaskB, 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. Because they point to the same RAM space, when TaskA regains CPU control, 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 it cannot 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 (adding the static indicator) 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 being 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 resource conditions. Therefore, there is 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 for 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. It turns out 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) and a task scheduling does not happen, it can be understood that the task actively gives up the control of the CPU. If an interrupt happens to occur between (3) and (4) and a task scheduling occurs, it is just an unnecessary task scheduling. And it is not expected that two or more task scheduling will occur after (3). I believe that readers also have this wish.
In addition, we can find a feature of the task: when the task restarts from (1), it does not matter what the values of 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 local variables of most tasks - if the task will not give up CPU control (before being preempted) 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 the real-time operating system take precedence?
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 functional adjustments, so it is generally not adopted. Then, we can only work 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 preemptively switched within their scope, these variables will not affect the correct execution of the task even if their values are not concerned after the task is deprived of CPU control;
②Using critical area protection can achieve the above-mentioned requirements;
③ 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 microcontroller. Write such a system below:
①Use non-preemptive task scheduling;
② It 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;
③Support 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
The stack initialization actually initializes the 0STaskStackBotton array, and designates 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 created 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=O:
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:
#definition OSStart() d0{\
OSTaskCreate(TaskIdle, NULL, OS_MAX_TASKS);\
EA=l:\
return;
}while(O)
First, it creates an idle task and turns on interrupts, 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 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 the priority 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 adopted, except for some local variables of individual tasks related to the whole 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 peripherals with strict timing requirements (such as DSl8820).
Previous article:Design of high-precision electronic scale based on AVR microcontroller
Next article:LED digital large screen display system based on single chip microcomputer
- Molex leverages SAP solutions to drive smart supply chain collaboration
- Pickering Launches New Future-Proof PXIe Single-Slot Controller for High-Performance Test and Measurement Applications
- CGD and Qorvo to jointly revolutionize motor control solutions
- Advanced gameplay, Harting takes your PCB board connection to a new level!
- Nidec Intelligent Motion is the first to launch an electric clutch ECU for two-wheeled vehicles
- Bosch and Tsinghua University renew cooperation agreement on artificial intelligence research to jointly promote the development of artificial intelligence in the industrial field
- GigaDevice unveils new MCU products, deeply unlocking industrial application scenarios with diversified products and solutions
- Advantech: Investing in Edge AI Innovation to Drive an Intelligent Future
- CGD and QORVO will revolutionize motor control solutions
- Innolux's intelligent steer-by-wire solution makes cars smarter and safer
- 8051 MCU - Parity Check
- How to efficiently balance the sensitivity of tactile sensing interfaces
- What should I do if the servo motor shakes? What causes the servo motor to shake quickly?
- 【Brushless Motor】Analysis of three-phase BLDC motor and sharing of two popular development boards
- Midea Industrial Technology's subsidiaries Clou Electronics and Hekang New Energy jointly appeared at the Munich Battery Energy Storage Exhibition and Solar Energy Exhibition
- Guoxin Sichen | Application of ferroelectric memory PB85RS2MC in power battery management, with a capacity of 2M
- Analysis of common faults of frequency converter
- In a head-on competition with Qualcomm, what kind of cockpit products has Intel come up with?
- Dalian Rongke's all-vanadium liquid flow battery energy storage equipment industrialization project has entered the sprint stage before production
- Allegro MicroSystems Introduces Advanced Magnetic and Inductive Position Sensing Solutions at Electronica 2024
- Car key in the left hand, liveness detection radar in the right hand, UWB is imperative for cars!
- After a decade of rapid development, domestic CIS has entered the market
- Aegis Dagger Battery + Thor EM-i Super Hybrid, Geely New Energy has thrown out two "king bombs"
- A brief discussion on functional safety - fault, error, and failure
- In the smart car 2.0 cycle, these core industry chains are facing major opportunities!
- The United States and Japan are developing new batteries. CATL faces challenges? How should China's new energy battery industry respond?
- Murata launches high-precision 6-axis inertial sensor for automobiles
- Ford patents pre-charge alarm to help save costs and respond to emergencies
- New real-time microcontroller system from Texas Instruments enables smarter processing in automotive and industrial applications
- Share a book about Cortex M3 core
- msp430 MCU AD conversion
- Fudan Micro FM33LC046N Evaluation + ADC Collection Alarm
- MSP430FR235x-based pulse oximeter system solution
- CCS compilation error: Solution for missing header file
- LWIP uses UDP to send data continuously
- It was very popular in the past, and now it is back, how should this button position be designed?
- Implementing USB source code with Verilog (FPGA)
- How good is Hangshun's chip? Let's see if Huawei 5G uses its chip
- ESP-USB-Bridge turns the ESP32-S2/S3 development board into a JTAG debugger, and cooperates with OpenOCD to debug STM32 and other chips