打印

最高效率使用单片机,放弃程序中的延时函数(转)

[复制链接]
10753|69
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
我是PC机底层编程转过来的,以前从来没接触过单片机,五个月前学习AVR,在这里学到很多东西。但也意识到电子工程师们的硬件编程思想与PC机底层编程思想上的很多不同,引发了一些思考。我说一说,供大家参考,只为学习,无意争论。

我第一次看到教程里Delay()函数的代码时我吓了一跳,竟然让单片机空转以实现和外界同步,这怎么可能?
试想,如果PC机CPU空转一秒,那么音乐会断一秒、画面会停顿一秒、下载文件会断一秒,这怎么可行?

我看到很多单片机程序,它们的单片机99.9%的工作时间都在打空转,99.9%大家可能感到有些危言耸听,那就让我们算一算:

已内部8M频的AVR单片机来说,单指令周期仅为1/8 = 0.125us,那一毫秒可以执行多少个单周期指令? 1%0.125*1000 = 8000个

而我看到论坛里下到的绝大多数程序,两个延时函数之间代码的执行时间要远远小于8000个指令周期。
说实话,很多16K以上的程序,把所有延时函数去掉,总体能执行几毫秒就不错了。

换句话说,我说单片机的利用率小于0.01%还是口下留情了。

要说怎么解决问题,就要先找到问题,我问问大家,程序中,我们为什么延时?

原因很多,可能是外设速度太慢,也可能是为了躲过人眼视觉停留时间,等等。
总之就是与外界不同步,而我们想要同步。

所以说这些延时应该是很有道理的,我不否定这一点,但问题的关键这些延时空转,我们为什么不能把这些时间回收起来做一些别的事呢?
试想,如果把这99.9%的时间回收,那可以一笔相当巨大的资源。

有很多人有些特殊方法回收过这些空转时间,比如说在延时函数中做点事。

但这些往往都不通用,下面我说一些我的两种方法:




1、前后台模式下延时时间回收的方法:

前后台模式就是大家最常用的主程序大循环 + 中断的模式。

首先解决外设太慢问题,像串口、键盘、LCD、SD卡等IO,这些收发可以建立外部缓冲区。比如串口收发在中断中完成保存到缓冲区,而主程序操作缓冲区而不直接操纵串口,这已经看到很多人这样用了。但像矩阵键盘的缓冲区,我很少看到有人这么用,在中断中接收按键信息保存到缓冲区。
还有像LCD,我们一个个往显存中写数据是很浪费的,也应该建立缓冲,统一处理。

建立缓冲区这类方式中间有一些技术难点,比如像串口接收,无法判断对发是否全部发完,怎么办?可以设立定时,如果一个字节接收之后1ms之内没收到下一个,则认为接收完毕。这只是一个思想,具体应用大家掌握。

可能有人会说,除了外设太慢,还有像视觉停留的问题怎么解决,总不能让流水灯快到人眼都看不清吧。
这就我下面要说的问题,这些延时的时间怎么回收?就是全部放到定时中断中!

可能又有些人会说,书里、教程都说了,中断处理东西的时间要尽量短,你这样整个中断有太多判断、很长,时间很长,这不行。

这是一种教条的思想,把书读死了。可以在中断中这样处理,比如:

void (*Task)(void);
ISR
{
      (*Task)(void);
}

中断里用的内容通过函数指针来调用,这样可以在主程序根据需要时任意改变要执行的任务,还可以改任务的周期。所用的判断都是在主程序需中执行,然后改变指针的指向,来确定中断中下一步的任务。

这样,在前后台系统中主程序将任务分配完,还有很多余力处理很多事。

比如有很多个键盘、LED点阵、数码管等,它们都需要实时响应,很容造成编程困难、响应迟钝,其实只要把延时的时间回收,处理这些就非常从容了。

可能还有人会说,有些项目用不了这么苛刻的时间,你回收的时间用不了,要那么多干嘛?
其实这时,你就可以用死循环扫描事件,可以实时响应。你的系统跟原来空循环延时比,实时性要高了不知多少倍。




2、变异的协作式内核

先说说嵌入式操作系统的内核,简单的说,它就是个任务调度器,让多个任务在同一个CPU上同时执行,所谓同时也是相对的,无非就第一个任务执行几毫秒、第二个任务在执行几毫秒。。。外表看起来就是同时执行。

至于可剥夺式内核和协作式内核的区别,大家可以百度一下。

说道能在单片机上用的嵌入式操作系统,大家会说出一些如uCosII、FreeOS等操作系统。
还有很多人对这些操作系统十分抗拒、十分反对,他们的理由是什么?

1、这些操作系统占用大量RAM、ROM
2、这些实时操作系统所谓的实时是相对非实时操作系统的,跟裸机比实际上是慢了

这些理由不是没道理,因为这些商用操作系统都是可剥夺式内核,它们的原则是保证最高优先级任务在可确定的时间内响应。
它们的有优点是任务切换时间是确定的,不会随任务的多少而改变。
有了这些确定性,让它们在商用产品大放光彩。因为其时间稳定性。

但它们的缺点也很明显,中断级节拍浪费很多时间。任务间同时调用时引发同步问题而引入许多如信号量、邮箱等机制浪费大量RAM、ROM。

综上,可剥夺式内核稳定可定量,在越高级的单片机上越有优势,在8位机上可用,但需要大量裁剪,并不一定合适。



而协作式内核的核心思想是什么?它不像剥夺式内核保证最高级任务速度最快,而是保证所有任务的平均速度最快!

正如我前面的说法,我连续两个延时函数之间的代码很难超过1ms,甚至很难超过100us,我们可以将其忽略。这样10个任务,第一个执行完主动放弃单片机控制权,交给第二个任务,第二个任务执行完主动放弃控制权,交给第三个任务。10个任务之间无间隙,每一个任务需要延时时,就主动放弃控制权。

基于这种思想,我们的就达到了回收空转延时的目的,而且应为每个任务是执行完后主动放弃,所以不存在剥夺式内核的同步问题,基本不需要邮箱、信号量等机制,对RAM、ROM的要求就非常低了。

这样来看,协作式内核非常适合8位机。但可能有太多嵌入式系统的书中对剥夺式内核不分场合的认可,造成很多人误解。而且uCos等系统的权威,也让很多RTOS作者争相效仿,没用对8位机的场合做合理分析。

商用系统中没有协作式内核,而民用的,还少有优秀的协作式内核,都是基于传统节拍。

传统协作式内核需要定时中断为时钟基准,也会间歇性打断任务,造成不必要的损失,这并不是我们想要的。

我们其实可以仅仅是让定时器以大分频系数开着, 而不给其产生中断的机会。当任务将要放弃使用权时,读取定时器,作为时钟基准,然后清零。

做法一句两句说不清,而效果是什么?可以做到任务是以不受干扰,与裸机相同的工作状态,这是传统协作式内核做不到的,而仅当它需要延时了,才放弃使用权,将延时的时间给其它任务。这正符合我全文的目的 -- 回收空转延时时间

这样的内核体积会非常小,运行方式与裸机无异,仅仅是把空转延时时间干些其它事。对使用者还没什么要求,不想以往系统那么复杂。

可惜市面上并没有基于这种方式的内核,我已经写了一个,非常精简,运行稳定。但作为一个想应用实际的内核,还需要检验。我最后检查一下后,过几天拿出来大家一起分享。




PS:好了,我上面两种方式,均为原创。希望能对大家有帮助~ 第二种中介绍的变异协作式内核思想很简单,有兴趣可以自己写一个,我过几天会把我的发上来

相关帖子

沙发
香水城主| | 2013-11-2 11:05 | 只看该作者
LZ的办法不错,但前提有点极端。

在MCU中,有很多时候它无事情可做,唯有等待外部事件的发生。比如电视机遥控器,你不按按钮时,它就什么事情也不做,一直等待你按。当你按了按钮后,MCU就要执行去抖动操作,这时完全可以使用程序延迟的办法。

使用特权

评论回复
板凳
lhchen922| | 2013-11-2 11:17 | 只看该作者
学习了。

使用特权

评论回复
地板
受不了了| | 2013-11-2 12:13 | 只看该作者
写过两次实际的程序后就不会这样玩了,只是学生娃娃才会这样写

使用特权

评论回复
5
ZG11211| | 2013-11-2 12:26 | 只看该作者
要想把钉子钉进去,不用大费周折,找个锤子就行了。

使用特权

评论回复
6
加油吧小鱼儿|  楼主 | 2013-11-2 13:10 | 只看该作者
香水城主 发表于 2013-11-2 11:05
LZ的办法不错,但前提有点极端。

在MCU中,有很多时候它无事情可做,唯有等待外部事件的发生。比如电视机 ...

那是循环等待,并不是程序延时,还是调度的问题呀。。。应该算是“处理事件”吧?

使用特权

评论回复
7
do_best| | 2013-11-2 14:50 | 只看该作者
能发个实际的例子研究研究吗

使用特权

评论回复
8
黄小俊| | 2013-11-2 16:19 | 只看该作者
楼主的说法是在看不起我们从硬件学到软件的人吧。以流水灯为例子,一方面它只是用来教学的,另一方面说到效率,肯定不会用软件延时而用定时器了。

使用特权

评论回复
9
不起眼| | 2013-11-2 18:43 | 只看该作者
曾作过一个,确实可以实现。其他类型的等待也可以有效利用。

使用特权

评论回复
10
z_no1| | 2013-11-2 19:02 | 只看该作者
优化守则第一条:能不优化的地方就别优化。
很多时候,或者说99.9%的时候,系统就是在等待处理,你想利用这些CPU时间做什么呢?难道玩个切西瓜吗?对单片机来说,提高性能是指提高响应速度,延时是必要的。RTOS不也有一个最低级别的任务就是延时么?

使用特权

评论回复
11
z_no1| | 2013-11-2 19:10 | 只看该作者
RTOS比前后台的裸机,如果你的前台任务里有长时间的任务,那RTOS的响应时间是要比前后台的快。实际上只有很少情况下,前后台的响应比RTOS快。(你把代码放中断里不算)协助式RTOS的响应时间和前后台差不多。所以现在不流行,UCOS,MDK_RTX等的流行,是有它的实力在这的。
8位机运行RTOS,老实说,意义不大。要RTOS的都是比较复杂的应用,值得用一个32位CPU,一个流水灯用不用也就这么回事。

使用特权

评论回复
12
micropower| | 2013-11-2 19:34 | 只看该作者
你的所谓协作式内核在以MCU为主的嵌入式系统中有一个很大的缺点:就是一旦遇到中断函数来的时候,因为省了资源而没用堆栈保护,所以中断返回后工作寄存器以及PC指针等就很有可能乱了。程序极其容易跑飞。
而如果不用中断的情况下,比如上面说的点流水灯的时候,或者实际点说某些实时检测温度时候AD模块需要延时等待采样,在这几百毫秒到几十微秒的时候难道你还想让单片机做别的事情?而且还想省RAM!一个单片机或一个M3才几块到几十块,有必要为了CPU的利用率而省这些么?
做嵌入式编程第一是考虑系统稳定性和经济性而不是像计算机编程那样考虑利用率。

使用特权

评论回复
13
不起眼| | 2013-11-2 20:53 | 只看该作者
micropower 发表于 2013-11-2 19:34
你的所谓协作式内核在以MCU为主的嵌入式系统中有一个很大的缺点:就是一旦遇到中断函数来的时候,因为省了 ...

1。LZ的內核是主动放弃当前任务,这种方式要保存的东西很少。
2。对,就是要利用这几十us的甚至几us时间干别的事。

使用特权

评论回复
14
lirunze| | 2013-11-2 20:57 | 只看该作者
路过参考了啊

使用特权

评论回复
15
Schvian| | 2013-11-2 21:19 | 只看该作者
单片机中控制时序的短延时还是需要的,完全放弃没必要也不现实,再者,对于功能相对单一的单片机系统,执行关键代码的可靠性和稳定性是目标

使用特权

评论回复
16
不起眼| | 2013-11-2 21:48 | 只看该作者
可靠性是任何人都要考虑的,用不用这种方法都可以,跟可靠牲没必然联系。

使用特权

评论回复
17
frlop| | 2013-11-2 22:09 | 只看该作者
在主程序里ms级别的延时不要用就好了,几个us级的实在没必要钻牛角尖了。

使用特权

评论回复
18
泰山特曲123| | 2013-11-3 00:04 | 只看该作者
MARK

使用特权

评论回复
19
icecut| | 2013-11-3 08:37 | 只看该作者
你不就是以前在os下写代码嘛....
单片机也能上os,装个就不要在这创造了....

使用特权

评论回复
20
悠悠飞雪| | 2013-11-3 09:02 | 只看该作者
都有道理,很纠结

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:人生最大的差距是勤奋的差距,人生最大的遗憾是勤奋不够!逸嵌电子工作室

18

主题

1231

帖子

5

粉丝