这篇拖了很久。。。
本来觉得没什么好写的,不过既然有时间还是写点浅显的总结吧
引
思考一下,当我们设计单片机程序时,我们在做什么,实际上在稍微复杂的程序下,我们需要单片机执行的任务会很多,而我们做的无非就是在安排任务,谁排前面谁排后面,什么时候该执行什么,这也是为什么我们需要引入逻辑判断,作为分配任务的方法,而判据,也就是今天要讨论的话题
举个简单的例子,现在我们需要温度传感器采集温度,光敏传感器采集环境光,同时将温度传感器的数值显示在8位数码管上。那么这时,我们需要onewire,iic协议获取数据,同时还是需要刷新数码管数据,如果不假思索地把他们堆进死循环里会发生什么?onewire采集完数据,iic采集完数据,接着轮到数码管循环点亮(动显),如此往复,我们或许会注意到数码管频闪严重,这是由于两个传感器采集数据的都将消耗时间,数码管点亮第一位,接着采集了两个传感器的数据,数码管才能点亮第二个。这并不是一个好现象,协议的大量延时让cpu处于实实在在的空跑。
分配
实际上我们注意到,我们并不需要每几ms就采集一次传感器数据,或许几十甚至几百ms采集一次都不会有太大区别,而数码管间隔过大却直接影响着显示效果(同理于按键),所以我们不妨,让数码管以2ms为周期刷新一次,温度传感器以99ms为周期采集一次,iic以49ms为周期采集一次,可以发现整体的效果好了很多,甚至可以将所有的功能都以这种方式打散——定时器可以很好地做到这点,我们知道单片机对于时间的概念来源于时钟(内部或外部晶振),而单片机本身却只能跟着时间走随着PC一条一条地执行下去,定时器给予了单片机窥探时间的能力,就像给了一个人块手表一样,有了定时器,我们就可以在它的中断服务函数里面写标志位,接着只需要在主函数里写逻辑判断到了做什么的时候即可。将任务拆分成为几个小任务,根据需要安排刷新周期,有点类似于线程。这是时间触发的单片机程序设计,也是定时器的一大用途
实现
实现这一机制同样简单
void Timer2_Init() { AUXR |= 0x04; T2H = 0x9a; T2L = 0xa9; AUXR |= 0x10; EA = 1; IE2 |= 0x04; } unsigned char count_100ms = 0; unsigned char count_200ms = 0; bit flag_100ms = 0; bit flag_200ms = 0; void Timer2_Service() interrupt 12 { count_100ms++; if(count_100ms == 50) { count_100ms = 0; flag_100ms = 1; count_200ms++;
} if(count_200ms == 2) { count_200ms = 0; flag_200ms = 1; } } void Logic() { if(flag_100ms == 1) { flag_100ms = 0; function01(); } if(flag_200ms == 1) { flag_200ms = 0; function02(); } } void main() { Timer2_Init(); while(1) { Logic(); } }
当然,这距离一个优秀的框架还差很远,仅仅是一个简单的思想,不过我已经没有折腾51和蓝桥杯的耐心和时间了
始终记住我们要做的不是编程手艺人,更不是编程运动员,囿于一类单片机尝试把他摸透直至无以复加的程度是不理智的,也是极其局限的。
没有意外的话,蓝桥杯系列就这样草草完结了
后续的话可能会看看8051的RTOS,当作折腾着玩
2021/7/25 11:30
LynnSX in HRB