我们在用c语言编程时往往第一行就是头文件,51单片机为reg51.h或reg52.h,51单片机相对来说比较简单,头文件里面内容不多,像飞思卡尔、ARM系列的单片机头文件往往内容就非常多,尽管如此,对一些初次接触单片机的朋友来说,51的头文件还是搞不太清楚,今天具体来说明一下。
寄存器地址及位地址声明的原因
reg51.h里面主要是一些特殊功能寄存器的地址声明,对可以位寻址的,还包括一些位地址的声明,如果如sfr P1=0x80; sfr IE=0xA8;sbit EA=0xAF等。 sfr P1 = 0x90这句话表示:P1口所对应的特殊功能寄存器P1在内存中的地址为0x80,sbit EA=0xAF这句话表示EA这一位的地址为0xAF。
注意这里出现了一个使用很频繁的sfr和sbit。 sfr 表示特殊功能寄存器的意思,它并非标准C语言的关键字,而是Keil 为能直接访问80C51中的SFR 而提供了一个新的关键词,其用法是:sfr 特殊功能寄存器名=地址值(注意对于头文件里“特殊功能寄存器名”,用户实际上也可以修改的,如P1=0 x80,也可改为A1=0x80,但sfr 和地址值则不能更改,否者会编译出错。)
sbit 表示位的意思,它也是非标准C 语言的关键字,编写程序时如需操作寄存器的某一位(可位寻址的寄存器才能用)时,需定义一个位变量,此时就要要到sbit,如sbit deng=P1^0,sbit EA = 0xAF;需要注意的是,位定义时有些特殊, 用法有三种:
第一种方法:sbit 位变量名=寄存器位地址值
第二种方法:sbit 位变量名=SFR 名称^寄存器位值(0-7)
第三种方法:sbit 位变量名=SFR 地址值^寄存器位值
具体内容如下:
/*--------------------------------------------------------------------------
REG51.H //REG 就是 Register(寄存器)的意思,对51单片机的操作就是对寄存器的操作
Header file for generic 80C51 and 80C31 microcontroller.
Copyright (c) 1988-2002 Keil Elektronik GmbH and Keil Software, Inc.
All rights reserved.
--------------------------------------------------------------------------------------------
51单片机是8位地址的,用十六进制来表示就是两位十六进制数,所以下面看到的都是两位十六进制的地址
关于位寻址及操作,有个简便方法判断,将字节地址换成10进制后能否被“8”整除,能被“8”整除的就
能进行位操作,不能被“8”整除就不能,如P1地址为90H,10进制为144 144/8=18,能被整除,所以可以位
操作。再如TMOD地址为89H, 10进制为137 137/8=17.125,不能被整除,所以不可以位操作。
--------------------------------------------------------------------------------------------*/
#ifndef __REG51_H__ //如果没有定义__REG51_H__,那么定义它
#define __REG51_H__
/* BYTE Register */
/* -------------------------------------------------
sfr 是 special function register,特殊功能寄存器
-------------------------------------------------*/
sfr P0 = 0x80; // P0口
sfr P1 = 0x90; // P1口
sfr P2 = 0xA0; // P2口
sfr P3 = 0xB0; // P3口
sfr PSW = 0xD0; // Program Status Word,程序状态字
sfr ACC = 0xE0; // 累加器
sfr B = 0xF0; // 乘除法辅助寄存器
sfr SP = 0x81; // Stack Point,堆栈指针
sfr DPL = 0x82; // 数据指针(低8位)
sfr DPH = 0x83; // 数据指针(高8位)
sfr PCON = 0x87; // Power Control,电源控制和波特率选择
sfr TCON = 0x88; // Timer Controller 定时器控制
sfr TMOD = 0x89; // Timer Mode 定时器方式控制寄存器,不可寻址
sfr TL0 = 0x8A; // 定时器0低8位
sfr TL1 = 0x8B; // 定时器1低8位
sfr TH0 = 0x8C; // 定时器0高8位
sfr TH1 = 0x8D; // 定时器1高8位
sfr IE = 0xA8; // Interrupt Enable 中断使能
sfr IP = 0xB8; // Interrupt Priority 中断优先级控制
sfr SCON = 0x98; // Seriel Controller 串行口控制寄存器,监视和控制51 芯片串行口的工作状态
sfr SBUF = 0x99; // Serial Buffer,串行数据收/发缓冲器
/* BIT Register */
/* PSW Program Status Word 程序状态字 */
/* ---------------------------------------------------------------------------------------------
sbit定义特殊功能寄存器的位变量,映射到IO口(P1^1这种IO口的“位”)
bit和int char差不多,只不过char=8位, bit=1位而已。都是变量,编译器在编译过程中分配地址。
除非指定,否则这个地址是随机分配的。这个地址是整个可寻址空间,RAM+FLASH+扩展空间。bit只
有0和1两种值,和Windows下VC中的BOOL类似。
---------------------------------------------------------------------------------------------*/
sbit CY = 0xD7; /* PSW.7,carry,进位标志,在执行加减运算指令时,如果运算结果的最高位(D7)
发生了进位或借位,则CY由硬件自动置1。*/
sbit AC = 0xD6; /* PSW.6,assistant carry,半进位标志位,也称为辅助标志位。在执行加减运算指令时,如
果运算结果的低半字节(D3)发生了向高半字节进位或借位,则AC由硬件自动置1。*/
sbit F0 = 0xD5; /* PSW.5,用户标志位。用户可以根据需要对F0、F1赋予一定的含义,由用户置1和清0,作为软件标
志。 */
sbit RS1 = 0xD4; /* PSW.4,工作寄存器组选择控制位。通过对这两位设定,可以从4个工作寄存器组中选择一组作为当
前工作寄存器。 */
sbit RS0 = 0xD3; // PSW.3
sbit OV = 0xD2; /* PSW.2,overflow,溢出标志位,有两种情况影响该位。一是执行加减运算时,如果D7或
D6任一位,并且只一位发生了进位或借位,则OV自动置1 */
/* sbit F1 = 0xD1 */ //这个原来的头文件是没有的,自己加上去的,和 F0 作用一样
sbit P = 0xD0; /* PSW.1,奇偶标志位。每条指令执行完后,该位都会指示当前累加器A中1的个数。如果A中有奇数
个1,则P自动置1。*/
/* TCON Timer Controller 定时器控制 */
sbit TF1 = 0x8F; // Timer1 Flag,T1中断标志位
sbit TR1 = 0x8E; // Timer1 Run,T1运行控制位
sbit TF0 = 0x8D; // Timer0 Flag,T0中断标志位
sbit TR0 = 0x8C; // Timer0 Run,T0运行控制位
sbit IE1 = 0x8B; // Interrupt1 Exterior,外部中断1中断标志位
sbit IT1 = 0x8A; // Interrupt1 Touch,外部中断1 触发方式选择位
sbit IE0 = 0x89; // Interrupt0 Exterior,外部中断0中断标志位
sbit IT0 = 0x88; // Interrupt0 Touch,外部中断0触发方式选择位
/* IE Interrupt Enable 中断使能 */
sbit EA = 0xAF; // IE.7,Enable All Interrupt,中断总允许位
sbit ES = 0xAC; // IE.4,Enable Serial,串行口中断允许位
sbit ET1 = 0xAB; // IE.3,Enable Timer 1,T1中断允许位
sbit EX1 = 0xAA; // IE.2, Enable Exterior 1,外部中断1中断允许位
sbit ET0 = 0xA9; // IE.1,Enable Timer 0,T0中断允许位
sbit EX0 = 0xA8; // IE.0,Enable Exterior 0,外部中断0中断允许位
/* IP Interrupt Priority 中断优先级控制 */
sbit PS = 0xBC; // IP.4,Priority Serial 串口优先级标志位
sbit PT1 = 0xBB; // IP.3,Priority Timer 1 定时器1优先级标志位
sbit PX1 = 0xBA; // IP.2,Priority Exterior 1 外部中断1优先级标志位
sbit PT0 = 0xB9; // IP.1,Priority Timer 0 定时器0优先级标志位
sbit PX0 = 0xB8; // IP.0,Priority Exterior 0 外部中断0优先级标志位
/* P3 */
sbit RD = 0xB7; // Read Data,读数据
sbit WR = 0xB6; // Write Data,写数据
sbit T1 = 0xB5; // Timer 1,定时器1,由TMOD的高四位控制
sbit T0 = 0xB4; // Timer 0,定时器0,由TMOD的低四位控制
sbit INT1 = 0xB3; // Interrupt 1,中断1
sbit INT0 = 0xB2; // Interrupt 0,中断0
sbit TXD = 0xB1; // Receive Data,串口接收端
sbit RXD = 0xB0; // Transmit Data,串口发送端
/* SCON Seriel Controller 串行口控制 */
sbit SM0 = 0x9F; // SM0和SM1组成4种工作方式,对应有不同特性
sbit SM1 = 0x9E; //
sbit SM2 = 0x9D; /* 多机通信控制位。多机通信是工作于方式2和方式3,SM2位主要用于方式2和方式3。
接收状态,当串行口工作于方式2或3,以及SM2=1时,只有当接收到第9位数据(RB8)
为1时,才把接收到的前8位数据送入SBUF,且置位RI发出中断申请,否则会将接受
到的数据放弃。当SM2=0时,就不管第位数据是0还是1,都难得数据送入SBUF,并发
出中断申请。工作于方式0时,SM2必须为0。*/
sbit REN = 0x9C; // Receive Enable,使能接收
sbit TB8 = 0x9B; // 发送接收数据位8
sbit RB8 = 0x9A; // 接收数据位8
sbit TI = 0x99; // 发送中断标志位
sbit RI = 0x98; // 接收中断标志位
#endif
bit与sfr用法类似,只是sbit是位操作,用于将某个sfr中具体位赋值给一个变量,这样后面程序就可用通过该变量为该位清0或置1。
sfr用于将一个单片机的特殊功能寄存器(special function register)赋值给一个变量,这样在后面的程序中就可以中这个变量指引(refer to)该寄存器。
sbit与sfr用法类似,只是sbit是位操作,用于将某个sfr中具体位赋值给一个变量,这样后面程序就可用通过该变量为该位清0或置1。
STC该系列单片机的特殊功能寄存器布局如下:
看过上图这么多特殊功能寄存器之后可能会产生一些困扰,我们用sfr P0 = 0×80表示P0,用sfr SP = 0×81表示SP,这个没有歧义。
有困扰的是:假如用sbit P0_1 = 0×81表示P0口的第一位,那么我想表示SP寄存器的第0位怎么办呢?如果也是定义成sbit SP_0 = 0×81那么明显会有二义性,编译器理解不了。其实这个问题是不存在的,从图1中可以看出,SFR又可以分为两个区域:可位寻址区和不可位寻址区。可位寻址区的寄存器地址能够被8整除,而不可位寻址区的寄存器地址不满足这一要求。因此例子中的sbit SP_0 = 0×81对于SP寄存器这是无效的应该写成sfr SP=0x81。
例如:sbit P0^1=0x81;sfr SP=0x81;
它们虽然都引用了同一个地址0×81,但是对于编译器来说,这两者的含义完全不同,前者因为有sbit关键字,所以是位寻址。后者因为是sfr关键字,所以是字节地址,表示的是一个特殊功能寄存器赋值。
51单片机一般直接操作寄存器(很少会用到指针;难道用的是直接寻址?具体待查阅)。