PersonalCorpus 版 (精华区)
发信人: Marigold (SARS燃烧的岁月), 信区: Asm
标 题: 在C代码中嵌入汇编指令
发信站: 哈工大紫丁香 (2003年04月24日09:29:05 星期四), 站内信件
在C代码中嵌入汇编指令
--转贴自21IC中国电子网AVR单片机论坛
hudaidai 发表于 6/27/2002 5:58:22 PM
(此文和《如何使用AVRGCC》原来在一起,太长了贴不上。经过我的重新编排以便于初
学者参考,在我下载的地方已经原作者不可考了。功劳属于原作者,修改编排后产生的
错误由hudaidai负责)
在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~r1
5,则会由于不适用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;
这样,一旦指针指向的变量发生变化,编译器就会重新加载最新的数值。
附录:AVR-GCC配置
汇编选项
选项 描述
-mmcu=name 指定目标器件name可以为:at90s1200,at90s2313,at90s2323,at90s233
3,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。
返回技术文档
--
夜中不能寐,起坐弹鸣琴。
薄帷鉴明月,清风吹我襟。
孤鸿号外野,翔鸟鸣北林。
徘徊将何见?忧思独伤心。
※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: 202.118.239.17]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:2.436毫秒