当前位置:首页 > 嵌入式 > 技术让梦想更伟大
[导读]关注、星标公众号,直达精彩内容ID:技术让梦想更伟大整理:李肖遥操作系统的运行是由系统节拍时钟驱动的。在FreeRTOS中,我们知道系统延时和阻塞时间都是以系统节拍时钟周期为单位。在配置文件FreeRTOSConfig.h,改变宏configTICK_RATE_HZ的值,可以改变...


关注、星标公众号,直达精彩内容

ID:技术让梦想更伟大

整理:李肖遥


操作系统的运行是由系统节拍时钟驱动的。

在FreeRTOS中,我们知道系统延时和阻塞时间都是以系统节拍时钟周期为单位。

在配置文件FreeRTOSConfig.h,改变宏configTICK_RATE_HZ的值,可以改变系统节拍时钟的中断频率,也间接的改变了系统节拍时钟周期(T=1/f)

比如设置宏configTICK_RATE_HZ为100,则系统节拍时钟周期为10ms,设置宏configTICK_RATE_HZ为1000,则系统节拍时钟周期为1ms。

系统节拍中断服务程序会调用函数xTaskIncrementTick()来完成主要工作,如果该函数返回值为真(不等于pdFALSE),说明处于就绪态任务的优先级比当前运行的任务优先级高。

这会触发一次PendSV中断,进行上下文切换。我们重点看一下函数xTaskIncrementTick()做了哪些事情,以及什么情况下返回真值。

1.调度器正常情况

调度器正常(没有挂起),即变量uxSchedulerSuspended的值为pdFALSE。

变量uxSchedulerSuspended是定义在tasks.c文件中的静态变量,记录调度器运行状态。

当调用API函数vTaskSuspendAll()挂起调度器时,会将变量uxSchedulerSuspended增1。

所以变量uxSchedulerSuspended为真时,表示调度器被挂起。

调度器正常情况下,首先将变量xTickCount增1

。变量xTickCount也是在tasks.c文件中定义的静态变量,它在启动调度器时被清零,在每次系统节拍时钟发生中断后加1,用来记录系统节拍时钟中断的次数。

内核会将所有阻塞的任务跟这个变量比较,以判断是否超时(超时意味着可以解除阻塞)。

变量xTickCount的数据类型跟具体硬件有关,32位架构硬件一般是无符号32位变量、8位或16位架构一般是无符号16位变量。

即便是32位变量,xTickCount累加到0xFFFFFFFF后也会溢出。

因此,在程序中要判断变量xTickCount是否溢出。

如果溢出(xTickCount为0),则调用宏taskSWITCH_DELAYED_LISTS()交换延时列表指针和溢出延时列表指针。

这个牵扯的有点广,我们慢慢说明。

为了解决xTickCount溢出问题,FreeRTOS使用了两个延时列表:xDelayedTaskList1xDelayedTaskList2

并使用延时列表指针pxDelayedTaskList和溢出延时列表指针pxOverflowDelayedTaskList分别指向上面的延时列表1和延时列表2(在创建任务时将延时列表指针指向延时列表)。

顺便说一下,上面的两个延时列表指针变量和两个延时列表变量都是在tasks.c中定义的静态局部变量。

比如我们使用API延时函数vTaskDelay( xTicksToDelay )将任务延时xTicksToDelay个系统节拍周期,延时函数会以当前的系统节拍中断次数xTickCount为参考,这个值加上参数规定的延时时间xTicksToDelay,即xTickCount xTicksToDelay,就是下次唤醒任务的时间。

xTickCount xTicksToDelay会被记录到任务TCB中,随着任务一起挂接到延时列表。如果内核判断出xTickCount xTicksToDelay溢出(大于32位可以表示的最大值),就将当前任务挂接到列表指针pxOverflowDelayedTaskList指向的列表中,否则就挂接到列表指针pxDelayedTaskList指向的列表中。

任务按照延时时间,顺序的插入到延时列表中。

所以当系统节拍中断次数计数器xTickCount溢出时,必须将延时列表指针pxDelayedTaskList和溢出延时列表指针pxOverflowDelayedTaskList交换以便正确处理延时的任务。宏taskSWITCH_DELAYED_LISTS()的代码如下所示:

#definetaskSWITCH_DELAYED_LISTS()                                                       \
{                                                                                       \
         List_t *pxTemp                                                          \
                                                                                        \
         /* The delayed tasks list should beempty when the lists are switched. */       \
         configASSERT( ( listLIST_IS_EMPTY( pxDelayedTaskList) ) );                     \
                                                                                        \
         pxTemp = pxDelayedTaskList;                                                    \
         pxDelayedTaskList = pxOverflowDelayedTaskList;                                 \
         pxOverflowDelayedTaskList = pxTemp;                                            \
         xNumOfOverflows ;                                                             \
         prvResetNextTaskUnblockTime                                                    \
}
这段代码完成两部分工作,第一是将延时列表指针pxDelayedTaskList和溢出延时列表指针pxOverflowDelayedTaskList交换;

第二是调用函数prvResetNextTaskUnblockTime()重新获取下一次解除阻塞的时间,这个时间保存在静态变量xNextTaskUnblockTime中,该变量也是定义在tasks.c中。

下面检查延时列表任务是否到期时,会用到这个变量。

接下来函数会检查延时列表,查看延时的任务是否到期。

前面我们说过,延时的任务根据延时时间先后,顺序的插入到延时列表中,延时时间短的在前,延时时间长的在后,并且下一个要被唤醒任务的时间数值保存在变量xNextTaskUnblockTime中。

所以使用xTickCountxNextTaskUnblockTime比较就可以知道是否有任务可以被唤醒。

if( xConstTickCount >=xNextTaskUnblockTime )
{
   /* 延时的任务到期,需要被唤醒 */
}
如果任务被唤醒,则将任务从延时列表中删除,重新加入就绪列表。如果新加入就绪列表的任务优先级大于当前任务优先级,则会触发一次上下文切换。

FreeRTOS支持多个任务共享同一个优先级,如果设置为抢占式调度(宏configUSE_PREEMPTION设置为1)并且宏configUSE_TIME_SLICING也为1(或未定义),则相同优先级的多个任务间进行任务切换。

最后还会调用时间片钩子函数vApplicationTickHook()。可以看到时间片钩子函数是在中断服务函数中调用的,所以这个钩子函数必须简洁、不可以调用不带中断保护的API函数。

2.调度器挂起情况

如果调度器挂起,正在执行的任务会一直继续执行,内核不再调度(意味着当前任务不会被切换出去),直到该任务调用了xTaskResumeAll()函数。

在调度器挂起阶段内,FreeRTOS使用静态变量uxPendedTicks记录挂起期间,系统节拍中断的次数。当调用恢复调度器函数xTaskResumeAll()时,会执行uxPendedTicks次本函数(xTaskIncrementTick())。变量uxPendedTicks同样是在tasks.c中定义的。

3.自动任务切换

函数的最后几行代码颇让人难以理解,其中局部变量xSwitchRequired是本函数的返回值,在文章开始也说过:“如果该函数返回值为真,说明处于就绪态任务的优先级高于当前运行任务的优先级,则会触发一次PendSV中断,进行上下文切换”,现在如果变量xYieldPending为真,则返回值也会为真,函数结束后会进行上下文切换。

这个变量xYieldPending的作用是什么?又是在什么时候被赋值为真呢?还真要从头说起。

if( xYieldPending != pdFALSE )
{
    xSwitchRequired = pdTRUE;
}
带中断保护的API函数,都会有一个参数pxHigherPriorityTaskWoken

如果API函数导致一个任务解锁,并且解锁的任务优先级高于当前运行的任务,则API函数将*pxHigherPriorityTaskWoken设置成pdTRUE。

在中断退出前,老版本的FreeRTOS需要手动触发一次任务切换。

比如在《 FreeRTOS系列第15篇---使用任务通知实现命令行解释器》一文中,我们在串口接收中断中调用了带中断保护的API函数vTaskNotifyGiveFromISR(),在函数执行完后,会使用代码portYIELD_FROM_ISR(xHigherPriorityTaskWoken)判断参数xHigherPriorityTaskWoken是否为真,为真则手动强制上下文切换。

BaseType_txHigherPriorityTaskWoken = pdFALSE;        
/*收到一帧数据,向命令行解释器任务发送通知*/ 
vTaskNotifyGiveFromISR(xCmdAnalyzeHandle,
本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭