嵌入式浅谈之合作式调度器

基于时间触发的合作式调度器

常见程序框架

注意,本章中的代码大部分都是不完整的,类似于伪代码,如果直接运行是无法运行的,需要自行添加其他内容,关键是用于核心知识讲解。

1  int main()
2  {
3    sys_init();
4    while(1)
5    {
6      update_sensors();
7      make_decision();
8      execute_decision();
9    }
10    return 0;
11  }

这是自动控制系统的经典框架,一般任务已经可以胜任了。

有什么问题?

while循环执行一次需要多少时间?

如果其中某个环节耗时非常久会怎么样?

如果需要执行多个对时间有精确时序要求的任务应该怎么办?

任务挑战

控制2LED灯,其中一个每亮0 . 1s ,灭0 .9秒交替,另一个每亮0 .5s ,灭0 .5s 替?

第一个灯我们可以这样

1  void f1()
2  {
3    digitalWrite(led1,HIGH);
4    delay(100);
5    digitalWrite(led1,LOW);
6    delay(900);
7  }

第二个我们这样

1  void f2()
2  {
3    digitalWrite(led2,HIGH);
4    delay(500);
5    digitalWrite(led2,LOW);
6    delay(500);
7  }

综合起来怎么办呢?

也不是没有办法,经过一段时间的思考,我们可以想到这样的方式:

1  void f3()
2  {
3    digitalWrite(led1,HIGH);
4    digitalWrite(led2,HIGH);
5    delay(100);
6    digitalWrite(led1,LOW);
7    delay(400);
8    digitalWrite(led2,LOW);
9    delay(500);
10  }

我们分别将led1led2的时序图画出来,发现是对的,但是上面的f3函数存在 很大的问题:

1. 改一发而动全身,可能因为某些原因,需要led2运行规律发生变 化,那么所有的led的延时都要重新计算。

2. 如果要控制的对象更多的话,这个延时就很难把握了,函数会异常 的复杂。

有没有更好的更通用的解决办法呢?

经过我们认真的思考,会发现问题出在delay()函数上,因为每个任务的delay    之间会影响,这样就导致了整个运行时间发生变化,能否减少这种影响呢?我 们来看另外一种不同的实现办法:

1  void f4(int frame)
2  {
3    int time_in_seconds=frame%1000;
4    if(time_in_seconds<100)
5    {
6      digitalWrite(led1,HIGH);
7    }
8    else
9    {
10      digitalWrite(led1,LOW);
11    }
12  }
13  void f5(int frame)
14  {
15    int time_in_seconds=frame%1000;
16    if(time_in_seconds<500)
17    {
18      digitalWrite(led2,HIGH);
19    }
20    else
21    {
22      digitalWrite(led2,LOW);
23    }
24  }
25  long frame=0;
26  void loop()
27  {
28    frame++;
29    f4(frame);
30    f5(frame);
31    delay(1);
32  }

上面的代码中,除了loop函数外,其他地方没有delay ,所以几乎是微秒级完  成,那么我们的全局变量frame实际上就实现了每1毫秒自增1 ,有了绝对时间  的概念,f4f5就可以解耦了。而且我们的loop的响应速度几乎能达到1毫秒。

在上面的代码中,f4 f5实现功能简单,但是代码却不少,如果需要,我们也 可以优化如下,但这这样代码可读性就不如上面高了。

1  long frame=0;
2  void loop()
3  {
4    frame++;
5    frame%1000<100?digitalWrite(led1,HIGH):digitalWrite(led1,LOW);
6    frame%1000<500?digitalWrite(led2,HIGH):digitalWrite(led2,LOW);
7    delay(1);
8  }

上面的代码利用了条件运算符来进行代码的缩减,代码量迅速减小,但是是否 已经达到最小代码量了呢?我们再深入思考,会发现digitalWrite函数的第二个 参数实际上就是前面表达式的值,所以还能优化如下:

1  long frame=0;
2  void loop()
3  {
4    frame++;
5    digitalWrite(led1,frame%1000<100);
6    digitalWrite(led2,frame%1000<500);
7    delay(1);
8  }

代码明显比上面的要更精简,但是可读性下降了,但是随着自己见识的增加, 这些小tricks可以说是非常容易在其他人代码里面看到的。通过上面的迭代例  子,我是想给大家传达一个思想,要写好代码不仅仅是实现功能就行,还  要思考这样是否是最佳的解决方案了,通用性、 易读性、编码速度、运  行速度、 内存占用等的权衡是否达到了比较优的方案,通过自己不断的思 考以及网络上的学习, 自己水平就会慢慢提升,每过段时间再回顾以前自己的 代码,就能发现自己的变化。慢慢也能够分辨出来网络上各种代码编写者的水平高低。

上面的方案还有什么需要提高的地方呢?

最大的就是通用性问题,因为点灯这个任务比较简单,如果任务比较复杂,而 且有很多种的话,就会出现问题。比如我们考虑下面的代码:

1  long frame=0;
2  void loop()
3  {
4    frame++;
5    f1(frame);
6    f2(frame);
7    f3(frame);
8    f4(frame);
9    f5(frame);
10   delay(1);
11  }

这个框架中,代码中f1-f5可以自己根据时间来实现不同的功能,扩展起来也比 较方便,唯独的问题就是整个loop的时间实际上是f1-f5全部执行时间加上1  秒还有frame++的时间,因为任务较多情况下,f1-f5的总执行时间跟1毫秒比起 来就可能不是那么容易忽略了,这样就造成frame的自增的时间实际上并不是1  毫秒,就会导致系统并未达到我们当初的设计要求。

有没有缓解办法?

当然有,我们分几个部分来考虑这个问题:

1. delay(1)改为delay(10)

这样frame的自增时间变成10毫秒,导致任务所占的比例减少,及时 准确性就会提高。

2. 不用delay ,将frame++放到硬件定时器当中

因为硬件计数器是采用硬件中断来完成的,并不受f1-f5的影响,

这样loop代码中就只有f1-f5的函数体而已,将frame明成全局变量

3. f1-f5依此执行,每次loop只执行其中一个,下次就换成下一个执 

这样loop的总时间就不是f1-f5的和,而是其中最长时间,代码如下:

1  long frame=0;
2  void timer_10ms()
3  {
4    frame++;
5  }
6  int state=0;
7  void loop()
8  {
9    switch(state)
10    {
11      case 0:f1(frame);break;
12      case 1:f2(frame);break;
13      case 2:f3(frame);break;
14      case 3:f4(frame);break;
15      case 4:f5(frame);break;
16    }
17    state++;
18    if(state>4)
19    {
20      state=0;
21    }
22  }

经过上面的优化,我们的代码框架实际上就已经具备调度器的雏形了,因为系 统能够实现在多个任务中切换,因为每个任务执行的很快,在外部看来就像是 多个任务同时在运行。

上面的代码在一般的项目上使用已经足够了,可能的问题就是如果添加任务, 要修改loop中的switch语句,还要修改下面的state的极限值,当工程比较小的  时候,这个影响是比较小的,还有的就是每个任务执行频率是相同的,要实现 不同频率执行就要在函数内部通过if语句来实现。

上面的代码能否再优化呢?

1  void msg_update() {
2      do_something();
3  }
4  void screen_update() {
5      do_something();
6  }
7  void key_scan() {
8      do_something();
9  }
10  sch_init_task();
11  sch_add_task(msg_update, 0, 11);            // 110ms
12  sch_add_task(screen_update, 0, 50);
13  sch_add_task(key_scan, 0, 1);
14  while (1) {
15    sch_run_tasks();
16  }

main .c 伪代码

可以看到上面的代码非常的容易理解,而且扩展起来很方便,如何才能实现上 面的效果呢?

xxx .h 部分头文件

1  #define SCH_MAX_TASKS   (4)
2  struct __attribute__ ((__packed__)) sTask
3  {
4      void (* pTask)(void);
5      u16 Delay;
6      u16 Period;
7      u8 RunMe;
8  };

xxx .c 合作式调度器伪代码

1  /*
2   * schedule.c, for system control.
3   * change log
4   * first commit
5   */
6  #include "public.h"
7  struct sTask sch_tasks[SCH_MAX_TASKS];
8  void sch_run_tasks(void) {
9      s16 index=SCH_MAX_TASKS;
10      while(index >0)
11      {
12          index-- ;
13          if (sch_tasks[index ].RunMe > 0)
14          {
15               (*sch_tasks[index ].pTask)(); // Run the task
16              sch_tasks[index ].RunMe = 0; // Reset /reduce RunMe flag
17              if (sch_tasks[index].Period) {
18                  sch_tasks[index ].Delay =sch_tasks[index ].Period;
19              }
20              else if (sch_tasks[index ].Period == 0)
21              {
22                  sch_delete_task(index);
23              }
24              break;
25           }
26      }
27      sch_goto_sleep();
28  }
29  u8 sch_add_task(void (*pFunction)(), const u16 delay, const u16 period) {
30      u8 index = 0;
31      while ((sch_tasks[index].pTask != NULL) &&(index < SCH_MAX_TASKS)) {
32          index++;
33      }
34      if (index == SCH_MAX_TASKS) {
35          return SCH_MAX_TASKS;
36      }
37      sch_tasks[index].pTask = pFunction;
38      sch_tasks[index].Delay = delay;
39      sch_tasks[index].Period = period;
40      sch_tasks[index].RunMe = 0;
41      return index; // return position of task (to allow later deletion)
42  } 
43  u8 sch_delete_task(const u8 index) {
44      u8 ret;
45      if (sch_tasks[index].pTask == 0) {
46          ret = ERROR;
47       } else {
48          ret = SUCCESS;
49      }
50      sch_tasks[index].pTask = 0x0000;
51      sch_tasks[index].Delay = 0;
52      sch_tasks[index].Period = 0;
53      sch_tasks[index].RunMe = 0;
54      return ret;
55  }
56  u8 sch_init_task(void) {
57      s16 index = SCH_MAX_TASKS;
58      while(index > 0)
59      {
60          index-- ;
61          sch_tasks[index].pTask = 0x0000;
62          sch_tasks[index].Delay = 0;
63          sch_tasks[index].Period = 0;
64          sch_tasks[index].RunMe = 0;
65       }
66      return SUCCESS;
67  }
68
69  inline void sch_update(void) {
70      s16 index = SCH_MAX_TASKS;
71      while(index > 0)
72      {
73          index-- ;
74          if (sch_tasks[index ].pTask) {
75              if (sch_tasks[index].Delay == 0) {
76                  sch_tasks[index].RunMe = 1; // Set the run flag
77              } else {
78                  sch_tasks[index].Delay -= 1;
79              }
80          }
81      }
82  }

yyy.c 定时器中断10ms触发

定时器中断 
__interrupt void Timer(void) {
      sch_update();
}

上面代码中用到了函数指针,这样在使用的时候就可以运行我们自定义的函数 了,函数指针算是c语言中比较高级的内容了,不熟悉的请自行查阅。上面实 际上已经是一个完整的合作式调度器的代码了,可以非常方便的移植到各种 微控制器上。

调度器完成

上面的代码就是个完整的合作式调度器代码,调度器分成合作式和抢占式,

合作式就是各个任务不会打断其它任务,依次调度,抢占式就是各个任务可能会被其它任务中断。其中合作式调度器实现起来简单,内存占用小,甚至可以 运行在89c51这类资源非常少的微控制器上,应该是以后项目上的首选。但是 也有一些局限性,就是每个单独任务的执行时间不能超过调度最大时间,不  然系统响应时间就没法保证,解决办法就是将耗时的任务拆分成一系列子任务 来运行。详细的情况要自己查阅相关书籍来进行更深入的学习。

  • 16
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值