Arduino-Lite, RoboPeak使用的高效轻量级AVR库(1)

Click here to view the English version

Arduino-Lite是由RoboPeak开发并使用的轻量级且高效率的AVR固件库,他基于Arduino项目的固件库改良而来,我们保留了Arduino固件库的简单易用的风格,同时有效的缩小了固件的代码尺寸,相比Arduino,Arduino-Lite拥有诸多优点。并且,他是相当易于使用的。

这里,我们很荣幸将Arduino-Lite开放下载并推荐给大家。

开放版本的Arduino-Lite可以在google code下载到: http://code.google.com/p/arduino-lite/

这是本系列文章的第一部分,后续我们将介绍Arduino-Lite的具体使用。

0. 支持的设备

Arduino-Lite支持基于Atmega328/168的标准Arduino控制板外,任何第三方兼容版,任何满足下文列出的AVR芯片构成的电路均可支持。我们将在后续文章中介绍并开源RoboPeak使用的免驱动AVR/51编程器RP USB Connector,它也是采用Arduino-Lite作为固件库开发的。

1. 为何要开发Arduino-Lite,为何要使用它?

同样使用C++/C编写且基于avr-gcc编译器。但与Arduino固件库相比,Arduino-Lite有如下优势。

  • 非常轻量级

使用Arduino-Lite的固件往往比使用Arduino固件库小了50%以上.

  • 高效率

许多Arduino-Lite提供的与Arduino固件相同功能的函数,例如digitalWrite之Arduino-Lite版本:DIGITAL_WRITE仅使用一条AVR指令实现.

  • 支持更多的AVR芯片和时钟频率

除了 Atmega8(A), Atmega168(PA), Atmega328(PA), Atmega1280 芯片外, Arduino-Lite 也支持以下芯片: Attiny2313, Attiny26, Atmega48(PA), Atmega88(PA)

对于时钟频率, Arduino-Lite 支持从1Mhz 至 20Mhz 的频率范围.

除此之外,Arduino-Lite还有如下特点:

  • 自包含,无需依赖任何第三方工具/编译器/库

只要系统中带有文本编辑器,即可直接用Arduino-Lite进行AVR固件开发、编译、烧录等动作。Arduino-Lite自带了avr-gcc(WINAVR)以及相关的函数库。

  • 灵活易与整合的编译环境,基于Make,但无需用户编写或是生成任何Makefile

创建一个新的Arduino-Lite工程,最简单的办法是将模板工程文件夹解压缩并重命名为希望的工程文件。并将相关的源代码以任何目录结构放置于工程目录下,Arduino-Lite就能编译项目,无需用户修改/编写/生成Makefile.

1.0 适合使用的情况和对象

我们认为Arduino-Lite适用于以下领域

  • 对固件代码尺寸/器件成本敏感的场合,比如需要使用Attiny或者Atmega48等小Rom尺寸的芯片的场合
  • 对固件执行速度有较高要求的场合,比如对实时性要求较高的工控领域和机器人控制器领域
  • 喜欢使用Make脚本、自定义IDE等的开发环境
  • Arduino/AVR爱好者,且有一定的编程经验,不满足于Arduino IDE环境,期望更高效的固件库
  • 希望将Arduino的简易开发特性运用于Attiny, Atmega48以及不同时钟条件下的硬件环境

相比Ardunio的固件库,Arduino-Lite或许不适合

  • 不喜欢命令行界面、Make编译脚本的人群 (我们也有计划将Arduino-Lite支持Arduino-IDE)
  • 希望直接使用Arduino各类第三方库,急需应用的情况

1.1 对Arduino固件库优点的延续

Arduino项目所提供的固件库凭借其易用性被广大爱好者所接受,例如可以使用digitalWrite() digitalRead() analogWrite()函数轻松实现对AVR芯片IO口输出输入的控制。这大大降低了AVR单片机的使用门槛,使用者无需知道传统AVR编程中遇到的DDR, PIN这些寄存器,甚至像PWM输出这类需要了解定时器工作模式的功能,即使是没有任何单片机开发背景的爱好者也能迅速用Arduino固件库上手。

例如,使用传统的avr-gcc (WINAVR)进行开发时,如果实现利用PWM输出实现LED小灯的渐变点亮,开发人员需要编写至少如下代码:

//Set PWM mode: fast mode for timer 0
sbi(TCCR0A, WGM01);
sbi(TCCR0A, WGM00);
//set timer 0 prescale factor to 64
sbi(TCCR0B, CS01);
sbi(TCCR0B, CS00);
sbi(DDRB, PB1);
sbi(TCCR, COM11);
unsigned char led_brightness = 0;
for (led_brightness=0; led_brightness = 255; led_brightness++)
{
   OCR01 = led_brightness;
}

上述代码要求编写者事先记住AVR相关寄存器的名称以及使用方式,这无疑加重了开发难度,即使是有一定经验的开发人员,也可能忘记某个寄存器的具体使用。另外,AVR不同型号芯片上的寄存器名称以及使用(定义)方式也是大不相同的。

而用Arduino固件库以后,上述代码则可以用如下简洁的代码所替代:

pinMode(9, OUTPUT);
unsigned char led_brightness = 0;

for (led_brightness=0; led_brightness = 255; led_brightness++)
{
   analogWrite(9, led_brightness);
}

Arduino固件库不但使得代码变得简洁易懂,同时也有使得固件代码实现了跨芯片编译。总体上说,我们认为Arduino固件库有如下优点值得学习:

  • 将常用操作封装为函数/类,无需用户配置具体的寄存器,实现了易用性和平台无关性
  • 将AVR具体的IO引脚重新用数字编号,相比PB1, PC3这类名称,有利于初学者接受,更重要的是,有利于程序逻辑脱离对具体芯片的依赖。同样的编号可以运用在AVR不同型号,甚至是非AVR单片机芯片中

基于上述优点以及Arduino硬件设计的易用性,Arduino固件也逐渐被大家所接受。Arduino-Lite同样保留了上述优点。上述Arduino代码在使用Arduino-Lite时,可以写为:

PIN_MODE(9, OUTPUT);
unsigned char led_brightness = 0;

for (led_brightness=0; led_brightness = 255; led_brightness++)
{
   ANALOG_WRITE(9, led_brightness);
}

上述代码除了大小写变化外,与Arduino固件代码一致。大部分Arduino-Lite函数均以原先Arduino对应函数的大写形式出现,方便Arduino使用者迅速迁移到Arduino-Lite。

1.2 Arduino固件库的不足

但在提供上述优点的同时,我们发现Arduino也有不足之处

运行效率低

当使用digitalRead() digitalWrite() analogWrite()等函数以及依赖他们的类/函数/库时,使用者往往会发现他们的运行效果不理想。这主要与这些函数的实现有关。

因为Arduino引入了将IO引脚用数字编号的特性,在调用操作IO引脚的函数时,它们需要查表将对应的数字编号转化成对应的控制寄存器,这里以analogWrite()函数的实现为例:

void analogWrite(uint8_t pin, uint8_t val)
{
 pinMode(pin, OUTPUT);
 if (digitalPinToTimer(pin) == TIMER1A) {
  // connect pwm to pin on timer 1, channel A
  sbi(TCCR1A, COM1A1);
  // set pwm duty
  OCR1A = val;
 } else if (digitalPinToTimer(pin) == TIMER1B) {
  // connect pwm to pin on timer 1, channel B
  sbi(TCCR1A, COM1B1);
  // set pwm duty
  OCR1B = val;

 } else if (digitalPinToTimer(pin) == TIMER0A) {
  if (val == 0) {
   digitalWrite(pin, LOW);
  } else {
   // connect pwm to pin on timer 0, channel A
   sbi(TCCR0A, COM0A1);
   // set pwm duty
   OCR0A = val;
  }
 } else if (digitalPinToTimer(pin) == TIMER0B) {
  if (val == 0) {
   digitalWrite(pin, LOW);
  } else {
   // connect pwm to pin on timer 0, channel B
   sbi(TCCR0A, COM0B1);
   // set pwm duty
   OCR0B = val;
  }
 } else if (digitalPinToTimer(pin) == TIMER2A) {
  // connect pwm to pin on timer 2, channel A
  sbi(TCCR2A, COM2A1);
  // set pwm duty
  OCR2A = val;
 } else if (digitalPinToTimer(pin) == TIMER2B) {
  // connect pwm to pin on timer 2, channel B
  sbi(TCCR2A, COM2B1);
  // set pwm duty
  OCR2B = val;
 } else if (val < 128)
  digitalWrite(pin, LOW);
 else
  digitalWrite(pin, HIGH);
}

代码是比较长的,相比之下,直接操作寄存器完成analogWrite同等功能,只需要如下简短的代码:

OCR01 = led_brightness;

如此的差距体现在执行时就是10倍以上的指令集消耗和效率的减缓。这使得Arduino固件库难以直接应用于对运行效率较高的产品和专业领域。

产生的固件代码尺寸大

同样,因为Arduino固件库函数多包含大量查表代码以及需要带有引脚映射表,使得基于Arduino固件库的固件代码尺寸较大。例如之前给出的LED渐变点亮的例子,使用Arduino的固件代码在将近2kb左右。对于Atmega48/Attiny这类单片机,2kb的固件已经消耗了一半甚至所有的程序空间。

如此大的体积消耗在价格敏感产品领域是难以接受的,即使是爱好者使用,较大的固件尺寸也限制了所开发程序的功能。

支持的AVR芯片有限,且对外围硬件有要求

目前的Arduino固件库仅支持Atmega8/Atmega168/Atmega328/Atmega1280等芯片,同时需要使用频率为8Mhz或者16Mhz的外部石英振荡器。然而,产品和业余制作中,可能会用到Atiny系列,Atmega48,Atmega88这类程序控件较少但成本更低廉的AVR型号,同时,具体应用中也会使用除了16Mhz,8Mhz之外的时钟。对于这些情况,Arduino库就无法使用了。

1.3 Arduino-Lite对Arduino的改良

Arduino-Lite延续了Arduino固件库易用的特性,Arduino所使用的IO引脚编号方式仍然可以在Arduino-Lite中使用,通过,几乎所有的Arduino核心函数(如digitalRead/digitalWrite/analogWrite)都有对应的Arduino-lite函数/宏。在实现相同的易用性情况下,Arduino-lite的代码更加精简和高效。实现这一点的主要方式是采用宏展开,将Arduino固件库进行的IO数字编号->寄存器的转化任务在编译阶段完成。省去了查表操作后,固件的体积和运算速度均会显著提高。

这里我们仍旧以PWM输出为例,analogWrite(pin, value)在Arduino-lite中对应于ANALOG_WRITE(pin, value) (大部分Arduino-Lite函数均以原先Arduino对应函数的大写形式出现)。

例如给9号Arduino Pin口 (PB1)输出PWM信号,可以写为

ANALOG_WRITE(9, pwm_value);

这虽然形式上和用法上和Arduino固件库一致,但是Arduino-Lite会将ANALOG_WRITE进行宏展开:

#define ANALOG_WRITE( pin, val )  
    do{                          
         PWM_ENABLE(pin);        
         PWM_SET(pin,val);       
    }                            
    while(0)

而PWM_ENABLE和PWM_SET宏最终会展开为:

sbi(TCCR1A, COM1A1);
OCR1A = pwm_value;

相比上面给出的长长地Arduino实现,Arduino-Lite仅产生2条AVR指令即可完成完全相同的任务。

并且,Arduino-Lite也提供了更加精简的接口,如PWM_SET(pin, value)完成PWM输出的控制,使用这类接口时,Arduino-Lite仅产生一条AVR指令。

这里我们用产生固件的尺寸做个直观对比

实现LED渐亮效果(PWM)

  • Arduino库产生的最终代码: 2048 byte
  • Arduino-Lite产生的最终代码: 100byte

另外,在Arduino固件库中,ADC引脚(PC)均只能供analogRead使用,无法用做传统的IO口。Arduino-Lite中,我们扩充了Arduino的引脚编号,将这些ADC引脚也纳入了普通IO控制范围,共开发者调用DIGITAL_READ/WRITE操作。如下是AtemgaX8(m48,m88,m168,m328)的Arduino-Lite引脚定义:

// ATMEL ATMEGA8 & 168
//
//                  +-/-+
//            PC6  1|    |28  PC5 (AI 5/*D19)
//      (D 0) PD0  2|    |27  PC4 (AI 4/*D18)
//      (D 1) PD1  3|    |26  PC3 (AI 3/*D17)
//      (D 2) PD2  4|    |25  PC2 (AI 2/*D16)
// PWM+ (D 3) PD3  5|    |24  PC1 (AI 1/*D15)
//      (D 4) PD4  6|    |23  PC0 (AI 0/*D14)
//            VCC  7|    |22  GND
//            GND  8|    |21  AREF
//     *(D20) PB6  9|    |20  AVCC
//     *(D21) PB7 10|    |19  PB5 (D 13)
// PWM+ (D 5) PD5 11|    |18  PB4 (D 12)
// PWM+ (D 6) PD6 12|    |17  PB3 (D 11) PWM
//      (D 7) PD7 13|    |16  PB2 (D 10) PWM
//      (D 8 )PB0 14|    |15  PB1 (D 9) PWM
//                  +----+

在下篇文章中,我们将详细介绍Arduino-Lite的安装、使用过程,以及如果用它来给AVR/Arduino设备编程。以及相关的函数介绍

5 thoughts on “Arduino-Lite, RoboPeak使用的高效轻量级AVR库(1)”

Leave a Reply

Your email address will not be published. Required fields are marked *