FreeRTOS专栏10:FreeRTOS时间管理

FreeRTOS时间管理

FreeRTOS的两个延时函数:

vTaskDelay()          相对延时

vTaskDelayUntil()   绝对延时

1 相对延时

主要过程:挂起任务调度器,将任务以参数延时值添加到演示列表,然后恢复任务调度器。

void vTaskDelay(const TickType_t xTicksToDelay)
{
    BaseType_t xAlreadyYielded = pdFALSE;

    /* A delay time of zero just forces a reschedule. */
    if (xTicksToDelay > (TickType_t)0U)
    {
        configASSERT(uxSchedulerSuspended == 0);
        vTaskSuspendAll();
        {
            traceTASK_DELAY();

            /* A task that is removed from the event list while the
				scheduler is suspended will not get placed in the ready
				list or removed from the blocked list until the scheduler
				is resumed.

				This task cannot be in an event list as it is the currently
				executing task. */
            prvAddCurrentTaskToDelayedList(xTicksToDelay, pdFALSE);
        }
        xAlreadyYielded = xTaskResumeAll();
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    /* Force a reschedule if xTaskResumeAll has not already done so, we may
		have put ourselves to sleep. */
    if (xAlreadyYielded == pdFALSE)
    {
        portYIELD_WITHIN_API();
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

函数实现过程分析:

添加任务到演示列表的过程:

先将任务从当前的状态列表中移除,然后计算当前tick值+延时值,以计算的值添加到延时列表,然后重新计算下一次任务切换的阻塞延时变量值

static void prvAddCurrentTaskToDelayedList(TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely)
{
    TickType_t xTimeToWake;
    const TickType_t xConstTickCount = xTickCount;

#if (INCLUDE_xTaskAbortDelay == 1)
    {
        /* About to enter a delayed list, so ensure the ucDelayAborted flag is
		reset to pdFALSE so it can be detected as having been set to pdTRUE
		when the task leaves the Blocked state. */
        pxCurrentTCB->ucDelayAborted = pdFALSE;
    }
#endif

    /* Remove the task from the ready list before adding it to the blocked list
	as the same list item is used for both lists. */
    if (uxListRemove(&(pxCurrentTCB->xStateListItem)) == (UBaseType_t)0)
    {
        /* The current task must be in a ready list, so there is no need to
		check, and the port reset macro can be called directly. */
        portRESET_READY_PRIORITY(pxCurrentTCB->uxPriority, uxTopReadyPriority);
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

#if (INCLUDE_vTaskSuspend == 1)
    {
        if ((xTicksToWait == portMAX_DELAY) && (xCanBlockIndefinitely != pdFALSE))
        {
            /* Add the task to the suspended task list instead of a delayed task
			list to ensure it is not woken by a timing event.  It will block
			indefinitely. */
            vListInsertEnd(&xSuspendedTaskList, &(pxCurrentTCB->xStateListItem));
        }
        else
        {
            /* Calculate the time at which the task should be woken if the event
			does not occur.  This may overflow but this doesn't matter, the
			kernel will manage it correctly. */
            xTimeToWake = xConstTickCount + xTicksToWait;

            /* The list item will be inserted in wake time order. */
            listSET_LIST_ITEM_VALUE(&(pxCurrentTCB->xStateListItem), xTimeToWake);

            if (xTimeToWake < xConstTickCount)
            {
                /* Wake time has overflowed.  Place this item in the overflow
				list. */
                vListInsert(pxOverflowDelayedTaskList, &(pxCurrentTCB->xStateListItem));
            }
            else
            {
                /* The wake time has not overflowed, so the current block list
				is used. */
                vListInsert(pxDelayedTaskList, &(pxCurrentTCB->xStateListItem));

                /* If the task entering the blocked state was placed at the
				head of the list of blocked tasks then xNextTaskUnblockTime
				needs to be updated too. */
                if (xTimeToWake < xNextTaskUnblockTime)
                {
                    xNextTaskUnblockTime = xTimeToWake;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        }
    }
#else  /* INCLUDE_vTaskSuspend */
    {
        /* Calculate the time at which the task should be woken if the event
		does not occur.  This may overflow but this doesn't matter, the kernel
		will manage it correctly. */
        xTimeToWake = xConstTickCount + xTicksToWait;

        /* The list item will be inserted in wake time order. */
        listSET_LIST_ITEM_VALUE(&(pxCurrentTCB->xStateListItem), xTimeToWake);

        if (xTimeToWake < xConstTickCount)
        {
            /* Wake time has overflowed.  Place this item in the overflow list. */
            vListInsert(pxOverflowDelayedTaskList, &(pxCurrentTCB->xStateListItem));
        }
        else
        {
            /* The wake time has not overflowed, so the current block list is used. */
            vListInsert(pxDelayedTaskList, &(pxCurrentTCB->xStateListItem));

            /* If the task entering the blocked state was placed at the head of the
			list of blocked tasks then xNextTaskUnblockTime needs to be updated
			too. */
            if (xTimeToWake < xNextTaskUnblockTime)
            {
                xNextTaskUnblockTime = xTimeToWake;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }

        /* Avoid compiler warning when INCLUDE_vTaskSuspend is not 1. */
        (void)xCanBlockIndefinitely;
    }
#endif /* INCLUDE_vTaskSuspend */
}

具体分析:

什么是相对延时?

void test_task(void *pvParameters)
{
    // 整个任务的执行时间 = 主体函数执行时间(不定)+ 延时值(800)
    for (; ;)
    {
        // 主体函数,如读取寄存器值,则这函数执行时间是不确定的
        task_main_function();     
        vTaskDelay(800);
    }
}

在上面的任务函数中,如主体函数是在某条件下读取寄存器值(状态寄存器可能需要等待),那么这个主体函数的执行时间是不确定的,整个循环的执行时间等于主体函数执行时间 + 延时值(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;  // 获取初始 Tick 值
    for (; ;)
    {
        task_main_function();           // 这个主体函数的执行时间浮动不定
        vTaskDelayUntil(origin_tick, 800);      // 绝对延时函数
    }
}

如下所示,绝对延时函数会获取进入循环时的系统 Tick 值,然后每隔固定的延时节拍数(800)都会唤醒任务,不管任务的主体函数执行时间如何浮动,内核都会在 初始Tick值 + 延时Tick值(×1 ×2 ×3...)唤醒任务,因此是绝对延时函数。

函数源码:

实现要点:获取下次唤醒时间值 = 上次唤醒时间值 + 绝对延时值,自动更新上次唤醒时间值(设定为当前唤醒值,以实现绝对时间间隔),根据下次唤醒时间值,以及当前的 Tick 值,来计算延时值并插入到延时列表中。

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();
    {
        /* Minor optimisation.  The tick count cannot change in this
			block. */
        const TickType_t xConstTickCount = xTickCount;

        /* Generate the tick time at which the task wants to wake. */
        // 获取下次唤醒时间值 = 上次唤醒时间值 + 绝对延时值
        xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;

        if (xConstTickCount < *pxPreviousWakeTime)
        {
            /* The tick count has overflowed since this function was
				lasted called.  In this case the only time we should ever
				actually delay is if the wake time has also	overflowed,
				and the wake time is greater than the tick time.  When this
				is the case it is as if neither time had overflowed. */
            if ((xTimeToWake < *pxPreviousWakeTime) && (xTimeToWake > xConstTickCount))
            {
                xShouldDelay = pdTRUE;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            /* The tick time has not overflowed.  In this case we will
				delay if either the wake time has overflowed, and/or the
				tick time is less than the wake time. */
            if ((xTimeToWake < *pxPreviousWakeTime) || (xTimeToWake > xConstTickCount))
            {
                xShouldDelay = pdTRUE;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }

        /* Update the wake time ready for the next call. */
        // 自动更新上次唤醒时间值(设定为当前唤醒值,以实现绝对时间间隔)
        *pxPreviousWakeTime = xTimeToWake;

        if (xShouldDelay != pdFALSE)
        {
            traceTASK_DELAY_UNTIL(xTimeToWake);

            /* prvAddCurrentTaskToDelayedList() needs the block time, not
				the time to wake, so subtract the current tick count. */
            // 根据下次唤醒时间值,以及当前的 Tick 值,来计算延时值并插入到延时列表中
            prvAddCurrentTaskToDelayedList(xTimeToWake - xConstTickCount, pdFALSE);
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    xAlreadyYielded = xTaskResumeAll();

    /* Force a reschedule if xTaskResumeAll has not already done so, we may
		have put ourselves to sleep. */
    if (xAlreadyYielded == pdFALSE)
    {
        portYIELD_WITHIN_API();
    }
    else
    {
        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)              /*!< System Control Space Base Address */
#define SysTick_BASE    (SCS_BASE + 0x0010UL)       /*!< SysTick Base Address */

#define SysTick         ((SysTick_Type *)SysTick_BASE)  /*!< SysTick configuration struct */

/**
  \brief  Structure type to access the System Timer (SysTick).
 */
typedef struct
{
    __IOM uint32_t CTRL;    /*!< Offset: 0x000 (R/W)  SysTick Control and Status Register */
    __IOM uint32_t LOAD;    /*!< Offset: 0x004 (R/W)  SysTick Reload Value Register */
    __IOM uint32_t VAL;     /*!< Offset: 0x008 (R/W)  SysTick Current Value Register */
    __IM uint32_t CALIB;    /*!< Offset: 0x00C (R/ )  SysTick Calibration Register */
} SysTick_Type;

FreeRTOS中,有关SysTick的中断服务函数:

其实就是在中断服务函数中,先禁用系统中断,然后调用 xTaskIncrementTick() 来递增系统计数值,根据函数返回值来判断是否要进行任务切换,通过将PENDSV寄存器中断位置1以启用中断,并在PENDSV中断服务函数中进行任务切换。

/**
  * @brief This function handles System tick timer.
  */
void SysTick_Handler(void)
{
#if (INCLUDE_xTaskGetSchedulerState == 1)

    if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
    {
        xPortSysTickHandler();
    }
#endif
}

void xPortSysTickHandler(void)
{
    vPortRaiseBASEPRI();
    {
        /* Increment the RTOS tick. */
        if (xTaskIncrementTick() != pdFALSE)
        {
            /* A context switch is required.  Context switching is performed in
			the PendSV interrupt.  Pend the PendSV interrupt. */
            portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
        }
    }
    vPortClearBASEPRIFromISR();
}

不管是什么系统,运行都需要有个系统时钟节拍,xTickCount 就是FreeRTOS 的系统时钟节拍计数器。每个滴答定时器中断中 xTickCount 就会加一,xTickCount 的具体操作过程是在函数 xTaskIncrementTick()中进行的,这个函数相当复杂,不用纠结。

发布了184 篇原创文章 · 获赞 100 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/dingyc_ee/article/details/104092532
今日推荐