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}
版权声明:本文标题:单片机c语言知识点 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/free/1702959518h437454.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论