由于不再国内,可能网络性能上会有差异。不过目前看来似乎比之前国内的服务器还快...
不会或许有不稳定的地方
由于不再国内,可能网络性能上会有差异。不过目前看来似乎比之前国内的服务器还快...
不会或许有不稳定的地方
原本是想把这篇文章的内容作为一个前言背景部分,不过发现写了不少,就干脆单独提出来一片。好给后续的文章做铺垫。
我会在明后几天发布一篇“ArduinoLite,高效且易用的AVR单片机运行库”的文章,来把这半年来我的一些工作成果推荐和分享给大家。
我从去年(2009)9月起开始接触AVR单片机。其实也就是利用业余时间基于他制作一些电子制作,当然也有将来会比较大规模的项目。或许大家也从之前几篇凑数性质的文章中看到了。
在正式开始本文前,我打算先介绍下一些背景知识,好让对这块领域暂时陌生的朋友有个了解。
虽然在很多专门从事单片机开发的人眼里看来这个东西基本和玩具无异(其实的确有自身的问题),但是作为一个没有接触过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我只要写入下的代码即可轻松实现。
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的改进,我将这个改进后的库称作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的具体细节,请见我的后续文章:-)