内容简介:我是潘,曾经是个工程师。这是为 Ardui.Co 制作的 “Arduino 魔法书” 系列的专栏。上次我们介绍了如何利用底层代码高效节约Arduino存储空间。本节课将测量一下,这些底层代码究竟让 Arduino 提高了多少效能。要测量底层代码的效率,得先研究一下如何测量 Arduino 的执行速度。最简单的方法自然是示波器,但我们先从 Arduino 入手,让它测量自身的运行速度(除了自己,Arduino 也可以其他设备速度,原理一样)。值得注意的是,测量自己必定会消耗一些自身的资源影响结果,所以只能做
我是潘,曾经是个工程师。这是为 Ardui.Co 制作的 “Arduino 魔法书” 系列的专栏。上次我们介绍了如何利用底层代码高效节约Arduino存储空间。本节课将测量一下,这些底层代码究竟让 Arduino 提高了多少效能。
要测量底层代码的效率,得先研究一下如何测量 Arduino 的执行速度。最简单的方法自然是示波器,但我们先从 Arduino 入手,让它测量自身的运行速度(除了自己,Arduino 也可以其他设备速度,原理一样)。
值得注意的是,测量自己必定会消耗一些自身的资源影响结果,所以只能做相对测量,而不能做绝对测量。 先看看Arduino UNO 在一秒钟内能执行多少次循环:
void setup() { Serial.begin(9600); Serial.println("Test begins now."); } extern volatile unsigned long timer0_millis; void loop() { unsigned long i = 0; // 测试值 unsigned long j; // 停止时间 j = millis() + 1000; // 当前时间 while (timer0_millis < j) i++; Serial.println(i); while (1); }
打开串口监视器,按下 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() 对比测试一下。
void setup() { Serial.begin(9600); Serial.println("Test begins now."); } extern volatile unsigned long timer0_millis; void loop() { unsigned long i = 0; // 测试值 unsigned long j; // 停止时间 j = timer0_millis + 1000; // 当前时间 while (timer0_millis < j) { digitalWrite(13, HIGH); // 亮灯 digitalWrite(13, LOW); // 灭灯 i++;// 计数 } Serial.println(i); while (1); }
Arduino UNO 在这个循环上一秒钟内执行了 112,811 次,平均每个循环用了 16,000,000 / 112811 = 141.83 个CPU 周期。两个digitalWrite() ,平均每个 71 个周期。
如果用置位 bitSet() 宏,能提升多少性能呢?将 bitSet() 和 bitClear() 置换 digitalWrite():
void setup() { Serial.begin(9600); Serial.println("Test begins now."); } extern volatile unsigned long timer0_millis; void loop() { unsigned long i = 0; // 测试值 unsigned long j; // 停止时间 j = timer0_millis + 1000; // 当前时间 while (timer0_millis < j) { bitSet(PORTB, 5); // 亮灯 bitClear(PORTB, 5); // 灭灯 i++;// 计数 } Serial.println(i); while (1); }
这么简单的替换,每秒就增加到 691,578 次循环,足足快了6倍!
23个CPU周期即完成一个循环。但是还不够快,我们把While()循环的代码修改一下:
while (timer0_millis < j) { bitSet(PORTB, 5); // 翻转LED i++;// 计数 }
现在每秒执行757,443个循环,21个CPU周期即执行一个循环。LED两个循环就被反转一次,相当快了。
虽然LED疯狂地闪烁没啥用处(实际上是个高速 PWM 信号驱动),但如果在其他应用中,能这么快地收发数据肯定是一件很棒的事。
还要再快点
这就是Arduino的极限吗?肯定不是,因为测量自身就需要消耗不少资源,现在把自身测量的代码去掉,用外部的设备,示波器来测量它的速度。
void setup() { bitSet(DDRB, 5); } void loop() { bitSet(PINB, 5); // 翻转 }
这个程序很简单,设置 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() 就可以解决问题:
void setup() { bitSet(DDRB, 5); } void loop() { while (1) { bitSet(PINB, 5); // 翻转 } }
现在速度提高到2MHz左右,只需要8个周期就可以执行一个循环! 但还有一个问题,示波器时不时出现跳跃和毛刺。这是因为Arduino的定时器是一直开着的,每秒钟产生1000次左右的中断,中断函数执行期间,消耗CPU资源,I/O 是不会翻转的。在setup() 里面增加 noInterrputs(); 告诉程序禁止中断,即可解决问题了。
通过简单的优化,不仅可以节省Arduino 的空间,而且更有效地执行程序。
以上所述就是小编给大家介绍的《魔法书2:测试Arduino 执行速度极限》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。