二.生成链接用的库文件
$(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 remset PATH=.;f:\AVRGCC\bin;%path% doskey :install remif %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,但要去掉remif %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可以看出,用户可以修改诸如输出文件名等多种选项。
首先看一个从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) )
31………………24 | 23………………16 | 15………………8 | 7…………………0 |
D | C | B | A |
如前所示,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;
这样,一旦指针指向的变量发生变化,编译器就会重新加载最新的数值。