Jade Dungeon

使用AVR-GCC编程Arduino

本文展示如何在Arduino IDE以外使用AVR-GCC给Arduino写程序, 包括烧写引导器和设置熔丝。

译者注:Arduino给新手提供了很大便利,但是稍微深入的应用, 如定时器和详细硬件控制就很麻烦了。可同时Arduino的硬件做的是很不错的。 所以本文以使用Arduino的硬件,但不使用Arduino的软件为主要目的。

Arduino是IDE和硬件平台,IDE以Java编写,并使用Processing语言简化了开发, 但是也比C要弱:

  • C有准确的执行时间,没有隐藏代码,写什么就执行什么
  • C更容易访问硬件和中断
  • 便于在多种MCU之间移植

本文编译和上传一个简单的纯C程序(使用avr-libc),而不用Arduino IDE。 只需要终端、文本编辑器、AVR-GCC工具链。

闪耀LED例子

从让Arduino引脚13的LED闪耀开始(实际是闪耀PORTB的所有位)。创建个文件夹来存放项目 ,并创建文件blink.c

#include <avr/io.h>
#include <util/delay.h>

int main(void) {
    unsigned char counter;
    DDRB=0xff;      //设置PORTB输出
    while(1) {
        PORTB=0xff; //设置PORTB为高
        counter=0;
        while(counter!=50) {
            _delay_loop_2(30000);
            counter++;
        }
        PORTB=0x00;
        counter=0;
        while(counter!=50) {
            _delay_loop_2(30000);
            counter++;
        }
    }
    return 1;
}

编译和上传

将Arduino连接到USB口之后,Linux-2.6会自动载入FTDI驱动ftdi_sio.ko

$ dmesg
...
usb 3-2: FTDI USB Serial Device converter now attached to ttyUSB0
usbcore: registered new interface driver ftdi_sio
drivers/usb/serial/ftdi_sio.c: v1.4.3:USB FTDI Serial Converters Driver

工具链(编译器/连接器/汇编器、标准C库和编程工具)包含在三个包中:

$ apt-get install gcc-avr avr-libc avrdude

C库的手册在/usr/share/doc/avr-libc/avr-libc-user-manual/index.html

建议仔细看看file:///usr/share/doc/avr-libc/avr-libc-user-manual/group__demo__project.html。 其末尾有个Makefile,可供定制到自己所需。改变程序名到 blink 并编译:

$ make

这会生成blink.hex,也就是要上传的镜像。有两种凡是可供上传到Arduino:

  • ICSP(In-Circuit Serial Programming)
  • 使用Bootloader,消耗2KB的程序存储器

第二个选项并不严格要求。实际上,第一个选项也并没有绝对优势。除非你只需要一个USB线,而不是两个。

通过Bootloader上传

此时AVR程序存储器已经包含了Bootloader,烧写blink.hex。确保熔丝的BOOTRST=0, 如果不是,Bootloader在复位后不会启动。

$ avrdude -p m168 -P /dev/ttyUSB0 -c stk500v1 -b 19200 -F -u -U flash:w:blink.hex

不通过Bootloader,而是用并口编程器

如果不用Bootloader,直接烧写blink.hex,通过并口编程器。要确保熔丝的BOOTRST=1 ,如果不是,程序在复位后不会执行(后面章节会解释如何设置熔丝):

$ avrdude -p m168 -P /dev/parport0 -c dapa -b 115000 -F -u -U flash:w:blink.hex

如果你使用ATmega8则用-p m8

不通过Bootloader,而是用AVR ISP MK-II编程器

要使用这种方法,你需要一个mkII编程器(约30欧元),并连接到Arduino, 通过ICSP连接器。在AVR Studio IDE, 通过[Tool]=>[Program AVR]=>[Connect ...]来选择AVR ISP mkII编程器,USB连接, 并选择Flash镜像,最后点击[Program]按钮。

注意

使用的引脚号与Arduino的定义不同

要使用AVR-GCC的术语访问端口和其他硬件,参考datasheet的SFR(特殊功能寄存器),一些ATmega8的不同于ATmega168/328p

如果你使用其他零件(ATmega8、ATmega168、ATmega328等),注意修改Makefile的MCU变量

最近Arduino转到ATmega328了,兼容ATmega168,但有更多程序空间,而 avr-libc@2009-01-01并不支持ATmeag328,编程工具的串口也不工作:

#define BAUD 19200
#include <util/setbaud.h>
    UBRR0H = UBRRH_VALUE;
    UBRR0L = UBRRL_VALUE;
#if USE_2X
    UCSR0A |= (1<<U2X0);
#else
    UCSR0A &= ~(1<<U2X0);
#endif

你应该替换为:

#define BAUD_RATE 19200
UBRR0L = (uint8_t)(F_CPU/(BAUD_RATE*16L)-1);
UBRR0H = (F_CPU/(BAUD_RATE*16L)-1)>>8;
UCSR0B = (1<<RXEN0) | (1<<TXEN0);
UCSR0C = (1<<UCSZ00) | (1<<UCSZ01);

启用内部上拉电阻,在D0(RX),来降低线路噪声:

DDRD &= ~_BV(PIND0);
PORTD |= _BV(PIND0);

使用ICSP烧写Bootloader

本节针对你的设备是空的,没有Bootloader。已经有Arduino Bootloader的可以直接跳过不看。 一个简单的检查是否有Bootloader的方法是复位后PIN13的等会闪3次。

什么是Bootloader

Bootloader是一种在特定存储区域的程序(bootloader区),其基本任务是接收新的固件, 并存储到AVR的Flash存储器(程序存储器)。每个Bootloader都是针对特定设备的, 使用特殊的协议。所有这些配置参数必须与主机编程器匹配(avrdude)。 avrdude可以用多种类型的协议,支持多种连接(串口、并口、USB、...)。

一个例子是ATmega168在16MHz,stk500v1协议,19200-8N1串口的Bootloader: http://www.javiervalcarce.eu/pub/avr/ATmegaBOOT_168_ng.hex

按照如下步骤来烧写到AVR设备。更换其他操作系统, 如Windows就是将/dev/parport0替换为LPT1,并安装giveio.sys即可。

(解释如何编译Bootloader,而不是提供预编译的)

连接到并口编程器dapa和ICSP,然后供电:

$ # write the following fuse bits: efuse=0x00, hfuse=0xdd, lfuse=0xff
$ # write the following fuse bits: lock=0x3f (unlock boot section)
$ avrdude -c dapa -p m168 -P /dev/parport0 115000 -U flash:w:ATmegaBOOT_168_ng.hex
$ # write the following fuse bits: lock=0x0f (lock boot section)

在烧写镜像之前,先把熔丝设置成:使用外部晶振、禁用时钟分频、最大化Bootloader段等。 然后烧写ATmegaBOOT_168_ng.hex到AVR。对于熔丝位,参考手册。

要访问并口,必须在lp组,修改/etc/group并退出会话来让改变生效。

读取熔丝位

$ avrdude -c dapa -p m168 -P /dev/parport0 115000 -U efuse:r:-:h #read efuse
$ avrdude -c dapa -p m168 -P /dev/parport0 115000 -U hfuse:r:-:h #read hfuse
$ avrdude -c dapa -p m168 -P /dev/parport0 115000 -U lfuse:r:-:h #read lfuse
$ avrdude -c dapa -p m168 -P /dev/parport0 115000 -U signature:r:-:h #读取设备签名

最后一个命令仅用于确认数据线正确连接。ATmega168的签名是0x1e, 0x94, 0x06

写入熔丝位

$ avrdude -c dapa -p m168 -P /dev/parport0 115000 -U efuse:w:0xff:m #写0xff到efuse
$ avrdude -c dapa -p m168 -P /dev/parport0 115000 -U hfuse:w:0xff:m #写0xff到hfuse
$ avrdude -c dapa -p m168 -P /dev/parport0 115000 -U lfuse:w:0xff:m #写0xff到lfuse

译者补充

  • 对于较新的Arduino,常用的芯片ATmega328P对应的器件名字叫"atmega328p"
  • avrdude烧写时的编程器为"arduino",波特率为57600,即完整命令:
avrdude -c arduino -p atmega328p -P /dev/tty.SLAB_USBtoUART -b 57600 -F -u -U flash:xxx.he