搜索
bottom↓
回复: 13

如何使用AVR-GCC(很详细)

[复制链接]

出0入0汤圆

发表于 2006-3-22 10:41:35 | 显示全部楼层 |阅读模式
我传不上来!

http://www.huihoo.com/gnu/gcc/avr-gcc.doc

/******************************************888888888

如何使用AVR-GCC

安装GNU C for AVR

一.执行安装程序

二.生成链接用的库文件

$(AVR)表示安装的根目录。(在本人系统里为f:\avrgcc)

生成库文件关键是要运行位于$(AVR)下的RUN.BAT。原程序如下:

  @echo off

  if NOT %AVR%!==! goto install

  rem set environment variables

  set AVR=f:\AVRGCC

  set CC=avr-gcc

  set PATH=.;f:\AVRGCC\bin;%path%

  doskey

:install

  if %1!==! GOTO end

  rem install libc

  cd f:\AVRGCC\lib\avr-libc-20010701\src

  rem first win32_make_dirs will make some errors(I don't know why?)

  make -f makefile-win32 win32_make_dirs

  make -f makefile-win32

  make -f makefile-win32 install

  make -f makefile-win32 clean

:end

  f:

  cd f:\AVRGCC

  mode con: lines=43

要修改为:

  @echo off

  if NOT %AVR%!==! goto install

  rem set environment variables

  set AVR=f:\AVRGCC

  set CC=avr-gcc

rem  set PATH=.;f:\AVRGCC\bin;%path%

  doskey

:install

rem  if %1!==! GOTO end

  rem install libc

  cd f:\AVRGCC\lib\avr-libc-20010701\src

  rem first win32_make_dirs will make some errors(I don't know why?)

  f:\AVRGCC\bin\make -f makefile-win32 win32_make_dirs

  f:\AVRGCC\bin\make -f makefile-win32

  f:\AVRGCC\bin\make -f makefile-win32 install

  f:\AVRGCC\bin\make -f makefile-win32 clean

:end

  f:

  cd f:\AVRGCC

  mode con: lines=43

在以后的应用中,运行的是修改之前的RUN.BAT,但要去掉rem  if %1!==! GOTO end 的“rem”。去掉“rem”之后,后续的语句将被跳过。因此MAKE部分的“f:\AVRGCC\bin\”可加可不加。

编译和链接应用程序

首先在www.avrfreaks.net上下载测试程序集gcctest.zip,然后安装。

1.        将GCCTEST\INCLUDE下的MAKE1、MAKE2拷贝到$(AVR)\ INCLUDE

2.        将工作目录的MAKEFILE (每个工程都要有一个此文件,且可由自己进行修改以适合自己的应用。如果要利用原有文件,则注意只能有一个C文件)中的MCU、TRG、SRC、ASRC、INC、LIB等项填入合适的内容

3.        在工作目录运行位于$(AVR)\BIN下的MAKE.EXE(注意:由于系统可能存在其他应用程序的MAKE,因此可能还需要加路径。也可以将其改名。)

4.        从MAKE1、MAKE2和MAKEFILE可以看出,用户可以修改诸如输出文件名等多种选项。















在C代码中嵌入汇编指令

一.GCC的ASM声明

首先看一个从PORTD读入数据的例子:

asm(“in %0, %1” : “=r”(value) : “I”(PORTD) : );

由上可以看出嵌入汇编的4个部分:

1.        汇编指令本身,以字符串“in %0, %1”表示;

2.        由逗号分隔的输出操作数,本例为“=r”(value)

3.        由逗号分隔的输入操作数,本例为“I”(PORTD)

4.        Clobber寄存器

嵌入汇编的通用格式为:

asm(code : output operand list : input operand list : clobber list);

例子中%0表示第一个操作数,%1表示第二个操作数。即:

%0 à “=r”(value)

%1 à “I”(PORTD)

如果在后续的C代码中没有使用到汇编代码中使用的变量,则优化编译时会将这些语句删除。为了防止这种情况的发生,需要加入volatile属性:

asm volatile (“in %0, %1” : “=r”(value) : “I”(PORTD) : );

嵌入汇编的的Clobber寄存器部分可以忽略,而其他部分不能忽略,但可以为空。如下例:

asm volatile(“cli” : :);

二.汇编代码

用户可以在C代码里嵌入任意的汇编指令,就如同在汇编器里写程序一样。AVR-GCC提供了一些特殊的寄存器名称:

符号        寄存器

__SREG__        状态寄存器SREG(0x3F)

__SP_H__        堆栈指针高字节(0x3E)

__SP_L__        堆栈指针低字节(0x3D)

__tmp_reg__        r0

__zero_reg__        r1。对于C代码而言其值永远为0

三.输入/输出操作数

约束符号        适用于        范围

a                r16~r23

b        指针        Y,Z

d                r16~r31

e        指针        X,Y,Z

G        浮点常数        0.0

I        6比特正常数        0~63

J        6比特负常数        -63~0

l                r0~r15

M        8比特正常数        0~255

N        整数常数        -1

O        整数常数        8,16,24

P        整数常数        1

r                r0~r31

t                R0

W        寄存器对        r24,r26,r28,r30

X        指针X        r27:r26

Y        指针Y        r29:r28

Z        指针Z        r31:r30

要注意的是,在使用这些约束符号时要防止选择错误。例如,用户选择了”r”约束符号,而汇编语句则使用了”ori”。编译器可以在r0~r31之间任意选择寄存器。若选择了r2~r15,则会由于不适用ori而出现编译错误。此时正确的约束符应该是”d”。

约束符号还可以有前置修饰符,如下表所示。

修饰符        指定

=        只写操作数

+        读-写操作数(嵌入汇编不支持)

&        寄存器只能用做输出

输出操作数必须为只写操作数,C表达式结果必须为l(r0~r15)。编译器不检查汇编指令中的变量类型是否合适。

输入操作数为只读。如果输入/输出使用同一个寄存器怎么办呢?此时可以在输入操作数的约束字符里使用一个一位数字来达到这个目的。这个数字告诉编译器使用与第n个(从0开始计数)操作数相同的寄存器。例如:

asm volatile(“SWAP %0” : “=r”(value) : “0”(value));

这条语句的目的是交换变量value的高低4位。约束符号“0”告诉编译器使用与第一个操作数相同的寄存器作为输入寄存器。要注意的是,即使用户没有指定,编译器也有可能使用相同的寄存器作为输入/输出。在某些情况下会引发严重的问题。如果用户需要区分输入/输出寄存器,则必须为输出操作数增加修饰符”&”。如下例所示。

asm volatile(“in %0, %1;

                          out %1, %2”

                         : “=&r”(input)

                         : “I”(port), “r”(output)

                        );

此例的目的是读入端口数据,然后给端口写入另一个数据。若编译器不幸使用了同一个寄存器作为参数input和output存储位置,则第一条指令执行后output的内容就被破坏了。而用了修饰符”&”之后,这个问题得以解决。

下面为一个高16位与低16位交换的32位数据操作的例子:

asm volatile(“mov  __tmp_reg__, %A0;

                         mov  %A0, %D0;

                         mov  %D0, __tmp_reg__;

                        mov  __tmp_reg__, %B0;

                        mov  %B0, %C0;

                        mov  %C0, __tmp_reg__”

                        : “=r”(value)

: “0”(value)

                        )

“0”代表第一个操作数,“A”,“B”,“C”,“D”表示:

31………………24        23………………16        15………………8        7…………………0

D        C        B        A

四.Clobber

如前所示,asm语句的最后一部分为clobber。如果用户在汇编代码里使用了没有作为操作数声明的寄存器,就需要在clobber里声明以通知编译器。下面为一个中断无关的加一操作例子。

asm volatile(“cli;

                        ld r24, %a0;

                        inc r24;

                        st %a0, r24;

                        sei”

                        :

                        :”z”(ptr)

                        :”r24”

                        )

编译结果为:

CLI

LD R24, Z

INC R24

ST Z, R24

SEI

当然,用户也可以用__tmp_reg__来取代r24。此时就没有clobber寄存器了。

下面为考虑更详细的例子:

c_func

{

        uint_t s;

        asm volatile(“in %0, __SREG__;

                                        cli;

                                        ld, __tmp_reg__, %a1;

                                        inc __tmp_reg__;

                                        st %a1, __tmp_reg__;

                                        out __SREG__, %0”

                                        : “=r”(t)

                                        :”z”(ptr)

                                );

}

现在看起来好象没问题了。其实不尽然。由于优化的原因,编译器不会更新C代码里其他使用这个数值的寄存器。出于同样的优化原因,上述代码的输入寄存器可能保持的不是当前最新的数值。用户可以加入特殊的”memory” clobber来强迫编译器及时更新所有的变量。

更好的方法是将一个指针声明为volatile,如下所示:

volatile uint8_t *ptr;

这样,一旦指针指向的变量发生变化,编译器就会重新加载最新的数值。



API

嵌入式编程的代码可以简单地分为两部分,一是与硬件无关的算法部分,对其编程与普通C编程没有区别;二是与硬件相关的寄存器/端口操作部分。不同的MCU实现方法各有不同。在AVR-GCC里则通过一系列的API来解决。当然,用户也可以定义自己的API。在此简单地介绍目前AVR-GCC里定义的API,以及AVR-GCC的工作过程。

一.应用程序启动过程(Start Up)

标准库文件包含一个启动模块(Start Up Module),用于为真正执行用户程序做环境设置。

启动模块完成的任务如下:

1.        提供缺省向量表

2.        提供缺省中断程序入口

3.        初始化全局变量

4.        初始化看门狗

5.        初始化寄存器MCUCR

6.        初始化数据段

7.        将数据段.bss的内容清零

8.        跳转到main()。(不用调用方式,因为main()不用返回)

启动模块包含缺省中断向量表,其内容为预先定义好的函数名称。这些函数名称可以由程序员重载。中断向量表的第一个内容为复位向量,执行结果是将程序跳转到_init_。在启动模块里,_init_表示的地址与_real_init_指向的地址相同。如果要加入客户代码,则需要在程序里定义一个_init_函数。在此函数的末尾跳转到_real_init_。具体实现如下:

void _real_init_(void);

void _init_(void) __attribute__((naked));

void _init_(void)

{

        // 用户代码

       

        // 最后的代码必须为:

        asm ("rjmp _real_init_");

}



在_real_init_部分,系统将设置看门狗和MCUCR寄存器。启动模块并没有真正取用相应寄存器的设置数值(以符号_init_wdctr_,_init_mcucr_,_init_emcucr_表示),而是通过地址来取得其值。因而用户可以通过链接器的--defsym选项来设置这些符号的地址。如果用户没有定义,则启动模块将使用缺省值。

接下来系统将从程序存储器里把具有初值的全局变量加载到数据存储器SRAM。然后是将数据段.bbs清零。此数据段包含所有没有的初值的非AUTO变量。

最后,系统跳转到main()函数,用户代码开始执行。系统对此特殊函数加入一些特殊的处理。进入此函数后,堆栈指向SRAM的末尾。

二.存储器API

AVR具有三种存储器:FLASH,SRAM和EEPROM。AVR-GCC将程序代码放在FLASH,数据放在SRAM。

I.程序存储器

如果要将数据(如常量,字符串,等等)放在FLASH里,用户需要指明数据类型__attribute__((progmem))。为了方便使用,AVR-GCC定义了一些更直观的符号,如下表所示。

类型        定义

prog_void        void __attribute__((progmem))

prog_char        char __attribute__((progmem))

prog_int        int __attribute__((progmem))

prog_long        long __attribute__((progmem))

prog_long_long        long long __attribute__((progmem))

PGM_P        prog_char const*

PGM_VOID_P        prog_void const*

提供的库函数有:

1.__elpm_inline

用法:uint8_t __elpm_inline(uint32_t addr);

说明:执行ELPM指令从FLASH里取数。参数为32位地址,返回一个8位数据。

2.__lpm_inline

用法:uint8_t __elpm_inline(uint16_t addr);

说明:执行LPM指令从FLASH里取数。参数为16位地址,返回一个8位数据。

3.memcpy_P

用法:void* memcpy_P(void* dst, PGM_VOID_P src, size_t n);

说明:memcpy的特殊版本。完成从FLASH取n个字节的任务。

4.PRG_RDB

用法:uint8_t PGR_RDB(uint16_t addr);

说明:此函数简单地调用__lpm_inline

5.PSTR

用法:PSTR(s);

说明:参数为字符串。功能是将其放在FLASH里并返回地址。

6.strcmp_P

用法:int strcmp(char const*, PGM_P);

说明:功能与strcmp()类似。第二个参数指向程序存储器内的字符串。

7.strcpy_P

用法:char* strcpy_P(char*, PGM_P);

说明:功能与strcpy()类似。第二个参数指向程序存储器内的字符串。

8.strlen_P

用法:size_t strlen_P(PGM_P);

说明:功能与strlen()类似。第二个参数指向程序存储器内的字符串。

9.strncmp_P

用法:size_t strncmp_P(char const*, PGM_P, size_t);

说明:功能与strncmp()类似。第二个参数指向程序存储器内的字符串。

10.strncpy_P

用法:size_t strncpy_P(char*, PGM_P, size_t);

说明:功能与strncpy()类似。第二个参数指向程序存储器内的字符串。

II.EEPROM

AVR内部有EEPROM,但地址空间与SRAM的不相同。在访问时必须通过I/O寄存器来进行。EEPROM API封装了这些功能,为用户提供了高级接口。使用时要包含eeprom.h。在程序里定义EEPROM数据的例子如下:

static uint8_t variable_x __attribute__((section(".eeprom"))) = 0;

不同的AVR器件具有不同数目的EEPROM。链接器将针对不同的器件分配存储器空间。

1.        eeprom_is_ready

用法:int eeprom_is_ready(void);

说明:此函数用于指示是否可以访问EEPROM。如果EEPROM正在执行写操作,则在4ms内无法访问。此函数查询相应的状态位来指示现在是否可以访问EEPROM。

2.        eeprom_rb

用法:uint8_t eeprom_rb(uint16_t addr);

说明:从EEPROM里读出一个字节的内容。参数addr用于指示要读出的地址。_EEGET(addr)调用此函数。

3.        Eeprom_read_block

用法:void eeprom_read_block(void* buf, uint16_t addr, size_t n);

说明:读出一块EEROM的内容。参数addr为起始地址,n表明要读取的字节数。数据被读到SRAM的buf里。

4.        eeprom_rw

用法:unint16_t eeprom_rw(uint16_t addr);

说明:从EEPROM里读出一个16位的数据。低字节为低8位,高字节为高8位。参数addr为地址。

5.        eeprom_wb

用法:void eeprom_wb(uint16_t addr, uint8_t val);

说明:将8位数据val写入地址为addr的EEPROM存储器里。_EEPUT(addr,val)调用此函数。

三.中断API

由于C语言设计目标为硬件无关,因此各种编译器在处理中断时使用的方法都是编译器设计者自己的方法。

在AVR-GCC环境里,向量表已经预先定义,并指向具有预定义名称的中断例程。通过使用合适的名称,用户例程就可以由相应的中断所调用。如果用户没有定义自己的中断例程,则器件库的缺省例程被加入。

除了中断向量表的问题,编译器还必须处理相关寄存器保护的问题。中断API解决了细节问题。用户只要将中断例程定义为INTERRUPT()或SIGAL()即可。而对于用户没有定义的中断,缺省例程的处理是reti指令。

函数定义可参见interrupt.h,中断信号符号表参见sig-avr.h。

1.        cli

用法:void cli(void);

说明:通过置位全局中断屏蔽位来禁止中断。其编译结果仅为一条汇编指令。

2.        enable_external_int

用法:void enable_external_int(uint8_t ints);

说明:此函数访问GIMSK寄存器(对于MEGA器件则是EIMSK寄存器)。功能与宏outp()一样。

3.        INTERRUPT

用法:INTERRUPT(signame)

说明:定义中断源signame对应的中断例程。在执行时,全局屏蔽位将清零,其他中断被使能。ADC结束中断例程的例子如下所示:

INTERRUPT(SIG_ADC)

{

}

4.        sei

用法:void sei(void);

说明:通过清零全局中断屏蔽位来使能中断。其编译结果仅为一条汇编指令。

5.        SIGNAL

用法:SIGNAL(signame)

说明:定义中断源signame对应的中断例程。在执行时,全局屏蔽位保持置位,其他中断被禁止。ADC结束中断例程的例子如下所示:

SIGNAL(SIG_ADC)

{

}

6.        timer_enable_int

用法:void timer_enable_int(uint8_t ints);

说明:此函数操作TIMSK寄存器。也可以通过outp()来设置。

四.I/O API

I.I/O端口API

1.        BV

用法:BV(pos);

说明:将位定义转换成屏蔽码(MASK)。与头文件io.h里的位定义一起使用。例如,置位WDTOE和WDE可表示为“BV(WDTOE) | BV(WDE)”

2.        bit_is_clear

用法:uint8_t bit_is_clear(uint8_t port, uint8_t bit);

描述:如果port的bit位清零则返回1。此函数调用sbic指令,故port应为有效地址。

3.bit_is_set

用法:uint8_t bit_is_set(uint8_t port, uint8_t bit);

描述:如果port的bit位置位则返回1。此函数调用sbis指令,故port应为有效地址。

4.cbi

用法:void cbi(uint8_t port, uint8_t bit);

说明:清零port的bit位。bit的值为0~7。如果port为实际I/O寄存器,则此函数生成一条cbi指令;否则,函数生成相应的优化代码。

5.inp

用法:uint8_t inp(uint8_t port);

说明:从端口port读入8比特的数值。如果port为常数,则函数生成一条in指令;若为变量,则函数用直接寻址指令。

6.__inw

用法:uint16_t __inw(uint8_t port);

说明:从I/O寄存器读入16位的数值。此函数用于读取16位寄存器(ADC,ICR1,OCR1,TCNT1)的值,因为读取这些寄存器需要合适的步骤。由于此函数只产生两条汇编指令,因此要在中断禁止时使用,否则有可能由于中断插入到指令之间造成读取错误。

7.__inw_atomic

用法:uint16_t __inw_atomic(uint8_t port);

说明:以原子语句方式读取16位I/O寄存器的数值。此函数首先禁止中断,读取数据之后再恢复中断状态,因此可以安全地应用在各种系统状态。

8.loop_until_bit_is_clear

用法:oidoid loop_until_bit_is_clear (uint8_t port, uint8_t bit);

说明:此函数简单地调用sbic指令来测试端口port的bit位是否清零。port必须为有效端口。

9.loop_until_bit_is_set

用法:oidoid loop_until_bit_is_set (uint8_t port, uint8_t bit);

说明:此函数简单地调用sbis指令来测试端口port的bit位是否置位。port必须为有效端口。

10.outp

用法:void outp(uint8_t val, uint8_t port);

说明:将val写入端口port。如果port为常数,则函数生成一条out指令;若为变量,则函数用直接寻址指令。

11.__outw

用法:void __outw(uint16_t val, uint8_t port);

说明:将16位的val写入端口port。此函数适合于操作16位寄存器,如ADC,ICR1,OCR1,TCNT1。由于此函数只产生两条汇编指令,因此要在中断禁止时使用,否则有可能由于中断插入到指令之间造成读取错误。

12.__outw_atomic

用法:void __outw_atomic(uint16_t val, uint8_t port);

说明:将16位的val写入端口port。此函数适合于操作16位寄存器,如ADC,ICR1,OCR1,TCNT1。此函数首先禁止中断,读取数据之后再恢复中断状态,因此可以安全地应用在各种系统状态。

13.sbi

用法:void sbi(uint8_t port, uint8_t bit);

说明:置位port的bit位。bit的值为0~7。如果port为实际I/O寄存器,则此函数生成一条 sbi指令;否则,函数生成相应的优化代码。

五.看门狗API

以下函数操作看门狗。宏定义参见wdt.h。

用户可以通过起动代码初始化看门狗。WDTCR的缺省值为0。如果你希望将其设置为其他值,则需要在链接命令里加入相应的命令。使用的符号为__init_wdtcr__。如下为将WDTCR设置为0x1f的例子:

avr-ld –defsym __init_wdtcr__=0x1f

1.        wdt_disable

用法:void wdt_disable(void);

说明:关闭看门狗。

2.        wdt_enable

用法:void wdt_disable(unit8_t timeout);

说明:使能看门狗。看门狗溢出时间为timeout。

timeout        周期

0        16K CLK

1        32K CLK

2        64K CLK

3        128K CLK

4        256K CLK

5        512K CLK

6        1024K CLK

7        2048K CLK



3.        wdt_reset

用法:void wdt_reset(void);

说明:产生喂狗指令wdr。



附录:AVR-GCC配置

汇编选项

选项        描述

-mmcu=name        指定目标器件name可以为:at90s1200,at90s2313,at90s2323,at90s2333,at90s2343,at904433,at90s8515,at90s8535,atmega103,atmega161

寄存器使用

如果用户需要进行汇编与C的混合编程,必须了解寄存器的使用。

1.寄存器使用

r0                可用做暂时寄存器。如果用户汇编代码使用了r0,且要调用C代码,则在调用之前必须保存r0。C中断例程会自动保存和恢复r0。

r1                C编译器假定此寄存器内容为“0”。如果用户使用了此寄存器,则在汇编代码返回之前须将其清零。C中断例程会自动保存和恢复r1。

r2-r17,r28,r29                C编译器使用这些寄存器。如果用户汇编代码需要使用这些寄存器,则必须保存并恢复这些寄存器。

R18-r27,r30,r31        如果用户汇编代码不调用C代码则无需保存和恢复这些寄存器。如果用户要调用C代码,则在调用之前须保存。

2.函数调用规则

参数表:函数的参数由左至右分别分配给r25到r8。每个参数占据偶数个寄存器。若参数太多以至r25到r8无法容纳,则多出来的参数将放入堆栈。

返回值:8位返回值存放在r24。16位返回值存放在r25:r24。32位返回值存放在r25:r24:r23:r22。64位返回值存放在r25:r24:r23:r22:r21:r20:r19:r18。

阿莫论坛20周年了!感谢大家的支持与爱护!!

一只鸟敢站在脆弱的枝条上歇脚,它依仗的不是枝条不会断,而是自己有翅膀,会飞。

出0入0汤圆

发表于 2006-3-22 10:53:43 | 显示全部楼层
很正啊, 簡而精!!

出0入0汤圆

发表于 2006-3-22 10:58:51 | 显示全部楼层
好。中文看得舒服



不过版本旧了些,部分内容已经被取消了,而且已经集成到WINAVR安装包,安装就不用那么麻烦了。

出0入0汤圆

发表于 2006-3-22 13:40:05 | 显示全部楼层
好得很,很清楚的

我来传楼主的文件

点击此处打开armok01109566.doc

出0入0汤圆

发表于 2006-9-19 17:43:35 | 显示全部楼层

出0入0汤圆

发表于 2006-10-31 10:44:50 | 显示全部楼层
顶一下

出0入0汤圆

发表于 2006-10-31 12:11:22 | 显示全部楼层
为什么我找不到RUN.BAT

出0入0汤圆

发表于 2006-11-11 22:40:41 | 显示全部楼层
谢谢!!

好的很啊!!

出0入0汤圆

发表于 2006-11-12 18:34:08 | 显示全部楼层
谢谢楼主,学习

出0入0汤圆

发表于 2006-11-13 22:53:01 | 显示全部楼层
非常感谢楼主!

出0入0汤圆

发表于 2011-11-18 16:40:41 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-11-22 15:53:04 | 显示全部楼层
谢谢分享,学习一下

出0入0汤圆

发表于 2011-11-23 16:19:43 | 显示全部楼层
很正啊, 簡而精!!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-6-18 00:01

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表