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

栏目: 服务器 · 发布时间: 6年前

内容简介:我是潘,曾经是个工程师。这是为 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 没空帮你测量自己了,就要靠我们的大家伙示波器了: 魔法书2:测试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个周期就可以执行一个循环! 魔法书2:测试Arduino 执行速度极限 但还有一个问题,示波器时不时出现跳跃和毛刺。这是因为Arduino的定时器是一直开着的,每秒钟产生1000次左右的中断,中断函数执行期间,消耗CPU资源,I/O 是不会翻转的。在setup() 里面增加 noInterrputs(); 告诉程序禁止中断,即可解决问题了。

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


以上所述就是小编给大家介绍的《魔法书2:测试Arduino 执行速度极限》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

代码之美

代码之美

Grey Wilson / 聂雪军 / 机械工业出版社 / 2008年09月 / 99.00元

《代码之美》介绍了人类在一个奋斗领域中的创造性和灵活性:计算机系统的开发领域。在每章中的漂亮代码都是来自独特解决方案的发现,而这种发现是来源于作者超越既定边界的远见卓识,并且识别出被多数人忽视的需求以及找出令人叹为观止的问题解决方案。 《代码之美》33章,有38位作者,每位作者贡献一章。每位作者都将自己心目中对于“美丽的代码”的认识浓缩在一章当中,张力十足。38位大牛,每个人对代码之美都有自......一起来看看 《代码之美》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

MD5 加密
MD5 加密

MD5 加密工具

SHA 加密
SHA 加密

SHA 加密工具