keil C语言与汇编语言混合编程

时间:2023-03-08 16:55:58

  C与汇编混合编程主要有以下几种:(1)C语言中嵌入汇编(2)无参数传递的函数调用(3)有参数传递的函数调用

  一、C语言中嵌入汇编

1、在 C 文件中要嵌入汇编代码片以如下方式加入汇编代码:

#pragma ASM
; Assembler Code Here
#pragma ENDASM 

2、在 Project 窗口中包含汇编代码的 C 文件上右键,选择“Options for ...”,点击右边的“Generate Assembler SRC File”和“Assemble SRC File”,使检查框由灰色变成黑色(有效)状态;

3、根据选择的编译模式,把相应的库文件(如 Small 模式时,是 KeilC51LibC51S.Lib)加入工程中, 该文件必须作为工程的最后文件;

keil C语言与汇编语言混合编程

4、编译,即可生成目标代码。例:

#include
void main(void)
{
   P1=;
   #pragma asm
        MOV  R7,#
    DEL:
        MOV  R6,#
        DJNZ R6,$
        DJNZ R7,DEL
        RET
   #pragma endasm
} 

  二、无参数传递的函数调用(C51调用汇编函数)

先来个例子:其中example.c和example.a51为项目中的两个文件

***********************example.c***********************************************
extern void delay100();

main()
{
    delay100();
} 
***********************example.a51***********************************************
?PR?DELAY100 SEGMENT CODE     ;在程序存储区中定义段
PUBLIC DELAY100;              ;声明函数
RSEG ?PR?DELAY100;            ;函数可被连接器放置在任何地方

DELAY100:

DEL:      

        DJNZ R6,$
        DJNZ R7,DEL
        RET
END 

在example.c文件中,先声明外部函数,然后直接在main中调用即可。

在example.a51中:

?PR?DELAY100 SEGMENT CODE  ;作用是在程序存储区中定义段,DELAY100为段名,?PR?表示段位于程序存储区内
PUBLIC DELAY100        ;作用是声明函数为公共函数
RSEG ?PR?DELAY100       ;表示函数可被连接器放置在任何地方,RSEG是段名的属性

段名的开头为PR,是为了和C51内部命名转换兼容,命名转换规律如下:

CODE -?PR?

XDATA-?XD

DATA-?DT

BIT-?BI

PDATA-?PD

  三、有参数传递的函数调用

注:c文件和A51文件不能使用同一个文件名

  带参数传递的函数调用,在C51和汇编之间传递参数的方式有两种,一种是通过寄存器传递参数,C51中不同类型的实参会存入相应的寄存器,在汇编中只需对相应寄存器进行操作,即达到传递参数的目的。调用汇编的参数传递规则,见下表:

传递的参数 char、1字节指针 int、2字节指针 long、float 一般指针
第一个参数 R7 R6,R7 R4~R7 R1,R2,R3
第二个参数 R5 R4,R5 R4~R7 R1,R2,R3
第三个参数 R3 R2,R3 R1,R2,R3

  在C和汇编混合编程的时候,存在C语言和汇编语言的变量以及函数的接口问题。

  在C程序中定义的变量,编译为.asm文件后,都被放进了.bss区,而且变量名的前面都带了一个下划线。在C程序中定义的函数,编译后在函数名前也带了一个下划线。例如:

extern int num就会变成 .bss _num, 1

extern float nums[5]就会变成.bss _nums, 5

extern void func ( )就会变成 _func,

  汇编和C的相互调用可以分以下几种情况

  1、汇编程序中访问c程序中的变量和函数。

  在汇编程序中,用_XX就可以访问C中的变量XX了访问数组时,可以用_XX+偏移量来访问,如_XX+3访问了数组中的XX[3]。

  在汇编程序调用C函数时,如果没有参数传递,直接用_funcname 就可以了。如果有参数传递,则函数中最左边的一个参数由寄存器A给出,其他的参数按顺序由堆栈给出。返回值是返回到A寄存器或者由A寄存器给出的地址。同时注意,为了能够让汇编语言能访问到C语言中定义的变量和函数,他们必须声明为外部变量,即加extern 前缀。

  2、C程序中访问汇编程序中的变量

  如果需要在C程序中访问汇编程序中的变量,则汇编程序中的变量名必须以下划线为首字符,并用global使之成为全局变量

  如果需要在C程序中调用汇编程序中的过程,则过程名必须以下划线为首字符,并且,要根据C程序编译时使用的模式是stack-based model还是register argument model 来正确地编写该过程,使之能正确地取得调用参数。

  3、在线汇编

  在C程序中直接插入 asm(“ *** ”),内嵌汇编语句,需要注意的是这种用法要慎用,在线汇编提供了能直接读写硬件的能力,如读写中断控制允许寄存器等,但编译器并不检查和分析在线汇编语言,插入在线汇编语言改变汇编环境或可能改变C变量的值可能导致严重的错误。

  汇编和C接口中寻址方式的改变

  需要注意的是,在C语言中,对于局部变量的建立和访问,是通过堆栈实现的,它的寻址是通过堆栈寄存器SP实现的。而在汇编语言中,为了使程序代码变得更为精简,TI在直接寻址方式中,地址的低7位直接包含在指令中,这低7位所能寻址的具体位置可由DP寄存器或SP寄存器决定。具体实现可通过设置ST1寄存器 的CPL位实现,CPL=0,DP寻址,CPL=1,SP寻址。在DP寻址的时候,由DP提供高9位地址,与低7位组成16位地址;在SP寻址的时候,16位地址是由SP(16位)与低7位直接相加得来。

  由于在C语言的环境下,局部变量的寻址必须通过SP寄存器实现,在混合编程的时候,为了使汇编语言不影响堆栈寄存器SP,通常的方式是在汇编环境中使用DP方式寻址,这样可以使二者互不干扰。编程中只要注意对CPL位正确设置即可。

  汇编中参数传递约定

  如果一个函数把a, b, r0-r7, psw, dptr等寄存器都使用了,这时r6, r7里面的数据将被破坏,程序将出现异常情况,那该如何是好呢?一个好的办法就是使用堆栈。在函数使用了什么寄存器,就在函数的开始把使用的寄存器入栈,在函数结束的地方再把这些寄存器恢复,听起来这是一个不错的主意。但是当使用的寄存器多时,入栈及出栈的开销可不小呢。还有一个问题是,当函数需要返回值时,它要把返回值放到哪里去呢?要知道函数破坏的寄存器是要恢复的哦!也许读者会想,既然是汇编,那么一切都由自己控制,规定一些约定就可以了。是的!但是否这个约定对每一个函数都适用,如果每个函数一种约定,那工程文件一变大,时间变长时,是否还能记下每个函数的约定。

  这里将介绍一种约定形式,即C51的形式。采用这种形式将使我们只关心函数内部的实现,而不必理会函数外的任何东西(这将极大减轻脑力负担);且方便汇编写的函数与C写的函数互相调用。(重点)(这样的约定可以简化C51的实现)

这个约定是:
    1,假定被调用函数会破坏ACC, B, PSW, DPTR, R0-R7等寄存器的值,因此调用函数在调用其它函数时不在这些寄存器里保存任何有用的数据。
    2,函数的返回值通过R0-R7, C返回。如果数据只有1bit, 则通过C返回, 1byte -r7, 2byte - r6-r7, 4byte - r4-r7, 如果数据很大,R0-R7都放不下,则可以把数据放在固定的数据区里,通过r1-r3返回数据区的起始地址。)

根据上面的两个原理的第一个,除了在堆栈中保存数据外。C51还采用了另一个办法,把数据放在固定的数据区,这样直接访问数据速度快。这样却又带来了另外一个问题,每个函数都占用部分数据区,51那么点点大的数据空间能支持写几个函数啊,Keil公司早想到了这个问题,因此它的链接器(BL51)比其它开发工具的链接器多了一项功能,就是覆盖处理功能。

既然在固定的数据区分配空间,那现在就介绍一些分配空间的伪指令。

使用equ的一个建议是为一些常量指定一个符号,如果要为地址空间指定符号,从可维护性考虑,使用bit, data,idata, pdata, xdata, code这些专用的伪指令要明显优于equ。

每个汇编器分配空间都有其自己的伪指令,下面看看A51的伪指令。db, dw, dd,这三个伪指令用于分配并初始化空间。dbit,ds, dsb, dsw, dsd,这几个伪指令用于分配空间却并不初始化。

还有一个问题是,如果有很多文件都需要调用这个delay_nms,那么就需要在调用这个函数的文件前面声明delay_nms是外部的。即包含下面这两句代码。

extrn code (delay_nms)

extrn data (delay_reg)

如果函数的接口改变,那就需要在都个文件里修改这两代码。当然我们把上面两句声明放到一个文件里,在需要调用这个函数的文件里include这个文件就可以了,这样就不用一个一个的去修改了.

注: 统一的一个约定是,汇编文件使用.asm扩展名,汇编头文件使用.inc扩展名,C文件使用.c扩展名,C头文件使用.h扩展名。

关于参数传递的方法,具体是通过寄存器传递,或通过固定数据区传递,还有一个是方法是通过模拟栈传递。

C51默认的参数传递方法是通过寄存器传递。