与C共舞
我们在之前的内容学习了段的声明以及相关变量内存的声明,现在我们要开始最重要的一节课——汇编和C的混合编程
首先是在C中调用汇编
我们在汇编的学习中知道了汇编代码的跳转和调用都是依赖所谓的标记来进行,我们还是拿LCD1602的驱动举例
- <div class="blockcode"><blockquote>LCDWRCOM:
- CLR LCD_RS;写命令
- SJMP GOON
- LCDWRDAT:
- SETB LCD_RS;写数据,这个没有jmp,应该更快一些
- GOON:
- SETB LCD_EN;两段代码合一,这种技巧很常见,甚至编译器都这么优化
- MOV LCD_BUS,R7;注意,根据文档,单个char会直接传入R7,多看文档
- MOV R6,#10H;等待LCD
- DJNZ R6,$
- CLR LCD_EN
- CLR LCD_RS
- RET
复制代码
- LCDINIT:
- CLR LCD_RW
- CLR LCD_EN
- MOV R7,#38H
- CALL LCDWRCOM
- MOV R7,#0CH
- CALL LCDWRCOM
- MOV R7,#06H
- CALL LCDWRCOM
- MOV R7,#01H
- CALL LCDWRCOM;启动序列,别处抄的
-
- MOV R7,#0FH
- LCDFINWAIT:
- MOV R6,#0FFH
- DJNZ R6,$
- DJNZ R7,LCDFINWAIT;LCD需要一段时间准备
-
- RET
复制代码
我们可以知道,上面有三个程序,一个是LCDWRCOM,一个是LCDWRDAT,以及LCDINIT,这三个都是程序的入口,我们要做的就是把入口声明告知C编译器,有这么个东西
我们以LCDINIT为例
首先,LCDINIT是一个没有输入输出的函数,所以一般来说它在C语言里的声明是这样的
其次,我们在C文件中调用的是别的文件中的函数,我们需要extern来表明,这个函数是从外部薅过来的(函数入口在别的文件)
最终,我们写在头文件里的声明函数是这样的
还记得我说过传到汇编编译器的名字不分大小写吗?这里就体现出来了,你只要名字对上就可以,它能找到认出来
光这样还不行,汇编语言的入口实际上是不符合C编译器的命名规则的,所以我们需要在汇编里做些操作,让汇编编译器知道,这个入口是可以被外界使用的,这里就要用到PUBLIC,具体用法是这样的
PUBLIC 标记名
所以最终的代码是这样的
- PUBLIC LCDINIT
- LCDINIT:
- CLR LCD_RW
- CLR LCD_EN
- MOV R7,#38H
- CALL LCDWRCOM
- MOV R7,#0CH
- CALL LCDWRCOM
- MOV R7,#06H
- CALL LCDWRCOM
- MOV R7,#01H
- CALL LCDWRCOM;启动序列,别处抄的
- MOV R7,#0FH
- LCDFINWAIT:
- MOV R6,#0FFH
- DJNZ R6,$
- DJNZ R7,LCDFINWAIT;LCD需要一段时间准备
- RET
复制代码
C语言中
这样,我们就完成了C语言调用汇编
但是对于有传入形参的函数,情况稍微复杂一些,C51传递参数有些不同,它是寄存器传参,参数放置在从R7开始的寄存器,然后才轮得到内存(可以关掉这个选项,但是内存能省则省),如果是一个char,那就R7,两个那就R7和R6,三个那就765,一个int就是R7和R6,以此类推,至于内存传参,你可以看看手册,一般来讲,超过四五个形参的我建议直接传指针进去
其次,汇编语言中的标记的前面必须带有一个下划线,比如ABC要变为_ABC
这里回收上一节埋下的伏笔,实际上内存段的标记也可以public
- ?DT?LCDMEM SEGMENT DATA
- PUBLIC LCDMEM
- RSEG ?DT?LCDMEM
- LCDMEM:
- DS 32
复制代码 C语言中是这样的
- extern unsigned char LCDMEM[32];
复制代码
好了,C调用汇编已经学会了,我们现在要倒反天罡,让汇编也揩一下C语言的油
这一次,我们直接扒keil一开始给的StartUp.A51里面的例子
从116行开始
- ?C_C51STARTUP SEGMENT CODE
- ?STACK SEGMENT IDATA
- RSEG ?STACK
- DS 1
- EXTRN CODE (?C_START)
- PUBLIC ?C_STARTUP
- CSEG AT 0
- ?C_STARTUP: LJMP STARTUP1
- RSEG ?C_C51STARTUP
复制代码
好了,那个?C_START就是我们的目标,它就是我们调用C语言的方法(也是STARTUP以后跳转主函数的入口)
EXTRN CODE (函数名)
实际上还能用EXTERN,两者的区别就是EXTERN只能把PUBLIC过的标记薅过来用(顺便一提,变量也可以薅过来用,只需要把CODE改成DATA)
我们这次不写函数给汇编调用,我们在StartUp.A51上搞事情,众所周知,StartUp.A51先于主函数运行,所以它一定会跳转到主函数,我们使个坏把主函数改成mian(我不太确定是不是有副作用,就仅仅整活用吧)
所以我们需要改成
好了,还有最后一步,我们可以在StartUp.A51的尾部看见这个
把它也改了,然后……
- StartUp.A51
- ……
- EXTRN CODE (mian)
- ……
- LJMP mian
- END
- main.c
- void mian(){
- ……
- }
复制代码 顺便一说,提示找不到?C_START也可以用这招
最后,汇编里面调用函数用CALL,这个应该不用多说
|