FreeRTOS时间管理
FreeRTOS的两个延时函数:
vTaskDelay() 相对延时
vTaskDelayUntil() 绝对延时
1 相对延时
主要过程:挂起任务调度器,将任务以参数延时值添加到演示列表,然后恢复任务调度器。
void vTaskDelay(const TickType_t xTicksToDelay)
BaseType_t xAlreadyYielded = pdFALSE;
if (xTicksToDelay > (TickType_t)0U)
configASSERT(uxSchedulerSuspended == 0);
prvAddCurrentTaskToDelayedList(xTicksToDelay, pdFALSE);
xAlreadyYielded = xTaskResumeAll();
mtCOVERAGE_TEST_MARKER();
if (xAlreadyYielded == pdFALSE)
mtCOVERAGE_TEST_MARKER();
函数实现过程分析:
添加任务到演示列表的过程:
先将任务从当前的状态列表中移除,然后计算当前tick值+延时值,以计算的值添加到延时列表,然后重新计算下一次任务切换的阻塞延时变量值。
static void prvAddCurrentTaskToDelayedList(TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely)
const TickType_t xConstTickCount = xTickCount;
#if (INCLUDE_xTaskAbortDelay == 1)
pxCurrentTCB->ucDelayAborted = pdFALSE;
if (uxListRemove(&(pxCurrentTCB->xStateListItem)) == (UBaseType_t)0)
portRESET_READY_PRIORITY(pxCurrentTCB->uxPriority, uxTopReadyPriority);
mtCOVERAGE_TEST_MARKER();
#if (INCLUDE_vTaskSuspend == 1)
if ((xTicksToWait == portMAX_DELAY) && (xCanBlockIndefinitely != pdFALSE))
vListInsertEnd(&xSuspendedTaskList, &(pxCurrentTCB->xStateListItem));
xTimeToWake = xConstTickCount + xTicksToWait;
listSET_LIST_ITEM_VALUE(&(pxCurrentTCB->xStateListItem), xTimeToWake);
if (xTimeToWake < xConstTickCount)
vListInsert(pxOverflowDelayedTaskList, &(pxCurrentTCB->xStateListItem));
vListInsert(pxDelayedTaskList, &(pxCurrentTCB->xStateListItem));
if (xTimeToWake < xNextTaskUnblockTime)
xNextTaskUnblockTime = xTimeToWake;
mtCOVERAGE_TEST_MARKER();
xTimeToWake = xConstTickCount + xTicksToWait;
listSET_LIST_ITEM_VALUE(&(pxCurrentTCB->xStateListItem), xTimeToWake);
if (xTimeToWake < xConstTickCount)
vListInsert(pxOverflowDelayedTaskList, &(pxCurrentTCB->xStateListItem));
vListInsert(pxDelayedTaskList, &(pxCurrentTCB->xStateListItem));
if (xTimeToWake < xNextTaskUnblockTime)
xNextTaskUnblockTime = xTimeToWake;
mtCOVERAGE_TEST_MARKER();
(void)xCanBlockIndefinitely;
具体分析:
什么是相对延时?
void test_task(void *pvParameters)
在上面的任务函数中,如主体函数是在某条件下读取寄存器值(状态寄存器可能需要等待),那么这个主体函数的执行时间是不确定的,整个循环的执行时间等于主体函数执行时间 + 延时值(800)。这里的相对,只是 vTaskDelay() 相对于主体函数延时800,而整个 for (; ;)的执行时间是浮动的。
2 绝对延时
void vTaskDelayUntil(TickType_t *const pxPreviousWakeTime, const TickType_t xTimeIncrement)参数说明:
绝对延时的原理:
void test_task(void *pvParameters)
uint32_t origin_tick = xTickCount;
vTaskDelayUntil(origin_tick, 800);
如下所示,绝对延时函数会获取进入循环时的系统 Tick 值,然后每隔固定的延时节拍数(800)都会唤醒任务,不管任务的主体函数执行时间如何浮动,内核都会在 初始Tick值 + 延时Tick值(×1 ×2 ×3...)唤醒任务,因此是绝对延时函数。
函数源码:
实现要点:获取下次唤醒时间值 = 上次唤醒时间值 + 绝对延时值,自动更新上次唤醒时间值(设定为当前唤醒值,以实现绝对时间间隔),根据下次唤醒时间值,以及当前的 Tick 值,来计算延时值并插入到延时列表中。
void vTaskDelayUntil(TickType_t *const pxPreviousWakeTime, const TickType_t xTimeIncrement)
BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;
configASSERT(pxPreviousWakeTime);
configASSERT((xTimeIncrement > 0U));
configASSERT(uxSchedulerSuspended == 0);
const TickType_t xConstTickCount = xTickCount;
xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;
if (xConstTickCount < *pxPreviousWakeTime)
if ((xTimeToWake < *pxPreviousWakeTime) && (xTimeToWake > xConstTickCount))
mtCOVERAGE_TEST_MARKER();
if ((xTimeToWake < *pxPreviousWakeTime) || (xTimeToWake > xConstTickCount))
mtCOVERAGE_TEST_MARKER();
*pxPreviousWakeTime = xTimeToWake;
if (xShouldDelay != pdFALSE)
traceTASK_DELAY_UNTIL(xTimeToWake);
prvAddCurrentTaskToDelayedList(xTimeToWake - xConstTickCount, pdFALSE);
mtCOVERAGE_TEST_MARKER();
xAlreadyYielded = xTaskResumeAll();
if (xAlreadyYielded == pdFALSE)
mtCOVERAGE_TEST_MARKER();
函数实现过程分析:
绝对函数执行图示:
绝对延时函数的调用方式:
其实使用函数 vTaskDelayUntil()延时的任务也不一定就能周期性的运行,使用函数vTaskDelayUntil()只能保证你按照一定的周期取消阻塞,进入就绪态。如果有更高优先级或者中断的话你还是得等待其他的高优先级任务或者中断服务函数运行完成才能轮到你。这个绝对延时只是相对于 vTaskDelay()这个简单的延时函数而言的。
滴答定时器
SysTick直属于Cortex-M内核,他不是stm32专属的,只要是Cortex-M内核的MCU就都有SysTick。SysTick计数器是个24位的向下计数器,这个计数器的使命就是为系统提供服务的。操作系统都需要一个系统时钟,每个系统时钟周期都会触发OS内核执行一些系统调用,比如进行任务管理任务切换等。SysTick就可以完成此功能,使能SysTick中断,设定好定时周期,SysTick就会周期性的触发中断,跟系统有关的操作就可以在SysTick的中断服务函数中完成。如果不使用系统的话,SysTick也可以当成普通的定时器来使用。
Cortex-M编程手册中关于SysTick和寄存器的描述:
在库函数中关于 SysTick 的描述:
#define SCS_BASE (0xE000E000UL)
#define SysTick_BASE (SCS_BASE + 0x0010UL)
#define SysTick ((SysTick_Type *)SysTick_BASE)
FreeRTOS中,有关SysTick的中断服务函数:
其实就是在中断服务函数中,先禁用系统中断,然后调用 xTaskIncrementTick() 来递增系统计数值,根据函数返回值来判断是否要进行任务切换,通过将PENDSV寄存器中断位置1以启用中断,并在PENDSV中断服务函数中进行任务切换。
void SysTick_Handler(void)
#if (INCLUDE_xTaskGetSchedulerState == 1)
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
void xPortSysTickHandler(void)
if (xTaskIncrementTick() != pdFALSE)
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
vPortClearBASEPRIFromISR();
不管是什么系统,运行都需要有个系统时钟节拍,xTickCount 就是FreeRTOS 的系统时钟节拍计数器。每个滴答定时器中断中 xTickCount 就会加一,xTickCount 的具体操作过程是在函数 xTaskIncrementTick()中进行的,这个函数相当复杂,不用纠结。
转载于:https://blog.csdn.net/dingyc_ee/article/details/104092532
文章评论(0条评论)
登录后参与讨论