From the single-chip microcomputer to the operating system 7 – In-depth understanding of the delay mechanism of Freertos

If you haven’t studied the source code of the operating system, it’s not considered learning the operating system.

FreeRTOS Time Management

Time management includes two aspects: system beat and task delay management.

System beat:

In the previous article, I also talked a lot. If you want the system to run normally, then the clock beat is indispensable, The clock tick of FreeRTOS is usually provided by SysTick, which periodically generates timing interrupts. The core of the so-called clock tick management is the service program of this timing interrupt. The core job of the clock beat isr of FreeRTOS is to call the vTaskIncrementTick() function. See the previous article for details.

Delay Management

FreeRTOS provides two system delay functions:

  • Relative delay functionvTaskDelay()?
  • Absolute delay function vTaskDelayUntil().

These delay functions are not like the delay functions we used to write code in bare metal. The operating system does not allow the CPU to wait and consume time because it is too inefficient.

At the same time, we must warn students who learn operating systems not to use bare metal thinking to learn operating systems.

Task Delay

The task may need to be delayed. There are two cases, one is that the task is set by vTaskDelay or vTaskDelayUntil delay, another case is when the task is waiting for an event (such as waiting for a certain semaphore, or a certain message queue) when timeout is specified (that is, the maximum waiting timeout time, if The waiting event has not occurred, then no longer continue to wait), there must be a blocking situation in the cycle of each task, otherwise the task with a lower priority than the task will never be able to run.

The difference between relative delay and absolute delay?

Relative delay: vTaskDelay():

Relative delay: vTaskDelay():

strong>

Relative delay means that each delay starts from the task execution function vTaskDelay() and ends at the specified time delay

< strong>Absolute delay: vTaskDelayUntil():

Absolute delay means that the task that calls vTaskDelayUntil() runs once every x time. That is, the task runs periodically.

Relative delay: vTaskDelay()

The relative delay vTaskDelay() is from calling vTaskDelay() This function starts to delay, but when the task is executed, an interruption may occur, which causes the task execution time to become longer, but the delay time of the entire task is still 1000 ticks, which is not periodic. Take a brief look at the following code:

void vTaskA( void * pvParameters) {while(1) {// ... // Here is the task body code // ... /* Call the relative delay function and block 1000 ticks */ vTaskDelay( 1000 );} }

It may not be clear enough, you can look at the diagram.

freertos-delay-1

When the task is running At this time, if it is interrupted by a certain advanced task or interrupt, the execution time of the task will be longer, but the delay is still 1000tick like this , The time of the entire system is confused.

If it is not clear enough, look at the source code of vTaskDelay()

void vTaskDelay( const TickType_t xTicksToDelay ){ BaseType_t xAlreadyYielded = pdFALSE; /* The delay time is zero Only forcibly switch tasks. */ if( xTicksToDelay> (TickType_t) 0U) (1) {configASSERT( uxSchedulerSuspended == 0 ); vTaskSuspendAll(); (2) {traceTASK_DELAY(); /*Remove the current task from the ready list, and according to the current The system beat counter value calculates the wake-up time, and then adds the task to the delay list*/ prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );} xAlreadyYielded = xTaskResumeAll();} else {mtCOVERAGE_TEST_MARKER();} /* Force a context switch*/ if( xAlreadyYielded == pdFALSE) {portYIELD_WITHIN_API();} else {mtCOVERAGE_TEST_MARKER(); }}
  • (1): If the delay time passed in is 0, It can only be forced to switch tasks. The call is portYIELD_WITHIN_API(), which is actually a macro, and what really works is portYIELD(). Here is its source code:
#define portYIELD() {/* Set PendSV to request context switching. */ portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; __dsb( portSY_FULL_READ_WRITE ); __isb( portSY_FULL_READ_WRITE ); }
  • (2): Suspend the current task

Then change the current task Delete from the ready list, and then add to the delay list. This process is completed by calling the function prvAddCurrentTaskToDelayedList(). Since this function is too long, I won’t explain it. If you are interested, you can take a look. I will briefly talk about the process. There is such a variable in FreeRTOS, which is used to record the value of systick.

PRIVILEGED_DATA?static?volatile?TickType_t?xTickCount?????=?(?TickType_t?)?0U;

At every tick xTickCount is incremented by one when it is interrupted. Its value indicates the number of times the system tick is interrupted. So when do you wake up the tasks that have been added to the delay list? In fact, it’s very simple. The method of FreeRTOS is xTickCount (current system time) + xTicksToDelay (time to delay). When the relative delay time is up, it wakes up. This (xTickCount+ xTicksToDelay) time will be recorded in the task control block of the task.

Seeing this, someone must ask, this variable is of type TickType_t (32-bit), it will definitely overflow, yes, the variable will overflow one day, but FreeRTOS is the world’s number one operating system. FreeRTOS uses two delay lists:

xDelayedTaskList1 and xDelayedTaskList2

And use two list pointer type variables pxDelayedTaskList and pxOverflowDelayedTaskList to point to the above delayed list 1 and delayed list 2 respectively (in creating task If the kernel determines that xTickCount+xTicksToDelay overflows, it will hook the current task to the list pointer?pxOverflowDelayedTaskList. Otherwise, it is hooked to the list pointed to by the list pointer pxDelayedTaskList. When the time is up, the delayed task will be deleted from the delay list and added to the ready list. Of course, at this time, it is the scheduler who decides whether the task can run. If the priority of the task is greater than the currently running task, then schedule The processor will perform task scheduling.

Absolute delay: vTaskDelayUntil()

The parameter of vTaskDelayUntil() specifies the exact tick count value

Calling vTaskDelayUntil() is to hope that the task will be executed periodically at a fixed frequency without being affected by external influences. The time interval from the beginning of the task to the beginning of the next operation is absolute. Not relative. Suppose the main task is interrupted 0.3s, but the next wake-up time is fixed, so it will still run periodically.

freertos-delay-2

Look below< code>vTaskDelayUntil(), pay attention, the usage of vTaskDelayUntil() is different from vTaskDelay():

void?vTaskA(?void?*?pvParameters?)??{??????/*? is used to save the last time. The system automatically updates after the call?*/????static?portTickType?PreviousWakeTime;????/*? Set the delay time and convert the time to the number of beats?*/????const?portTickType?TimeIncrement?= ?pdMS_TO_TICKS(1000);?????/*?Get the current system time?*/????PreviousWakeTime?=?xTaskGetTickCount();?????while(1)??????{? ??????????/*? Call the absolute delay function, the task time interval is 1000 ticks?*/????????? vTaskDelayUntil(?&PreviousWakeTime, TimeIncrement?);??? ????????//??...?????????//?? Here is the task body code?????????//??...? ????}??}?

When using, the delay time should be converted into system beats, and the delay function should be called before the main task.

The task will first call vTaskDelayUntil() to make the task enter the blocking state. When the time is up, it will be released from the blocking, and then execute the main code, and the main task code is executed. Will continue to call vTaskDelayUntil() to make the task enter the blocking state, and then execute in a loop like this. Even if the task is interrupted during the execution, it will not affect the running cycle of the task, but only shorten the blocking time.

Let’s take a look at the source code of vTaskDelayUntil():

void?vTaskDelayUntil(?TickType_t?*?const?pxPreviousWakeTime,? const?TickType_t?xTimeIncrement?)(????TickType_t?xTimeToWake;????BaseType_t?xAlreadyYielded,?xShouldDelay?=?pdFALSE;????configASSERT(?pxPreviousWakeTime?);????configASSERT(?( ?xTimeIncrement?>?0U?)?);????configASSERT(?uxSchedulerSuspended?==?0?);????vTaskSuspendAll();??????????????? ??????????????????//?(1)????{????????/*? Save the system beat interruption counter?*/? ???????const?TickType_t?xConstTickCount?=?xTickCount;????????/*? Generate the tick time to wake up the task. */????????xTimeToWake?=?*pxPreviousWakeTime?+?xTimeIncrement;????????/*?pxPreviousWakeTime saves the last wake-up time, and it takes a certain amount of time to execute the task body after waking up Code, ???????????? If the last wake-up time is greater than the current time, it means that the beat counter has overflowed? See the picture for details?*/????????if(?xConstTickCount??xConstTickCount?)?)?????????? ?{???????????????xShouldDelay?=?pdTRUE;???????????}???????????else??? ????????{???????????????mtCOVERAGE_TEST_MARKER();???????????}??????? }?? ??????else????????{???????????/* The tick time does not overflow. In this case, if the wake-up time overflows, or the tick time is less than the wake-up time, we will delay. */???????????if(?(?xTimeToWake??xConstTickCount?)?)????????? ??{???????????????xShouldDelay?=?pdTRUE;???????????}???????????else?? ?????????{???????????????mtCOVERAGE_TEST_MARKER();???????????}??????}?? ????/*? Update the wake-up time to prepare for the next call to this function. ?*/??????*pxPreviousWakeTime?=?xTimeToWake;????? if(?xShouldDelay?!=?pdFALSE ?)??????{??????????traceTASK_DELAY_UNTIL(?xTimeToWake?);??????????/*?prvAddCurrentTaskToDelayedList() requires block time, not wake-up time , So subtract the current tick count. ?*/??????????prvAddCurrentTaskToDelayedList(?xTimeToWake?-?xConstTickCount,?pdFALSE?);??????}??????else??????{?? ????????mtCOVERAGE_TEST_MARKER();??????}??}??xAlreadyYielded?=?xTaskResumeAll();??/*? If xTaskResumeAll has not yet performed rescheduling, we may put ourselves to sleep . */??if(?xAlreadyYielded?==?pdFALSE?)? {????portYIELD_WITHIN_API();? }? else {????mtCOVERAGE_TEST_MARKER();? }}

The delay function vTaskDelay is different. This function adds a parameter pxPreviousWakeTime to point to a variable. The variable saves the time when the last task was unblocked. After that, the function vTaskDelayUntil( ) This variable is automatically updated internally. Since the variable xTickCount may overflow, the program must detect various overflow situations and ensure that the delay period cannot be less than the execution time of the task body code.

There will be the following 3 situations before the task can be added to the delay list.

Please remember the meaning of these words:

  • xTimeIncrement: task cycle time
  • pxPreviousWakeTime : The time of the last wake-up
  • xTimeToWake: The time of the next wake-up
  • xConstTickCount: The time point of the entry delay
  1. The third case: the normal case without overflow.

With time as the horizontal axis, the time point of the last awakening is less than the time point of the next awakening. This is normal.

freertos-delay-3

  1. 第Two situations: the wake-up time counter (xTimeToWake) overflows.

That is the code if( (xTimeToWake <*pxPreviousWakeTime) || (xTimeToWake> xConstTickCount) )

 freertos-delay-4

  1. The first case: Wake-up time (xTimeToWake< /code>) and the time point of the entry delay (xConstTickCount) are overflowing.

In the code if( (xTimeToWake <*pxPreviousWakeTime) && (xTimeToWake> xConstTickCount) )

freertos -delay-5

It can be seen from the figure that whether it is overflow or no overflow, it will be required next time Before waking up the task, the main code of the current task must be executed. That is to say, the task execution time is not allowed to be greater than the delay time, and there cannot always be a task that needs to be executed every 10ms for a time of 20ms. After the calculated wake-up time is legal, the current task is added to the delay list, and there are also two delay lists. Every time the system tick is interrupted, the interrupt service function will check the two delay lists to see if the delayed task has expired. If the time expires, the task will be deleted from the delay list and re-added to the ready list. If the priority of the task newly added to the ready list is greater than the current task, a context switch will be triggered.

Summary

If the task call is relatively delayed, its running cycle is completely unpredictable. If the priority of the task is not the highest, the error is even greater. It is like a task that must be within 5ms. If a relative delay of 1ms is used, then it is likely to be interrupted by a higher priority task during the execution of the task and miss it.< code>5ms, but the absolute delay is called, the task will periodically remove the task from the blocking list, but whether the task can run depends on the priority of the task, if the priority is At the highest, the task cycle is relatively accurate (relative to vTaskDelay). If you want to perform a task more accurately and periodically, you can use the system beat hook function vApplicationTickHook(), it is called in the tick interrupt service function, so the code in this function must be concise, and blocking is not allowed.

Follow me

Welcome to follow my official account

For more information, please pay attention to the official account of "IoT Development"!

Leave a Comment

Your email address will not be published.