一、题目要求
修改代码,要求将原汇编语言 Init_1函数的类型改为 int Init_1(init) ,此函数功能修改为传入一个整型数x,函数运行后返回整型数 x+100。 请编程实现,并仿真跟踪调试; 如果要求在汇编函数中调用一个 C语言写的函数,应该如何修改汇编代码?
二、项目创建
打开Keil ARM,Project ->New μVision Project ->选择STM32F103ZE
添加main.c 和Func.s 文件
Func.s
AREA MY_FUNCTION,CODE,READONLY
EXPORT Init_1 ; 与在c文件中定义的Init_1函数关联起来
; 高级语言中的声明和使用变量其实是对板子寄存器的使用,所以我们只需要直接使用寄存器即可
Init_1
MOV R1,
MOV R2,
LOOP ; 写在最左边的是程序段的段名,执行跳转程序时用到
CMP R1,
BHS LOOP_END ; 如果R1大于等于10,则跳转到LOOP_END程序段,反之忽略该语句,直接执行下面的语句
ADD R2,
ADD R1,
B LOOP ; 循环
LOOP_END
NOP
END ; 必须空格后再写END,不然会被认为是段名,表示程序结束
main.c
# include<stdio.h>
extern void Init_1(void);
int main(){
Init_1();
return 0;
}
三、C语言调用汇编-无参数调用
1、仿真设置
打开魔法棒 ,选择Debug ,勾选Use Simulator 将左侧的Dialog DLL 中的内容改为DARMSTM.DLL ,将Parameter 的内容改为-pSTM32F103C8
2、设置断点
在目标行前侧点击,生成小红点,即完成断点设置
3、编译并调试
编译build 或rebuild 后,进行调试 在左上角点击step 或step over 或Run To Cursor Line 观察R1、R2的寄存器值的变化 可以发现其由0逐步加到10
四、C语言调用汇编语言-有参数调用
1、修改 三 中代码
Func.s
AREA MY_Function,CODE,READONLY
EXPORT Init_1 ; 与在c文件中定义的Init_1函数关联起来
; 高级语言中的声明和使用变量其实是对板子寄存器的使用,所以我们只需要直接使用寄存器即可
Init_1
ADD R0,
MOV PC,LR ; 返回R0
LOOP ; 写在最左边的是程序段的段名,执行跳转程序时用到
CMP R1,
BHS LOOP_END ; 如果R1大于等于10,则跳转到LOOP_END程序段,反之忽略该语句,直接执行下面的语句
ADD R2,
ADD R1,
B LOOP ; 循环
LOOP_END
NOP
END ; 必须空格后再写END,不然会被认为是段名,表示程序结束
main.c
# include<stdio.h>
extern int Init_1(int x);
int main(){
int xx = Init_1(10);
printf("%d", xx);
return 0;
}
在Keil中,子函数的参数值传递按顺序存放到了R0 、R1 、R2 、R3 中,超过四个参数值传递放栈帧里。
由此,Init_1(10) 传入的10放到了R0 中,由MOV PC,LR 返回110.
2、设置断点
3、编译并调试
操作同上 此时值为6E 即110 ,调用成功!
五、汇编语言调用C语言函数
1、修改 四 中代码
Func.s
AREA MY_Function,CODE,READONLY
EXPORT Init_1 ; 与在c文件中定义的Init_1函数关联起来
IMPORT get5 ; 声明get5 为外部引用
; 高级语言中的声明和使用变量其实是对板子寄存器的使用,所以我们只需要直接使用寄存器即可
Init_1
MOV R1,
MOV R2,
LOOP ; 写在最左边的是程序段的段名,执行跳转程序时用到
CMP R1,
BHS LOOP_END ; 如果R1大于等于10,则跳转到LOOP_END程序段,反之忽略该语句,直接执行下面的语句
ADD R2,
ADD R1,
BL get5 ; 调用get5,返回的值传入R0
B LOOP ; 循环
LOOP_END
NOP
END ; 必须空格后再写END,不然会被认为是段名,表示程序结束
main.c
# include<stdio.h>
extern void Init_1(void);
int get5(void);
int main(){
printf("Begin...\n");
Init_1();
return 0;
}
int get5(){
return 5;
}
2、设置断点
3、编译并调试
执行get5后,R0变为了5,调用成功!
六、其他
1、C与汇编之间函数调用ATPCS简介
- ARM-Thumb 过程调用标准 ATPCS(ARM-Thumb Procedure Call Standard)
- ATPCS 标准既是ARM 编译器使用的函数调用规则,也是设计可被 C 程序调用的汇编函数的编写规则
- ATPCS 规定,ARM 的数据堆栈为 FD 型堆栈,即满递减堆栈
- 函数是通过寄存器和堆栈来传递参数和返回函数值的,形参和返回值都应定义在具有暂存性质的寄存器和堆栈中
2、参数如何传递
汇编程序调用C函数时,函数的入口参数使用栈来传送,参数的传递顺序是从右到左。即函数最后(最右边的)一个参数先入栈,而最左边的第一个参数最后入栈,然后执行 CALL 指令去调用C函数。
3、参数的清除
在C函数返回后,汇编程序需要把先前压入栈中的函数参数清除掉,即调用者负责清除参数占用的栈空间。
4、C语言与汇编语言混合程序
在C程序中内联或嵌入式汇编代码,以提高程序的效率
内联汇编
- 在 C 程序中直接编写汇编程序段而形成一个语句块,这个语句块可以使用除了 BX 和 BLX之外的全部ARM指令来编写
- 可以使程序实现一些不能从C获得的底层功能
- 汇编语句块中,如果有两条指令占据了同一行,那么必须用分号“ ;”将它们分隔
- 如果一条指令需要占用多行,那么必须用反斜线符号“ \ ”作为续行符
- 可以在内联汇编语言块内的任意位置使用C/C++格式的注释
- 内联汇编代码中定义的标号可被用作跳转或C/C++ goto 语句的目标,同样,在C/C++代码中定义的标号,也可被用作内联汇编代码跳转指令的目标
- 在内联汇编语句块中最好使用 C 或 C++ 变量作为操作数
- 状态寄存器 PSR ,任何对 PSR 的引用总是执行指向物理 PSR
void enable_IRQ(void) { int tmp; _ _asm //声名内联汇编代码 { MRS tmp, CPSR BIC tmp, tmp, #0x80 MSR CPSR_c, tmp } }
- 内联汇编的限制
- 它不支持 Thumb 指令;除了程序状态寄存器 PSR 之外,不能直接访问其他任何物理寄存器
- 如果在内联汇编程序指令中出现了以某个寄存器名称命名的操作数,那么它被叫做虚拟寄存器,而不是实际的物理寄存器。编译器在生成和优化代码的过程中,会给每个虚拟寄存器分配实际的物理寄存器,但这个物理寄存器可能与在指令中指定的不同。
- 在内联汇编代码中不能使用寄存器 PC(R15)、LR(R14)和SP(R13)
- 处理器模式会禁止使用 C 操作数或对已编译 C 代码的调用,直到将处理器模式恢复为原设置之后
嵌入式汇编
- 嵌入式汇编程序是一个编写在C程序外的单独汇编程序,该程序段可以像函数那样被 C 程序调用
- 嵌入式汇编具有真实汇编的所有特性,数据交换符合 ATPCS 标准,同时支持 ARM 和Thumb,所以它可以对目标处理器进行不受限制的低级访问
- 不能直接引用 C/C++ 的变量
- 参数名只允许使用在参数列表中,不能用在嵌入式汇编函数体内
- 在 C 程序中调用嵌入式汇编程序的方法与调用 C 函数的方法相同
定义一个嵌入式汇编函数的语法格式为: _ _asm return–type function–name(parameter-list) { 汇编程序段 }
return–type:函数返回值类型,C语言中的数据类型 function–name:函数名 parameter-list:函数参数列表
项目 | 内联汇编 | 嵌入式汇编 |
---|
指令集 | 仅限于ARM | ARM和Thumb | ARM汇编程序命令 | 不支持 | 支持 | C表达式 | 支持 | 仅支持常量表达式 | 优化代码 | 支持 | 不支持 | 内联 | 可能 | 从不 | 寄存器访问 | 不使用物理寄存器 | 使用物理寄存器 | 返回指令 | 自动生成 | 显式编写 |
七、总结
ARM在C语言中调用汇编函数的方法 在C中调用汇编文件中的函数,要做的主要工作有两个: 一是在C中声明函数原型,并加extern 关键字; 二是在汇编中用EXPORT导出函数名,并用该函数名作为汇编代码段的标识,最后用mov pc, lr 返回。然后,就可以在C中使用该函数了。
参考: [1] https://blog.csdn.net/longintchar/article/details/79511747 [2] https://blog.csdn.net/sinat_27421407/article/details/78829508
|