魔法书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 执行速度极限》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

人件

人件

Tom DeMarco、Timothy Lister / UML China / 清华大学出版社 / 2003-6 / 35.00元

《人件(第2版)》专门讨论了软件开发和维护的团队管理问题,并向人们的传统认识提出了挑战。作者汤姆·迪马可,蒂姆·李斯特在书中推崇人本管理思想,指出知识型企业的核心是人,而不是技术。《人件(第2版)》于1987年首次出版后,曾在西方引起了轰动,被誉为“对美国软件业影响最大的一本书”。《人件(第2版)》还对大中型组织中的软件开发团队如何运作进行了深入探讨。《人件》已成为软件图书中的经典之作。它和《人月......一起来看看 《人件》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

MD5 加密
MD5 加密

MD5 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试