概述
曾今只是使用过移植好的RTOS进行任务开发,对其实现的底层原理一直一知半解,正好接触到了李述桐老师的课程以及一些网上的资料,让我对实时操作系统的原理有了更深的理解,特此把一些原理和思考记录下来和大家一起分享,同时若有理解上的错误还恳请大家能够及时指正。
1、裸机中的软延时与硬件延时
在裸机编程的时候,最常用的就是软延时,其本质的原理就是让MCU循环执行空指令来达到延时的效果(阻塞延时)。常用的编写方法就是在主循环程序中加一个delay函数让其执行,这种方法最大的优势就是编程简单、程序好理解,但是缺点也比较明显,比如延时的时间精准不高,浪费MCU的资源因为它的延时是让MCU执行空指令。在写一些功能比较简单的项目,对于短延时且延时时间精度要求不高的情况下,我认为软延时是最优解。
硬件延时就是指利用具有计时功能的硬件进行延时(非阻塞延时),比如Timer定时器,RTC实时时钟,SysTick系统滴答定时器等。常用的编写方法就是在主循环中开启定时器,然后继续处理其他的程序,等定时时间到了之后跳转到定时器中断服务程序中去处理相应的服务程序,这种方法的最大优势就是延时的时间可以做的很精准、同时不会浪费MCU的资源,在延时的时候可以去处理其他的程序。缺点就是程序比较复杂,并且通常一个MCU的硬件定时器的个数是有限的,如果延时多的话可能会不够用。对于一些需要精准延时的程序,以及延时的时间比较长的裸机项目,硬件延时是最优解。
2、RTOS中的延时策略
RTOS最重要的特点就是实时性,任务一旦需要延时等待就应该切换到其余的任务中去执行,所以对于上述裸机编程中的软延时让MCU运行空指令原地等待的现象是要杜绝出现的,但是所有任务的延时都用硬件延时会导致任务很难管理,编程难度徒增的同时消耗大量的硬件资源,显然也是不理想的。RTOS提供的延时API,相比于其他软延时,最大的特点就是在任务延时的过程中,能够切换到其他任务去运行。这样就避免了死等,可以执行其他的代码,MCU的利用率就大大提高。
大部分M3内核的RTOS中的策略是使用Systick来实现所有任务的延时,用极小的资源来实现所有任务的延时同时保证任务的实时性响应。其他内核的MCU可能没有SysTick,可以使用硬件定时器来代替。实现原理大体是通过一个硬件定时器产生中断,定时器的时间称之为时钟节拍。在每个时钟节拍发生时,就递减任务内部的延时计数器,当延时计数器的值减至0时,就将任务插入就绪列表等待MCU执行,而当延时计数器不为0时,则一直呆在延时队列中,得不到MCU的光临。
以下用李述铜老师的代码(基于M3内核)来分析具体的实现方法:
/*SysTick初始化,设置时钟节拍*/
void tSetSysTickPeriod (uint32_t ms) //SysTick初始化 设置时钟节拍
{
SysTick->LOAD = ms * SystemCoreClock /1000 - 1; //重装载值
NVIC_SetPriority(SysTick_IRQn,(1 << __NVIC_PRIO_BITS) - 1); //中断优先级
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
}
/*SysTick中断服务函数,递减任务内部的延时计数器,
当延时计数器的值减至0时,就将任务插入就绪列表等待MCU执行,
而当延时计数器不为0时,则一直呆在延时队列中。*/
void tTaskSystemTickHandler(void) //系统定时器中断处理函数
{
tNode * node;
uint32_t status = tTaskEnterCritical(); //进入临界区
for(node = tTaskDelayedList.headNode.nextNode; node!=&(tTaskDelayedList.headNode); node = node->nextNode) //遍历延时任务队列
{
tTask * task = tNodeParent(node,tTask,delayNode);
if(--task->delayTicks == 0)
{
tTimeTaskWakeUp(task); //将任务从延时任务队列移除
tTaskSchedRdy(task); //将任务插入就绪列表
}
}
tTaskExitCritical(status); //退出临界区
tTaskSched();
}
/*RTOS延时API,将当前任务插入延时队列,并且配置延时的时钟节拍*/
void tTaskDelay (uint32_t delayTicks)
{
uint32_t status = tTaskEnterCritical(); //进入临界区
tTimeTaskWait(currentTask, delayTicks); //将当前任务插入延时队列并且配置延时节拍
tTaskSchedUnRdy(currentTask); //将当前任务从就绪列表移除
tTaskExitCritical(status); //退出临界区
tTaskSched(); //任务调度
}
以下是ucos-ii的实现方式,具体实现方式都差不多,但是没有用延时队列,而是在Systick中遍历所有任务,有延时就减1,当延时计数器的值减至0时,就将任务插入就绪列表等待MCU执行,代码效率稍微低了一些。
//Systick中断服务函数代码 稍作删减
ptcb = OSTCBList; /* Point at first TCB in TCB list */
while (ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO) /* Go through all TCBs in TCB list */
{
OS_ENTER_CRITICAL(); /* 进入临界区 */
if (ptcb->OSTCBDly != 0u) { /* No, Delayed or waiting for event with TO */
ptcb->OSTCBDly--; /* Decrement nbr of ticks to end of delay */
if(ptcb->OSTCBDly == 0u) { /* Check for timeout */
if((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY)
{
if((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) /*Is task suspended?*/
{
OSRdyGrp |= ptcb->OSTCBBitY;
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
}
}
}
ptcb = ptcb->OSTCBNext; /* Point at next TCB in TCB list */
OS_EXIT_CRITICAL();/* 退出临界区 */
}
//ucos-ii 延时API
void OSTimeDly (INT32U ticks)
{
INT8U y;
if (ticks > 0u) /* 0 means no delay! */
{
OS_ENTER_CRITICAL(); /*进入临界区*/
y = OSTCBCur->OSTCBY; /* Delay current task */
OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
} /*将任务从就绪列表中删除*/
OSTCBCur->OSTCBDly = ticks; /* Load ticks in TCB */
OS_EXIT_CRITICAL(); /*退出临界区*/
OS_Sched(); /* Find next task to run! */
}
}
3、RTOS中延时使用的注意事项
在使用RTOS提供的延时API的时候,所有的延时只是一个大概的时间,其延时精度是基于时钟节拍的,所有的延时的最小单位就是时钟节拍,当时钟节拍单位为1ms时,就不能使用此API实现延时100us的功能。还有在任务延时期间如果更高优先级的任务被唤醒,可能会增加延时时间。
所以当需要延时的精度小于1个时钟节拍,就只能考虑软延时/硬件定时器延时。
总之在RTOS中什么延时都可以使用,没有最好只有最合适,对一些很简短的延时直接软延时也不会对系统的实时性造成很大的影响。
最后
以上就是洁净战斗机为你收集整理的RTOS——关于delay问题的理解的全部内容,希望文章能够帮你解决RTOS——关于delay问题的理解所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复