中断+定时器+主循环的前后台架构
上例的一个最大问题就是主循环的每次执行都要完整地将所有逻辑都执行一遍,而每个逻辑中为了控制自身的周期又用了延时。各个延时就不可避免地影响到其他逻辑的执行,再由于顺序执行的逻辑,其他逻辑的执行又影响到了自身,产生恶性循环,最终没有一个逻辑是符合其自身的周期的。
既然如此,我们可以使用定时器产生一个时间标志,这个标志代表了当前系统运行的时间,主循环中的逻辑再检测这个时间,如果满足自身执行的时间,那么就执行自身逻辑,如果不满足则直接跳出,让其他逻辑执行,中断逻辑仍然不变。这种情况下前台就是中断,后台就是主循环,其代码形式如下:
<p>// 按键中断</p><p>void key_isr(void) {</p><p> do_b(); // 按键按下的操作</p><p>}</p><p>
</p><p>// 定时器中断 1ms 进一次</p><p>unsigned int tick = 0;</p><p>void timer_isr(void) {</p><p> tick++;</p><p> if (tick > 10000) tick = 0;</p><p>}</p><p>
</p><p>void do_a(void) {</p><p> if (tick % 100 == 0) {</p><p> // do_a 逻辑</p><p> } else {</p><p> return;</p><p> }</p><p>}</p><p>
</p><p>void do_c(void) {</p><p> if (tick % 50 == 0) {</p><p> // do c 逻辑</p><p> } else {</p><p> return;</p><p> }</p><p>}</p><p>
</p><p>int main(void) {</p><p> while (1) {</p><p> do_a();</p><p> do_c();</p><p> }</p><p>}</p>
由上述代码可以看到定时器中断为 1 毫秒,每进一次中断 tick 加 1,在主循环中的 do_a 和 do_c 会首先判断 tick 的值,一旦发现与自己的运行周期相同,则执行自身逻辑,否则退出。此时理想的运行图如下:
由于去掉了每个逻辑中的延时,取而代之的是标志位的判断,其执行速度是非常快的,如上图所示 ,灰色的块表示在运行判断逻辑并且没有满足运行要求。这种情况下每个逻辑都能在其指定的周期内得到执行。
这种架构在裸机编程中可以算得上一种中高级的架构,能够满足大多数不是特别复杂的需求。当然,在上图中我们可以看到 do_a 和 do_b 一个为 100 毫秒,一个为 50 毫秒,存在公倍数情况,也就是说在某一时刻,如这里的 0 毫秒和 100 毫秒,就会出现两个逻辑同时运行的场景。实际在项目中如果要求比较严格,会对这个周期进行一个控制和计算,尽量减少各逻辑同时执行的概率,避免由于同时执行的逻辑过多且过于频繁,执行时间的总和仍然会太长,从而影响整体运行稳定性的问题。
|