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

本文详细介绍了在Keil环境下,C语言如何调用和被调用汇编函数的过程,包括不带参数和带参数的调用方式,以及在汇编函数中调用C函数的实现。通过实例展示了子程序调用、参数传递和返回的细节,强调了格式规范和理解ARM-ThumbProcessorCallstandard的重要性。同时,探讨了C与汇编混合编程在嵌入式领域的优势,如提高实时性和减少资源占用。
摘要由CSDN通过智能技术生成


前言

如今C语言很强大,在嵌入式编程当中应用广泛,但是为什么还要在c语言中还要嵌入汇编语言,对于没有学过汇编语言的情况下,难学又不易理解?

因为汇编语言实时性比C语言好,占用单片机资源少,生成的执行文件更小,汇编语言程序直接被转换成机器指令。执行效率更高,这对某些嵌入式领域所要求的高实时性有一定帮助,此外,有时在涉及到硬件底层操作的代码时必须使用汇编代码。

所以说,在一些性能要求比较高的情况下,通常会在c语言程序在内嵌一些汇编代码。下面让我们来体会一下,在Keil环境下C与汇编语言的混合使用。


一、在c函数中调用汇编函数

关于新建一个MDK工程,并添加一个新项目的步骤
可参考我的另一篇博客

MDK下汇编语言调试分析

1.调用不带参数的汇编函数

  • 新建一个main.c文件
#include <stdio.h>

extern void Init_1(void);
int main()
{
	Init_1();
	return 0;
}
  • 新建一个func.s文件
    AREA	 My_Function ,CODE,READONLY  
	
	EXPORT Init_1 ;将Init_1导出,供工程中其他文件调用实现
		
Init_1
	MOV	R1,	#1   ;将立即数1放到寄存器R1中,即将R1寄存器初始化为1
	MOV R2,	#2	 ;将立即数2放到寄存器R2中,即将R2寄存器初始化为2
		
LOOP			 ;循环开始的地方
	CMP R1,	#10  ;比较R1与10的大小
	BHS	LOOP_END ;如果R1大于等于10,则跳转到LOOP_END,循环结束;否则,执行下一语句
	ADD R2,	#1	 ;R2=R2+1;
	ADD R1,	#1   ;R1=R1+1;
	B	LOOP     ;无条件跳转到LOOP执行下一次循环
	
LOOP_END	     ;循环结束
	NOP          ;空指令,延时等待
	
	
	END

注意:必须空格后再写END,不然会被认为是段名,表示程序结束,还有就是

AREA	My_Function ,CODE,READONLY  

这个指令一定不要顶格写!
这个指令一定不要顶格写!
这个指令一定不要顶格写!

在这里插入图片描述
汇编指令对格式要求比较高,大家在编写代码时一定要注意!

  • 开始调试
    在这里插入图片描述
  • 单步执行
    在这里插入图片描述
    程序执行到如上图所示时,可以发现R0R1寄存器的值已经变为12

2.调用带形参的汇编函数

下面由于要涉及到arm寄存器的一些分析,所以我在这先简单介绍一下。

R1~R3通常用来传递函数参数
R4~R11用来保存程序运算的中间结果或函数的局部变量
R12通常用来作为函数调用过程中的临时寄存器
R13寄存器是堆栈寄存器(SP),用来保存堆栈的当前指针
R14寄存器是链接寄存器(LR),用来保存函数的返回地址
R15寄存器是程序寄存器(PC),指向程序当前的地址

  • 修改代码

将原汇编语言 Init_1函数的类型改为 int Init_1(init) ,此函数功能修改为 传入一个整型数x,函数运行后返回整型数 x+100

修改main.c

#include <stdio.h>
extern int Init_1(int a);
int main()
{
	Init_1(66);
	return 0;
}

修改func.s

Init_1
    ADD R0,R0,#100	;R0记录传入的形参,并且执行R0=R0+100;
	BX LR           ;跳转的LR的地址执行,在这里,LR记录main函数调用子函数的返回地址
  • 初始化形参

在这里插入图片描述
在这里插入图片描述
可以看到我传入的函数参数是66,转换为16进制即为0x42,说明函数形参已被写入寄存器R0

  • 记录返回地址

在这里插入图片描述
在这里插入图片描述
我们看到在进入子函数时,一般有两个准备工作

1)初始化形参
2)R14(LR)寄存器值发生变化,记录从子函数返回当前函数的地址

  • 进入子函数、执行指令

在这里插入图片描述
我们看到,下一条指令执行是R0=R0+0x64,此时R0的值为0x42,我们可以预测执行完这步,R0的值应变为0xA6

果然R0的值变为0xA6,如下图

在这里插入图片描述

  • 返回主程序

在这里插入图片描述

当程序运行到如上图所示时,点击单步调试,注意观察到R14(LR)中的地址0x080003D5-1被加载到R15(PC)指针,PC指针指向该地址0x080003D4,即可返回主程序。

在这里插入图片描述
至此,一个函数的调用过程到此为止。

本例是以一个参数为例的,但当函数参数多于4个时,所调用的方式有所不同。前4个参数分别用R0~R3寄存器来记录,多余参数被加载到内存中使用栈来传递。

关于ARM体系下有多个函数形参的情况下,我写了一篇博客具体分析了一下

ARM体系下函数形参调用寄存器详解

二、在汇编函数中调用c函数

  • main.c中修改代码
int sum(int a,int b)
{
	int c;
	a=100;
	b=200;
	c=a+b;
	return c;


}
  • func.s中修改代码
    AREA	 My_Function ,CODE,READONLY  	
	IMPORT sum
	ENTRY
	EXPORT __main  ;注意这里有两个下划线,中间没空格

__main ;注意这里有两个下划线,中间没空格

    BL sum
	BX LR
	
	END
  • 仿真调试

调用子函数的过程与c文件调用汇编函数的过程大同小异,有兴趣的话可以自己尝试一下仿真跟踪调试。

注意:ARM汇编指令不支持顶格写,否则不能识别;声明变量时不要有空格,不然会出现奇奇怪怪的错误。

三、汇编函数与c函数混合调用

  • 修改main.c中的代码
extern int SUM_ASM(void);
int sum(int a,int b)
{
	int c;
	a=100;
	b=200;
	c=a+b;
	return c;
}
int main(void)
{
	SUM_ASM();
	return 0;
}
  • 修改func.s中代码
    AREA	 My_Function ,CODE,READONLY  	
	IMPORT sum
	EXPORT SUM_ASM
		
	ENTRY

SUM_ASM
	LDR R0,=0X3
	LDR R1,=0X4
	BL sum 
	MOV PC,LR
	
	
	END
  • 小结
    此代码实现了在c函数中调用汇编函数,再从汇编函数中调用c函数,调用方式与之前大同小异,但是了解c程序和汇编程序相互调用,混合编程还是很有帮助的。

如在MDK下stm32程序的执行过程为

先执行.s的汇编程序,再.s文件中执行以下操作

  1. 初始化堆栈指针 SP=_initial_sp
  2. 初始化 PC 指针 =Reset_Handler
  3. 初始化中断向量表
  4. 配置系统时钟
  5. 调用 C 库函数 _main 初始化用户堆栈,从而最终调用 main 函数去到 C 的世界

然后再跳转到main函数,执行一些c语言程序。
在一些对性能要求比较高的情况下,有时也会在c语言程序在内嵌一些汇编代码。


总结

无论是在汇编程序中调用c程序,还是c程序中调用(内嵌)汇编程序,往往都涉及到子程序的调用、参数的传递、子程序的返回等问题。不同的语言有着不同程度的指令集封装,在了解这些函数调用过程时,首先要初步了解ATPCS(ARM-Thumb Processor Call standard),其核心内容是子程序调用的基本规则和堆栈的使用约定等。

此外,了解了这些子程序的具体调用规则,设计程序仿真调试去验证、理解每一步如何得来,亲自实践操作对于理解子程序的调用方式是很有帮助的。

以上是我的相关学习体会,如有问题,望大家不吝指教。

参考书籍
1.嵌入式c语言自我修养 从芯片、编译器到操作系统 电子工业出版社 王利涛著

2.<STM32库开发指南–基于野火指南者开发板>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值