内容简介:我是潘,曾经是个工程师。这是为 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:释放人类的才能
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
计算机是怎样跑起来的
[日] 矢泽久雄 / 胡屹 / 人民邮电出版社 / 2015-5 / 39.00元
本书倡导在计算机迅速发展、技术不断革新的今天,回归到计算机的基础知识上。通过探究计算机的本质,提升工程师对计算机的兴趣,在面对复杂的最新技术时,能够迅速掌握其要点并灵活运用。 本书以图配文,以计算机的三大原则为开端、相继介绍了计算机的结构、手工汇编、程序流程、算法、数据结构、面向对象编程、数据库、TCP/IP 网络、数据加密、XML、计算机系统开发以及SE 的相关知识。 图文并茂,通俗......一起来看看 《计算机是怎样跑起来的》 这本书的介绍吧!