preface
Thank Mr. Wei Dongshan for his live teaching. I learned a lot of practical knowledge in today's teaching. Have an essential understanding of logic development and RTOS development.
Bloggers also recommend an embedded learning website here Baiwen official website . Here are all embedded learning videos, and Mr. Wei Dongshan's personal teaching will answer your doubts.
Bare metal development mode
The so-called bare metal development refers to the development of single chip microcomputer without operating system. The operation of the program completely depends on the logical design of the code and the fixed setting of the hardware equipment. There is no need for the participation and scheduling of the operating system.
Here we will analyze the example given by Mr. Wei in class
It's about a treasure mother who needs a series to feed her children and reply to her colleagues.
So the first way we think of is polling
Polling mode
void main{}{ while(1){ eat();//Feeding children message();//Reply to colleague messages } }
This is a very classic MCU program. Is it your running lantern program. Let's analyze this program:
When the function of feeding children is executed, the function of replying to colleagues' messages cannot be executed. When the function of replying to colleagues' messages is executed, the program cannot execute the function of feeding children, so for colleagues, Baoma always disappears for a period of time and cannot reply to messages. For children, my mother is always unable to feed me for a period of time.
Neither side (colleagues and children) seems to be satisfied. Obviously, this program is not very good, so how can we optimize it?
I believe some friends have compared our initial MCU experiment. We can use interrupt! Yes, interrupt is our optimization method, also known as event driven method
Event driven mode
Event is a broad concept. What is event? It can be: press the key, the serial port receives data, the module generates an interrupt, and a global variable is set.
What is event driven? When an event occurs, the corresponding function is called, which is called event driven.
We will improve the above example:
- When the child cries, Baoma feeds him
- When a colleague sends a message, the computer prompts him to reply to his colleague
void crying_isr(){//Interrupt function to detect whether the child is crying eating();//When crying, execute the function of feeding the child } void message_isr(){//Function to detect whether colleagues send messages message();//Execute back message function } void main(){ while(1){ } }
This programming method makes the two interrupt functions execute quickly, and there is no need to wait for the execution of the last function like polling.
However, if two interrupts occur at the same time, they will affect each other:
- Two interrupts, only one can be processed at a time
- If the current interrupt processing time is relatively long, it will affect the processing of another interrupt.
Let's continue to optimize
Improved event driven approach
The problem with the above program is that when two interrupts are generated at the same time, only one can be processed at the same time. If one interrupt takes a long time, it will affect the processing of the other interrupt.
Let's improve our program for these problems.
In principle, interrupt processing is "as soon as possible". Otherwise, it will affect other interrupts, resulting in the processing delay or even loss of other interrupts.
Let's improve the program by setting the flag bit.
void crying_isr(){//Check whether the child is crying is_crying=1;//If you cry, put the sign in position 1 } void message_isr(){ is_message=1;//There will be message flag position 1. } void main(){ while(1){ if(is_crying==1) eating(); if(is_message==1) message(); } }
After setting the flag bit, our interrupt processing function will be executed quickly, so it will not affect the processing of other interrupts, delay and loss of interrupts.
I believe you have thought that the subsequent processing after the interrupt duration trigger will be returned to polling. Isn't it that we haven't improved? Don't worry, let's continue to improve!
Common time driving mode: timer
Here, I will first introduce this method to you with Mr. Wei's example, and then analyze the improvement method of the above example.
Example: Baoma feeding only has two tasks. If there are multiple tasks, some experienced engineers will use timers to drive them
- Set a timer, such as an interrupt every 1ms
- For function A, you can set its execution cycle, such as every 1ms
- For function B, you can set its execution cycle, such as every 2ms
- For function C, its execution cycle can be set, such as every 3ms
- Note: 1ms, 2ms and 3ms are only assumptions, and you can adjust them according to the actual situation.
Then we can write the code as follows
typedef struct soft_timer{ int remain;//Indicates how much time is left. You need to call the following function int period;//Represents the period void (*function)(void);//Processing function }soft_timer,*p_soft_timer; static soft_timer timers[]={ {1,1,A}, {2,2,B}, {3,3,C} };//Meet the requirements of the topic void main(){ while(1){ } } void timer_isr(){ int i;//Is the remain ing of each timer array member minus 1 for(i=0;i<3;i++){ timers[i].remain--; } //When remain is reduced to 0, it means to call the function in the corresponding structure for(i=0;i<3;i++){ if(timers[i].remain==0){ timers[i].function();//Call function timers[i].remain=timers[i].period;//Reset remain } } }
After this setting, we have solved the processing time of each number of people. However, when a program takes a long time to execute, the following consequences will occur:
- Affect calls to other functions
- Delay the whole time benchmark
So how can we improve it? To solve this problem, I use the second example above for analysis. The same is true for Baoma problem
typedef struct soft_timer{ int remain; int period; void (*function)(void); }soft_timer,*p_soft_timer; static soft_timer timers[]={ {1,1,A}, {2,2,B}, {3,3,C} }; void main(){ while(1){ for(int j=0;j<3;j++){ if(flag[i]){ timers[i].function();//Call function } } } } void timer_isr(){ for(i=0;i<3;i++){ timers[i].remain--; } for(i=0;i<3;i++){ if(timers[i].remain==0){ flag[i]=1;//Set flag bit timers[i].remain=timers[i].period; } } }
Set the flag bit above to solve the time benchmark that affects the whole process due to the long execution time of a function.
Using state machine for improvement
Question: if the execution time of task processing functions is very long, what should our bare metal do?
Here we can use the idea of state machine to solve this problem (in fact, the idea is the time slice of the operating system)
void crying_isr(void) { static int state = 0; switch (state) { case 0: /* start */ { /* take cooked rice out of a cooker into a bowl */ state++; return; } case 1: /* dish out food */ { /* dish out food */ state++; return; } case 2: { /* Take a spoon */ state++; return; } } } void mesage_isr(void) { static int state = 0; switch (state) { case 0: /* start */ { /* Turn on the computer */ state++; return; } case 1: { /* View information */ state++; return; } case 2: { /* typing */ state++; return; } } } void main() { while (1) { crying_isr(); message_isr(); //In fact, this function with a long execution time is split into short time for processing. } }
Obviously, the state machine splitter is used here:
- More trouble
- Some complex programs cannot be split into state machines.
summary
In general, the problem that bare metal programs are difficult to solve is to control the running time of each task. It is difficult to eliminate the interaction between tasks.
Introduction of RTOS
Suppose you want to call two functions ab. AB takes a long time to execute. When using bare metal programs, you can transform AB functions into "state machines" and use RTOS. The core of both methods is "time-sharing multiplexing":
- Time sharing: function A runs for A short period of time, and function B runs for A short period of time
- Reuse: reuse who? It's the CPU
Here is an example of Baoma:
Compare Baoma to CPU, feed children to function A, and reply messages to function B
Baoma will feed the children and answer the news later. When this time is short enough, from a macro point of view, two events occur at the same time; From a micro point of view, these are still two things.
// RTOS program Feed() { while (1) { Feed a mouthful of rice(); } } Return information() { while (1) { Return a message(); } } void main() { create_task(Feed);//Create a task create_task(Return information);//Create a task start_scheduler();//Execute task list while (1) { sleep(); } }
The key is that RTOS allows multiple tasks to run in turn, which eliminates the need for us to manually use the state machine to split the program in the task function.
Note: the implementation principle of RTOS is the operation of linked list. The order of traversing the linked list is formed through the priority. If the priority is high, deal with it first. Judge whether the linked list is empty and whether the function needs to be executed. At the same time, the same linked list, through time sharing, after executing a task in the time slice, place the task at the end of the linked list, and then execute the next task.
About sleep and wake-up, in fact, it is to put the task to sleep (or the task that does not meet the conditions) into the sleep linked list, and then wake up the task when the conditions are met.
This part will be described in detail later.
Problems needing attention in RTOS programming
Access to critical resources
In fact, just as we usually program on Linux, we should consider the access of critical resources. The solution is still to set mutex.
Sleep wake of task
When we set conditions for the execution of a task, if we do not sleep the task with the conditions set, this function will constantly judge the conditions, as follows:
void main(){ A(){ //When A is about to finish execution, execute this content (assuming that it may also be A condition) if(xxx){ flag=1; } }; if(flag){//If B is not dormant, if A executes 100000 times, this if judgment condition will be executed so many times. So this will cause A waste of resources and unnecessary expenses. B(); } }
Therefore, set the sleep of the task, sleep B, and let A execute all the time. When the flag is 1, wake up B, so as to avoid this waste.
summary
Tip: here is a summary of the article:
On a very full day, I reviewed my previous knowledge. At the same time, I understand the difference between RTOS and bare metal development, as well as the essence of RTOS.
Keep going, keep trying!