我关于Arduino AVR核心的一个抓地力是它不是高效嵌入式编程的一个例子。 C ++(PDF)的基础之一是零开销抽象,但Arduino内核具有非常显着的开销。 Arduino Basic Blink示例编译为近1KB,大多数由从未使用的代码占用的空间。重写AVR核心是一项任务I' M&#39尚未准备好解决,但在编写小心之后,我意识到我可以在Arduino库中使用许多相同的优化技术。结果是ArduInoshRink,一个可以大大降低arduino项目的编译大小的库。在这篇文章中我' ll解释了一些我用来实现更快,更好,更小的编码三十字的技术。
Arduino Core实际上是一个与项目代码相关联的静态库。由于Eli在静态链接上解释了这篇文章,Libc等库通常只有一个函数,以避免在不必要的代码中链接。 Arduino没有使用那种模块化方法,但是通过使用GCC' S" - 功能 - 部分"选项,它确实会降低由于非模块化方法而导致的代码融合量。
使用Arduinoshrink,我写了更多的模块化,独立的代码。例如,Arduino Delay()函数调用micros(),它依赖于32位定时器0中断溢出计数器。我简化了延迟函数,使其仅需要8位定时器值。如果用户代码永远不会调用micros()或millis(),则Timer0 ISR代码从未被链接。通过使用更高效的算法并在AVR汇编程序中编写代码,我将延迟功能的大小减少到12指令24闪光的字节。
为了最小化代码大小和最大化速度,几乎一半的代码是AVR汇编程序。尽管过去几十年的编译器优化技术有所改善,但在像AVR这样的架构上,我几乎总是可以始终编写更好的汇编代码,而不是编译器生成的代码。 '对于中断服务例程尤其如此,例如用于维护Millis()和micros()的计数器的Timer0中断。我的汇编程序版本的中断仅使用56个字节的闪光,并且比在C中写的Arduino ISR更快
仍然用C仍写的一个部分是DigitalWrite()函数。 Arduino Core使用Flash中的一组表来将给定的PIN码映射到IO端口和位,使大量代码具有DigiteWrite(13,低)清除PortB5。利用比尔' S发现,这些闪存表查找可以在编译时解析,DigiteWrite(13,低)编译为单个指令:" CBI Portb,5"。
ArduinoshRink还设计用于显着降低中断延迟。原始Timer0中断需要左右5us运行,在此期间任何其他中断都会延迟。我的ISR中的第一个指令是' sei',它允许运行其他中断,将延迟影响降低到几个周期的最小值。官方Arduino Core禁用几个地方的中断,例如在阅读Millis柜台时。我的解决方案是检测毫米计数器是否已更新并重新读取它,从而避免任何中断延迟影响。
与官方AVR核心相比的唯一限制是编译器必须能够在编译时解析数字IO函数的PIN码。虽然PIN可以硬编码,但即使使用LTO启用,AVR-GCC也不总是能够识别引脚是编译时间常数。由于AVR不是GCC优化的优先目标,因此我可以' t依赖于编译器改进来解决此限制。因此,我计划写一版本的DigitialWrite,即使AVR-GCC可以在编译时弄清楚PIN。
虽然ArduinoshRink应该与任何Arduino素描兼容,但是给予了一些编译器技巧I' ve使用它'不太可能我错过了一个潜在的错误。 如果您确实找到了您认为错误,请在GitHub存储库中打开一个问题。