introduction
In the field of embedded operating systems, μC/OS developed by Jean J. Labrosse once caused a strong response in the field of embedded systems due to its open source code and powerful and stable functions. He has also long been a member of the advisory board of the Embedded Systems Conference (USA).
Whether for beginners or experienced engineers, μC/OS open source approach allows them to know not only what it is, but also why it is. Through in-depth understanding of the internal structure of the system, it is more convenient to develop and debug; and under this condition, it is completely possible to reasonably cut, expand, configure and transplant according to the design requirements. Usually, it often takes a lot of money to buy RTOS, which makes ordinary learners discouraged; while μC/OS is completely free for school research, and only a small amount of copyright fees are required when it is applied to profit-making projects. It is particularly suitable for general users to learn, research and develop. Since the first edition came out in 1992, thousands of developers have successfully applied it to various systems, and its safety and stability have been certified. It has now passed the US FAA certification.
1 Major components of μC/OS-II
μC/OS-II can be roughly divided into five parts: core, task processing, time processing, task synchronization and communication, and CPU transplantation.
The core part (OSCore.c) is the processing core of the operating system, including operating system initialization, operating system operation, interrupt in and out preamble, clock beat, task scheduling, event processing, etc. The parts that can maintain the basic work of the system are all here.
Task processing part (OSTask.c) The contents in the task processing part are closely related to the operation of tasks, including task creation, deletion, suspension, and resumption. Because μC/OS-II is scheduled based on tasks as the basic unit, this part is also very important.
Clock section (OSTime.c) The smallest clock unit in μC/OS-II is timetick (clock beat). Task delay and other operations are completed here.
The task synchronization and communication part is the event processing part, including semaphores, mailboxes, mailbox queues, event flags, etc.; it is mainly used for mutual communication between tasks and access to critical resources.
The interface part with the CPU refers to the porting part of μC/OS-II for the CPU used. Since μC/OS-II is a universal operating system, the implementation of key issues still needs to be ported according to the specific content and requirements of the specific CPU. This part of the content involves system pointers such as SP, so it is usually written in assembly language. It mainly includes the underlying implementation of interrupt-level task switching, the underlying implementation of task-level task switching, the generation and processing of clock beats, and the related processing parts of interrupts.
2 Interrupt processing for MSP430
2.1 Function call and interrupt call operations
The most commonly used C compiler for MSP430 should be IAR Embedd-ed WorkBench. Through analysis and research, it is found that this compiler has the following rules.
(1) Function call
If it is a function-level call, the compiler will first push the current function PC onto the stack when calling the function, then call the function and the PC value will change.
If the called function has parameters, the compiler follows the following rules.
If the two leftmost parameters are not structs or unions, they will be assigned to registers, otherwise they will be pushed onto the stack. The remaining parameters of the function will be pushed onto the stack. Depending on the type of the two leftmost parameters, they will be assigned to R12 (for 32-bit types, they will be assigned to R12:R13) and R14 (for 32-bit types, they will be assigned to R14:R15).
(2) Interrupt call
If the interrupt service subroutine is called during an interrupt, the compiler will push the PC of the current execution statement onto the stack and push the SR onto the stack. Then, depending on the complexity of the interrupt service subroutine, select registers R12 to R15 to push onto the stack. Then, the interrupt service subroutine is executed. After the interrupt processing is completed, the Rx register, SR register, and PC register are popped off the stack. The system is restored to the state before the interruption, so that the program can continue to run after the interruption.
2.2 Task switching steps and principles at task level and interrupt level
(1) Task-level task switching principle
μC/OS-II is a multi-tasking operating system. In the absence of user-defined interrupts, the switching steps between tasks are as follows: Switching between tasks generally calls the OSSched() function. The function structure is as follows:
void OSSched(void){
Disable interrupts
if (not interrupt nesting and the system can be scheduled) {
Identify the highest priority tasks
if (top level task is not current task) {
Call OSCtxSw();
}
}
Enable interrupt
}
We call this function the leading function of task scheduling. It first determines the conditions for task switching. If the conditions allow task scheduling, OSCtxSw() is called. This function is the function that actually implements task scheduling. Since the stack needs to be operated during the process, OSCtxSw() is generally written in assembly language. It pushes the SR register of the CPU of the running task into the stack, and then pushes R4~R15 onto the stack. Then the current SP is saved in TCB->OSTCBStkPtr, and then the value of the highest priority TCB->OSTCBStkPtr is assigned to SP. At this time, SP has already pointed to the task stack of the highest priority task. Then the stack is popped and R15~R4 is popped. Then RETI is used to return, so that SR and PC are popped. Simply put, μC/OS-II switches to the highest priority task, just restoring all the registers of the highest priority task and running the interrupt return instruction (RETI). In fact, what is done is just artificially imitating an interrupt. [page]
(2) Task switching principle at interrupt level
The interrupt service subroutine of μC/OS-II is slightly different from the general foreground and background operations, and often requires the following operations:
Save all CPU registers
Call OSIntEnter() or OSIntNesting++
Open interrupt
Execute user code
Disable interrupts
Call OSIntExit();
Restore all CPU registers
RETI
OSIntEnter() is to add 1 to the global variable OSIntNesting. OSIntNesting is a variable for the number of nested interrupts. μC/OS-II uses it to ensure that no task scheduling is performed when interrupts are nested. After executing the user's code, μC/OS-II calls OSIntExit(), a function very similar to OSSched(). In this function, the system first decrements OSIntNesting by 1, and then determines whether the interrupt is nested. If not, and the current task is not the highest priority task, then find the highest priority task and execute the interrupt task switching function OSIntCtxSw(). Because the stack has been pushed before; in this function, R15~R4 must be popped. Moreover, some registers may have been pushed into the stack when the function was called before. Therefore, the stack pointer must be adjusted so that it can be popped from the correct position.
3 Problems and solutions when using μC/OS-II
Since μC/OS-II will occupy some resources on the microcontroller when it is applied, such as system clock, RAM, Flash or ROM, thus reducing the utilization of resources by the user program. For MSP430, RAM occupation is a particularly prominent problem. For 8- and 16-bit microcontrollers, the on-chip RAM capacity is very small, and the same is true for MSP430 (the largest on-chip RAM is only 2KB, such as MSP430F149). If extended memory is used, it will greatly increase the design difficulty.
Through the analysis of μC/OS-II, we can know that the RAM occupied by μC/OS-II is mainly used for the TCB of each task, the stack of each task, etc. Through further analysis, it is found that the reason why the task stack is large is that the interrupt stack and the task stack are not separated in the hardware design of MSP430. As a result, when applying μC/OS-II, when considering the task stack size of each task, it is necessary not only to calculate the number of local variables and function nesting levels in the task, but also to consider the maximum number of interrupt nesting levels. Because, for the original interrupt processing design of μC/OS-II, the register size and local variable memory size required for stacking in the interrupt nesting process during the interrupt processing need to be calculated in the task stack of each task, and this part of memory needs to be reserved for each task, so a lot of RAM is wasted. From this, it can be seen that the direct way to solve this problem is to separate the interrupt stack from the stack of each task. In this way, when calculating each task stack, it is not necessary to calculate the memory occupied in the interrupt processing (including the interrupt nesting process) into the task stack of each task. It is only necessary to calculate the memory size required by each task itself, thereby improving the utilization of RAM and alleviating the problem of memory shortage.
In this design, the interrupt stack area uses the original system stack area in MSP430. In the foreground and background design, the push and pop operations in the interrupt are completed in the system stack area. Based on the principle of task switching of μC/OS-II, we divide the functions of the task stack and the system stack as follows: when the task generates an interrupt and the task switches during operation, the PC, SR and register Rx are all saved in the task stack of each task; and the push and pop operations generated by interrupt nesting are all placed in the system stack. This division method is designed based on the idea of separating interrupt tasks from ordinary tasks as much as possible.
From the analysis of the default operation of IAR EW above, there are two possible stack structures. One is to design the μC/OS-II task stack as shown in Figure 1. This method puts the compiler's default stack push operation in front, and then pushes the remaining registers into the stack. However, since the compiler pushes an indefinite number of registers into the stack when processing interrupt service routines of varying complexity, it will increase the complexity of the subsequent stack push and pop operations of the remaining registers. Here, we use the method shown in Figure 2 to generate the stack. In this stack, after PC and SR are pushed, the SP pointer is adjusted so that the R4~R15 registers cover the compiler's default stacked registers. In this way, the processing difficulty will be a little less.
For such a design, the CPU must be able to:
◆ There are corresponding CPU registers that can imitate some functions of SP, and corresponding instructions can be used to complete some operations similar to SP;
◆ It is best not to use the register used as SP by the compiler by default during the compilation process. In the IAR compiler, there is an option to avoid using R4 and R5 during the compilation process.
MSP430 can do both of these.
The following is an analysis of several situations that may occur when a running task with a priority level of 6 is interrupted.
1) During the interrupt processing, no higher priority interrupt is generated, that is, interrupt nesting will not occur.
Figure 3 shows the operations performed on the task stack of task priority 6 after an interrupt occurs. After an interrupt occurs, PC and SR are pushed onto the stack by the system②. For the IAR C compiler, some registers are pushed onto the stack by default③ according to the requirements of interrupt service routines of different complexities. Because the stack format we require is as shown in Figure 2, we need to adjust SP to the back of SR④, and then push R4~R15 onto the stack to form the stack format we require⑤.
After pushing the task stack, you can adjust the SP pointer to the system stack, as shown in Figure 4. After pushing, the SP points to the last pushed content①. We assign the value of SP to TCB->OSTCBStkPtr of the priority 6 task so that it can be popped for task scheduling②. Then, adjust SP to the system stack③. During interrupt processing, a push operation may occur, in which case the SP pointer will move accordingly. Since it is now in the interrupt stack, the format of the task stack will not be destroyed.
[page]
Since there is no interrupt nesting, no other interrupt occurs during interrupt processing, so the return step is exactly the opposite of the above-mentioned stacking operation. After the interrupt is processed, SP will automatically return to the SP position ③ in Figure 4. Then, the system will query the task with the highest priority, and then move the SP pointer to the task stack of the task with the highest priority, perform the stacking of R15~R4, and finally use the RETI interrupt return instruction to return to the new task. Because we have defined all task stacks in the same format, there will be no problems between them. It should be noted here that because the system will pop the registers that are pushed by default when the interrupt enters the stack during the interrupt processing of the C compiler, when designing the stacking program, these contents must be pushed first so that they can be popped correctly.
2) During the interrupt processing, other interrupts are generated, resulting in interrupt nesting.
As shown in Figure 5, when processing an interrupt, the SP has been moved to the system stack. Only when the interrupt exits can the SP be moved to the task stack of another task. Therefore, when an interrupt is nested, the interrupt processing is the same as the first time. The difference is that this time the registers saved in the stack are not the registers in the task running, but the registers in the interrupt processing, and they are saved in the system stack instead of the task stack. From this, we can see the effect of optimizing memory. All registers in the interrupt nesting are pushed into the system stack, which greatly reduces the requirement for the task stack memory size.
Because μC/OS-II will set the global variable OSIntNesting++ when entering an interrupt, and OSIntNesting-- when exiting an interrupt. Before exiting an interrupt and switching tasks, μC/OS-II will first determine whether OSIntNesting is 0. If it is 0, it will schedule tasks. When the second interrupt ends and the interrupt nesting is exited, OSIntNesting is not 0, and task scheduling will not be performed. Therefore, the system stack is still popped, and the system will continue the interrupt service program that was not completed before.
Then the order of exiting interrupts is the same as the order of non-interrupt nesting. After the interrupt is processed, SP will automatically return to the SP position ③ in Figure 4. Then, the system will query the task with the highest priority, and then move the SP pointer to the task stack of the task with the highest priority. Pop R15~R4, and finally use the RETI interrupt return instruction to return to the new task.
The interrupt situations are basically the two mentioned above. As for the situation mentioned in some literature that a higher priority task will be scheduled in an interrupt, I think it should not happen. Because from the above analysis, it can be seen that the default (μC/OS-II design idea) interrupt processing will increase and decrease the global variable OSIntNesting at the same time to give the conditions for whether task scheduling is needed. Then even if a higher priority task is ready in the interrupt service program, it will wait until the interrupt exits before scheduling, unless a higher priority task function is directly called in the interrupt. But this method should be contrary to the principles of μC/OS-II, and the previous front-end and back-end design ideas are used.
For such a design, the clock beat processing method must be the same as the general interrupt processing method. Generally speaking, MSP430 uses the WATCHDOG clock interrupt as the source of the clock beat. In essence, the clock beat itself is also an interrupt processing process, so the processing of the clock beat should be the same as other interrupt processing processes. In fact, there may also be interrupt nesting problems in the clock beat processing process.
The program flow of the interrupt stack and task stack separation design is shown in Figure 6.
4. Some suggestions
① When writing interrupt programs, try to use assembly language if possible, because this can avoid some operations performed by the compiler itself and reduce the number of pointer adjustments.
② When programming interrupt services in C, some functions must be implemented by calling assembly functions. When calling functions, sometimes the PC pushed on the stack will destroy the stack structure. At this time, the stack needs to be properly adjusted to ensure the correct stack format.
③ When OSIntExit() is called during interrupt processing, because the SP pointer is sometimes not adjusted in the original design of μC/OS-II, after OSIntExit() returns, it is necessary to determine whether the interrupt is nested. This is because sometimes it is necessary to switch tasks.
Previous article:Development of embedded low power RF/IR conversion controller
Next article:Application of FLASH in MSP430F149 Embedded System
Recommended ReadingLatest update time:2024-11-17 00:51
Professor at Beihang University, dedicated to promoting microcontrollers and embedded systems for over 20 years.
- 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
- At 9:43 on June 23, the last satellite of my country's BeiDou-3 system was successfully launched.
- Keil simulator plays RTT multi-threaded lighting
- Sensor circuit problem
- Color recognition system based on FPGA and color-sensitive sensor
- Help!
- IME2020 Western Microwave Conference
- Does anyone know where the slope of 6db/oct in the red circle in this post came from? Can anyone teach me?
- 256Mx32 8Gb DDR4 SDRAM
- [RVB2601 Creative Application Development] + Development Environment Construction and Download Test
- 【BearPi-HM Micro】Part 2: Building an OpenHarmony compilation environment from scratch