内容简介:我是潘,曾经是个工程师。这是为 Ardui.Co 制作的 “Arduino 魔法书” 系列的专栏。从现在开始,我将尝试通过各种方式,去挖掘Arduino的潜能极限。本篇将探讨如何利用底层代码,让程序变得更小、让 Arduino 变得更高效,进而让产品更可靠。“魔法书”不是教程,而是实验性的栏目,仅供参考。任何机器只能认1和0,二进制是最基础的机器语言,但从人类的角度来看,几乎不可能用二进制代码来编程,效率太低。所以需要更通俗易懂的“高级”语言,C语言就是其中一种,而Arduino的编程语言就是通过C语言的
我是潘,曾经是个工程师。这是为 Ardui.Co 制作的 “Arduino 魔法书” 系列的专栏。从现在开始,我将尝试通过各种方式,去挖掘Arduino的潜能极限。本篇将探讨如何利用底层代码,让程序变得更小、让 Arduino 变得更高效,进而让产品更可靠。“魔法书”不是教程,而是实验性的栏目,仅供参考。
任何机器只能认1和0,二进制是最基础的机器语言,但从人类的角度来看,几乎不可能用二进制代码来编程,效率太低。所以需要更通俗易懂的“高级”语言,C语言就是其中一种,而Arduino的编程语言就是通过 C语言 的简化、演变过来。
当编写好程序后,编译器(内置在Arduino IDE中)就可以把高级语言,翻译成机器能懂的二进制代码。但这个过程存在一个弊端,编译器总是无法翻译出最优的代码,要么体积臃肿,要么各种资源浪费。如果我们希望程序变得更高效,就必须往底层深入。
但别误会,我们不会直接写二进制代码。其实,C语言已非常接近底层,只要通过 Arduino 兼容C语言的特性,即可让程序得到不错的优化,让Arduino 的资源有效利用,大幅提升执行效率。
Arduino UNO 的 ROM 空间只有 32K 极为有限,如果你编写程序时,发现空间不够,别急着买更高级的板子,而是先看看你的代码,是否还有压缩和改进的空间。现在我们尝试一下,将最简单的示例程序 Blink (LED闪烁)优化,看看能压缩多少空间出来。
编译后,Arduino IDE 会给出二进制代码的大小,因此我们可以清晰看到改进的空间:
示例Blink 程序编译后,二进制代码的大小是928字节,占用了Arduino UNO 总空间的2%。
压缩这个程序前,先看看它是怎样运作的:
1、配置数字引脚 D13 为输出;
2、改变这个输出引脚的状态,LED亮或灭;
3、暂停一下让人感知亮/灭时间,然后回到第2步。
这个程序为了初学者明白易懂,所以没有做任何优化,每一步都清晰简单:点亮LED,停留一段时间,关闭LED,停留一段时间,再重复。不过,点亮和关闭也可以用一个抽象概念来代替——翻转LED的状态。明白这点后,这程序起码可以压缩一半。
必须 pinMode() 吗?
我们先拿pinMode()函数来开刀,如果把它注释掉,程序从928字节,减少到864字节,也就是说pinMode() 占用了64字节的空间。
但没有pinMode() 引脚就会一直处于输入状态。此时,引脚抗阻极高,即使其电平可以被控制,但也无法驱动LED。用什么底层代码来实现 pinMode() 的效果呢?首先,我们先得了解pinMode() 的工作原理。
Arduino 基于 Atmel AVR 单片机简化而来。不同的 AVR 单片机具有不同数量的端口,每个端口有一定数量的引脚。同时,每个端口也关联着一个数据方向寄存器(data direction register,即DDRx)。
DDRx 决定了端口上不同引脚的输入输出状态。例如,端口 A 有 6 个引脚,给寄存器赋值 000001 的话,前面4个引脚,都是输入状态,最后一个引脚则为输出状态。pinMode() 简化将指定端口、寄存器赋值两个过程完全省略了。我们只要告诉它,某个引脚的状态,它就会设定设定好。
现在用 bitSet(DDRB, 5) 来代替它。LED的引脚D13 对应 AVR 的是端口B,第5个引脚,因此,我们设置端口B的寄存器 DDRB的第5位为1,即可以设置D13为输出状态。
此时D13为输出状态了,但程序空间只增加了2字节。不过,bitSet() 为何那么神奇呢?因为他不是一个函数,而只是一个宏定义。他被包含在编译器的 wiring.h 头文件中,它的定义和Arduino 的代码一起被打包。
这个函数需要两个参数:值(value)和位(bit)。bitSet(DDRB, 5),在Arduino 编译时,会直接替换成C语言: DDRB |= 1<<5,其含义是,将DDRB的第5位置位(赋值1)。“I=” 是一个复合赋值运算符,表示按“位”运算,“<<”是左移运算符,这里用了5表示要操作DDRB寄存器的第5位,即对应I/O端口B的第5个引脚:PB5(关于端口对应的的列表,可以参考 官方MAP )。另外,也可以用二进制0b00100000、八进制040、十进制32、16进制0x20数值来赋值,不过可读性就差很多。
简单的语句替换,就把笨重的pinMode()占用的空间节省出来了。 不过,前提是你很熟悉 Arduino 的端口、引脚和C语言的表达。
digitalWrite() 太费力
digitalWrite()虽然很方便,但是代价却相当大,如果把它注释掉,足足释放了226个字节的空间:
Blink 程序的原理是点亮LED,停留一段时间,再熄灭,停留一段时间,再点亮。这个亮-灭交替的过程可以看作是输出引脚在翻转。
对于 ATmage168 或者以上型号的芯片,AVR的翻转方式很简单,只要向输入寄存器的地址写入一个1即可,但对于老的型号ATmega8/16/32则无效。
如果将digitalWrite()函数都换成bitSet(PINB,5);那么空间可以大幅缩少至:644个字节,只用了4个字节就完成 digitalWrite() 的功能。
程序足够精简了吗?不,loop() 本身就是一个循环,每次循环 LED 都可以翻转,即亮灭替换,因此loop() 中可以进一步精简,去掉一个 bitSet(PINB, 5)和delay(1000),效果完全是一样的,但节约了44字节。
delay() 纯粹为了浪费时间,但是非常浪费系统空间,注释掉之后系统减少了152个字节,堪比digitalWrite()。
最有有效浪费时间的方法,就是利用内部定时器。这里引入time0(Arduino 三个定时器之一),定时会不断递增一个变量timer0_millis ,即“毫秒递增变量”,我们要引用这个变量就需要在setup() 和 loop()之外,加入 extern volatile unsigned long timer0_millis; 来声明这个变量。现在引入一个while()循环来替代delay():
while(timer0_millis < 1000); // 如果 timer0_millis每一毫秒加1,如果不到1000,就一直执行循环;
timer0_millis = 0; // 将 timer0_millis 复位置0,下一个loop() 时重新计数;
这个计数方法使得millis()、delay() 函数完全失效了,因为这些函数都是基于timer0来实现的。
经这一番压缩,最后Blink 从 928字节压缩到490字节,差不多只有原来一半大小,现在你还急着购买空间更大、价格更贵的芯片吗?
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 漫画:如何证明sleep不释放锁,而wait释放锁?
- golang内存释放
- 【缺陷周话】第13期:二次释放
- Rietspoof恶意软件释放多个恶意有效载荷
- CocosCreator 引擎资源加载与释放原理简析
- Spark The Change:释放人类的才能
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Pro CSS and HTML Design Patterns
Michael Bowers / Apress / April 23, 2007 / $44.99
Design patterns have been used with great success in software programming. They improve productivity, creativity, and efficiency in web design and development, and they reduce code bloat and complexit......一起来看看 《Pro CSS and HTML Design Patterns》 这本书的介绍吧!
HTML 编码/解码
HTML 编码/解码
RGB CMYK 转换工具
RGB CMYK 互转工具