在使用单片机进行嵌入式开发时,时常会遇到这样的场景:
假设被开发的是小车。要使用多种LED进行状态显示,不同状态下有不同的走马灯效果;小车的直走驱动电机要控制在一定速度下;小车的舵机也要控制;小车可能还要实现一些特殊功能,如抓物,障碍识别,巡线,进行无线通讯。以上的行为都要求同时进行,小车在某个条件下处于一种行为状态,改变条件则状态改变,如手机通过蓝牙遥控小车进行直走行为和转向行为。这类开发在单片机嵌入式开发中尤为常见,特点为行为任务的个数(控制元器件的个数和执行代码本身的行为)是有限的,一般不超过20个;任务要同时进行;每个任务都有空闲的时间,如小车灯的亮灭时间,只需要改变灯的电平,中间不需要一直等待,需要实现灯的定时闪烁功能时,程序可以写成
for(;;)
{
灯亮
休眠0.5s
灯灭
}
这样的程序问题在于CPU设置灯亮灯灭只需要一瞬间,而要休眠0.5s,程序执行的代码一直在此处运行就相当于浪费了99.99%以上的CPU性能。同时,有些单片机还面临着没有软中断,CPU性能低,内存少等问题,这意味着甚至无法移植实时操作系统到单片机上,任务无法享有独立的堆栈。
在这种情况下如何编写高效的程序,避免浪费掉99%性能的情况是个值得思考的问题。
一种低效调度方法:
实现是:
灯亮
Timer实现0.5s定时中断
Timer中断服务函数中实现灯灭
该实现的缺点是使用了一个定时器的业务逻辑来实现,定时器的个数是有限的,实现业务逻辑在定时器中,也增加了业务的耦合性,不利于后续程序的维护。
那么,可以如何实现更好的Timer调度管理呢?
可以将CPU的时间分配为N份,例如1ms一份,每份CPU时间里,仅允许一个任务独占CPU资源。休眠的任务池中,可通过延时唤醒加入待唤醒任务池,待唤醒任务池中的任务需要在特定时间苏醒。
但是,依旧有些需要考虑的问题,如何实现在特定时间唤醒对应任务,当两个任务需要在同一时间节点唤醒,该如何处理?以及,如何保证唤醒的任务执行与任务没有上下文切换时的结果是一样的。
对于实现在特定时间唤醒特定任务,实现可以有很多种。例如定时器每过1ms,全局变量T的值就加1。而对于每个待唤醒任务,假设设置延时唤醒了t ms,设置时T值为T0。那么相对来讲,当该任务到需要唤醒的时刻时,T就等于T0+t。也就是讲,唤醒该任务的条件是T=T0+t。那就可以如下面方式进行算法设计(选择最小的T0+t可以使用最小堆,插入排序的列表来实现,但是T0+t溢出怎么办也是一个值得思考的问题):
那么任务唤醒时间点相同怎么处理?可以进行优先级的设定,优先级高先唤醒,优先级低的随机唤醒或先进任务池先唤醒。
如何保证唤醒的任务执行与任务没有上下文切换时的结果是一样的呢?对于没有状态区分的程序,例如灯亮灯灭,只要记录任务执行到哪一步。例如
label 0:灯亮
label 1:灯灭
全局变量:灯的变量:0/1
当传参为0时,跳转到label 0;当传参为1时,跳转到label 1,使用C语言的goto关键词很容易实现。更高效的方法是使用汇编实现
对于任务本身是有状态的,例如对于下面的状态流图:
通过传递全局变量任务状态和执行到哪一句的方式依旧能够实现结果的一致性,因为单片机上任务的状态一般都是全局变量。对于有局部变量的任务,就不应采用这种方法,或者每次上下文切换(任务切换)都要将局部变量重新赋值。
另一种调度方法,没有中断,能够实现简单程序的调度:
Task(状态)
{
代码段开关;
代码功能;
}
主程序
{
for(;;)
{
Task1();
Task2();
Task3();
状态改变程序;
}
}
复杂的嵌入式程序,那肯定上配置上RTOS了。