admin 管理员组

文章数量: 887021


2023年12月19日发(作者:影音网站源码下载)

定义

#ifndef x

#define x

...

#endif

这是宏定义的一种,它可以根据是否已经定义了一个变量来进行分支选择,一般用于调试等等.实际上确切的说这应该是预处理功能中三种(宏定义,文件包含和条件编译)中的一种----条件编译。 C语言在对程序进行编译时,会先根据预处理命令进行“预处理”。C语言编译系统包括预处理,编译和链接等部分。

#ifndef x//先测试x是否被宏定义过

#define x

程序段1 //如果x没有被宏定义过,定义x,并编译程序段1

#endif

程序段2 //如果x已经定义过了则编译程序段2的语句,“忽视”程序段1。 千万不要忽略了头件的中的#ifndef,这是一个很关键的东西。比如你有两个C文件,这两个C文件都include了同一个头文件。而编译时,这两个C文件要一同编译成一个可运行文件,于是问题来了,大量的声明冲突。

还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用,你都要加上这个。一般格式是这样的:

#ifndef <标识>

#define <标识>

......

......

#endif

<标识>在理论上来说可以是自由命名的,但每个头文件的这个“标识”都应该是唯一的。标识的命名规则一般是头文件名全大写,前后加下划线,并把文件名中的“.”也变成下划线,如:stdio.h

#ifndef _STDIO_H_

#define _STDIO_H_

......

#endif

这里是static是静态局部变量,不会随着函数的结束而撤销,放在main函数里是没有实际意义的,下面一个是static的例子:

void f()

{

static int x=0;

int y=0;

x++;

y++;

printf("%d %dn", x, y);

}

void main()

{

f();

f();

f();

}

这里运行了3次f(),但是static只会被定义一次,并不会随着f()函数的结束而消亡,但是y是局部变量,运行了3次它就被创建了3次消亡了3次,所以它的输出为:

1 1

2 1

3 1

. typedef & #define的问题

有下面两种定义pStr数据类型的方法,两者有什么不同?哪一种更好一点?

typedef char *pStr;

#define pStr char *;

答案与分析:

通常讲,typedef要比#define要好,特别是在有指针的场合。请看例子:

typedef char *pStr1;

#define pStr2 char *;

pStr1 s1, s2;

pStr2 s3, s4;

在上述的变量定义中,s1、s2、s3都被定义为char *,而s4则定义成了char,不是我们所预期的指针变量,根本原因就在于#define只是简单的字符串替换而typedef则是为一个类型起新名字。

#define用法例子:

#define f(x) x*x

main( )

{

int a=6,b=2,c;

c=f(a) / f(b);

printf("%d n",c);

}

以下程序的输出结果是: 36。

因为如此原因,在许多C语言编程规范中提到使用#define定义时,如果定义中包含表达式,必须使用括号,则上述定义应该如下定义才对:

#define f(x) (x*x)

当然,如果你使用typedef就没有这样的问题。

4. typedef & #define的另一例

下面的代码中编译器会报一个错误,你知道是哪个语句错了吗?

typedef char * pStr;

char string[4] = "abc";

const char *p1 = string;

const pStr p2 = string;

p1++;

p2++;

答案与分析:

是p2++出错了。这个问题再一次提醒我们:typedef和#define不同,它不是简单的文本替换。上述代码中const pStr p2并不等于const char * p2。const pStr p2和const

long x本质上没有区别,都是对变量进行只读限制,只不过此处变量p2的数据类型是我们自己定义的而不是系统固有类型而已。因此,const pStr p2的含义是:限定数据类型为char *的变量p2为只读,因此p2++错误。

(注:关于const的限定内容问题,在本系列第二篇有详细讲解)。

#define与typedef引申谈

1) #define宏定义有一个特别的长处:可以使用 #ifdef ,#ifndef等来进行逻辑判断,还可以使用#undef来取消定义。

2) typedef也有一个特别的长处:它符合范围规则,使用typedef定义的变量类型其作用范围限制在所定义的函数或者文件内(取决于此变量定义的位置),而宏定义则没有这种特性。

5. typedef & 复杂的变量声明

在编程实践中,尤其是看别人代码的时候,常常会遇到比较复杂的变量声明,使用typedef作简化自有其价值,比如:

下面是三个变量的声明,我想使用typdef分别给它们定义一个别名,请问该如何做?

>1:int *(*a[5])(int, char*);

>2:void (*b[10]) (void (*)());

>3. doube(*)() (*pa)[9];

答案与分析:

对复杂变量建立一个类型别名的方法很简单,你只要在传统的变量声明表达式里用类型名替代变量名,然后把关键字typedef加在该语句的开头就行了。

(注:如果你对有些变量的声明语法感到难以理解,请参阅本系列第十篇的相关内容)。

>1:int *(*a[5])(int, char*);

//pFun是我们建的一个类型别名

typedef int *(*pFun)(int, char*);

//使用定义的新类型来声明对象,等价于int* (*a[5])(int, char*);

pFun a[5];

>2:void (*b[10]) (void (*)());

//首先为上面表达式蓝色部分声明一个新类型

typedef void (*pFunParam)();

//整体声明一个新类型

typedef void (*pFun)(pFunParam);

//使用定义的新类型来声明对象,等价于void (*b[10]) (void (*)());

pFun b[10];

>3. doube(*)() (*pa)[9];

//首先为上面表达式蓝色部分声明一个新类型

typedef double(*pFun)();

//整体声明一个新类型

typedef pFun (*pFunParam)[9];

//使用定义的新类型来声明对象,等价于doube(*)() (*pa)[9];

pFunParam pa;

sfr,sfr16,esfr,sbit 收藏

伪指令 sfr、sfr16 和 sbit 与 Cx51 编译器完全兼容,我们可以在两种情况下都使用 SFR 寄存器定义文件:Ax51 宏汇编器和 Cx51 编译器。伪指令 esfr 在 Philips 80C51MX 架构的扩展 SFR 空间定义符号。该伪指令只能在 AX51 宏汇编器中使用。这些伪指令的格式如下: sfr sfr_symbol = address;

esfr sfr_symbol = address;

sfr16 sfr_symbol = address; ; 被 Ax51 忽略

sbit sfr_symbol = bit_address;

其中

sfr_symbol 是要定义的特殊功能寄存器(SFR)符号的名称。

address 是在 0x80 - 0xFF 范围内的一个 SFR 地址。

bit_address 是一个 SFR 位的地址,形式为地址^位位置(address^bitpos)或 sfr_symbol ^ bitpos。地址(address)或特殊功能寄存器符号(sfr_symbol)指向一个位可寻址的 SFR 和位位置,指明 SFR 中的位位置,范围为 0-7。

使用伪指令 esfr、sfr 或 sbit 定义的符号可以用在适合 SFR 地址或 SFR 位地址使用的任意位置。

例程

sfr P0 = 0x80;

sfr P1 = 0x90;

sbit P0_0 = P0^0;

sbit P1_1 = 0x90^1;

esfr MXCON = 0xFF; /* 扩展的 Philips 80C51MX SFR */

sfr16 T2 = 0xCC; /* 被 Ax51 忽略 */

reg51.h 详解

/* BYTE Register */

sfr P0 = 0x80; //P0口

sfr P1 = 0x90; //P1口

sfr P2 = 0xA0; //P2口

sfr P3 = 0xB0; //P3口

sfr PSW = 0xD0; //程序状态字,具体位意义见位定义

sfr ACC = 0xE0; //累加器,程序员最常用的

sfr B = 0xF0; //寄存器,主要用于乘除

sfr SP = 0x81; //堆栈指针,初始化为07;先加1后压栈,先出栈再减1,

sfr DPL = 0x82;

sfr DPH = 0x83; //数据指针,用途大

sfr PCON = 0x87; //电源控制

sfr TCON = 0x88; //Timer/Counter控制

sfr TMOD = 0x89; //Timer/Counter方式控制

sfr TL0 = 0x8A;

sfr TL1 = 0x8B; //

sfr TH0 = 0x8C; //存着当前的计数值

sfr TH1 = 0x8D; //我就想不明白,当时设计的时候,为什么不把TH0,TL0放在连续的地址!

sfr IE = 0xA8; //好东西,中断控制

sfr IP = 0xB8; //中断优先级控制,没有设计过要求时间严格的系统,所以至今没有用过

sfr SCON = 0x98; //哇,熟悉,串口控制寄存器

sfr SBUF = 0x99; //哇,更熟悉,串口缓冲寄存器

/* BIT Register */

/* PSW */

sbit CY = 0xD7; //进位或借位,有就是1,没有就是0

sbit AC = 0xD6; //辅助进借位,(麻烦b)

sbit F0 = 0xD5; //没有具体用途,可以由用户决定他的意义,所以它就没有意义

sbit RS1 = 0xD4;

sbit RS0 = 0xD3; //工作寄存器选择,这个在下面解释

sbit OV = 0xD2; //over!溢出,有是1,没有是0

sbit P = 0xD0; //奇偶校验,奇数个1是1

/* TCON */

sbit TF1 = 0x8F; //T1的中断请求标志

sbit TR1 = 0x8E; //Timer 1 running,好记吧~

sbit TF0 = 0x8D; //

sbit TR0 = 0x8C; //把上面两个1换成0

sbit IE1 = 0x8B; //interrupt external 1 外中断请求标志

sbit IT1 = 0x8A; //interrupt triggle 1 外中断触发方式

sbit IE0 = 0x89;

sbit IT0 = 0x88; //同样,把上面的两个1换成0

/* IE */

sbit EA = 0xAF; //Enable all哇,重要,全局中断控制,光着他,哈哈,什么都不用作了,就像放假一样

sbit ES = 0xAC; //Enable Serial,开串口中断

sbit ET1 = 0xAB; //Enable Timer/Counter 1

sbit EX1 = 0xAA; //Enable External 1

sbit ET0 = 0xA9; //Enable Timer/counter 0

sbit EX0 = 0xA8; //Enable External 0

/* IP */

sbit PS = 0xBC; //串行中断优先级

sbit PT1 = 0xBB; //T1优先级

sbit PX1 = 0xBA; //外部中断1优先级

sbit PT0 = 0xB9; //

sbit PX0 = 0xB8; //上面两个1换成0

/* P3 */ //控制寄存器!!!!

sbit RD = 0xB7; //读

sbit WR = 0xB6; //写

sbit T1 = 0xB5; //T/C1

sbit T0 = 0xB4; //T/C0

sbit INT1 = 0xB3; //外中断1

sbit INT0 = 0xB2; //外中断0

sbit TXD = 0xB1; //串行发送

sbit RXD = 0xB0; //串行接收

/* SCON */

sbit SM0 = 0x9F; //

sbit SM1 = 0x9E; //串口工作方式

sbit SM2 = 0x9D; //什么鬼特征位,要用查书,或者等我以后解释,啊哈

sbit REN = 0x9C; //串行接收允许

sbit TB8 = 0x9B; //收到的第九位

sbit RB8 = 0x9A; //要发的第九位

sbit TI = 0x99; //哇,熟悉吧,发送完成中断标志

sbit RI = 0x98; //接收完成中断标志

bit和sbit都是C51扩展的变量类型。

bit和int char之类的差不多,只不过char=8位, bit=1位而已。都是变量,编译器在编译过程中分配地址。除非你指定,否则这个地址是随机的。这个地址是整个可寻址空间,

RAM+FLASH+扩展空间。bit只有0和1两种值,意义有点像Windows下VC中的BOOL。

sbit是对应可位寻址空间的一个位,可位寻址区:20H~2FH。一旦用了sbi xxx =

REGE^6这样的定义,这个sbit量就确定地址了。sbit大部分是用在寄存器中的,方便对寄存器的某位进行操作的。

sbit 要在最外面定义,就是说必须定义成外部变量.

sbit定义的是SFR(特殊功能寄存器)的bit

sbit更像是类型定义,不像是变量定义。

bit 可以在外部或内部定义。

bit 动态分配的,有编译器来指定内存地址。

sbit: 指示说明性说明

bit : 编译时分配空间

SFR 是特殊功能寄存器的总称,是单片计算机中的一组特殊的临时存储区域,用于动态存放计算机运行过程的一些状态信息、并依此做相应的控制。如楼上介绍的一样,MCS-51单片机就设有18个专用寄存器,P0-P3端口,定时/计数器T0、T1,TMOD、TCON、PCON、SCON 、PSW、IE、A、B、IP等等。SFR越多,编和控制功能越强、越灵活,但需要硬资源,所以系统设计时会根据需要来确定。

MCS-51特殊功能寄存器(SPR)的C51定义 收藏

MCS - 51单片机中,除了程序计数器PC和4组工作寄存器组外,其它所有的寄存器均为特殊功能寄存器(SPR),分散在片内RAM区的高128字节中,地址范围为80H~0FFH。SFR中有11个寄存器具有位寻址能力,它们的字节地址都能被8整除,即字节地址是以8或0为尾数的。

为了能直接访问这些SPR,Franklin C51提供了一种自主形式的定义方法,这种定义方法与标准C语言不兼容,只适合与对MCS-51系列单片机进行C语言编程,特殊的能寄存器C51定义的一般语法格式如下:

sfr sfr-name = int constant;

“sfr”是定义语句的关键字,其后必须跟一个MSC - 51单片机真实存在的特殊功能寄存器名,“=”后面必须是一个整型常数,不允许带有运算符的表达式,是特殊功能寄存器“sfr-name”的字节地址,这个常数值的范围必须在SFR 地址范围内,位于0x80~0x FF。

例如:

sfr SCON=0x98; /* 串口控制寄存在器地址98H */

sfr TMOD=0x89; /* 定时器/计数器方式控制寄存器地址89H

*/

MCS-51系列单片机的特殊功能寄存器的数量与类型不尽相同,因此建议将所

有特殊的“sfr”定义放入一个头文件中,该文件应包括MCS-51单片机系列机型中的SFR定义。C51编译器的 “reg51.h”头文件是这样一个文件。

在新的 MCS-51系列产品中,SFR在功能上经常组合为16位值,当SFR的高字节地址直接位于低字节之后时,对16位SFR的值可以直接进行访问。例如52子系列的定时器/计数器2就是这种情况。为了有效地访问这类SFR,可使用关键字“sfr16”来定义,其定义语句的语法格式与8位SFR相同,只是“=”后面的地址必须用16位于的SFR的低字节地址,即低字节地址作为“sfr16”的定义地址。例如:

sfr16 T2=0xCC /* 定时器/计数器2;T2低8位地址为0CC

H,T2高8位地址为0CDH */

这种定义适用于所有的新的16位SFR,但不能用于定时器/计数器0和1。

对于位寻址的SFR中的位,C51的扩充功能支持特殊位的定义,像SFR一样不与标准C兼容,使用“ sbit” 来定义位寻址单元。

定义语句的一般语法格式有如下三种:

第一种格式:sbit bit-name=sfr-name^int constant ;

“sbit”是定义语句的关键字,后跟一个寻址位符号名(该位符号名必须是MCS-51单片机中规定的位名称),“=”后的“sfr=name”中的位号,必须是0~7范围中的数。例如:

sfr PSW=0Xd0; /*定义PSW予寄存器地址为D0H*/

sfr OV=PSW^2; /*定义OV位为PSW.2,地址为D2H/ *

sfr CY=PSW^7; /*定义CY位为PSW.7 地址为D7H^*/

第二种格式:sbit bit-name=int constant^int constant;

“=”后的 int constant为寻址地址们所在的特殊功能寄存器的字节地址,“^” 符号后的int constant为寻址位在 特殊功能寄存器中的位号。例如:

sbit OV=0Xd0 ^2; /* 定义OV位地址是D0 H字节中的第2位 */

sbit CY=0XD0^7; /* 定义C Y位地址是D0H字节中的第7位 */

第三种格式:sbit bit-name=int constant;

“=”后的int constant为寻址位的绝对地址。例如:

sbit OV=0XD2; /* 定义OV位地址为D2H */

sbit OY=0XD7; /* 定义CY位地址为D7H */

特殊功能位代表了一个独立的定义类,不能与其它位定义和位域互换。

C51笔记(1)—基本概念 收藏

1. 存储类型

a) Data, 直接寻址的内部RAM地址,RAM低128 byte,一个周期内寻址

b) bData,data区的16字节按位寻址区

c) iData,间接寻址的内部RAM地址,RAM高128 byte

d) pData,外部存储的256个字节。

e) xData,外部存储区,需要使用DPTR访问数据地址

f) Code,代码存储区,存放代码和查询表,需要使用DPTR访问数据地址

2. 寻址方式:

a) 直接:SFR,data, 位地址

b) 间接:code, idata, xdata, data

3. 常用SFR

a) R0,R1,可以用作数据区指针,寻址iData。

b) IP,中断优先级寄存器

c) IE,中断使能寄存器。

d) SMOD:控制串行通信波特率

e) SCON:设置串口工作模式

f) PSW即程序状态字(有些教材也叫程序状态寄存器)

RS0,RS1,用于选择4个寄存器组之一

设置 nv(清除) ov(溢出)

方向 dn(减) up(增)

中断 ei(启用) di(禁用)

正负 ng(负) pl(正)

零 zr(0) nz(非0)

辅助进位 ac(进位) na(不进位)

奇偶校验 pe(偶校验) po(奇校验)

进位 cy(进位) nc(不进位)

4. 中断

a) 中断向量位于代码段最低地址

b) 8051在每个周期查询中断标志,如有中断请求,置位标志,下个周期查询标志位。

5. 电源控制:

a) 节电(低功耗)模式

i. PCON进入;

ii. RAM保存;

iii. 晶振停止工作,定时器,串口不工作

iv. 通过上电、复位退出

b) 空闲模式。

i. IDLE进入

ii. RAM保存;

iii. 晶振工作,但与CPU断开,定时器,串口工作

iv. 通过中断、上电、复位退出

6. Timer

a) TCON:控制timer0,timer1;溢出位。

b) TMOD:控制timer0,timer1工作方式、时钟源。计数器模式:对IO引脚脉冲计数;定时器模式:对内部时钟脉冲计数。

c) Timer工作模式:

i. 方式 0为 13位的定时器/计数器方式。

低位 TLx为 5位,高位 THx为 8位(x= 0,1)。当低位加1计数溢出时向高位进位,高位计数溢出则置"1"TFx。

ii. 方式1与方式0相似,16位计数器。

iii. 方式2为自动恢复初值方式的8位定时器/计数器。

此时 TLx为 8位计数器,THx为常数缓冲器,当 TLx溢出时,置"1" TFx,并将THx中的计数初值送入TLx,使 TLx再次重新计数。

iv. 方式3将T0分为两个独立的8位计数器TL0及TH0。

TL0使用控制位C/-T、GATE、TR0、TF0及控制端-INT0。TH0为一个固定的8位定时器,使用T1的TR1及TF1。通常只有在T1作为串行接口的波特率发生器时,T0才定义为方式3。此时T1仍可用定时器或计数器方式,并可定义为方式0,方式1及方式2。

向1602电子钟写数据时(+0x30)是什么意思?

查下ASCII码0x30就是十进制的48,就是在ACSII是0,01秒+0x30就是在1602显示1;

LCD1602的液晶资料你还是重新看看,它说明在1602显示使用ACSII来显示的

你看看C语言的书有写这个

你的意思是说向1602写入字符时编译器会自动转成ASCII码,但是写入数字时编译器是不会自动转成ASCII码的,所以要加48也就是0x30对吧??

1是数字,'1'是字符,数字当然不能自动变成字符,+0x30就是个最简单的itoa"涵数"咯.不然的话你只好用库函数了,比如说sprintf(),嘿嘿.

引用:向1602写入字符时编译器会自动转成ASCII码

非也! 并不是转成了ASCII 而是本来就是ASCII码....

a = '8'; // a = 0x38 字符 按字符的ASCII码的值进行操作 8的ASCII码是0x38

aa = 8 ; // a = 0x08 数字 直接赋值

aa[] = "2A";// aa = {'2','A',0x0}


本文标签: 定义 地址 寄存器 使用 变量