Reference content: Chapter 13 of [wildfire] uCOS-III Kernel Implementation and application development Practical Guide - based on STM32.
1 data type definition and macro definition
1.1 time base list related macro definitions and global variables (os_cfg_app. H / C & OS. H)
In os_cfg_app.h, the macro defines the size of the time base list, and its recommended value is task number / 4. It is recommended to use prime number, not even number. If the calculated size is an even number, add 1 to become a prime number.
/* Time base list size */ #define OS_CFG_TICK_WHEEL_SIZE 17u
Next on OS_ cfg_ app. In C, two global variables are defined, which are the size of time base list (array) and time base list. Note that you need to be in OS H.
OS_TICK_SPOKE OSCfg_TickWheel[OS_CFG_TICK_WHEEL_SIZE]; /* Time base list */ OS_OBJ_QTY const OSCfg_TickWheelSize = (OS_OBJ_QTY)OS_CFG_TICK_WHEEL_SIZE; /* Time base list size */
On OS H defines a 32-bit Tick counter, i.e. SysTick cycle counter, which records how many SysTick cycles have passed since the system was started or since the last reset. Each time a SysTick interrupt is initiated, the variable will be incremented by one.
OS_EXT OS_TICK OSTickCtr; /* SysTick Cycle counter */
1.2 time base list definition (os.h)
On OS H defines the time base list structure, which is composed of:
- FirstPtr: used to point to the first node of TCB bidirectional linked list. Each array element of the time base list records the information of a TCB one-way linked list, and the TCBS inserted into the linked list will be arranged in ascending order according to the delay time.
- NbrEntries: records the number of nodes in the TCB linked list.
- NbrEntriesMax: records the number of nodes in the TCB linked list at most. It will be refreshed when nodes are added, but not when nodes are deleted.
typedef struct os_tick_spoke OS_TICK_SPOKE; /* Rename the time base list to uppercase format */ struct os_tick_spoke{ OS_TCB *FirstPtr; /* TCB Head node of one-way linked list */ OS_OBJ_QTY NbrEntries; /* How many nodes does a one-way linked list have */ OS_OBJ_QTY NbrEntriesMax; /* The maximum number of nodes in a one-way linked list will be refreshed */ };
1.3 modify TCB definition (os.h)
Five new members have been added to TCB, namely:
- TickNextPtr: points to the next TCB node in the bidirectional linked list.
- TickPrevPtr: points to the previous TCB node in the bidirectional linked list.
- TickSpokePtr: pointer to the time base list array, which is used to indicate which bidirectional linked list the task TCB belongs to.
- TickCtrMatch: this value indicates the value of the current time base counter plus the period to be delayed by the task.
- Tickmain: indicates how many SysTick cycles the task needs to delay. The function of tasks delay can be replaced by the function of tasks delay.
struct os_tcb{ CPU_STK *StkPtr; CPU_STK_SIZE StkSize; //OS_TICK TaskDelayTicks; /* Number of task delay cycles*/ OS_PRIO Prio; /* Task priority */ OS_TCB *NextPtr; /* The next pointer of the two-way linked list of the ready list */ OS_TCB *PrevPtr; /* The previous pointer of the two-way linked list of the ready list */ OS_TCB *TickNextPtr; /* Point to the next TCB node of the bidirectional linked list of time base list */ OS_TCB *TickPrevPtr; /* Point to the previous TCB node of the time base list bidirectional linked list */ OS_TICK_SPOKE *TickSpokePtr; /* Used to refer back to the root of the linked list */ OS_TICK TickCtrMatch; /* This value is equal to the value of the time base counter OSTickCtr plus the value of TickRemain */ OS_TICK TickRemain; /* Set how many clock cycles the task needs to wait */ };
Therefore, the currently implemented uCOS has two lists: ready list and time base list. They both establish a TCB two-way linked list according to their own rules, and the array in the list will store the information of the two-way linked list. The array subscript of the ready list indicates priority, and the array subscript of the time base list indicates time period.
The following figure shows the structure of the time base list. One of its array elements stores a TCB two-way linked list information, which is a two-way linked list with three nodes. Note that each node refers back to the address of the array element, and the third node is omitted.
2 related functions of time base list
2.1 initialization time base list OS_TickListInit()(os_tick.c)
This function is used to initialize the time base list array and clear all members.
/* Initialize time base list */ void OS_TickListInit (void) { OS_TICK_SPOKE_IX i; OS_TICK_SPOKE *p_spoke; for (i = 0u; i < OSCfg_TickWheelSize; i++) { p_spoke = (OS_TICK_SPOKE *)&OSCfg_TickWheel[i]; p_spoke->FirstPtr = (OS_TCB *)0; p_spoke->NbrEntries = (OS_OBJ_QTY )0u; p_spoke->NbrEntriesMax = (OS_OBJ_QTY )0u; } }
You need to call this function in OSInit() to complete the initialization. The time base list after initialization is as follows:
2.2 Insert task control block OS into time base list_ TickListInsert()(os_tick.c)
This function is used to insert the task TCB into the time base list, that is, the corresponding TCB two-way linked list. Note that this is a two-way linked list sorted in ascending order, which is sorted in ascending order according to the length of delay time. The work completed by this function includes:
- After obtaining the time to delay of the task, record the time when the delay expires in TCB, that is, record the value of the current time base counter plus the time to delay of the task in TickCtrMatch.
- Record the time of task delay in TCB. The function of tickmain can be equivalent to that of TaskDelayTicks.
Let's stop and take an example to understand the above principle. For example, the current time base counter OSTickCtr = 10 (i.e. 10 SysTick cycles have passed since the system was started), and the time that the task needs to be delayed (tickmain) = 2 (i.e. the task needs to be delayed for 2 SysTick cycles), which means that when OSTickCtr = 12 (i.e. 12 SysTick cycles have passed since the system was started), the task delay ends, 12 here is the value of TickCtrMatch. At that time, when the base counter is equal to 12, the delay end of the task is captured (so this also means Match?).
Um... Seeing this, do you think OSTickCtr is a bit like a timestamp... But in fact, OSTickCtr records "periodic timestamp", while the real timestamp records the real time!
Interestingly, TickRemain is actually dispensable. It is more used as a transition variable when calculating the remaining time. This is reflected in the code related to TickRemain in this section. In Section 2.4, you can try to comment out the lines of code related to tickmain, and you will find that this has no impact on the simulation results.
Recall the method of task delay in the previous chapters: when SysTick initiates an interrupt, the system will traverse the TCBS in the ready list and reduce the TaskDelayTicks in each TCB by one. Now our approach is to define a global variable and count it from the start of the system. The period of task end delay is the global variable plus the period length of delay. This value is recorded in TCB. Why change the method? Look at the difference between the two methods: the former is that we reduce each TCB by one, so it's more troublesome; The latter is the total time we count outside. You TCBS who need to delay should watch the time and report on your own when the time comes. What's more, we arrange these TCBS in order according to the delay time. If the delay time of the previous TCB is not up, the later TCBS don't have to look at it. It's definitely not up. You see, simplify complex things, uCOS absolutely!
- TickCtrMatch divided by the array size of the time base list oscfg_ The tickwheelsize is used as a remainder to access the time base list array as a subscript.
Continue to take the above example as an example. Now, TickCtrMatch = 12, we define OSCfg_TickWheelSize = 17, and the remainder is 12, so oscfg is to be accessed_ TickWheel[12]. Hash algorithm is used here. Next, this task will be inserted into oscfg_ In the two-way ascending linked list under tickwheel [12], the remaining work is the familiar operation of inserting the linked list.
- If the two-way linked list is empty: the forward and backward pointer fields of the TCB are zero.
- If the two-way linked list is not empty: you need to traverse the whole linked list, access each node, calculate the remaining time of the node, and compare it with the remaining time of the node to be inserted: if the accessed node is large, insert it in front of it; Otherwise, the size of the node to be inserted is large, and it is inserted behind the accessed node. If no node larger than the node to be inserted is found after traversal, it will be in the last row.
- Finally, the newly inserted TickSpokePtr of TCB needs to point to the corresponding array element.
Continue with the above example. This task has been inserted into oscfg_ In the two-way ascending linked list under tickwheel [12], the TickSpokePtr of the task TCB will point to OSCfg_TickWheel[12] to indicate the 12th position in the time base list.
/* Insert a task TCB into the time base list and arrange it in ascending order according to the delay time */ void OS_TickListInsert (OS_TCB *p_tcb, OS_TICK time) { OS_TICK_SPOKE_IX spoke; OS_TICK_SPOKE *p_spoke; OS_TCB *p_tcb0; OS_TCB *p_tcb1; /* TickCtrMatch Equal to the value of the current time base counter plus the time to delay the task */ p_tcb->TickCtrMatch = OSTickCtr + time; /* TickRemain Indicates how many SysTick cycles the task needs, and each cycle will be reduced by one */ p_tcb->TickRemain = time; /* (Hash algorithm) the spare part is used as the time base list oscfg_ Index of tickwheel [] */ spoke = (OS_TICK_SPOKE_IX)(p_tcb->TickCtrMatch % OSCfg_TickWheelSize); /* Get the root pointer of the bidirectional linked list */ p_spoke = &OSCfg_TickWheel[spoke]; /* If the two-way linked list is empty */ if (p_spoke->NbrEntries == (OS_OBJ_QTY)0u) { p_tcb->TickNextPtr = (OS_TCB *)0; p_tcb->TickPrevPtr = (OS_TCB *)0; p_spoke->FirstPtr = p_tcb; p_spoke->NbrEntries = (OS_OBJ_QTY)1u; } else /* If the two-way linked list is not empty */ { p_tcb1 = p_spoke->FirstPtr; /* Get the first node first */ /* Start traversing the two-way linked list */ while (p_tcb1 != (OS_TCB *)0) { p_tcb1->TickRemain = p_tcb1->TickCtrMatch - OSTickCtr; /* Calculate the remaining time of the visited node */ if (p_tcb->TickRemain > p_tcb1->TickRemain) /* (1) If the remaining time of the new node is greater than the remaining time of the visited node */ { if (p_tcb1->NextPtr != (OS_TCB *)0) /* (1.1) If the visited node is not the last node */ { p_tcb1 = p_tcb1->NextPtr; /* Go to the next node and continue the search */ } else /* (1.2) Otherwise, it means that the whole linked list has been traversed, and the remaining time of the new node is the maximum. Insert the new node in the last node */ { p_tcb->TickNextPtr = (OS_TCB *)0; p_tcb->TickPrevPtr = p_tcb1; p_tcb1->TickNextPtr = p_tcb; p_tcb1 = (OS_TCB *)0; /* After the insertion operation is completed, it will be cleared, and the next time it will jump out of the loop */ } } else /* (2) Otherwise, if the remaining time of the visited node is less than or equal to the remaining time of the new node, it will be inserted in front of the node */ { if (p_tcb1->TickPrevPtr == (OS_TCB *)0) /* (2.1) If the visited node is the first node */ { p_tcb->TickPrevPtr = (OS_TCB *)0; p_tcb->TickNextPtr = p_tcb1; p_tcb1->TickPrevPtr = p_tcb; p_spoke->FirstPtr = p_tcb; } else /* (2.2) Otherwise, a new node is inserted between the two nodes */ { p_tcb0 = p_tcb1->TickPrevPtr; p_tcb->TickPrevPtr = p_tcb0; p_tcb->TickNextPtr = p_tcb1; p_tcb0->TickNextPtr = p_tcb; p_tcb1->TickPrevPtr = p_tcb; } p_tcb1 = (OS_TCB *)0; /* After the insertion operation is completed, it will be cleared, and the next time it will jump out of the loop */ } } p_spoke->NbrEntries++; /* Number of nodes plus one */ } if (p_spoke->NbrEntriesMax < p_spoke->NbrEntries) /* Refresh Max */ { p_spoke->NbrEntriesMax = p_spoke->NbrEntries; } /* TickSpokePtr in task TCB refers back to the root node */ p_tcb->TickSpokePtr = p_spoke; }
Another example. We now OSTickCtr = 10, OSCfg_TickWheelSize = 12. There are three tasks as follows:
- Task 1: need to delay 1 cycle. Therefore, TickRemain = 1, TickCtrMatch = 11, and the index of the array to be inserted is 11% 12 = 11.
- Task 2: need to delay 13 cycles. Therefore, TickRemain = 13, TickCtrMatch = 23, and the index of the array to be inserted is 23% 12 = 11.
- Task 3: need to delay 25 cycles. Therefore, TickRemain = 25, TickCtrMatch = 35, and the index of the array to be inserted is 35% 12 = 11.
The situation after inserting the time base list is as follows:
Note that the three tasks TCB are in the same linked list, which is just a coincidence. If I change the delay of task 2 to 14 cycles, the subscript of the inserted array is 0 and will be inserted into other linked lists.
2.3 delete a specified task control block OS in the time base list_ TickListRemove()
This function deletes a specified task TCB from the time base list. The completed work includes:
- According to the TickSpokePtr of TCB, obtain the corresponding time base list array elements, and then obtain the head pointer of the linked list.
- If the header pointer is empty, it indicates that the linked list does not exist. Exit directly.
- If it is not empty, it indicates that the linked list exists. Delete the specified node.
- After deletion, clear the forward and backward pointer fields and the remaining time of the TCB. Reduce the number of nodes by one.
/* Delete a specified task TCB from the previous time base list */ void OS_TickListRemove (OS_TCB *p_tcb) { OS_TICK_SPOKE *p_spoke; OS_TCB *p_tcb1; OS_TCB *p_tcb2; /* Get the root pointer of the linked list where the task TCB is located */ p_spoke = p_tcb->TickSpokePtr; if (p_spoke != (OS_TICK_SPOKE *)0) /* If the linked list exists */ { if (p_tcb == p_spoke->FirstPtr) /* (1) If the deleted node is the first node */ { p_tcb1 = p_tcb->TickNextPtr; p_spoke->FirstPtr = p_tcb1; if (p_tcb1 != (OS_TCB *)0) /* If the linked list is not empty after deletion, the forward pointer field of the new head node is set to 0, otherwise no operation is needed */ { p_tcb1->TickPrevPtr = (OS_TCB *)0; } } else /* (2) If the deleted node is not the first node */ { p_tcb1 = p_tcb->TickPrevPtr; p_tcb2 = p_tcb->TickNextPtr; p_tcb1->TickNextPtr = p_tcb2; if (p_tcb2 != (OS_TCB *)0) /* If the last node is not deleted, the forward pointer field of the new tail node is set to 0, otherwise no operation is needed */ { p_tcb2->TickPrevPtr = p_tcb1; } } p_tcb->TickNextPtr = (OS_TCB *)0; p_tcb->TickPrevPtr = (OS_TCB *)0; p_tcb->TickSpokePtr = (OS_TICK_SPOKE *)0; p_tcb->TickRemain = (OS_TICK)0u; /* Clear remaining time */ p_tcb->TickCtrMatch = (OS_TICK)0u; p_spoke->NbrEntries--; /* Number of nodes minus one */ } }
2.4 check whether the task delay expires_ TickListUpdate()(os_tick.c)
This function is used to update the time base counter and scan whether the task delay in the time base list expires. The work completed is:
- The time base counter is incremented by one.
- The array subscript of the time base list is obtained by summing the value of the current time base counter, and then the corresponding two-way linked list is obtained.
- Start to traverse the linked list and visit each node. If it is found that the delay of any node has not arrived, then the subsequent nodes do not need to visit, because the linked list is sorted in ascending order, and the delay of the subsequent nodes must not have arrived; If a node is found to be delayed, set the task to ready state, and then continue to access the next node.
Did you find out? Which array element of the time base list is accessed is closely related to the value of the current time base counter. The corresponding array element is accessed through the hash algorithm. However, the purpose of doing so does not seem very clear (?).
/* Update the time base counter and scan whether the task delay in the time base list expires */ void OS_TickListUpdate (void) { OS_TICK_SPOKE_IX spoke; OS_TICK_SPOKE *p_spoke; OS_TCB *p_tcb; OS_TCB *p_tcb_next; CPU_BOOLEAN done; CPU_SR_ALLOC(); OS_CRITICAL_ENTER(); /* Time base counter plus one */ OSTickCtr++; /* (Hash algorithm) the spare part is used as the time base list oscfg_ Index of tickwheel [] */ spoke = (OS_TICK_SPOKE_IX)(OSTickCtr % OSCfg_TickWheelSize); /* Get the root pointer of the bidirectional linked list */ p_spoke = &OSCfg_TickWheel[spoke]; /* Get the first node of the two-way linked list */ p_tcb = p_spoke->FirstPtr; done = DEF_FALSE; while (done == DEF_FALSE) { if (p_tcb != (OS_TCB *)0) /* If node exists */ { p_tcb_next = p_tcb->TickNextPtr; p_tcb->TickRemain = p_tcb->TickCtrMatch - OSTickCtr; /* Calculate the remaining time of the node */ if (OSTickCtr == p_tcb->TickCtrMatch) /* If the delay time of the node has expired */ { OS_TaskRdy (p_tcb); /* Make the task ready */ } else /* Otherwise, the delay time of the node is not up */ { done = DEF_TRUE; /* Then exit the cycle, because the linked list nodes are arranged in ascending order of delay time. The delay time of this node has not arrived, and the subsequent nodes must not have arrived */ } p_tcb = p_tcb_next; } else { done = DEF_TRUE; } } OS_CRITICAL_EXIT(); }
for instance. We define oscfg_ When tickwheelsize = 12 and OSTickCtr = 7, the following three tasks are inserted:
- Task 1: need to delay 16 cycles. Therefore, TickRemain = 16, TickCtrMatch = 28, and the index of the array to be inserted is 11% 12 = 11.
- Task 2: need to delay 28 cycles. Therefore, TickRemain = 28, TickCtrMatch = 35, and the index of the array to be inserted is 23% 12 = 11.
- Task 3: need to delay 40 cycles. The subscript ctr12 = match 40 is required, so the subscript ctr12 = match 47 is required.
The time base list is shown in the following figure:
When the SysTick interrupt comes, OSTickCtr = 8, for oscfg_ If the remainder of tickwheelsize (equal to 12) is equal to 8, oscfg_ Scan the linked list under tickwheel [8]. It can be seen from the figure that if there is no node under the index 8, you can exit directly. These three TCB S are in oscfg_ The linked list under tickwheel [11] does not need to be scanned at all, because the time has just passed one clock cycle. They have just been inserted into the time base list, which is far from reaching the delay time they need.
2.5 panorama of time base list operation
When we implemented the relevant code of the time base list, a very interesting thing happened: we assumed OSCfg_TickWheelSize = 17, the size of the real-time base list is 17, then:
- OSTickCtr = 0 when the system is just started;
- OSTickCtr = 1 after SysTick interrupt, only check OSCfg_TickWheel[1];
- After the SysTick interrupt occurs again, OSTickCtr = 2, only check OSCfg_TickWheel[2];
- After the SysTick interrupt occurs again, OSTickCtr = 3, and only oscfg is checked_ TickWheel[3];
- ...
- After the SysTick interrupt occurs again, OSTickCtr = 16, and only oscfg is checked_ TickWheel[16];
- After the SysTick interrupt occurs again, OSTickCtr = 17, and only oscfg is checked_ TickWheel[0];
- After the SysTick interrupt occurs again, OSTickCtr = 18, and only oscfg is checked_ TickWheel[1];
- ...
- After the SysTick interrupt occurs again, OSTickCtr = 33, and only oscfg is checked_ TickWheel[16];
- After the SysTick interrupt occurs again, OSTickCtr = 34, and only oscfg is checked_ TickWheel[0];
- After the SysTick interrupt occurs again, OSTickCtr = 35, and only oscfg is checked_ TickWheel[1];
- ...
This cycle checks each array element. What happens when we insert a TCB that needs to be delayed? Suppose a TCB is inserted when OSTickCtr = 4 and the delay time is 3, then:
- OSTickCtr = 0 when the system is just started;
- OSTickCtr = 1 after SysTick interrupt, only check OSCfg_TickWheel[1], no task found;
- After the SysTick interrupt occurs again, OSTickCtr = 2, only check OSCfg_TickWheel[2], no task found;
- After the SysTick interrupt occurs again, OSTickCtr = 3, and only oscfg is checked_ Wheel [tick], no task found;
- After the SysTick interrupt occurs again, OSTickCtr = 4, and only oscfg is checked_ Tickwheel [4], no task found; Then the task initiates blocking, and the delay time is 3. It is calculated that the delay expires when OSTickCtr = 7 (TickCtrMatch = 7), so it is in oscfg_ Insert TCB under tickwheel [7];
- After the SysTick interrupt occurs again, OSTickCtr = 5, only check OSCfg_TickWheel[5], no task found;
- After the SysTick interrupt occurs again, OSTickCtr = 6, and only oscfg is checked_ Tickwheel [6], no task found;
- After the SysTick interrupt occurs again, OSTickCtr = 7, and only oscfg is checked_ Tickwheel [7], a task is found, and TickCtrMatch == OSTickCtr, the delay expires and moves out of the time base list;
- ...
Let's assume that a TCB is inserted when OSTickCtr = 25 and the delay time is 19, (OSCfg_TickWheelSize = 17), then:
- OSTickCtr = 0 when the system is just started;
- OSTickCtr = 1 after SysTick interrupt, only check OSCfg_TickWheel[1], no task found;
- After the SysTick interrupt occurs again, OSTickCtr = 2, only check OSCfg_TickWheel[2], no task found;
- After the SysTick interrupt occurs again, OSTickCtr = 3, and only oscfg is checked_ Tickwheel [3], no task found;
- ...
- After the SysTick interrupt occurs again, OSTickCtr = 25, only check OSCfg_TickWheel[8], no task found; Then the task initiates blocking, and the delay time is 19. It is calculated that the delay expires when OSTickCtr = 44 (TickCtrMatch = 44), so it is in oscfg_ Insert TCB under tickwheel [10];
- After the SysTick interrupt occurs again, OSTickCtr = 26, and only oscfg is checked_ Tickwheel [9], no task found;
- After the SysTick interrupt occurs again, OSTickCtr = 27, and only oscfg is checked_ Tickwheel [10], it is found that there is a task but it is not expired (tickctrmatch! = ostickctr), which continues to be in the time base list;
- ...
- After the SysTick interrupt occurs again, OSTickCtr = 43, and only oscfg is checked_ Tickwheel [9], no task found;
- After the SysTick interrupt occurs again, OSTickCtr = 44, and only oscfg is checked_ Tickwheel [10], a task is found, and TickCtrMatch == OSTickCtr, the delay expires and moves out of the time base list;
- ...
I hope this will help you understand the operation mechanism of the whole time base list.
3 add and modify corresponding codes
3.1 set the task to ready OS_TaskRdy()(os_core.c)
This is a newly added function, located in os_core.c is used to set the task to ready status. The specific operations are as follows:
- Delete the TCB from the time base list.
- Add the TCB to the ready list.
/* Task ready */ void OS_TaskRdy (OS_TCB *p_tcb) { OS_TickListRemove (p_tcb); /* Remove from time base list */ OS_RdyListInsert (p_tcb); /* Insert ready list */ }
3.2 blocking delay function OSTimeDly() (os_time.c)
This function needs to be modified to set the task to blocking status. The specific operations are as follows:
- Add the TCB from the time base list.
- Delete the TCB from the ready list.
- Execute task scheduling.
be careful:
- The above work is located in the critical section.
We find that the operation of the blocking delay function is just the opposite to that of the above function.
/* Blocking delay */ void OSTimeDly (OS_TICK dly) { CPU_SR_ALLOC(); OS_CRITICAL_ENTER(); /* Insert time base list */ OS_TickListInsert (OSTCBCurPtr, dly); /* Remove from ready list */ OS_RdyListRemove (OSTCBCurPtr); OS_CRITICAL_EXIT(); /* Task switching */ OSSched(); }
3.3 SysTick initiates an interrupt and calls OSTimeTick() (os_time.c)
Every time SysTick initiates an interrupt, it will call this function to complete two things:
- Update the time base list. Increase the time base counter by one and check whether there is a task delay end in the ready list. If so, call the OS_TaskRdy(). If not, you don't have to do anything.
- Then perform task scheduling.
void OSTimeTick (void) { /* Update time base list */ OS_TickListUpdate(); /* task scheduling */ OSSched(); }
4 Application of time base list
4.1 main function (app.c)
app.c do not change.
#include "ARMCM3.h" #include "os.h" #define TASK1_STK_SIZE 128 #define TASK2_STK_SIZE 128 #define TASK3_STK_SIZE 128 static CPU_STK Task1Stk[TASK1_STK_SIZE]; static CPU_STK Task2Stk[TASK2_STK_SIZE]; static CPU_STK Task3Stk[TASK3_STK_SIZE]; static OS_TCB Task1TCB; static OS_TCB Task2TCB; static OS_TCB Task3TCB; uint32_t flag1; uint32_t flag2; uint32_t flag3; void Task1 (void *p_arg); void Task2 (void *p_arg); void Task3 (void *p_arg); int main (void) { OS_ERR err; /* Initialize relevant global variables and create idle tasks */ OSInit(&err); /* CPU Initialization: initialization timestamp */ CPU_Init(); /* Turn off the interrupt because the OS is not started at this time. If you turn on the interrupt, SysTick will cause the interrupt */ CPU_IntDis(); /* Initialize SysTick, configure SysTick to interrupt once every 10ms, and Tick = 10ms */ OS_CPU_SysTickInit(10); /* Create task */ OSTaskCreate ((OS_TCB*) &Task1TCB, (OS_TASK_PTR) Task1, (void *) 0, (OS_PRIO) 1, (CPU_STK*) &Task1Stk[0], (CPU_STK_SIZE) TASK1_STK_SIZE, (OS_ERR *) &err); OSTaskCreate ((OS_TCB*) &Task2TCB, (OS_TASK_PTR) Task2, (void *) 0, (OS_PRIO) 2, (CPU_STK*) &Task2Stk[0], (CPU_STK_SIZE) TASK2_STK_SIZE, (OS_ERR *) &err); OSTaskCreate ((OS_TCB*) &Task3TCB, (OS_TASK_PTR) Task3, (void *) 0, (OS_PRIO) 3, (CPU_STK*) &Task3Stk[0], (CPU_STK_SIZE) TASK3_STK_SIZE, (OS_ERR *) &err); /* Start the OS and will not return */ OSStart(&err); } void Task1 (void *p_arg) { for (;;) { flag1 = 1; OSTimeDly (2); flag1 = 0; OSTimeDly (2); } } void Task2 (void *p_arg) { for (;;) { flag2 = 1; OSTimeDly (2); flag2 = 0; OSTimeDly (2); } } void Task3 (void *p_arg) { for (;;) { flag3 = 1; OSTimeDly (2); flag3 = 0; OSTimeDly (2); } }
4.2 operation process
4.2.1 in the main function
- System initialization: initialize various global variables, initialize priority table, initialize ready list, initialize time base list, and initialize idle tasks (including initializing idle task stack and idle task TCB).
- CPU initialization: temporarily empty.
- Off interrupt: because the OS is not started at this time, if the interrupt is enabled, SysTick will cause an interrupt and interrupt the initialization process.
- Initialize SysTick: configure SysTick to interrupt once every 10ms, and Tick = 10ms.
- Create task: including creating task stack and task TCB, inserting TCB into ready list and setting it in the corresponding position of priority table.
- Start the system: first find the highest priority, then start running the task corresponding to the highest priority (the highest priority is 1, that is, task 1), start the first task switching (at this time, the final initialization process will be completed, that is, the interrupt priority configuration of PendSV, and then trigger PendSV exception to initiate task switching), and hand over the CPU ownership to task Task1.
4.2.2 in task 1
- flag1 = 1.
- Execute to the blocking function OSTimeDly: insert the TCB of task 1 into the time base list (TickCtrMatch = 2), remove the TCB in the ready list (at the same time, clear the corresponding position in the priority table, that is, the position of priority 1), and then start task scheduling.
- Execute task scheduling OSSched: the task scheduler finds the highest priority first, and then finds the task with the highest priority. TCB. If the task is found to be the current task, task switching will not be performed. In this case, it is found that the highest priority is 2, and the corresponding task is task 2. If it is not the current task, then task switching is initiated (PendSV exception is initiated).
- PendSV exception handler: save the status of task 1, load the status of task 2, and update the value of global variables.
4.2.3 in task 2
- flag2 = 1.
- Execute to the blocking function OSTimeDly: insert the TCB of task 2 into the time base list (TickCtrMatch = 2), remove the TCB in the ready list (at the same time, clear the corresponding position in the priority table, that is, the position of priority 2), and then start task scheduling.
- Execute task scheduling OSSched: the task scheduler finds the highest priority first, and then finds the highest priority task TCB. If the task is found to be the current task, task switching will not be performed. In this case, it is found that the highest priority is 3, and the corresponding task is task 3. If it is not the current task, the task switching is initiated (PendSV exception is initiated).
- PendSV exception handler: save the status of task 2, load the status of task 3, and update the value of global variables.
4.2.4 in task 3
- flag3 = 1.
- Execute to the blocking function OSTimeDly: insert the TCB of task 3 into the time base list (TickCtrMatch = 2), remove the TCB in the ready list (at the same time, clear the corresponding position in the priority table, that is, the position of priority 3), and then start task scheduling.
- Execute task scheduling OSSched: the task scheduler finds the highest priority first, and then finds the highest priority task TCB. If the task is found to be the current task, task switching will not be performed. In this case, if the task is found to be idle in d31, it is the task with the highest priority (if the task is found to be idle in d31, it is not the task with the highest priority).
- PendSV exception handler: save the status of task 3, load the status of idle tasks, and update the value of global variables.
Note that at this time, the time base counter is 0, and the delay time of the three tasks is 2 SysTick cycles, OSCfg_TickWheelSize = 17, so they are all located in oscfg_ Two way linked list under tickwheel [2].
4.2.5 SysTick initiates an interrupt in an idle task
- Execute OSTimeTick: add one to the time base counter (OSTickCtr = 1), check the time base list (OSCfg_TickWheel[1]), and find that the delay of each task has not expired (TickCtrMatch = 2). Finally, initiate task scheduling. It is found that there is no need to switch tasks, and idle tasks continue to run.
- Initiate the interrupt again, execute OSTimeTick: add one to the time base counter (OSTickCtr = 2), check the time base list (OSCfg_TickWheel[2]), find that the delay of each task has expired (TickCtrMatch = 2), and set them all to the ready state. The process of setting to the ready state is to delete the corresponding TCB in the time base list and add the corresponding TCB in the ready list (at the same time, reset the priorities of task 1, task 2 and task 3 in the corresponding positions in the priority table). Finally, initiate task scheduling and find that the highest priority is 1, corresponding to task 1. Switch to task 1 for operation.
4.2.6 again in task 1
- Change the global variable flag1 from 1 to 0.
- Execute to the blocking function OSTimeDly: insert the TCB of task 1 into the time base list (TickCtrMatch = 4), remove the TCB in the ready list (at the same time, clear the corresponding position in the priority table, that is, the position of priority 1), and then start task scheduling.
- Execute task scheduling OSSched: the task scheduler finds the highest priority first, and then finds the highest priority task TCB. If the task is found to be the current task, task switching will not be performed. In this case, it is found that the highest priority is 2, and the corresponding task is task 2. If it is not the current task, then task switching is initiated (PendSV exception is initiated).
- PendSV exception handler: save the status of task 1, load the status of task 2, and update the value of global variables.
After such repetition, the three tasks enter the blocking state again. At this time, the time base counter is 2 (OSTickCtr = 2), and the delay time of the three tasks is 2 SysTick cycles (TickCtrMatch = 4), OSCfg_TickWheelSize = 17, so they are all located in oscfg_ Two way linked list under tickwheel [4]. The next steps are similar to the above. When initiating the fourth SysTick interrupt (OSTickCtr = 4), check the time base list (OSCfg_TickWheel[4]) and find that the three tasks have been delayed, so they enter the ready state again.
4.3 experimental phenomena
The experimental phenomenon is the same as that in the previous record and also conforms to the above analysis, as shown in the figure: