深圳市华胄科技有限公司 >> MCU专题 >> 单片机教程


AVR 单片机与GCC 编程----之八



提交者 阳光总在风雨后  在  2008-6-12 14:34:55 

上一篇 下一篇
第八章 汇编语言支持


8.1 C 代码中内联汇编程序


一些对AVR 硬件的操作须遵守特定的时序,例如要禁止WatchDog,先同时置位WDTOE和WDE 后在四个时钟周期内清零WDE 才可实现。但在C 程序的置位WDE 操作是否在四个时钟内完成的,不够明确,所以类似的操作通常用直接的汇编程序完成。


avr-gcc 提供一种内联汇编机制,支持C 与汇编程序的关联编译。使上述问题的解决变得比C 和汇编程序单独编译链接更方便。


内联汇编声明
示例: asm("in %0,%1 " : "=r " (value) : "I" (_SFR_IO_ADDR(PORTD)));
如上例所示,嵌入的汇编由四部分组成:
(1)汇编指令本身,示例中用“in %0,%1”表示
(2)由逗号隔开的输出操作数列表 ,示例中为“=r”(value)
(3)由逗号隔开的输入操作数列表 ,示例中为"I" (_SFR_IO_ADDR(PORTD))
(4)Clobber 寄存器指示,示例中为空


本例实现的是读取PORTD 寄存器到C 变量value 中,由于I/O 寄存器常量是按I/O 寄存器在内部存储器中的统一地址定义的,所以要用宏_SFR_IO_ADDR 来将它转换成I/O 寄存器相对地址供汇编指令使用。


汇编指令中用符号“%”后加索引值来引用输入输出操作数列表中的操作数,%0 引用第一个操作数,%1 引用第二个操作数,依次类推。在本例中:
%0 引用 "=r " (value)
%1 引用"I" (_SFR_IO_ADDR(PORTD))。
内联汇编声明格式:
asm(code:output operand list : input operand list [: clobber list])如上声明格式,四部分之间用符号 “:”隔开,Clobber 寄存器指示部分空时可以忽略,其它部分可空但不可忽略。例如:
asm volatile(“cli” : : );
如果在后续的C 代码中没有使用到汇编代码中使用的变量,则在编译器的优化操作将这指令省略,为了防止这种情况的发生,需要在内联声明中指定volatile 属性,如:
asm volatile("in %0,%1 " : "=r " (value) : "I" (_SFR_IO_ADDR(PORTD)));volatile 关键字强制编译器不论在后续代码是否出现变量value 此操作执行代码作都要生成。


汇编指令


(1)在引用操作数时用百分号后的字母(A,B ?)代表操作数第几字节(A 为低字节),随后的数字代表第几个操作数,下面为一个32 位数据交换高低16 位的例程:
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)
)
如下表 %A0,%B0,%C0,%D0 分别代表0 号变量四个字节
31??????24 23??????16 15??????8 7???????0 value
%D0 %C0 %B0 %A0 引用


(2)在一次内联声明中的汇编指令部分可包含多个汇编指令,如:
asm volatile("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
::);
要注意的是用换行和TAB 字符(“\n\t”)将它们隔开。


(3)当操作数为常数时可直接写入到汇编指部分,例如:
asm volatile("in %0,%1 " : "=r " (value) : "I" (_SFR_IO_ADDR(PORTB)));
等同于
asm volatile("in %0,0X18 " : "=r " (value) :);
但如果写成
asm volatile("in %0, _SFR_IO_ADDR(PORTB)" : "=r " (value) :);
编译器就会报告错误,由于汇编指令部分是按字符串形式给出,C 预处理器不处理字符串内容,在传递到汇编器时预处理宏_SFR_IOADDR(PORTB)没有被正确的值替换,所以导致了错误。因此在汇编指令部分是不可使用C 预处理宏定义的。


尽管如此,avr-gcc 还是提供了一些特殊寄存器的表达符号,在汇编指令部分里它们总是可以直接书写,表8-1 列出了这些符号和对应寄存器。


表8-1 专用寄存器定义


符号 寄存器
__SREG__ 状态寄存器 SREG(0X3F)
__SP_H__ 堆栈指针高字节(0X3E)
__SP_L__ 堆栈指针低字节 (0X3E)
__tmp_reg__ R0
__zero_reg__ R1,对C 代码来说其值永远为0


R0 被C 编译器看作是临时寄存器,在汇编中引用R0 后无需恢复其原来的内容。
输入/输出操作数
输入/输出操作数列表是个C 表达式列表,用约束符的方式通知编译器操作数属性,例如在以下代码中:
asm("in %0,%1 " : "=r " (value) : "I" (_SFR_IO_ADDR(PORTD)));
“=r”说明了表达式value 结果要分配到r0~r31 中任意一个寄存器中处理。这样编译器会根据这一描述并关联其它程序分配一合适的寄存器给value;


表8-2 列出了约束符及其意义


表8-2 操作作数约束符




操作作数约束符1


操作作数约束符2


注:输出操作数必须为只写操作数, 输入操作数为只读,c 表达式结果是左置的。


表8-3 祥细列出了AVR 汇编助记符和对应操作数所应使用的约束符


表8-3 AVR 指令约束符对应表


助 记 符 约 束 符 助 记 符 约 束 符
adc r,r add r,r
adiw w,I and r,r
andi d,M asr r
bclr I bld r,I
brbc I,label brbs I,label
bset I bst r,I
cbi I,I cbr d,I
com r cp r,r
cpc r,r cpi d,M
cpse r,r dec r
elpm t,z eor r,r
in r,I inc r
ld r,e ldd r,b
ldi d,M lds r,label
lpm t,z lsl r
lsr r mov r,r
movw r,r mul r,r
neg r or r,r
ori d,M out I,r
pop r push r
rol r ror r
sbc r,r sbci d,M
sbi I,I sbic I,I
sbiw w,I sbr d,M
sbrc r,I sbrs r,I
ser d st e,r
std b,r sts label,r
sub r,r subi d,M
swap r


在输入输出操作数使用同一个寄存器的情况下可用操作数索引作为约束符,表示操作数使用与指定索引操作数同一寄存器,如:
asm volatile(“SWAP %0” : “=r”(value) : “0”(value));
约束符“0” 告诉编译器使用与第一个操作数相同的寄存器作为输入寄存器,有时即使用户没有指定,编译器也有可能使用相同的寄存器作为输入/输出,为避免此类现象可以在输出操作增加修饰符 “&”。如下例:
asm volatile(“in %0,%1;
out %1,%2”
: “=&r”(input)
: “I”(port), “r”(output)
);
此例的目的是读入端口数据后给端口写入另一个数据. 修饰符 “ &”防止了input 和outpu 两个操作数被分配到同一个寄存器Clobber


Clobber 指示使编译器在调用汇编程序的前后分别保存和重新装入指定的寄存器内容。
如果指定的寄存器不仅一个要用逗号隔开。


如果用户在汇编代码里直接使用了没有作为操作数声明的寄存器,就需要在Clobber 里声明以通知编译器. 表8-4 列出了一个加一原子操作示例的它编译后生成的汇编代码。





表8-4 Clobber 示例


以下为考虑编译器优化的例程
{
uint8_t s;
asm volatile(
"in %0, __SREG__" "\n\t"
"cli" "\n\t"
"ld __tmp_reg__, %a1" "\n\t"
"inc __tmp_reg__" "\n\t"
"st %a1, __tmp_reg__" "\n\t"
"out __SREG__, %0" "\n\t"
: "=&r" (s)
: "e" (ptr)
);
}


程序是将ptr 指针指向RAM 字节加一的原子操作。由于编译器不知道汇编程序修改RAM数据,在执行汇编程序前此数据可能暂时保存到其它寄存器中,而汇编程序执行结束后编译器没有更新寄存器,导致可能的错误产生。为了避免此类错误的发生可在内联汇编程序中指定Clobber :“memory”,这将通知编译器汇编程序可能修改RAM,执行完代码后要重新装载与被修改RAM 相关的寄存器。


正确的代码应如下:
{
uint8_t s;
asm volatile(
"in %0, __SREG__" "\n\t"
"cli" "\n\t"
"ld __tmp_reg__, %a1" "\n\t"
"inc __tmp_reg__" "\n\t"
"st %a1, __tmp_reg__" "\n\t"
芯 艺 作 品
85
"out __SREG__, %0" "\n\t"
: "=&r" (s)
: "e" (ptr)
: "memory"
);
}
解决此类问题的别外一个更好的方法是将指针声明时指定volatile 属性,如下所示:
volatile uint8_t *ptr
这样,一旦指针指向的变量发生变化,编译器就会重新加载最新的数值。
Clobber 的更多应用在直接使用寄存器上。Clobber 的指定示必会影响编译器优化过程,应当尽可能减少Clobber 指示。


汇编宏应用


将汇编程序定义成宏后写进头文件,这样可以方便的多次使用该段程序,avr-libc 的包含头文件里有很多这样的例子,可从WINAVR 安装目录avr/include 中找到很多示例。


为了避免编译器的警告定义汇编宏时要把asm 和volatile 分别替换成 __asm__ 和——__volatile__ , 实际相它们作用是相同的。
以下是全例子:
#define loop_until_bit_is_clear(port,bit) \
__asm__ __volatile__ ( \
"L_%=: " "sbic %0, %1" "\n\t" \
"rjmp L_%=" \
: \
: "I" (_SFR_IO_ADDR(port)), \
"I" (bit) \
)
正如上段程序中那样,在汇编中用到标号时可以通过在自义一个标号后加专用宏 %= 来告诉编译器预理程序产生一个唯一的标号。


8.2 独立的汇编语言支持


像at90s1200 这样的内部没有RAM 的AVR 器件avr-libc 只提供汇编语言支持,但与其它汇编器不同的是,avr-gcc 使用C 预处理器,从而使符号常量的定义与C 程序一样。另外,一个完整的汇编应用程序还要基于与C 程序一样的应用程序运行框架,既汇编程序不必考虑中断向量表、初始化堆栈等初始化配置过程。


由此可以看出,avr-libc 对汇编语言的支持不是完整的,却能很好的适应与C 程序的混合编程。


一.avr-libc 汇编程序示例


以下是在avr-libc 用户手册上的一个示例程序,它实现了从at90s1200 的PD6 口输出100KHz 方波。
; 系统时钟:10.7 MHz
#include ; 包含器件I/O 端口定义文件,要注意的是,并不是所
; 有avr-lib 提供的头文件都可以这样包含,如果我们
; 打开一个ioxxxx.h 就会发现内部都是符号定义,正
; 因为如此它才可以在C 和汇编中均可包含
work = 16 ; 为寄存器定义符号常量,可用#define work 16 替代
tmp = 17
inttmp = 19
intsav = 0
SQUARE = PD6
tmconst= 10700000 / 200000 ; 100 kHz => 200000 edges/s
fuzz= 8 ; # clocks in ISR until TCNT0 is set
.section .text ; 指定段
.global main ; 外部函数声明,与C 程序一样main 标号是应用程序
main: ; 入口函数, 必需将它声明成外部函数使它对于
; avr-libc 应用框架可见,应用框架初始化程序的最
; 后会跳转到这里
rcall ioinit
1:
rjmp 1b ; 主循环
.global SIG_OVERFLOW0 ; 定时器0 中断处理,与C 程序一样中断函数使用固定
;名称,这些名称在中已被定义。中断函数
; 必需声明成 .global
SIG_OVERFLOW0:
ldi inttmp, 256 - tmconst + fuzz
out _SFR_IO_ADDR(TCNT0), inttmp
in intsav, _SFR_IO_ADDR(SREG)
sbic _SFR_IO_ADDR(PORTD), SQUARE
rjmp 1f
sbi _SFR_IO_ADDR(PORTD), SQUARE
rjmp 2f
1: cbi _SFR_IO_ADDR(PORTD), SQUARE
2:
out _SFR_IO_ADDR(SREG), intsav
reti
ioinit:
sbi _SFR_IO_ADDR(DDRD), SQUARE
ldi work, _BV(TOIE0)
out _SFR_IO_ADDR(TIMSK), work
ldi work, _BV(CS00) ; tmr0: CK/1
out _SFR_IO_ADDR(TCCR0), work
ldi work, 256 - tmconst
out _SFR_IO_ADDR(TCNT0), work
sei
ret
.global __vector_default ; 程序中未指定中断的处理例程,与中断名称一样
; 名称__vector_default 是固定的,注意的是必
; 需将它声明成 .global,如果不重写此例程,
; 应用框架就会在此处插入一个跳转到0 地址的
;一条指令(即复位)
__vector_default:
reti
.end


编译:
编译时通常不会直接调用汇编器(avr-as),而是通过给avr-gcc 通用命令接口(命令avr-gcc)设置选项的方式将程序传递到汇编器中。如:
avr-gcc –mmcu=at90s1200 –x assembler-with-cpp -o target.elf target.s
选项 -x assembler-with-cpp 用于指定语言类型是汇编,target 为文件名。


这样做是因为:让avr-gcc 自动调用预处理程序和链接器最终生成可执行目标文件,预处理器用于解释在汇编程序中定义的常量符号,avr-gcc 调用链接器时会将器件对应的应用程序框架链接进来。


8.3 C 与汇编混合编程


C 编译器如何使用寄存器


(1)r0 和r1 :在C 程序中这两个寄存器用于固定目的,从不分配局部变量。


r0 是暂存寄存器,在汇编程序中可以自由的使用,若在汇编程序中使用了r0 并且要调用C 函数则必需在调用C 函数前要保存它。若在中断函数中使用此寄存器要在使用前保存,和退出时恢复,C 中断函数会自动保存的恢复r0。


r1 是零寄存器,C 编译器总认为其内容为零。如果汇编程序要使用这个寄存器,则在汇编代码返回之前要清零它。中断函数要使用r1 则必需在退出时将其清零,C 中断函数会自动保存的恢复r1。


(2)r2-r17、r28-r29:


局部变量分配寄存器,汇编程序若改变了这些寄存器内容,则必需恢复其内容。


(3)r18-r27、r30-r31


局部变量分配寄存器,汇编程序可自由的使用这此寄存器,无需恢复,但若在汇编程序中使用了这些寄存并要调用C 函数,则需在调用前保护所用的寄存器。


函数调用规则


参数传递:函数参数按从左至右的规则分别被分配到r25 到r18。毎个参数从偶数编号寄存器开始分配低字节,若参数长度为寄数则高编号寄存器闲置。如:一个char 类型的参数传递时分配给r24,这时r25 被闲置,尽管后边还有参数要传递也不会使用r25。如下表为函数 void test(unsigned char parbyte,unsigned int parword)的参数分配情况:


R25 R24 R23 R22
闲置 parbyte parword 高字节 parword 低字节


另外,如果参数太多以至于r25 到r8 无法容纳,则剩余的部分从堆栈传递。


返回值传递:与参数分配类似,8 位返回值存放到r24、16 位值分保存到r25:r24、32
位值保存到r25:r24:r23:r22、64 位值保存到r25:r24:r23:r22:r21:r20:r19:r18。


在默认情况下C 和汇编程序使用同样的函数名,但可以在C 程序中给函数指定在汇编里
调用名,如:
extern long Calc(void) asm ("CALCULATE");
声明了函数Calc,在汇编中用符号CALCULATE 调用。
例一. 在C 程序中调用汇编函数
C 源文件main.c
/*
cpu:atmega8
时钟:4MHz
*/
#include
#include
#define uchar unsigned char
#define SET_GRN_LED PORTC&=0XFD //PC1 接绿色发光管
#define CLR_GRN_LED PORTC|=0X02
//声明两个汇编函数
void set_grn_led(void);
void clr_grn_led(void);
void DelayMs(unsigned int t)
{
unsigned int i;
for(i=0;i_delay_loop_2(4*250);
}
int main(void)
{
//LED 口初始化
DDRC=0X0F;
PORTC=0X0F;
while(1)
{
DelayMs(1000);
set_grn_led();
DelayMs(1000);
clr_grn_led();
}
}
汇编源文件asm.s
#include
.section .text
.global set_grn_led ;外部函数声明
set_grn_led: ;点亮绿发光管
cbi _SFR_IO_ADDR(PORTC),1
ret
.global clr_grn_led
clr_grn_led: ;熄灭绿发光管
sbi _SFR_IO_ADDR(PORTC),1
ret
程序实现接在PC1 口的发光管闪烁


例二. 在汇编程序中调用C 函数
C 源文件main.c
/*
cpu:atmega8
时钟:4MHz
*/
#include
#include
#define uchar unsigned char
#define SET_GRN_LED PORTC&=0XFD //PC1 接绿色发光管
#define CLR_GRN_LED PORTC|=0X02
void DelayMs(unsigned int t) //延时一ms
{
unsigned int i;
for(i=0;i_delay_loop_2(4*250);
}
汇编源文件asm.s
#include
.extern DelayMs ;外部C 函数声明
.section .text
.global main
main:
;i/o 初始化
ldi r25 , 0x0f
out _SFR_IO_ADDR(DDRC),r25
LOOP:
ldi r25,1
ldi r24,0xf4
rcall DelayMs ;DealyMs(500);
cbi _SFR_IO_ADDR(PORTC),1
ldi r25 ,1
ldi r24,0xf4
rcall DelayMs ;DelayMs(500);
sbi _SFR_IO_ADDR(PORTC),1
rjmp LOOP
程序实现接在PC1 口的发光管每一秒闪烁一次



单片机教程,五系列(55讲)电子书全集下载

论坛精选:
单片机教程,MCS51单片机从零开始 第一讲:初识单片机
http://bbs.huazhoucn.com/Topic.aspx?id=2539
单片机教程,MCS51单片机从零开始 第二讲:MCS-51单片机简述
http://bbs.huazhoucn.com/Topic.aspx?id=2540
单片机教程,MCS51单片机从零开始 第三讲:单片机相关常用名词解释
http://bbs.huazhoucn.com/Topic.aspx?id=2541
单片机教程,MCS51单片机从零开始 第四讲:计算机中数的表示及运算
http://bbs.huazhoucn.com/Topic.aspx?id=2542
单片机教程,MCS51单片机从零开始 第五讲:常用逻辑电路
http://bbs.huazhoucn.com/Topic.aspx?id=2544
单片机教程,MCS51单片机从零开始 第六讲:51单片机的结构及其组成
http://bbs.huazhoucn.com/Topic.aspx?id=2545
单片机教程,MCS51单片机从零开始 第七讲:51单片机的引脚
http://bbs.huazhoucn.com/Topic.aspx?id=2546
单片机教程,MCS51单片机从零开始 第八讲:8051单片机I/O引脚工作原理
http://bbs.huazhoucn.com/Topic.aspx?id=2547
单片机教程,MCS51单片机从零开始 第九讲:8051单片机的存储器结构
http://bbs.huazhoucn.com/Topic.aspx?id=2548
单片机教程,MCS51单片机从零开始 第十讲:编码及译码器工作原理
http://bbs.huazhoucn.com/Topic.aspx?id=2549
单片机教程,MCS51单片机从零开始 第十一讲:存储器的存储原理
http://bbs.huazhoucn.com/Topic.aspx?id=2550
单片机教程,MCS51单片机从零开始 第十二讲:51单片机的特殊功能寄存器
http://bbs.huazhoucn.com/Topic.aspx?id=2551
单片机教程,MCS51单片机从零开始 第十三讲:51单片机CPU的内部结构
http://bbs.huazhoucn.com/Topic.aspx?id=2552
单片机教程,MCS51单片机从零开始 第十四讲:定时器/计数器的基本结构及工作原理
http://bbs.huazhoucn.com/Topic.aspx?id=2553
单片机教程,MCS51单片机从零开始 第十五讲:51单片机的中断系统
http://bbs.huazhoucn.com/Topic.aspx?id=2554
单片机教程,MCS51单片机从零开始 第十六讲:51单片机的复位
http://bbs.huazhoucn.com/Topic.aspx?id=2555
单片机教程,MCS51单片机从零开始 第十七讲:51单片机执行指令的过程
http://bbs.huazhoucn.com/Topic.aspx?id=2556
单片机教程,MCS51单片机从零开始 第十八讲:51单片机的延时及时序分析
http://bbs.huazhoucn.com/Topic.aspx?id=2557
单片机教程,MCS51单片机从零开始 第十九讲:汇编语言基础
http://bbs.huazhoucn.com/Topic.aspx?id=2558
单片机教程,MCS51单片机从零开始 第二十讲:汇编语言及汇编过程
http://bbs.huazhoucn.com/Topic.aspx?id=2556
单片机教程,MCS51单片机从零开始 第二十一讲:汇编程序的基本结构
http://bbs.huazhoucn.com/Topic.aspx?id=2560
单片机教程,MCS51单片机从零开始 第二十二讲:51单片机的寻址方式
http://bbs.huazhoucn.com/Topic.aspx?id=2561
单片机教程,MCS51单片机从零开始 第二十三讲:数据传送类指令分析
http://bbs.huazhoucn.com/Topic.aspx?id=2562
单片机教程,MCS51单片机从零开始 第二十四讲:算术运算类指令分析
http://bbs.huazhoucn.com/Topic.aspx?id=2563
单片机教程,MCS51单片机从零开始 第二十五讲:逻辑运算及移位指令分析
http://bbs.huazhoucn.com/Topic.aspx?id=2564
单片机教程,MCS51单片机从零开始 第二十六讲:控制转移类指令分析
http://bbs.huazhoucn.com/Topic.aspx?id=2565
单片机教程,MCS51单片机从零开始 第二十七讲:布尔变量操作指令分析
http://bbs.huazhoucn.com/Topic.aspx?id=2566
单片机教程,MCS51单片机从零开始 第二十八讲:伪指令分析
http://bbs.huazhoucn.com/Topic.aspx?id=2567

1 楼  提交者:耕牛 在 2008-6-13 9:25:18
hao
2 楼  提交者:Guest 在 2008-6-18 15:47:28
OK!
3 楼  提交者:Guest 在 2008-6-23 17:14:06
kan kan
4 楼  提交者:Guest 在 2008-7-10 20:43:56
5 楼  提交者:Guest 在 2008-7-11 19:27:28
不好会后悔的
6 楼  提交者:Guest 在 2008-7-13 14:19:44
7 楼  提交者:Guest 在 2008-7-17 9:55:13
wefuiwhsdadsddf
8 楼  提交者:Guest 在 2008-7-17 11:41:05
vb
9 楼  提交者:Guest 在 2008-7-17 21:00:59
kan
10 楼  提交者:Guest 在 2008-7-21 15:59:47
层v
11 楼  提交者:Guest 在 2008-7-23 18:00:43
see
12 楼  提交者:Guest 在 2008-7-26 9:29:25
好的
13 楼  提交者:Guest 在 2008-7-28 13:53:23
不错的资料  
14 楼  提交者:fightsqlee 在 2008-7-30 9:05:14
还想看看
15 楼  提交者:Guest 在 2008-8-6 8:54:56
_BV
16 楼  提交者:Guest 在 2008-8-6 16:18:52
多谢。
17 楼  提交者:Guest 在 2008-8-6 21:31:11
hao
18 楼  提交者:Guest 在 2008-8-7 10:59:48
谢谢
19 楼  提交者:qsjit 在 2008-8-7 20:04:45
20 楼  提交者:Guest 在 2008-8-14 23:29:19
fgdsfgdfgdfgdfg
21 楼  提交者:Guest 在 2008-8-16 21:55:07
kan
22 楼  提交者:Guest 在 2008-8-20 13:13:57
看看
23 楼  提交者:Guest 在 2008-8-21 16:03:43
学习
24 楼  提交者:Guest 在 2008-8-29 20:28:40
tsyg
25 楼  提交者:Guest 在 2008-8-29 20:30:00
26 楼  提交者:Guest 在 2008-8-29 20:31:51
27 楼  提交者:Guest 在 2008-8-29 20:38:47
无内容
28 楼  提交者:fuckccp 在 2008-8-29 21:29:01
29 楼  提交者:Guest 在 2008-8-29 22:28:36
不好会后悔的
30 楼  提交者:要飞的鸟 在 2008-8-31 8:17:06
上一篇 下一篇
当前第〖1〗页 共有6页 转到第 1 2 3 4 5 6