Chinaunix首页 | 论坛 | 博客
  • 博客访问: 652325
  • 博文数量: 128
  • 博客积分: 4385
  • 博客等级: 上校
  • 技术积分: 1546
  • 用 户 组: 普通用户
  • 注册时间: 2010-07-22 14:05
文章分类

全部博文(128)

文章存档

2012年(2)

2011年(51)

2010年(75)

分类: 嵌入式

2010-11-11 10:21:10

一. 在线汇编( in-line assembly)
    在C代码中插入一段汇编代码,比如
    while(1)
    {
        ...
        port=0x00aa55ff;
        __asm
        {
            mov r4,port
            str r5,[r4] 
        }
        ...
    }

    具体格式为
    __asm
    {
        指令1;指令2;指令3;...
        指令n;指令n+1;...
        ...
    }
    其中指令的操作数可以是物理寄存器,也可以是C语言的变量、行号和
    表达式,这点是比纯汇编最大的的好处(窃以为如斯,比如上面的例子)

    说到这其实写在线汇编代码的思路就和纯汇编差不多了,但要注意几个问题:

1.不能直接向PC赋值实现程序跳转,只能使用B和BL这两条指令
2.在线汇编器会使用R0-R3,R12-R13计算表达式,因此复杂的表达式不能和以上寄
存器出现在同一条指令中:
  mov r0,一个复杂的表达式     ----汇编器可能报错
  mov r5,一个复杂的表达式     ----汇编可以通过
3.在线汇编中使用物理寄存器时,如果上下文的C代码也使用了同一个,则不必自己
保存和恢复该物理寄存器,C代码会自己负责处理

4.尽量不使用物理寄存器,因为
  a.影响代码效率,很有可能编译器为此把一个本应放在寄存器中的变量放在存储
器中.就如同在C中定义局部变量哪个是register的,哪个是volatile的,
编译器自己的优化才是最佳的。
  b.发生错误:
    比如
        ...
        mov r0,#0xaa
        mov r1,x%y
        mov r2,r0
        ...
     很可能r2的值不是0xaa,因为在计算表达式x%y时会用到r0,将r0的内容冲掉
5.可以使用b指令
  b 标号;         实现跳转,标号可以时C的也可以是汇编的
6.因为要计算表达式的值,所以一条虚拟指令要被展开为若干条真实指令(如上面
的mov r1,x%y)
  因此实际上对标志为C.N.V.Z的影响是展开后最后一条作用的结果,但
  如果总指令(就是你程序中写的那条虚拟指令)是算术指令,那么能正确设置cv
nz,  如果是逻辑指令,那么N.z是正确的,V不影响,C不可靠
7.不支持除nop之外的其它伪指令(ADR,ADRL,LDR)
8.不支持BX和BLX指令
9.LDM和STM指令的寄存器列表只允许物理寄存器,不能用变量和表达式
10.逗号表达式要用一个括号扩起来,使其为一个整体
   add x,y,(f(),z)
11.不要干预堆栈

    当然还有更复杂的规则,比如子程序调用(BL指令),但我觉得过于繁琐,
失去了在线汇编这种简便快捷的风格,如果以上介绍的不能满足你的要求,我建议
使用ATPCS规则。
ATPCS规则体现了一种模块化设计的思想,其基本内容是C模块(函数)
和汇编模块(函数)相互调用的一套规则(C51中也有类似的一套规则)。
我感觉比在线汇编功能强大(不用有很多忌讳),条理更清楚(很简单的几条
规则)。

规则内容

一.被调用模块的寄存器使用
   1.调用模块和被调用模块通过R0-R3传递参数,因此参数少于四个时可以随意
     使用剩余的而不必保存和恢复
   2.使用R4-R11之前一定要先在堆栈中保存起来,退出时再恢复
   3.可以使用堆栈,但一定要保证堆栈指针(R13)在进入时和退出时相等
   4.R14用于保存返回地址,使用前一定要备份
二.被调用模块的堆栈使用
   ATPCS规则规定堆栈是满递减(fD)型的,因此使用STMFD/LDMFD指令操作,
   注意保证进入和退出时堆栈指针相等
三.参数传递
   当少于四个时,按从左到右的顺序依次放在r0,r1,r2,r3中;
   当多于四个时,前四个放在r0,r1,r2,r3中,剩余的放在堆栈中,最后一个
   参数先入栈,第五个参数最后入栈,即从右到左入栈
四.返回值
   结果为32位时,通过R0返回
   结果为64位时,r0放低32位,r1放高32位

如果不涉及Thumb指令集,全部规则就这几条,
比较有条理,很清楚,我举两个例子:

1.C主程序调用汇编子程序

C主程序:

#define UINT unsigned int
extern UINT add_six(UINT a,UINT b,UINT c,UINT d,UINT e,UINT f);
int main(void)
{
    add(1,2,3,4,5,6);
    return 0;
}

汇编子程序:

    area cdo,code,readonly  ;段名cdo,代码段
    code32          ;ARM指令
add_six
    global add_six      ;add_six是全局标号
    stmfd r13,{r4,r5}       ;将r4,r5压入堆栈,但R13不变
    ldr r4,[r13]        ;将第五个参数从堆栈中提出
    ldr r5,[r13,#4]     ;将第六个参数从堆栈中提出
    add r0,r0,r1
    add r0,r0,r2
    add r0,r0,r3
    add r0,r0,r4
    add r0,r0,r5        ;32位结果保存在R0中
    sub r3,r13,#8
    ldmfd r3,{r4,r5}        ;从堆栈中恢复r4,r5
    mov r15,r14         ;返回主程序
    end

2.汇编主程序调用C子程序

汇编主程序

    area main,code,readonly ;代码段
    entry           ;声明程序入口
    code32          ;32位ARM指令
    extern add_six      ;声明标号add_six
start
    mov r13,#0xa000     ;初始化堆栈指针
    mov r0,#1
    mov r1,#2
    mov r2,#3
    mov r3,#4           ;前四个参数通过寄存器传递
    mov r4,$5
    mov r5,#6
    stmfd r13!,[r4,r5]      ;后两个参数通过堆栈传递
    bl add_six          
    mov r1,r0           ;调用后结果将放在r0中
    end


C子程序:
#define UINT unsigned int
UINT add_six(UINT a,UINT b,UINT c,UINT d,UINT e,UINT f)
{
    return a+b+c+d+e+f;
}

以上代码均在ARM ADS 1.2中调试通过。

    总结一下,在线汇编比较简洁,但功能有限,而且规则有点零碎;
ATPCS功能强大而且条理清楚,但需要单独在开一个模块;
写代码时可以根据需要自由选择.
阅读(3450) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~