CSK.Blog--个人原创Weblog

« 贴个充数,不太成功的无线供电试验本周末进行网站迁移 »

说说我从去年9月份开始的AVR单片机学习和使用

原本是想把这篇文章的内容作为一个前言背景部分,不过发现写了不少,就干脆单独提出来一片。好给后续的文章做铺垫。

我会在明后几天发布一篇“ArduinoLite,高效且易用的AVR单片机运行库”的文章,来把这半年来我的一些工作成果推荐和分享给大家。

我从去年(2009)9月起开始接触AVR单片机。其实也就是利用业余时间基于他制作一些电子制作,当然也有将来会比较大规模的项目。或许大家也从之前几篇凑数性质的文章中看到了。

在正式开始本文前,我打算先介绍下一些背景知识,好让对这块领域暂时陌生的朋友有个了解。

AVR单片机是Atmel(atmel.com)公司推出的一款RISC指令集的8位单片机。因为它的高性价比(片上集成PWM, ADC, I2C, SPI,etc)和高运算效率(16MIPS@16Mhz),所以目前被广泛应用。
同时,另一个使得AVR受欢迎的原因是他的开发工具链很丰富。所谓丰富就是由商业的收费编译器(如ICC),也有开源免费的编译器gcc-avr。这使得对AVR的开发几乎是0成本的,并且gcc的强大也吸引着不少人。
我之前对单片机的了解仅是大二学校的一门电子科技创新课上接触过89S51单片机,从去年9月份开始打算利用AVR单片机来做些事情。于是就和其他人一样,我也开始去学习使用这个系列的芯片。这里就介绍下我再这个过程中的一些体会和心得,如果有朋友想开始涉足这个领域,不妨听我说说。顺便也是给后面的文章做铺垫...
首先我并没有去买市面上各类单片机的教程,而只是下载了AVR芯片的datasheet。毕竟一切的细节和资料都是来自datasheet的。不过如果光看着datasheet开始AVR编程实在有点痛苦,需要有一个容易上手的途径。
之前我已经从Makezine.org上听说有一个意大利的开源硬件项目Arduino(arduino.cc)是基于AVR芯片的(Atmega168),这个项目的特点是他定义了一个基于Atmega168的标准PCB版以及标准的引脚接口,并且提供了一套运行库和IDE环境方便开发者对AVR编程。

Arduino Board

图:标准Arduino板(图片来自arduino.cc首页)

 

虽然在很多专门从事单片机开发的人眼里看来这个东西基本和玩具无异(其实的确有自身的问题),但是作为一个没有接触过AVR芯片甚至完全没有单片机开发经验的人来说,Arduino大大降低了入门门槛,同时因为全世界有许多人都贡献了基于Arduino的一些应用,比如简单的如驱动马达转动到复杂的去实现一个机器人、基于Arduino的webserver等。丰富的外围代码和各类扩展硬件使得即使一个完全没有硬件设计背景的人也可以轻松的实现很复杂的应用。关于Arduino我就介绍到此,如果有兴趣可以去google了解下。

我当时就直接在taobao上购买了一块Arduino版正式开始AVR的使用。目标是一方面可以立刻基于Arduino带来的易用性来实现出我的想法,同时开始看Arduino运行库的代码,作为我对AVR芯片学习的开始。

Arduino的确是相当易用,首先它提供的IDE已经自包含了几乎所有单片机开发的工具链(avr-gcc, 烧录程序, 串口调试),同时也包含了不少的例子程序可以让使用者立刻解决一些现实问题,比如要实现控制LED很柔和的明暗渐变交替,有一个fading的现成例子就可以实现,如果略加改动,开发者还可以实现如通过PC来控制LED亮度这样的实验。

下面举个具体点的例子:

上图中可以看到PCB版上上下各有一排插口,并标有着数字。实际上这些插口是直接和AVR的芯片引脚连接的。那么,如果我将一个LED连接在上面标号为#9的针脚,希望做到的效果是让AVR控制这个LED从0%的亮度逐渐变成100%亮度,用Arduino我只要写入下的代码即可轻松实现。

pinMode(9, OUTPUT);

unsigned char led_brightness = 0;

for (led_brightness=0; led_brightness = 255; led_brightness++)

{

   analogWrite(9, led_brightness);

}

代码相信不需要我解释大家也能知道原理了,analogWrite就是设置一个引脚的模拟值(可以认为是#9的电压,自然数值越大LED就越亮),不过这里其实并不是真的设置了模拟的电压量,而是PWM占空比(wiki:Pulse-width modulation

这个analogWrite就是由Arduino所提供的函数。他已经将许多冗长的硬件寄存器配置操作包装了起来,提供给使用者易用的接口。这的确给初学者减轻了不少负担。如果不使用Arduino,仅依靠gcc提供的标准函数来实现这个看似简单的功能,则需要编写如下的代码。

//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的人头晕的。并且相信大部分人并不可能记住要实现这样一个功能需要设置哪些寄存器,更多的时候还是会去参考datasheet或者从已有的代码里复制过来。其实这些都是无谓的劳动,也容易增加出错的概率。

不过,作为对AVR的学习,还是必须了解其中底层的运行机制的。同时Arduino也有一些缺陷所以并不是所有情况下都能使用。所以之后我就边使用Arduino来做事情,同时对他的运行库代码作了许多修改并且进行大规模优化。这个将在后续的文章中介绍了。半年过去,我觉得至少在AVR这个领域,我应该算是精通了。

 

接下来我来分析下Arduino的一些限制和缺陷,也为衔接后续文章:-P

正如前面所说的,在很多专门从事单片机开发的人眼里,Arduino就像玩具一样。那么为何那么易用的东西会被如此看待?

当然一方面,正是因为宜用所以有人认为这是玩具,这种逻辑很多见:C#很易用,有人认为是玩具,Mac很易用,有人认识为玩具, etc... 当然这是不理性的想法,对于完全没有调查研究就下这样的结论自然是没有道理的。

撇开这个,我觉得主要还是因为Arduino自身的限制和缺陷造成的。

首先,Arduino这个概念其实有2个含义

a. Arduino PCB电路板

b. Arduino IDE,运行库

其实很多时候提到Arduino是2个含义兼有的,比如看到Arduino的标准电路板,自然就是a含义,而开发起来所谓的Arduino就是运行库。

那么这里先看Arduino作为一块硬件电路时候的情况:我想成熟产品中总不止于把一块Arduino板放进去吧...自然在这个含义下,那个就是“玩具”。这块版的用途很明显,就是给业余爱好者使用的。

那么作为运行库(软件)的情况下,Arduino也面临一些问题:

1). 仅支持Atmega8/Atmega168等/Atmega1280

2). 程序体积大,代码效率低

3). 对外围硬件存在一定假设,不能自由运用在任何外围电路中

1)和3)其实可以认为是一个问题,就是Arduino的运行库是专门为Arduino板或者兼容硬件设计的。Arduino板不可能用于产品,那么自然这个Arduino运行库也是没法用的(至少不加修改是不可能了)。同时还有别的问题,比如AVR的芯片种类很多,有不少是基于特定应用的。例如要采用AVR开发锂电池充电器,对于这样的需求,Attiny系列的芯片可能更加合适(价格便宜,充电器逻辑简单,过于强大的芯片资源也是浪费)。Arduino仅支持那些“中高端”部分的AVR芯片显然是不实用的。对于3),一个具体的例子是Arduino板上AVR普遍工作在16Mhz和8Mhz,所以Arduino运行库的代码实际上也仅支持16Mhz和8Mhz。但实际应用中可能要求AVR运行在很低的主频(1Mhz或者更低)。

其实上面这2个问题通过简单的修改Arduino运行库代码就可以解决了,应该说相对arduino库带来的好处相比还是很划得来的。当时关键就出在2)上。

程序体积大和代码效率低是关联的。再举一个很实际的例子:前面我提到的让LED渐变显示的功能,如果用Arduino,编译出来的程序需要消耗1-2Kb。如果用x86上的情况来看,1-2kb非常小。但其实情况是单片机都只有8KB左右的存储空间。有些型号,比如前面提过的Attiny系列只有2Kb。仅仅是实现一个LED渐变,或者说PWM输出,就消耗了Attiny的全部程序空间,这个肯定是不合算也没有人愿意的。就算是Arduino板采用的16KAVR,也意味着消耗了1/8的空间。这几乎都没做别的事情。做一个对比,用前面我给出的直接调用标准库的实现方式,虽然代码看上去啰嗦,但是编译产生的程序只有100字节左右。100:2048,这个差距实在是太大了。如此大的程序体积,自然效率就会低很多。

这几乎也是为什么Arduino不会被专门从事开发的人接受的根本原因了。

其实看过Arduino实现就知道出现2)的根本原因了:a.采用C++,b.过分的注重灵活性。

关于a.其实也不是核心问题,但是C++在默认情况下的确会产生加大的代码。而b.这里就非常明显。

这里再举前面的例子,我们来研究下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);
}

实在太长了,我把其中的注释已经删去。可以看出arduino在实现上很讲究灵活性(这算是一种比较客气的说法)。那么,真的需要这么多代码吗?看看不用arduino时对他的等效实现: OCR01 = led_brightness;

仅仅是一行简单的赋值语句,即使是编译成了AVR机器码,也只有1条outb指令(2字节),而前者会产生多少代码就不用具体给出了。而这个的运行效率差别有多可怕也可以看出来了。

那么,这么多代码可否精简,但又能保持和以前完全一样的功能呢?

这个几乎是不可能的,如果仔细分析代码,可以看出那么多代码都只是在做一件事情:将Arduino采用的数字引脚的标号通过查表的手段映射回对应的AVR引脚。而只要这个函数允许接受任何Arduino引脚作为参数,它就必须有这样的动作存在。

但是实际情况中,PCB已经是做好的,比如LED和AVR的连接时固定不变的,自然也没有必要每次运行中都去查表。如果这个函数所操作的引脚是固定的话,自然就可以去除那些查表的操作了。

对Arduino运行库的改进:

针对上面这些分析,如果把Arduino运行库存在的这些问题都加以克服,并且仍旧继承Arduino易用的特点。那样相信会有很多人乐于使用它。从这里开始,Arduino所表示的含义将是他的运行库。硬件(Arduino板)自然是无法有什么改进的。

下面是我对arduino的改进,我将这个改进后的库称作ArduinoLite。

和Arduino运行库相比,ArduinoLite有如下增强

1. 增加了Attiny2313和Attiny26的支持

2. 支持1Mhz至20Mhz所有频率

3. 没有外部硬件假设,也就是说ArduinoLite可以用于任何支持的AVR芯片中,不需要对Arduino板兼容

4. 代码体积非常小,运行效率高

5. 新增加了一些工具函数

6. 全部改用C实现

具体的介绍和代码以及参考我将在后续的文章中给出,下面给一个直观的例子:

要在Attiny2313芯片上实现LED灯渐变点亮:假设LED连接在用ArduinoLite编号法则#9的引脚。

代码如下

#define LED_PIN 9

PIN_MODE(LED_PIN, OUTPUT);

PWM_ENABLE(LED_PIN);

unsigned char led_brightness = 0;

for (led_brightness=0; led_brightness = 255; led_brightness++)

{

   PWM_SET(LED_PIN, led_brightness);

}

 

这代码是否和用Arduino非常相似呢?编写者也不用去关心具体的寄存器配置状况。而且产生的代码是和之前直接去操作寄存器的版本完全一致的。像PIN_MODE() PWM_ENABLE() PWM_SET()最终均只产生一条指令。而且,和直接去操作寄存器相比,也有很大的优势:Attiny2313寄存器配置情况和Atmega168存在比较大的不同,采用ArduinoLite接口就完全不必考虑这种细节。自然也提高了代码的可移植性。

 

关于ArduinoLite的具体细节,请见我的后续文章:-)

  • quote 1.silasoni
  • 介绍下关于"临时凑数一片"里的
    触摸屏 舵机 等等东西好么
  • 2/3/2010 3:32:05 AM 回复该留言
  • quote 2.imtcn
  • 你好,这里真正碰到国内的Arduino专家了。
    起敬!
    下面这个链接是你做的吗?
    code.google.com/p/arduino-lite/
    我想做一个Linux下面的Arduino IDE,不使用Java,改进text Editor,Arduino的Editor实在太烂了。
    多指教。
  • 2/21/2010 9:40:37 PM 回复该留言
  • quote 3.imtcn
  • 已经看到了。呵呵呵
    =======
    Links: Shikai Chen's Blog(Chinese only)
    Arduino Project
    =======
    我先做个类似这个的ide
    code.google.com/p/arduino-pyqt/
    yao 于 4/28/2010 7:50:20 AM 回复
    你这个能不能弄到arduino官网上?
  • 4/28/2010 7:50:20 AM 回复该留言
  • quote 4.yao
  • 为什么不把这个加到arduino的库里面 那样就更加的通用?
  • 4/28/2010 7:49:39 AM 回复该留言
  • quote 5.xiao
  • 實在有深度的BLOG,有機會向你多請教

    有空也來指教,hk.myblog.yahoo.com/xiao-laba
  • 7/26/2010 9:41:29 PM 回复该留言
  • quote 6.john
  • 恕本人才疏学浅,请赐教arduino库在什么地方体现出了对硬件的假设呢?谢谢!
  • 1/29/2013 1:09:58 AM 回复该留言
  • quote 7.john
  • 恕本人才疏学浅,请赐教arduino库在哪里体现出了对外围硬件的假设呢?源代码其实也都是使用的avr libc这些库啊。谢谢!
  • 1/29/2013 1:11:50 AM 回复该留言
  • quote 8.csk
  • 比如说arduino的库需要使用8Mhz/16Mhz的时钟

    其他时钟频率(比如使用内部的1Mhz RC OSC或者20Mhz,亦或12Mhz),则无法正常使得delay工作,且ADC初始化的prescaler设置是会存在问题的

    此外,arduino库默认将所有PWM开启、占用timer0 timer1等手段,也给产品定制造成一些问题。虽然这些大部分情况都是有利于使用的
  • 1/29/2013 11:07:53 PM 回复该留言

发表评论:

注意:为了有效防止SPAM,任何含有http://字样的消息会被阻止发布同时,本站仅供技术交流,请不要讨论任何政治敏感话题或者低级趣味问题。

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

日历

最新评论及回复

最近发表

Copyright Shikai Chen 2000-2012. Powered By Z-Blog(CSK Modified)