本文所用硬件平台为S3C2440开发板。通过一个点亮数码管的程序说明ARM汇编调用C函数的方法。
根据C语言中函数参数的个数,可以将汇编调用C函数分为两种情况,调用没有参数的函数和调用有参数的函数。
这种方式是最简单的调用,只需关闭看门狗,设置堆栈即可实现这种调用。
该例子程序包括汇编代码start.s,C语言代码led.c,相关寄存器定义在s3c2440.h中。改程序通过start.s调用led.c中main函数,实现led灯的闪烁。
led.c内容如下:
#include "s3c2440.h"
#define DELAYTIME 100000
void Delay(unsignedlong delaytime);
int main(void)
{
GPIOFCON->PORT4 = OUTPUTCFG;
GPIOFDATA->PORT4 = RESETBIT;
while(1)
{
Delay(DELAYTIME);
GPIOFDATA->PORT4 = SETBIT;
Delay(DELAYTIME);
GPIOFDATA->PORT4 = RESETBIT;
}
return;
}
void Delay(unsignedlong delaytime)
{
while(delaytime--);
}
main函数实现LED灯的闪烁,该函数并没有参数。start.s直接通过bl指令调用main函数。
start.s内容如下:
.text
.global_start
_start:
ldr r0,=0x56000010 @关看门狗
mov r1,#0x0
str r1,[r0]
ldr sp,= 1024*4 @设置堆栈
bl main @调用c语言main函数
main_loop:
b main_loop @一直循环
首先需要定义入口_start;接着要关看门狗,否则会cpu会不断重启;由于调用了函数,所以必须要设置堆栈SP;然后直接调用C语言中的main函数。
main函数主要是实现led灯的不断闪烁,程序比较简单。Led.c包含了一个名为s3c2440.h的头文件,该文件主要定义了用到的寄存器。
S3c2440.h内容如下所示:
#ifndef __S3C2440_H__
#define __S3C2440_H__
#define SETBIT (0x01)
#define RESETBIT (0x00)
#define INPUTCFG (0x00)
#define OUTPUTCFG (0x01)
#define EINTCFG (0x10)
//为实现位操作,故采用结构体位域数据结构。
typedef volatile struct PortData
{
unsigned PORT0:1;
unsigned PORT1:1;
unsigned PORT2:1;
unsigned PORT3:1;
unsigned PORT4:1;
unsigned PORT5:1;
unsigned PORT6:1;
unsigned PORT7:1;
unsigned :24;
} *PortData_st;
typedef volatile struct PortCon
{
unsigned PORT0:2;
unsigned PORT1:2;
unsigned PORT2:2;
unsigned PORT3:2;
unsigned PORT4:2;
unsigned PORT5:2;
unsigned PORT6:2;
unsigned PORT7:2;
unsigned :16;
} *PortCon_st;
PortData_st GPIOFDATA = (PortCon_st)0x56000054;//GPIOF端口数据寄存器
PortCon_st GPIOFCON = (PortCon_st)0x56000050;//GPIOF端口配置寄存器
#endif
之所以要将寄存器定义成位域结构体的形式是为了位操作的方便。
由于我们我们再C语言中用到了全局变量,所以需要在链接脚本中定义数据段。
链接脚本led.lds内容如下:
ENTRY(_start)
SECTIONS
{
. = 0x00000;
.text :{start.o(.text) *(.text)}
. = 0x00100;
.data : {*(.data)}
.bss : {*(.bss)}
}
由于start.s中有程序入口所以将start.o放在text段的第一个位置。
有了以上这几个文件就可编译并下载了,makefile内容如下:
all:led.bin
sudo oflash 0 1 0 0 0 led.bin
led.bin:start.sled.c
arm-linux-gcc -g -c -o start.o start.S
arm-linux-gcc -g -c -o led.o led.c
arm-linux-ld -Tled.lds -g led.o start.o -oled_elf
arm-linux-objcopy -O binary -S led_elfled.bin
clean:
rm -f led.bin led_elf *.o *.s
当函数参数个数少于等于4个时,在调用函数前只需将参数1到参数4依次存到R0-R3中即可实现参数的传递;当函数参数大于四个时,多出来的参数需要存在堆栈中。
我们在led.c中定义一个带参数的函数,该函数实现LED灯闪烁给定的次数,闪烁的次数就是要传递的值。Led.c内容如下;
#include "s3c2440.h"
#define DELAYTIME 100000
void Delay(unsignedlong delaytime);
int LightLed(unsignedint unLightNum)
{
int i = 0;
GPIOFCON->PORT4 = OUTPUTCFG;
GPIOFDATA->PORT4 = RESETBIT;
for(i = 0; i < unLightNum; i++)
{
GPIOFDATA->PORT4 = RESETBIT;
Delay(DELAYTIME);
GPIOFDATA->PORT4 = SETBIT;
Delay(DELAYTIME);
}
return;
}
void Delay(unsignedlong delaytime)
{
while(delaytime--);
}
然后在start.s中调用该函数
.text
.global_start
_start:
ldr r0,=0x53000000 @关看门狗
mov r1,#0x0
str r1,[r0]
ldr sp,= 1024*4 @设置堆栈
mov r0,#0x05 @设置函数参数
bl LightLed @调用c语言main函数
@点亮port5上的led灯
ldr r0,= 0x56000050
ldr r1,[r0]
mov r2,#0x03
mvn r2,r2, lsl #(2*5)
and r1,r1, r2
mov r3, #0x01
orr r1,r1, r3, lsl #(2*5)
str r1,[r0]
ldr r0,= 0x56000054
ldr r1,[r0]
mov r2,#0x01
mvn r2,r2, lsl #5
and r1,r1, r2
str r1,[r0]
main_loop:
b main_loop @一直循环
如程序所示,通过r0将参数传递给了函数LightLed。执行完该函数后,返回并接着点亮另一个LED灯。
那如果函数参数个数大于四个了,那该怎么办呢?
例如,我们将LightLed定义为一个有6个参数的函数。当然除了第六个参数其他参数都没有实际意义。
int LightLed(inti, int j, int k, int l, intm, unsigned int unLightNum)
{
GPIOFCON->PORT4 = OUTPUTCFG;
GPIOFDATA->PORT4 = RESETBIT;
for(i = 0;i < unLightNum; i++)
{
GPIOFDATA->PORT4 = RESETBIT;
Delay(DELAYTIME);
GPIOFDATA->PORT4 = SETBIT;
Delay(DELAYTIME);
}
return;
}
然后我们在start.s中调用该函数
.text
.global_start
_start:
ldr r0,=0x53000000 @关看门狗
mov r1,#0x0
str r1,[r0]
ldr sp,= 1024*4 @设置堆栈
mov r0,#0x05
stmfd sp!,{r0} @设置函数第六个参数
mov r0,#0x00
stmfd sp!,{r0} @设置函数第五个参数
mov r0,#0x00 @设置函数第一个参数
mov r1,#0x00 @设置函数第二个参数
mov r2,#0x00 @设置函数第三个参数
mov r3, #0x00 @设置函数第四个参数
bl LightLed @调用c语言main函数
add sp,sp, #4 @清空第五个参数
add sp,sp, #4 @清空第六个参数
@点亮port5上的led灯
ldr r0,= 0x56000050
ldr r1,[r0]
mov r2,#0x03
mvn r2,r2, lsl #(2*5)
and r1,r1, r2
mov r3, #0x01
orr r1,r1, r3, lsl #(2*5)
str r1,[r0]
ldr r0,= 0x56000054
ldr r1,[r0]
mov r2,#0x01
mvn r2,r2, lsl #5
and r1,r1, r2
str r1,[r0]
main_loop:
b main_loop @一直循环
从程序中我们可以看出,我们将第五个和第六个参数压入了堆栈中,且第六个参数位置更深。这样就实现了参数的传递。由于调用函数并不会对压入堆栈的参数出栈,所以在调用完函数后需要出栈操作。
ARM汇编调用C语言函数分为两种情况,一种是调用没有参数的函数,另一种是调用带有参数的函数。若函数没有参数直接用bl调用即可。若函数有参数需要将参数传递给r0-r3寄存器,如果参数个数超过四个,超过的参数放在堆栈中,且参数索引值越大,在堆栈中的位置更深。