4、其他的需要汇编的应用:在这里我们不可能举出所有可能要用汇编的例子,在你的应用中,你可能在一个或多个应用中感到C语言的不足,而需要用到汇编指令,请你记住,可以在C中嵌入汇编子程序,这对你的程序非常有用。 首先介绍一下调用汇编的参数传递规则,见下表 传递的参数 第一个参数 第二个参数 第三个参数 char、1字节指针 R7 R5 R3 int、2字节指针 R6,R7 R4,R5 R2,R3 long、float R4~R7 无 一般指针 R1,R2,R3 R1,R2,R3 R1,R2,R3 在下面的例子中我们首先给出一个C程序调用汇编的例子,先说明一下,在这个例子中,你完全可以用C完成,我用这个例子,只是为了说明嵌入汇编的方法。 例1: 下面是C语言的主程序 #include
?_asm?BIT: VAL:DBIT 1
RSEG ?PR?_asm?ASMTEST _asm:MOV A,R7 MOV C,VAL DJNZ R5,JP1 RRC A JP1:
DJNZ R5,JP2 RLC A JP2:
MOV 90h,A MOV R7,A MOV VAL,C RET END
现在对这个例子简短的说明,这个例子是用来驱动一个不带细分的三相步进电机,用的是循环移位的指令来实现的,由于一个字节是8位,如果算上进位位,共9位,通过付值,可以得到这样的数100100100,大家可以看到任意取中间的3位,相邻的每3位与它都一样,这样我们就可以去中间的3位输出到端口去驱动一个三相的步进电机,通过对这个9位的二进制数进行循环移位,可以实现电机的步进,在51系列单片机中有两条指令可以帮助我们进行循环移位操作,这就是RRC A和RLC A指令,我们只要把第九位保存好,在需要移位操作时,把它付给PSW中C,再执行循环移位就可以了。
在这个例子中,我发现了几个需要注意的地方:
1、在C程序中,我不能把VAL变量设为extern类型,否则在连接时会有警告,导致数值不能传递;
2、在汇编模块中,不能把SEGMENT BIT段置为OVERLAYABLE即可覆盖段,如果置为可覆盖段,那么在进入汇编模块时VAL变量值丢失;
3、如果把VAL变量作为函数的参数传递,出现在返回后在执行printf函数后变量值丢失;
上面是本人写的一个C程序调用汇编的小例子,在本例中的几个printf函数是为了便于在Keil C51中模拟调试时观察运行结果的,在实际应用中可以将这几条去掉,这只是一个简短的示例,目的在于介绍一下C调用汇编的用法,希望对大家有帮助,同时由于本人水平有限,在程序中有些地方可能仍有不周到之处,欢迎广大单片机高手不吝指出。
Keil C语言与汇编语言混合编程
C51和汇编混合编程(1)-C语言中嵌入汇编
1、在 C 文件中要嵌入汇编代码片以如下方式加入汇编代码: #pragma ASM
;Assembler Code Here #pragma ENDASM
2、在 Project 窗口中包含汇编代码的 C 文件上右键,选择“Options for ...”,点击右边的“Generate Assembler SRC File”和“Assemble SRC File”,使检查框由灰色变成黑色(有效)状态;
3、根据选择的编译模式,把相应的库文件(如 Small 模式时,是 Keil\\C51\\Lib\\C51S.Lib)加入工程中, 该文件必须作为工程的最后文件;
4、这点也是本人要重要说明的!即一定要将c:\\keil\\C51下的STARTUP.A51文件加入项目 5、编译,即可生成目标代码。 来个实例吧:
#include
P2=1;
#pragma asm MOV R7,#10 DEL:
MOV R6,#20 DJNZ R6,$ DJNZ R7,DEL
#pragma endasm P2=0; } C51和汇编混合编程(2)-无参数传递的函数调用 先来个例子:其中example.c和example.a51为项目中的两个文件 ***********************example.c*********************************************** extern void delay100(); main() {delay100;} ***********************example.a51*********************************************** ?PR?DELAY100 SEGMENT CODE; // 在程序存储区中定义段 PUBLIC DELAY100; // 声明函数 RSEG ?PR?DELAY100; // 函数可被连接器放置在任何地方 DELAY100: MOV R7,#10 DEL: MOV R6,#20 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 C51和汇编混合编程(3)-有参数传递的函数调用 在写这片文章之前,写了个试验程序,但总是通不过,查看汇编代码发现c文件中的语句根本没有被编译进去,怎么也找不到原因,郁闷~~ 最后在网上搜了个试验程序,把我的程序复制过去,可以编译成功,奇怪了,在我的project里就是不行,我注意到我的project编译后出现一条WARNING: *** WARNING L7: MODULE NAME NOT UNIQUE MODULE: 8.obj (8) 而同样的程序代码在另外一个project中没有WARNING,肯定是这条WARNING语句导致的,里面提到NAME,难道和名字有关,马上把A51文件改个名字(原来c文件和a51文件名字一样),编译,哈哈,WARNING不见了,查看汇编代码,一切按预想的进行,唉,一个名字害得我不浅啊,记住哦,c文件和A51文件不能使用同一个文件名,不过我还不知道为什么会这样,有高手知道得话请告知,还是进行今天的作业吧! 今天说说带参数传递的函数调用,在C51和汇编之间传递参数的方式有两种,一种是通过寄存器传递参数,C51中不同类型的实参会存入相应的寄存器,在汇编中只需对相应寄存器进行操作,即达到传递参数的目的。 不同类型的数据及其传递参数的寄存器如下表所示: 参数类型 char int long/float 通用指针 第1个 R7 R6&R7 R4-R7 R1-R3 第2个 R5 R4&R5 R4-R7 R1-R3 第3个 R3 R2&R3 -- R1-R3 举个例子吧,void delay(unsigned char i, unsigned int j) 当执行语句delay(10,1000)时,10会存入R7中,1000高位会存入R4中,低位存入R5中。在汇编语句中从这几个寄存器中取数,再进行操作就行了,说起来也很简单的嘛,呵呵~ 来个最简单的实例吧,没什么意义,傻瓜式的程序: ****************************main.c********************************************* extern void DELAY(unsigned char i,unsigned int j); main() { DELAY(10,1000); while(1); } **********************DELAY.A51******************************************** ?PR?_DELAY?DELAY SEGMENT CODE PUBLIC _DELAY RSEG ?PR?_DELAY?DELAY _DELAY: DJNZ R4,$ DJNZ R5,$ DJNZ R7,$ RET END 还要说的是,函数名前要加下划线,表示是有参数传递的函数调用! C51和汇编混合编程(4)-函数的返回值传递参数 函数返回值所用的寄存器 返回值类型 寄存器 说明 Bit C 由具体标志位返回 char/unsigned char / 1 byte 指针 R7 int/unsigned int / 2 byte 指针 R6&R7 高位在R6 long/unsigned long / 3 byte 指针 R4-R7 高位在R4 float R4-R7 32bit IEEE格式,指数和符号位在R7 通用指针 R1-R3 存储类型在R3,高位在R2 实例:
********************main.c**************************************** unsigned int example(unsigned char i) {
return(i*i); }
main() {
example(80); #pragma asm DJNZ R7,$ DJNZ R6,$
#pragma endasm while(1); }
函数返回值在R6,R7中。
深入剖析Keil C51 —— 从汇编到C51
/*******************************************************************/ 内容一:main()函数和启动代码
/*******************************************************************/ 汇编是从org 0000h开始启动,那么keil c51是如何启动main()函数的? keil c51有一个启动程序startup.a51,它总是和c程序一起编译和链接。下面看看它和main()函数是如何编译的;
主函数如下;
void main(void) {
//这是个无条件空循环。 while(1) { // } }
把上面的main()函数编译后的汇编程序和反汇编代码整理后对照如下;
;******** From STARTUP.A51 ******** $NOMOD51
IDATALEN EQU 80H XDATASTART EQU 0 XDATALEN EQU 0 PDATASTART EQU 0H PDATALEN EQU 0H
IBPSTACK EQU 0 ; set to 1 if small reentrant is used. IBPSTACKTOP EQU 0xFF +1 ; default 0FFH+1
XBPSTACK EQU 0 ; set to 1 if large reentrant is used. XBPSTACKTOP EQU 0xFFFF +1 ; default 0FFFFH+1
PBPSTACK EQU 0 ; set to 1 if compact reentrant is used. PBPSTACKTOP EQU 0xFF +1 ; default 0FFH+1
PPAGEENABLE EQU 0 ; set to 1 if pdata object are used. PPAGE EQU 0
PPAGE_SFR DATA 0A0H
;------------------------------------------------------------------------------ ; Standard SFR Symbols ACC DATA 0E0H B DATA 0F0H SP DATA 81H DPL DATA 82H DPH DATA 83H
NAME ?C_STARTUP ?C_C51STARTUP SEGMENT CODE ;在实际操作中未找到原文中的下1行信息 ;?PR?main?TESTMAIN 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
STARTUP1: ;该段程序把内存清零 ;<> 0 条件判断,不等于0的意思。 IF IDATALEN <> 0
MOV R0,#IDATALEN - 1 CLR A
IDATALOOP: MOV @R0,A
DJNZ R0,IDATALOOP ENDIF