汇编可以让开发人员从根源上理解程序的运行逻辑,本文介绍如何在keil环境下如何把一个c文件中的某一个函数,转换为汇编函数,并编译运行。
右击某个c文件,选择Option for File。。。
图1
然后把下图中的Generate Assembler SRC File(生成汇编源文件)打黑色勾。(默认是灰色勾)
图2
然后执行编译,这样就会在工程的输出文件夹(一般会被命名为OBJ)下,找到与.c文件同名的.s文件,这个C文件中的所有函数,都会被一一转换为汇编函数。
以我的ad.c为例,里面有个test()函数,这个函数中会调用另外一个函数uint16_t AD_getValue1(void),还会对一个全局变量var做写操作。
图3
转换出的汇编函数如下:
图4
由这段代码可以看到在汇编中是如何调用C函数的,以及汇编是如何读写c语言中的全局变量var的。
写var的代码是这两行:
图5
首先LDR指令从|L0.260|标签处载入var变量的内存地址,然后VSTR指令把s0的值写入该内存地址处。汇编中的标签和C语言中的goto语句标签,是类似的。
那么|L0.260|标签定义了什么?在s文件中搜索可见:
图6
有上图可见,这个标签对应的flash地址处,通过DCD命令占用了一个32bit的空间,这个空间中的值被初始化成了全局变量var的地址。DCD本身并不是一个可执行语句,只是一个占位而已。
上图还可以看到,对于C语言中定义的很多常量,也用DCD命令给保存下来了。
接下来我们就可以把c语言中的这个test()函数,用汇编给替换一下:
float var;
void test(void)
{
volatile float k = 1.5f;
var = AD_getValue1() * k;
}
//前缀__asm代表这是个汇编语言编写的函数,以便编译时供编译器识别
__asm void test(void)
{
THUMB
REQUIRE8
PRESERVE8
//本汇编函数所有要调用的c函数,必须全部用IMPORT指令导入,不然编译报错
IMPORT AD_getValue1[CODE]
//全局变量也必须IMPORT导入
IMPORT var
//以下代码直接从生成的.s文件中对应的test函数拷贝而来,见前文图4,我加了注释
PUSH {r3,lr}//PC指针存储到如r3寄存器
VMOV.F32 s0,#1.50000000//把常量1.5加载到浮点寄存器s0中
VSTR s0,[sp,#0]//把s0中的值,存储到(sp指针+0)指向的内存处
BL AD_getValue1//调用c函数,其返回值(uint16类型)会被存到r0寄存器
VMOV s0,r0//把R0寄存器中的数转存到浮点寄存器s0中
VCVT.F32.U32 s0,s0//把s0中的值由uint类型转成float类型
VLDR s1,[sp,#0]//把(sp指针+0)内存处的值加载到s1中
VMUL.F32 s0,s0,s1//等价于s0=s0*si
LDR r0,|L0.260|/从|L0.260|标签处载入全局变量var的地址
VSTR s0,[r0,#0]//把s0中的值存储到var中
POP {r3,pc}//函数返回
//以上代码需要调用|L0.260|标签处的数据,也手动复制进来:
|L0.260|
DCD var
}
至此,这个汇编函数__asm void test(void)和C语言函数void test(void),功能完全一致了。把这个c函数void test(void)删掉后,只保留汇编函数,仍然可以编译通过。这样我们就实现了:在一个c源文件中,既有c函数,又有汇编函数共存。
如果编译时看到以下报错:
error: A1875E: Register Rn must be from R0 to R7 in this instruction
报错对应的代码为LDR r0,|L0.260|,这行代码本身并没有问题,问题出在|L0.260|这个标签没有4字节对齐。
同时伴随上述报错的,还有一个警告:
warning: A1581W: Added 2 bytes of padding at address 0x2aa
这个你警报的意思是ROM没有4字节对齐,编译器自动添加了2字节的空白。这个问题来源于图6中第一行DCW,这个指令占用了2字节的位置,导致后面的DCD无法4字节对齐了。解决方法就是直接把DCW这行删掉。