在GPIO的实验中,我们首先编写汇编程序操作寄存器点亮LED,奈何汇编语言可读性和可移植性太差,所以编写启动代码,设置栈顶指针SP,然后调用C语言中的main函数,转入C语言的世界,由C语言访问控制寄存器,点亮LED,程序的可读性和可移植性大大提高,那么,我们可曾想过,在汇编语言中是如何来调用C语言入口函数main
呢?
其实,对于ARM处理器,在ARM指令集汇编程序和THUMB指令集汇编程序中制定了子程序调用的规则 —— ATPCS规则,这个规则包括:
- 寄存器使用规则
- 数据栈使用规则
- 参数传递规则
1.寄存器使用规则
ARM处理器中有R0-R15这16个寄存器,每个寄存器都由ATPCS规则规定了用途,并根据其用途规定了别名,如下表所示:
寄存器 | 别名 | 用途 |
---|---|---|
R15 | pc | 程序计数器 |
R14 | lr | 链接寄存器(用于保存子程序返回地址) |
R13 | sp | 数据栈指针(指向栈顶) |
R12 | ip | 子程序内部调用的scratch寄存器 |
R11 | v8 | ARM状态局部变量寄存器8 |
R10 | v7 | ARM状态局部变量寄存器7 |
R9 | v6 | ARM状态局部变量寄存器6 |
R8 | v5 | ARM状态局部变量寄存器5 |
R7 | v4 | ARM状态局部变量寄存器4 |
R6 | v3 | ARM状态局部变量寄存器3 |
R5 | v2 | ARM状态局部变量寄存器2 |
R4 | v1 | ARM状态局部变量寄存器1 |
R3 | a4 | 参数/结果/scratch寄存器4 |
R2 | a3 | 参数/结果/scratch寄存器3 |
R1 | a2 | 参数/结果/scratch寄存器2 |
R0 | a1 | 参数/结果/scratch寄存器1 |
总结如下:
- 子程序间通过寄存器R0-R3传递参数和返回结果;
- 子程序中通过寄存器R4-R11保存局部变量;
- 寄存器R12用作子程序间scratch寄存器;
- 寄存器R13用作数据栈指针,指向栈顶;
- 寄存器R14用作链接寄存器,保存子程序的返回地址;
- 寄存器R15用作程序计数器;
2. 数据栈使用规则
ATPCS规定数据栈为FD类型(Full Descending,满递减),即栈指针指向栈顶元素,并且向内存地址减小的方向增长,操作的时候对数据栈的操作是8字节对齐的,使用stmdb/ldmia
批量内存访问指令来操作FD数据栈。
FD类型的数据栈具体是这样操作的:
- 保存内容时先递减SP指针,再保存数据;
- 恢复数据时先获得数据,再递增SP指针;
3.参数传递规则
- 函数调用传递参数时,如果不超过4个,使用R0-R3依次传递,如果超过4个,剩余的参数通过数据栈传递;
- 函数返回传递结果时,使用R0-R3依次传递;
实验 —— 汇编调用函数时传递参数实验
1. 实验目的
在汇编语言中调用函数并且传递参数。
2. 实验内容
main函数定义参数,如果传入参数是1,点亮第一个LED,如果传入参数是2,点亮第二个LED。
3. 实验代码
3.1.启动代码
代码语言:javascript复制@ brief: S3C2440启动文件
@ author: mculover666
.text
.global _start
_start:
@ 关闭看门狗
LDR R0,=0x53000000
MOV R1,#0
STR R1,[R0]
@ 设置栈顶指针SP(从Nand启动)
LDR SP,=4096
@ 传递参数1调用led_on,点亮第一个LED
LDR R0,=1
BL led_on
@ 传递参数100000,调用delay,延时
LDR R0,=100000
BL delay
@ 传递参数2调用led_on,点亮第二个LED
LDR R0,=2
BL led_on
@ 程序暂停
halt:
B halt
3.2.C代码
代码语言:javascript复制void delay(volatile int xms)
{
while(xms--);
}
int led_on(int led)
{
if(led == 1)
{
/* 设置GPFCON寄存器,配置GPF4引脚为输出模式 */
*(unsigned int *)0x56000050 &= ~(3<<(2*4));
*(unsigned int *)0x56000050 |= 1<<(2*4);
/* 设置GPFDAT寄存器,GPF4输出低电平,点亮LED */
*(unsigned int *)0x56000054 &= ~(1<<4);
}
else if(led == 2)
{
/* 设置GPFCON寄存器,配置GPF4引脚为输出模式 */
*(unsigned int *)0x56000050 &= ~(3<<(2*5));
*(unsigned int *)0x56000050 |= 1<<(2*5);
/* 设置GPFDAT寄存器,GPF4输出低电平,点亮LED */
*(unsigned int *)0x56000054 &= ~(1<<5);
}
return 0;
}
3.3.编译
代码语言:javascript复制TARGET = led_blink
CFLAGS = -Wall #输出所有warning
$(TARGET).bin:$(TARGET).elf
arm-linux-objcopy -O binary -S $(TARGET).elf $(TARGET).bin
#注意:启动文件必须第一个链接
$(TARGET).elf:start.o $(TARGET).o
arm-linux-ld -Ttext 0 start.o $(TARGET).o -o $(TARGET).elf
$(TARGET).o:$(TARGET).c
arm-linux-gcc -c $(TARGET).c $(CFLAGS) -o $(TARGET).o
start.o:start.s
arm-linux-gcc -c start.s $(CFLAGS) -o start.o
clean:
rm -rf *.o *.elf *.bin
download_to_nand:
#下载到nand flash
oflash 0 1 0 0 0 $(TARGET).bin
4.下载运行
程序开始运行,第一个LED点亮:
延时1s左右,第二个LED也被点亮:
5.实验总结
通过本实验掌握了ATPCS规则
在实际开发中的使用,在调用main函数时使用R0寄存器传递参数,总结如下:
- ARM处理器中子程序调用规则由ATPCS制定,包括寄存器使用规则,数据栈使用规则,参数传递规则;
- R0-R3可以传递参数/结果,R4-R11可以保存局部变量,R13是数据栈指针SP,R14保存子程序返回地址;
- ATPCS中数据栈是FD类型,即满递减类型;
- ATPCS中参数传递使用R0-R3依次传递,如果大于4个参数,剩余的使用数据栈传递。