延时实现V2.0前面V1.x版本的演进,很好的解决了延时问题,但有经验的你会发现,上述实现方式有一个硬伤,那就是都是采用死等方式实现,在等待延时内单片机除了
能响应中断外,什么也干不了,如果说
延时时间很短(微秒级别)或者必须采用死等方式处理外,其他情况我们应该尽可能让延时和其他任务同时处理。假设我们有这样一个需求,4个按键检测、1个LCD屏幕显示。按键滤波10毫秒,LCD屏幕
最低30毫秒刷新一次,执行时间1毫秒(为啥不说按键执行时间,因为执行时间太短了,这里忽略不计了)。如果在裸机环境下开发,入门级的会这样处理(大一时的鱼鹰):主循环不停执行按键扫描,因为要滤波,所以使用郭天祥老师那种方式滤波10毫秒后等待按键电平稳定,然后检测,并且在主循环不停刷新LCD,反正这里说刷新周期为30毫秒,而我CPU任务不多,有时间就刷新呗。按键滤波10毫秒,LCD屏幕执行时间1毫秒,这样在按键按下情况,LCD屏幕刷新周期可达到11毫秒,可达到要求(如果按键没有按下,刷新周期1毫秒)。后来随着需求增加,比如增加串口通信处理,增加大量浮点数据处理,执行时间20毫秒,要求这两个任务能在100毫秒内执行完毕,并且数据处理过程可被外部中断而不影响。假如把这个串口和数据处理继续放在主函数,先前最差情况下(按键按下),执行周期为11毫秒,增加新任务后,最差情况下31毫秒可以完成串口和数据处理任务,小于100毫秒。但是当你增加新任务后,你会发现LCD刷新要求达不到了(最少30毫秒),那该怎么办?通过分析你可以很容易的发现,按下按键那一刻有10毫秒白白浪费了CPU,如果这段时间能用于数据处理就好了。问题点找到了,但是该怎么处理呢?我们很容易想到把按键检测功能放到中断中去处理,为什么呢?因为它的执行很短,只是滤波耗时,如果我们能把
按键检测和滤波分开执行就好了。于是我们想到了使用两个变量将按键和检测分开(关于中断下按键检测可参考蓝桥杯代码,看似很难理解,但理解后你会发现这是一种非常高效的实现方式):一个变量用于记录按键电平状态,一个变量用于计时,定时器1ms中断一次,当按键按下后(定时中断中检测是否按下),记录电平状态,并且初始化计时变量为10,在定时中断中发现计时变量不为0,递减变量,直到为零后再次检测按键状态和当前状态是否一致即可(关于按键检测鱼鹰可能会在后期分享一篇笔记进行深入分析,实现单击、双击、长按等功能,关注鱼鹰即可,在这里描述的这种实现方式鱼鹰感觉还是复杂了点)。

因上述代码执行时间很短,所以在解决了按键问题后,就可以达到任务要求了。由此我们可以得到V2.0版本基本实现思想:使用一个变量,专门用于计时。思想很简单,但是实现起来有点麻烦:第一:你需要设置初始计时值第二:你需要在特定的地方递增或递减这个计时值,比如定时中断函数。第三:计时时间达到后进行相关处理。以上第二点是一个很麻烦的事情,在
特定的地方修改值不是很大麻烦(也算一个麻烦,因为一旦你的计时任务增多,你的定时中断任务复杂度必然会增加),最大问题在于修改计数值本身,修改变量会导致一些可重入问题,这个问题留待后面解决。即使经过上述方式优化代码结构后,如果在后期再增加新任务后,你可能会发现LCD屏幕刷新周期可能又不符合要求,每次增加任务后都可能会导致刷新频率不符合要求(因为目前就是任务空闲即刷新,并没有固定刷新时间),反反复复修改代码谁也受不了,难道这就是所谓的码农,难道就没有更好的解决办法了吗?
延时实现V2.1借用上述V2.0的思想,我们可以将其扩展成V2.1,使得任务中对时间要求较高的功能进行精确控制。前面介绍的任务可以细分好几个小任务,按键检测任务、LCD刷新任务、计算任务、串口任务。上述任务中,LCD刷新任务有30 ms的硬性要求(保证屏幕不闪烁)。所以借用V2.0思想,使用计时变量对它精确控制,但是又不能像按键处理那样将LCD刷新任务放到中断任务中处理,因为LCD执行时间长达1 ms。很容易的,我们可以安排一个变量,在主循环中设置初始值,并执行刷新任务,而中断中检测这个值是否大于零,大于零即递减。代码如下所示:

这样一来,只要单片机不是满负荷运行,应该就能达到刷新要求了(这里最终延时应该是31ms,可自行测试一下)。但是有经验的你会发现,计时变量在中断和主函数都进行了修改操作,这就导致了一个问题:是否需要对这个变量保护?原则上需要,但是在这里却不需要。为什么?通过认真思考,你会发现两次修改都是有条件的:中断下修改需要变量大于0,而主函数下修改需要变量等于零。这样一来,你会发现变量修改是
互斥的,也就是不存在同时修改的可能!!!这也是为什么你没有从别人类似的代码中看到对共享资源保护的原因!但是有些道友对共享变量不进行保护,在使用上存在顾虑,所以就会思考是否存在一种更好的方式去实现上述方法,由此V2.2现世。
延时实现V2.2 V2.1存在的问题在于两个地方同时修改了变量,导致变量成为了
伪共享变量(为什么是伪共享变量已经在前面解释了)。 有没有一种方法,让变量只在中断被修改,而主函数可以根据变量当前的值判断延时时间是否达到呢? 这里介绍一种常规的方法,就是让变量的初始化赋值直接在中断进行:

这样主函数就只需要做判断即可,而不需要修改变量。但是这种处理有一个隐患,你必须在1ms内(假设中断1ms)查询到超时时间,一旦主函数有任务运行超过此时间,延时时间必然不准。所以以上解决方式不建议采用。那么是否有更好一点的解决方式呢?再说一个更好的方式之前,再介绍一种方式。
延时实现V2.3计时变量递增,但不是30时清零,而是240时清零,即0~239循环(为什么不是增长到255后自然溢出?)。

这种实现方式好处就是减少了重新赋值计时变量的次数,但不可避免的是,仍然存在1ms查询频率的限制,一旦超出,延时不再准确!是否存在一种没有查询频率限制的实现方法呢?换句话说,我们不是根据
时刻判定延时时间的到达,而是通过
时长判断呢?即1~2倍超时时间内都可以认为超时时间到,而不是刚好就是超时时间呢?
推荐阅读:
-THE END-
如果觉得文章对你有帮助,欢迎转发、分享给朋友,感谢你的支持!
如果对本文有问题,欢迎留言!即使没有问题也可以留下走心评论。
如需转载请联系我。
微信公众号「鱼鹰谈单片机」
每周一更单片机知识

长按后识别图中二维码关注