嵌入式系统开发
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.3 ARM汇编和C语言的混合编程的实例

汇编语言是效率最高的计算机语言,因为它可以直接控制硬件电路,不需要中间编译解释的过程,所以拥有很高的执行效率。但正是由于汇编语言和某种处理器结构是紧密联系的这一特点导致这类语言的跨平台特性很差,一种处理器平台的汇编程序很难直接应用到其他处理器平台。所以,嵌入式系统的引导系统(BootLoader),包括设备驱动为了获得程序的高效率,往往把密切操作硬件的部分代码采用汇编语言来编写,而其他部分为了获得较好的可移植性和提高开发效率,仍然采用C语言进行编写。本节将介绍通常采用的变通方法——嵌入汇编、混合编程。

1.3.1 在C语言程序中内嵌汇编实例

在C语言程序中内嵌的汇编指令包含大部分的ARM和Thumb指令,不过其使用与直接在汇编文件中的指令有些不同,主要存在以下4个方面的限制。

① 不能直接向PC寄存器赋值,程序跳转要使用B或者BL指令。

② 在使用物理寄存器时,不要使用过于复杂的C语言表达式,避免物理寄存器冲突。

③ R12和R13可能被编译器用来存放中间编译结果,计算表达式值时可能将R0到R3、R12及R14用于子程序调用,因此要避免直接使用这些物理寄存器。

④ 一般不要直接指定物理寄存器,而让编译器进行分配。

内嵌汇编使用的标记是__asm或者asm关键字,用法如下:

      __asm
      {
          指令 [; 指令]
          …
          [指令]
      }

如果在ARM嵌入式平台下,需要把数组一赋值给数组二,要求每一个字节都相符。首先,如果采用C语言最基本的做法如下:

      char string1[1024], string2[1024];  /*定义两个字符类型的数组*/
      int i;
      for (i=0; i<1024; i++)
      *(string2+i) = *(string1+i);

如果采用C语言和ARM汇编混合编写,代码如下:

      #ifdef CP
      int I;
      for(I=0; I<1024; I++)
            *(string2+I)=*(string1+I);
      #else
      #ifdef_ARM_
      _asm
      {
            MOV R0,string1
            MOV R1,string2
            MOV R2,#0
            loop:
            LDMIA R0!,[R3-R11]
            STMIA R1!,[R3-R11]
            ADD R2,R2,#8
            CMP R2, #400
            BNE loop
      }
      #endif

直接采用C语言编写的代码是最常见的方法,需要使用1024次循环;而第二种实现方法则根据平台不同做了区分,在ARM平台下,用嵌入汇编仅用128次循环就完成了同样的操作。这里有读者也许会问:为什么不用标准的内存复制函数呢?这是因为在源数据里可能含有数据为0的字节,这样标准库函数会提前结束而不会完成要求的操作。这个例程典型应用于LCD数据的复制过程。

1.3.2 在汇编中使用C语言程序定义的全局变量实例

内嵌汇编不用单独编辑汇编语言文件,比较简洁,但是有诸多限制。当汇编的代码较多时一般放在单独的汇编文件中。这时就需要在汇编和C语言之间进行一些数据的传递,最简便的办法就是使用全局变量,下面是一个简单的实例。

      /*定义全局变量,并作为主调程序 */
      #include <stdio.h>
      int  gVar_1 = 12;  /*定义一个全局变量,可以在汇编文件中操作*/
      extern asmDouble(void);
      int main()
      {
            printf("original value of gVar_1 is: %d", gVar_1);
            asmDouble();
            printf(" modified value of gVar_1 is: %d", gVar_1);
            return 0;
      }

对应的汇编语言文件代码实例如下:

      ;called by main(in C),to double an integer, a global var defined in C is used.
      AREA asmfile, CODE, READONLY
      EXPORT asmDouble
      IMPORT gVar_1    ;引入全局变量
      asmDouble
      ldr r0, gVar_1
      ldr r1, [r0]
      mov r2, #2
      mul r3, r1, r2
      str r3, [r0]
      mov pc, lr
      END

1.3.3 在C语言程序中调用汇编的函数实例

在C语言程序中调用汇编文件中的函数,要做的主要工作有两个,一是在C语言中声明函数原型,并加extern关键字;二是在汇编中用EXPORT导出函数名,并用该函数名作为汇编代码段的标识,最后用mov pc, lr返回。然后,就可以在C语言中使用该函数了。从C语言的角度,并不知道该函数的实现是用C语言还是汇编。更深的原因是因为C语言的函数名起到表明函数代码起始地址的作用,这个和汇编的label是一致的。

      /* 在C文件中,调用ARM汇编的函数 asm_strcpy */
      #include <stdio.h>
      extern void asm_strcpy(const char *src, char *dest); /*申明是调用外部函数*/
      int main()
      {
          const char *s = "seasons in the sun";
          char d[32];
          asm_strcpy(s, d);
          printf("source: %s", s);
          printf(" destination: %s",d);
          return 0;
      }

而ARM汇编程序实现的代码如下:

      ;asm function implementation
      AREA asmfile, CODE, READONLY
      EXPORT asm_strcpy
      asm_strcpy
      loop
      ldrb r4, [r0], #1 ;在读取数据后,增加地址操作符
      cmp r4, #0
      beq over
      strb r4, [r1], #1
      b loop
      over
      mov pc, lr        ;利用该指令返回
      END

在这里,C语言和汇编之间的参数传递是通过ATPCS(ARMThumb Procedure Call Standard)的规定来进行的。简单的说就是如果函数有不多于4个参数,对应的用R0-R3来进行传递,多于4个时需要借助栈的帮助,函数的返回值通过R0来返回。

1.3.4 在汇编中调用C语言的函数实例

在汇编中调用C语言的函数,需要在汇编中IMPORT对应的C语言函数名,然后将C语言的代码放在一个独立的C语言文件中进行编译,剩下的工作由连接器来处理。

      ;the details of parameters transfer comes from ATPCS
      ;if there are more than 4 args, stack will be used
      EXPORT asmfile
      AREA asmfile, CODE, READONLY
      IMPORT cFun
      ENTRY
      mov r0, #11
      mov r1, #22
      mov r2, #33
      BL cFun          ;call the function defined in c program
      END

对应的C语言代码如下

      /*被arm汇编调用的c函数 */
      int cFun(int a, int b, int c)
      {
          return a + b + c;
      }

在汇编中调用C语言的函数,参数的传递也是通过ATPCS来实现的。需要指出的是当函数的参数个数大于4时,要借助栈的帮助,具体见ATPCS规范。