ARM汇编与C语言混合编程之汇编调用C函数


  • 调用没有参数的函数
  • 调用有参数的函数
  • 总结

本文所用硬件平台为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寄存器,如果参数个数超过四个,超过的参数放在堆栈中,且参数索引值越大,在堆栈中的位置更深。

你可能感兴趣的:(arm,汇编)