程序延时里的思维
大概所有的51单片机初学者在用到延时的时候都有一个共同点,那就是使用一段空循环消耗CPU的时间从而达到延时目的,常见的函数就是图1-1中的delay函数。这种模式在初学阶段的确是简单易懂,学习起来会得心应手,这可以称得上是我们单片机生涯的童年时代,它使我们学会了走起来。但对于学习了三四年单片机的人来说,不能再单单走了,要跑起来!
1 void delay(unsigned int ms) 2 { 3 unsigned int i,j; 4 for( i = 0; i < ms; i++ ) 5 { 6 for( j = 0; j < 1141; j++ ); 7 } 8 }
图1-1
为何说delay函数只是走起来?这得要看清楚作为初学者我们的思维是一种什么样的概念,其实我也说不清。。。只能打个比喻,假如有个空间里面有按键、显示屏、串口接收、led。在我的认知里有三种思维维度:
一维度:一个平面里的一条直线。程序从开始就一步一个模块地走,我们都知道,按键存在这机械抖动,为了消除这个抖动,就需要在判断按键后延时10ms左右的时间再次判断,如图1-2,那么这10ms的时间被固定在了这条时间轴里面。10ms的时间对单片机来说是很长很长的,会导致显示屏刷新变慢,LED驱动迟钝,串口数据丢失。
1 keyscan(void) 2 { 3 unsigned char keynum; 4 if(Key==0) //检测是否有键按下 5 { 6 delay(10); //延时10ms去抖动 7 if(Key==0) //再次检测是否依然有键按下 8 { 9 10 keynum = 1; 11 while(Key==0); //等待按键释放 12 } 13 } 14 }
图1-2
二维度:一个平面里的曲线。串口接收采用了中断方式,按键扫描采用了定时器中断扫描方式,因为串口数据不是一直有,且按键不是一直被按,所以时间轴比一维度的要缩短了,但是当定时器中断时,进入中断扫描按键,delay函数依然占用了10ms的时间。
三维度:空间里不同维度交织的直线。立体图太难画了,就当串口和按键是来自不同的平面吧~这里引用了缓存的概念,通过变量buf缓存数据,串口通过中断把数据存到串口buf中,按键通过定时器中断扫描,把值存到按键buf中,这时候按键扫描用的是图1-3中的函数,配置定时器为2.5ms中断,如此一来,取消了delay函数,主函数轴中只需要几条指令的时间就把按键扫描的功能完成。在需要扫描按键的时候,读取按键buf的值,判断它是否大于10即可,而按键其实在另一个时间轴里完成了10*2,5个ms的滤波。串口和按键各自在不同的时间轴中运行,通过buf变量和主时间轴交织在一起,仿佛形成了一个三维的空间,用专业的术语描述这就叫做——分时系统。
1 void timer() interrupt 1 //定时器中断函数 2.5ms 2 { 3 if(key == 0) //如果按键按下 4 { 5 if(buf < 20) //滤波20*2.5ms 6 { 7 buf++; 8 } 9 } 10 else 11 { 12 if(buf > 0) 13 { 14 buf--; 15 } 16 } 17 }
图1-3
现在回过头来看看delay函数,是不是处在一维度里?这种思维会约束我们写代码的习惯,使我们的代码得不到高效率、高稳定性,我们必须要往上冲破更高维度的思维。CPU的时间是非常宝贵的,哪怕是1ms的时间。为了得到更快的速度,IC厂商不断地提高CPU时钟频率,主流的STM32F4系列达到了168M的主频;CPU架构也由冯诺依曼结构发展成了哈佛结构;过去取指令一个时钟,而后再执行指令又一个时钟,已经演变成多级流水线架构,,如图1-4。IC厂商如此努力把CPU速度提高,我们怎么能让CPU原地转几万圈来延时,这太对不起人家了~