网上关于C51的资料有很多, 市面上的书目也有很多详细的介绍的C51编程。这里就谈谈个人对这种语言的对照学习。 很多单片机初学者都是从汇编开始的,当然我也包括在内,汇编代码会让人看许久都可能没有半点头绪, 但是模块化的汇编代码也是很容易理解的。怕的就是所有的代码都在MAIN主程序中, 看上去很恼火。 呵呵, 本人以前就是这样做的。后来看看别人这么把汇编代码清晰化, 模块化。都写成子函数的形式用RET返回,既有C的韵味,又很明了。不说废话,现在我们先看个例子。
一.模块化结构:
【C51代码】
void main()
{
unsigned int data a = 0x21;
unsigned int data b = 0x34;
unsigned int data c = a + b;
}
我想这样简单的双字节整数乘法(int在KEIL编译器中占两个字节),很简单的几句就可以搞定了。让我们现在看看编译以后的汇编代码(C51程序编译以后的汇编代码大家可以在debug模式中看到)。
【汇编代码】
;**********************************************************
; 输入参数 :R6,R7分别为被乘数的高位和低位
; R4,R5分别为乘数的高位和低位
; 输出参数 :R7,R6为结果的高位和低位
;**********************************************************
DOUBLE_BYTE_MULTIPLY:
MOV A, R7
MOV B, R5
MUL AB
MOV R0, B
XCH A, R7
MOV B, R4
MUL AB
ADD A, R0
XCH A, R6
MOV B, R5
MUL AB
ADD A, R6
MOV R6, A
RET
;**********************************************************
呵呵,可能大家看到这样的代码不号理解, 做双字节整数乘法步骤, (256*R6+R7)*(256*R4+R5),大家只要做多项式展开以后就是上面的汇编代码了,当然数值的大小不能超过unsigned int 的数值范围(最大为65536),因为上面定义了变量c是unsigned int数据。C51中用两个数据存储器来存放int数据,long就是占用4个数据存储区,所以没有特别的必要时候少用int和long类型的数据类型,而且最好多用unsigned char类型的变量,不需要符号判别而且符合存储器的存放格式,一个字节。代码的效率才高,执行的速度才快。
二.关于中断:
51系列单片机中断都有对应的入口地址,通常用汇编伪指令ORG定义。
如:
【汇编代码】
ORG 0000H
LJMP MAIN
ORG 0003H
LJMP EX_INT0
ORG 0030H
MAIN:
……..
SJMP $
EX_INT0:
…….
RETI
【C51】
Interrupt关键字
如:
void ex_int0() interrupt 0 using n
{
……
}
这里的using n可以不写,默认。这里主要是指定工作功能寄存器组。在汇编代码中切换寄存器是通过寄存器PSW中的RS0与RS1切换,默认时,第0组工作。这里多说一句就是因为两个寄存器之间不能直接传递数据,如:
MOV R7, R6 ;这样的代码肯定是要编译出错的,当然你可以通过累加器ACC做中间替换来大道目的,个人觉得简单的就是间接访问。在第0组工作功能寄存器工作的时候用MOV R7, 0X06 ;这样就可以达到寄存器间数据之间的传递,中间变量的代码可能有些繁琐,如:XCH A, R7; XCH A, R6;这里代码的写法有多种,可能执行的机器时间差不多,也没有去考究过,大家可以查查51指令集看看。
三.关于堆栈指针SP
初始化时,SP = 07H,这样设置的理由是,SP+1以后就是08H,这就进入寄存器第1工作组,而寄存器工作组切换很少用到,所以默认时候就是07H,大家在写代码的时候可以设置为30H
四.EQU指令※ define宏定义
变量定义:
【汇编代码】
CMD EUQ 30H
【C51】
#define CMD 0X30 //CMD替换0X30
位定义:
【汇编代码】
STATUS_MSB BIT 00H ;定义为0x20.0
【C51】
bit bdata(data) STATUS_MSB
五.总线方式访问外设地址定义
【汇编】
CMD_ADDR EQU 8000H
MOVX CMD_ADDR, #30H ;给外部8000H单元送30H数据
MOVX A, CMD_ADDR ;外部8000H数据读入累加器A
【C51】
#define CMD_PORT (*(unsigned char volatile xdata * 0x8000))
CMD_PORT = 0X30; //给外部8000H单元送数据30H
CMD_BUFFER = CMD_PORT; //将外部8000H单元数据读入变量
六.C51变量存储类型关键字,不声明时候根据编译模式来定
Data 直接寻址片内数据存储区,访问速度快(128字节)
Bdata 可位寻址片内数据存储区,允许位与字节混合访问(16字节)
Idata 间接寻址片内数据存储区,可访问片内全部RAM地址空间(256字节)
Pdata 分页寻址片外数据存储区(256字节)由MOV @Ri访问(i=0,1)(最不常用)
Xdata 片外数据存储区(64 KB)由MOVX @DPTR访问
Code 程序存储器64 KB空间,由MOVC @DPTR访问(相当与DB指令,数据放表中)
七.包含文件(*.h文件※ *.inc文件)
【C51】
C语言中通常把函数声明、宏命令写成头文件,即*.h
【汇编代码】
为了保持程序结构的模块化,通常把汇编写好的函数写成*.inc文件,类比*.h文件
八.PC(16位)
PC 程序功能计数器,记录执行程序中机器码的位置,比如当前PC = 0030H,下一条指令机器代码为3个字节,执行完以后PC = 0033H。可用做查表,但很少用。如:MOVC A, @A+PC
九.DPTR(16位)
用做查表,或者访问外部数据单元
如:
【查表】
MOV DPTR, #TAB
CLR A
MOV A, @A+DPTR
SJMP $
TAB:
DB:……
END
【访问外部设备】
MOVX A, @DPTR ;DPTR单元数据读入累加器
MOVX @DPTR, A ;向外部DPTR单元送累加器A中的数据
十.指针
C语言重要的部分就是指针,通过变量所在数据存储器上的单元可以迅速访问其数据内容,这里我们可以通过例子来说明。
【C51】
void main()
{
unsigned char idata a = 0x25;// 数据存储器中定义一个变量a
unsigned char xdata * p = &a; //外部数据存储器中存放一个指针变量,该地址指向数据存储器a变量所在单元。
unsigned char data b = *p; //p所指向的单元数据交变量b
}
【编译后的汇编代码】
5: unsigned char a = 25;
C:0x000F 750819 MOV 0x08,#0x19
6: unsigned char xdata *p = &a;
C:0x0012 900008 MOV DPTR,#0x0008
7: unsigned char b = *p;
C:0x0015 E0 MOVX A,@DPTR
C:0x0016 F509 MOV 0x09,A
十一. 位运算
C语言中位运算的运算符分别为(~, & , |, ^)
~, 汇编可以用CPL 。
&, 汇编可以用指令代码ANL
|, 汇编代码 ORL
^, 汇编代码 XRL
C语言移位(<<, >>)
unsigned char a = 25;
a = a>>2;//右移两个单位
这里还有一中代码形式:b = a – ((a >> 2) << 2), 可以实现b = a % 4;
汇编代码:可以用RR,或者RRC,不同的这里是循环移位
十二. 条件运算符
c = (a > b) ? a : b(取较大者)
编译以后的汇编代码:
;******************************************************************************
: R7,R6分别为两个输入参数
; 08H单元存放结果
;*****************************************************************************
C:0x0007 EF MOV A,R7
C:0x0008 D3 SETB C
C:0x0009 9E SUBB A,R6 ;带借位相减
C:0x000A 4002 JC C:000E ;
C:0x000C 8002 SJMP C:0010
C:0x000E AF06 MOV R7,0x06
C:0x0010 8F08 MOV 0x08,R7
C:0x0012 22 RET
十三. C与汇编混合编程
【C语言中嵌入汇编代码】
格式。
#pragma asm
;汇编代码
#pragma endasm
设置。
在工程窗口中选择包含汇编代码的C文件,右击,选择“OPTIONS FOR”,点击“GENERRATE ASSEMBLER SRC FILE”和“ASSEMBLE SRC FILE”,使得检查框由灰色编程黑色。最后把库文件KEIL\C51\LIB\C51S.LIB加入工程,编译即可。
【C语言调用汇编子程序】
void main()
{
Delay();
}
汇编文件(保存为*.A51)
NAME DELAYASM //定义模块名,可任意定义
Delay_Code SEGMENT CODE
PUBLIC Delay //外部声明,与C文件中的函数名相同
RESG Delay_Code
汇编子程序
RET
END
十四、C位段定义
关键字 STRUCT(这里个人觉得有时候当访问的积存器特殊位定义的时候非常好用)
union
{
unsigned char Register;
struct
{
unsigned RegisterBit0 :1;
unsigned RegisterBit1 :1;
......
}RegisterBit;
}RegisgterName;
十五、51汇编指令集中ORG伪指令的作用 既然是伪指令也就是这个指令是不参加编译的,通常这样的指令只不过是编译器编译的时候才需要,也就是告诉编译器我的程序是从哪里存放,相当于存储器的地址数据,这个可以参考HEX文件数据格式就很清楚了,例如:ORG 0030H 程序的机器码就从ROM的30H开始存放,在编译器编译结束后自然而然大家就会发现当前的PC值就变为0030H了哈。 十六、hex文件数据格式 hex文件为一行一行的由ASCII组成的文本文件,是可以用记事本打开内部的数据的哦,hex文件和被烧入ROM的数据并不是一样的,被烧入ROM中的数据只不过是hex文件中数据域中的内容。首先可以先看下hex文件的数据格式: :&&****$$[......]@@ hex文件都是以冒号开头的,上面每个同种符号的数据代表不同的含义 &&为数据域的长度,也就是[......]中的内容, ****为数据存放的位置,也就是即将要被写入FLASH中的地址 $$这里仅仅当为00的时候表示数据,01表示文件结束 [......]数据内容,也就是51指令的机器码 @@数据校验,这里具体怎么校验可以不用管 首先看一个测试程序: ORG 0000H hex文件为:(空格为方便看加上的) :02 0000 00 802E 50 //02表示数据域内容为2个byte 0000程序从0000位置存储 00表示数据 802E数据内容 50校验 |
文章评论(0条评论)
登录后参与讨论