
延时实现V1.0首先使用的延时实现就是上面所说的死等方式:

类似上面这种CPU运行在一个循环中,直到达到条件后离开该函数,从而达到延时功能。这种实现方式只要对C语言有所了解,很容易理解,但是在使用过程中,你会发现延时并不准确,或者说可能在这个单片机里面,延时很准,移植到另一个单片机可能一点就不准了,所以为了达到准确的延时功能,必须借助示波器等仪器调整参数以确定真正的延时时间。并且还有一种情况就是,如果这个函数被中断后再执行,那么你的延时将不再准确!这个问题留在V1.5版本讨论。当然如果你使用KEIL开发,鱼鹰还可以告诉你另一个确定时间的方法:


这个时间在参数设置正确的情况下还是非常准的(M3内核以上,在线或者模拟,右下角时间可清零用于重新计时),当然你还是得确定这个时间和现实时间的换算关系(当你把下面的参数根据实际情况设置正确后,它们的换算关系是1:1,这个可以通过示波器确定),当你了解了换算关系,那么你就可以脱离示波器,来知道某些代码的运行时间(精度可达每条指令运行时间,这个时间实际上用的是DWT,这个后面说)。


与上述方式类似的实现是采用nop指令(空指令,即除了浪费CPU没有任何功能的指令),但鱼鹰不建议使用nop指令实现微秒级别以上的延时,因为鱼鹰不觉得nop指令能比上述实现方式优势更大:1、都要采用某种方式确定实际延时时间(比如示波器,当然还有鱼鹰上面的方式,通过对比示波器你会发现,准确到让你怀疑人生!);2、当优化级别提高时,有可能出现延时不一致的情况;有人会说可以通过nop指令的执行时间,进而确定延时时间,但是又引入以下问题:1、去哪查找nop指令运行时间?上网,没错,但好像这种资料比较难找,就算你找到了你怎么就能确定这个资料是对的,你还是需要通过示波器(还有上述方式,再次强调!)之类的确定。2、不同平台下的nop指令执行时间不一致,比如M3内核和M4内核nop指令执行周期不一致,即使相同平台下,如果哪天心血来潮,改变了系统时钟频率,那么参数你还是得重新确定。3、nop汇编移植性不是很好,单片机中,大多数时候采用C语言编写,你要在C中嵌入汇编需要折腾一下。以上就是鱼鹰不建议采用nop指令的原因,既然能用C语言解决的,干嘛需要汇编指令,这种方式并没有比上述代码有更多优势,反而缺点不少。那么真的如上面所说,nop指令没有一点优势吗?有,精确延时(这里的精确延时指的是微小延时情况下的精确)!当芯片手册告诉你某些功能需要延时多少个系统时钟周期时,采用nop指令无疑是最好的方式,因为这是能达到最小延时(指不会额外浪费CPU)的最佳方式,这样你也不用考虑进入、退出函数时额外消耗的时间了(当延时足够长时,进入、退出函数消耗的时间可以忽略不计,而延时很短情况下需要考虑)。延时实现V1.5前面介绍了版本1.0有一个很大的弊端,那就是在更改优化级别的情况下,可能影响延时效果。所以有必要找到更好的方式去实现延时效果。事实上,鱼鹰在很长时间都是采用V1.0进行延时的,比如流水灯、按键消抖、数码管显示等。直到看到正点原子的延时函数:

注释很清晰,简单来讲就是设置一个初始值,然后由硬件负责递减这个值,当减到零后设置标志位,循环中只要不停地查询这个标志位是否置位即可,一旦置位,即代表延时时间达到了。通过代码和注释可以知道,最大延时时间1864毫秒,1秒多点,对于单片机来说,时间很长了。现在我们来分析这种实现方式的优势:1、延时时间相对精确,也就是说,只要配置正确,精度可达systick时钟精度(当然如果延时在微秒级别时,误差较大,除了进出函数消耗外,还有循环体外语句和判断语句的执行时间,这些很难避免)。2、即使代码采用最高级别进行优化编译,对于毫秒级别的延时影响也非常小。3、即使在延时过程中被中断了(裸机环境下被硬件中断,系统环境下被硬件和其他任务中断),延时时间在绝大多数情况下是准确的,但是 V1.0版本采用纯软件的方式总是将被中断的时间包含在延时时间内。

虽说软件版或硬件版都可能存在延时不准确的问题,但事实上在软件中多延时几个毫秒是没有多大问题的(可通过关中断确保延时准确),所以这种超过延时的情况不必太纠结,但是如果你的延时函数的延时时间可能比需要延时时间更短,那么就要引起注意了!分析了好处,咱们说说缺点:1、就像上面所说的,延时时间最大1秒多点,对于有些需求来说,延时有点短了(有些人可能会说这是鸡蛋里挑骨头,谁没事延时那么久,就算需要延时很久,多延时几次就行了,嗯,算你过关)。2、占用systick时钟。用过嵌入式系统的都知道,大多数操作系统都会采用systick作为系统的心跳时钟,也就是说,如果将来你的裸机代码需要移植到系统中执行,必须重新实现延时功能(可能你会说,我就在裸机上开发,不上系统行不行,OK)。3、函数非可重入!这一点很多人可能都没有意识到,在写这段话之前,鱼鹰也没有意识到(延时这么简单的功能,谁会想那么多,鱼鹰亦是如此。但鱼鹰在思考它的缺点时,也以为在裸机环境下不需要考虑可重入和不可重入问题,因为裸机就一个大循环,肯定顺序执行,也就不需要考虑这种问题(为什么顺序执行就不需要考虑可重入问题?),但是却突然想到了硬件中断可打断主循环的情况,而在中断中执行微秒级别的延时是有可能的)。当你在主循环中延时毫秒级别时,突然中断来了,开始延时微秒级别的代码,那么必然修改systick寄存器,导致返回主循环时快速退出延时,最终达不到预期的延时效果!这是很可怕的事情,比如模拟I2C通信时,出现了这种情况……通过上述分析,你应该知道使用这种实现方式有多大风险了吧!那么该如何改进呢?
推荐阅读:
-THE END-
如果觉得文章对你有帮助,欢迎转发、分享给朋友,感谢你的支持!
如果对本文有问题,欢迎留言!即使没有问题也可以留下走心评论。
如需转载请联系我。
微信公众号「鱼鹰谈单片机」
每周一更单片机知识

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