Beginner Programmer's Guide: uC/OS-II User Manual is here~
[Copy link]
uC/OS-II Introduction uC/OS-II is a priority-based preemptive hard real-time kernel. Since its release in 1992, it has been widely used all over the world. It is a kernel designed specifically for embedded devices. It has been ported to more than 40 CPUs with different structures and runs on various systems from 8-bit to 64-bit. It is particularly worth mentioning that since version 2.51, the system has passed the US FAA certification and can run on systems with extremely stringent safety requirements such as spacecraft. Given that the code of uC/OS-II is available for free, choosing uC/OS is undoubtedly the most economical choice for embedded RTOS. 2b Basic structure of uC/OS-II application program To apply uC/OS-II, you must develop an application program for it. The following discusses the basic structure and precautions of an application program based on uC/OS-II. Each uC/OS-II application must have at least one task. And each task must be written in the form of an infinite loop. The following is the recommended structure: void task ( void* pdata ) { INT8U err; InitTimer(); // Optional For( ;; ) { // Your application code … … Calibri]. … … Calibri].OSTimeDly(1); // Optional } } The above is the basic structure. Why should it be written in the form of an infinite loop? That is because the system will reserve a stack space for each task. When the task is switched, the system will restore the context and execute a reti instruction to return. If the task is allowed to execute to the last curly brace (which generally means a ret instruction), it is likely to destroy the system stack space and make the execution of the application uncertain. In other words, it is "running away". Therefore, each task must be written in the form of an infinite loop. Programmers must believe that their tasks will give up the right to use the CPU, regardless of whether it is forced by the system (through ISR) or actively given up (by calling OS API). Now let's talk about the InitTimer() function in the above program. This function should be provided by the system. The programmer is obliged to call it in the task with the highest priority and not in the for loop. Note that this function is related to the CPU used. Each system has its own Timer initialization program. In the help manual of uC/OS-II, the author specifically emphasizes that the Timer initialization program must not be called in OSInit() or OSStart(), which will destroy the portability of the system and bring performance losses. Therefore, a compromise is to call it in the program with the highest priority as shown above, so that when OSStart() calls the system internal function OSStartHighRdy() to start multitasking, the Timer initialization program is executed first. Or you can start a task with the highest priority and do only one thing, which is to execute the Timer initialization, and then suspend itself by calling OSTaskSuspend(), and never execute it again. However, this will waste a TCB space. For those systems with tight RAM, it is better not to use it. 3 Introduction to some importantuC/OS-II APIAny operating system will provide a large number ofAPIs for programmers to use, and uC/OS-II is no exception. Since uC/OS-II is aimed at embedded development and does not require large and comprehensive, the APIs provided by the kernel are mostly closely related to multitasking. The main categories are as follows:1) Task class2) Message class3) Synchronization class4) Time class5) Critical section and event classI personally think that for junior programmers, task class and time class are the two types of APIs that must be mastered first. Now I will introduce the more important ones:1) OSTaskCreate functionThis function should be called at least once in the main function, after the OSInit function is called. Its function is to create a task. There are currently four parameters, namely the entry address of the task, the parameters of the task, the first address of the task stack and the priority of the task. After calling this function, the system will first apply for an empty TCB pointer from the TCB free list, then initialize the task stack according to the parameters given by the user, and mark the task as ready in the internal task ready table. Finally, it returns, and such a task is successfully created. 2) OSTaskSuspend function This function is very simple. You can understand its function by looking at the name. It can suspend the specified task. If the current task is suspended, it will also trigger the system to execute the task switching leader function OSShed to perform a task switch. This function has only one parameter, which is the priority of the specified task. Why is it priority? In fact, within the system, the priority not only indicates the order of execution of a task, but also plays the role of distinguishing each task. In other words, the priority is the ID of the task. Therefore, uC/OS-II does not allow tasks with the same priority. 3) OSTaskResume function This function is the opposite of the above function. It is used to restore the specified suspended function to the ready state. If the priority of the restored task is higher than the current task, it will also trigger a task switch. Its parameters are similar to OSTaskSuspend function, which is the priority of the specified task. It should be noted that this function does not require to be used in pairs with the OSTaskSuspend function. 4) OS_ENTER_CRITICAL macro Many people think it is a function, but it is not. Let's analyze it carefully OS_CPU.H file, it and OS_EXIT_CRITICAL which will be discussed below are both macros. They are all related to the implementation of a specific CPU. Generally, they are replaced by one or several embedded assembly codes. Since the system hopes to hide the internal implementation from the upper-level programmer, it is generally claimed that the system enters the critical section after executing this instruction. In fact, it just turns off the interrupt. In this way, as long as the task does not actively give up the right to use the CPU, other tasks will not have the opportunity to occupy the CPU. Relative to this task, it is exclusive. So it enters the critical section. This macro should be used less or less, because it will destroy some system services, especially time services. And reduce the system's response performance to the outside world. 5) OS_EXIT_CRITICAL macro This is another macro used in conjunction with the macro introduced above. Its description in the system manual is to exit the critical section. In fact, it just reopens the interrupt. It should be noted that it must appear in pairs with the above macro, otherwise it will bring unexpected consequences. In the worst case, the system will crash. We recommend that programmers use these two macro calls as little as possible, because they will indeed destroy the system's multitasking performance. 6) OSTimeDly function This should be the function that programmers call the most. This function is very simple to complete. It is to suspend the current task first, then switch tasks. After the specified time arrives, the current task is restored to the ready state, but it does not necessarily run. If it is the highest priority ready task after restoration, then it will run. In simple terms, it can delay the task for a certain period of time before executing it again, or temporarily give up the right to use the CPU. A task can not explicitly call these APIs that can cause the right to use the CPU to be given up, but in that case the multitasking performance will be greatly reduced, because at this time, the task switching is only based on the clock mechanism. A good task should actively give up the right to use after completing some operations. Good things should be shared by everyone! 4 uC/OS-II Analysis of multi-tasking implementation mechanism As mentioned before, uC/OS-II is a priority-based preemptive multi-tasking kernel. So, how is its multi-tasking mechanism implemented? Understanding these principles can help us write more robust code. Xinyingda Embedded Internet of Things Intelligent Hardware and other courses Penguin 117 Woohoo Bar 9091Since we are targeting junior programmers, this article is not intended to be another uC/OS-II source code analysis. There are too many such articles. I intend to explore this issue from the perspective of implementation principles. First, let's take a look at why the multitasking mechanism can be implemented. In fact, in the case of a single CPU, there is no real multitasking mechanism. There are only different tasks that take turns to use the CPU, so it is essentially a single task. But because the CPU executes very fast, and the task switching is very frequent and fast, it feels like there are many tasks running at the same time. This is the so-called multitasking mechanism. From the above description, it is not difficult to find that to implement the multitasking mechanism, the target CPU must have a way to change the PC during operation, otherwise it cannot switch. Unfortunately, there is currently no CPU that supports such instructions to directly set the PC pointer. However, general CPUs allow indirect modification of the PC through instructions such as JMP and CALL. Our implementation of the multitasking mechanism is based on this starting point. In fact, we use CALL instructions or soft interrupt instructions to modify PC, mainly soft interrupts. But on some CPUs, there is no such concept as soft interrupts, so we use several PUSH instructions plus a CALL instruction on those CPUs to simulate the occurrence of a soft interrupt. Recall what you learned in the microcomputer principle course. When an interrupt occurs, the CPU saves the current PC and status register values to the stack, and then sets the PC to the entry address of the interrupt service program. After the next machine cycle, the interrupt service program can be executed. After the execution is completed, a RETI instruction is generally executed. This instruction will pop the current stack value back to the status register and PC. In this way, the system will return to the place before the interrupt and continue to execute. So imagine? What will happen if the value in the stack is manually changed during the interrupt? Or what will happen by changing the value of the current stack pointer? If the change is random, the result will be unpredictable errors. Because we cannot be sure what instruction the machine will execute next, but if the change is planned and follows certain rules, then we can implement a multi-tasking mechanism. In fact, this is the core part of almost all OSs at present. But their implementation is not as simple as this. Next, let's take a look at how uC/OS-II handles this. In uC/OS-II, each task has a task control block (Task Control Block), which is a relatively complex data structure. At the offset 0 of the task control block, a pointer is stored, which records the dedicated stack address of the task to which it belongs. In fact, in uC/OS-II, each task has its own dedicated stack, which cannot be infringed upon by each other. This requires programmers to ensure this in their programs. The general practice is to declare them as static arrays. And they must be declared as OS_STK types. When a task has its own stack, each task stack can be recorded there to the place where the task control fast offset is 0 mentioned above. Whenever a task switch occurs in the future, the system will inevitably enter an interrupt first, which is generally implemented through a soft interrupt or a clock interrupt. Then the system will first save the stack address of the current task, and then restore the stack address of the task to be switched. Since the stack of that task must also store the address (remember what we said before, whenever a task switch occurs, the system will inevitably enter an interrupt first, and once the interrupt occurs, the CPU will push the address into the stack), in this way, the purpose of modifying the PC to the address of the next task is achieved. The above is uC/OS-II's multi-tasking implementation mechanism. We have spent a lot of time and effort to discuss this issue here, hoping that our programmers can make good use of this mechanism and write more robust and efficient code.
|