魔法书2:测试Arduino 执行速度极限

我是潘,曾经是个工程师。这是为 Ardui.Co 制作的 “Arduino 魔法书” 系列的专栏。上次我们介绍了如何利用底层代码高效节约Arduino存储空间。本节课将测量一下,这些底层代码究竟让 Arduino 提高了多少效能。

要测量底层代码的效率,得先研究一下如何测量 Arduino 的执行速度。最简单的方法自然是示波器,但我们先从 Arduino 入手,让它测量自身的运行速度(除了自己,Arduino 也可以其他设备速度,原理一样)。

值得注意的是,测量自己必定会消耗一些自身的资源影响结果,所以只能做相对测量,而不能做绝对测量。 先看看Arduino UNO 在一秒钟内能执行多少次循环:

打开串口监视器,按下 Arduino UNO 的 Reset 按钮,显示 837173,这个数字说明Arduino 在一秒钟内执行了83万多次的循环,相当厉害!

不过,这个数字只告诉了我们 Arduino 在什么都不做时的速度,这个值不是绝对的。再强调一点,测量自身是要消耗资源的。

Arduino UNO使用了16MHz的晶振,每秒钟可以执行16,000,000条指令,或者说,CPU 每秒可以运行 16,000,000 个周期。

上面的程序中,16,000,000 / 837,173 = 19.1 周期/循环,每个while()循环大约需要19个周期的CPU资源。

时间判断使用了 timer0_millis 来计算系统时间,这个变量在上一节介绍过,属于定时器 timer0,系统每过1ms就会增加1。只要不重置它,millis() 这个Arduino 包装过的时间函数依然有效。不过,一旦重置,millis() 就失效了。

直接调用 timer0_millis,可以节省不少系统资源,如果换成millis() 效率会低很多(可尝试自行替换)。

 digitalWrite() 的性能

之前提到,digitalWrite() 很占空间而且效率很低,我们拿它跟 bitSet() 对比测试一下。

Arduino UNO 在这个循环上一秒钟内执行了 112,811 次,平均每个循环用了 16,000,000 / 112811 = 141.83 个CPU 周期。两个digitalWrite() ,平均每个 71 个周期。

如果用置位 bitSet() 宏,能提升多少性能呢?将 bitSet() 和 bitClear() 置换 digitalWrite():

这么简单的替换,每秒就增加到 691,578 次循环,足足快了6倍!

23个CPU周期即完成一个循环。但是还不够快,我们把While()循环的代码修改一下:

现在每秒执行757,443个循环,21个CPU周期即执行一个循环。LED两个循环就被反转一次,相当快了。

虽然LED疯狂地闪烁没啥用处(实际上是个高速 PWM 信号驱动),但如果在其他应用中,能这么快地收发数据肯定是一件很棒的事。

还要再快点

这就是Arduino的极限吗?肯定不是,因为测量自身就需要消耗不少资源,现在把自身测量的代码去掉,用外部的设备,示波器来测量它的速度。

这个程序很简单,设置 D13 为输出,利用 loop() 循环来执行翻转。此时,Arduino 没空帮你测量自己了,就要靠我们的大家伙示波器了: 示波器探针与 D13 连接,测出的频率是1.33MHz,16MHz / 1.33MHz = 12.03MHz,相当于12个周期执行一次翻转循环。

除了循环,Arduino 什么都不做,为什么还需要12个周期。原因是基于C构建的 Arduino IDE 隐藏了 main() 函数,每次执行loop()时,其实都是被 main() 调用。这12个周期就是loop()的开销。

能否将 loop() 的资源节省下来?可以,只要使循环在 loop() 完成即可,一个while() 就可以解决问题:

现在速度提高到2MHz左右,只需要8个周期就可以执行一个循环! 但还有一个问题,示波器时不时出现跳跃和毛刺。这是因为Arduino的定时器是一直开着的,每秒钟产生1000次左右的中断,中断函数执行期间,消耗CPU资源,I/O 是不会翻转的。在setup() 里面增加 noInterrputs(); 告诉程序禁止中断,即可解决问题了。

通过简单的优化,不仅可以节省Arduino 的空间,而且更有效地执行程序。

Leave a Reply

Your email address will not be published. Required fields are marked *