一、基本概念:
1、引脚
图1.1
这里只介绍常用及主要的引脚。
I/O口引脚:P0、P1、P2、P3
P0口:39脚~32脚,双向8位三态I/O口,每个口可独立控制,但内部无上拉电阻,为高阻态,故不能正常输出高低电平,使用该口时通常连接10K的上拉电阻。
P1口:1脚到8脚,准双向8位I/O口,每个口可独立控制,内带上拉电阻,该口在作为输入使用前需先写入1,此时单片机才可正确读出外部信号,故而称准双向口。
P2口:21脚到28脚,与P1相似。
P3口:作为普通I/O使用时与P1口相似,第二功能如下:
表1.1
标号 | 功能 | 说明 |
---|---|---|
P3.0 | RXD | 串行输入口 |
P3.1 | TXD | 串行输出口 |
P3.2 | INT0 | 外部中断0 |
P3.3 | INT1 | 外部中断1 |
P3.4 | T0 | 定时器/计数器0外部输入端 |
P3.5 | T1 | 定时器/计数器1外部输入端 |
P3.6 | WR | 外部数据存储器写脉冲 |
P3.7 | RD | 外部数据存储器读脉冲 |
RST(9脚):复位引脚,输入连续两个机器周期以上高电平有效,用来完成单片机复位初始化操作,也就是让单片机从头开始执行程序。
XTAL1(19脚)、XTAL2(18脚):外接时钟引脚,通常在组成最小系统时连接10p~30p电容和晶振。
EA(31脚):当该脚接高电平时,单片机读取内部程序存储器,故而始终接高电平。
2、最小系统
最小系统是保证单片机能正常运行的必要条件(电路图如2.1)。
图2.1
3、四种周期:
(1) 时钟周期:也称振荡周期,为时钟频率的倒数,即单片机外接晶振的倒数。时钟频率越高,单片机工作越快。STC89C系列单片机始终范围约在1MHz~40MHz。
(2) 状态周期:时钟周期的两倍。
(3) 机器周期:单片机的基本操作周期,由12个时钟周期组成。
(4) 指令周期:CPU完成一条指令所需时间,一般一个指令周期含有1~4个机器周期。
4、第一个程序:点亮一个发光二极管
4.1、电路图如下:
图4.1
4.2、程序如下:
#include<reg52.h>sbit led=P1^5;void main()
{while(1)led=1;
}
二、中断
2.1、概念:中断时单片机重要的功能之一。是指CPU在处理某一件事A时,遇到了事件B,需要CPU迅速去处理事件B,待事件B处理完毕后,CPU在回到事件A被中断的地方继续处理事件A,这一过程称为中断。
过程图如下:
图2.1
举个更通俗的例子,在王者荣耀对局中,你是打野的猴子,你正在你方野区打野,突然发现敌方小卤蛋独自在中路a塔,身为打野,这个时候还打什么野怪啊,收人头啊,二技能大招一技能直接送他回泉水,收完人头在回去继续打野发育。
2.2、51单片机的6个中断源
表2.1
中断源 | 中断号 | 说明 |
---|---|---|
INT0 | 0 | 外部中断0,由P3.2端口线引入,低电平或下降沿引起 |
INT1 | 2 | 外部中断1,由P3.3端口线引入,低电平或下降沿引起 |
T0 | 1 | 定时器/计数器0中断,由T0计数器记满回零引起 |
T1 | 3 | 定时器/计数器1中断,由T1计数器记满回零引起 |
T2 | 5 | 定时器/计数器2中断,由T2计数器记满回零引起 |
TI/RI | 4 | 串行口中断,串行端口完成一帧字符发送/接受后引起 |
2.3、中断允许寄存器IE:用1和0来设定各个中断源的打开和关闭。如表2.2:
表2.2
中断 | 说明 |
---|---|
EA: | 全局中断允许位 |
ET0: | 定时器/计数器0中断允许位 |
ET1: | 定时器/计数器1中断允许位 |
ET2: | 定时器/计数器2中断允许位 |
ES: | 串行口中断允许位 |
EX0: | 外部中断0中断允许位 |
EX1: | 外部中断1中断允许位 |
2.4、定时器中断:定时器/计数器的实质时加1计数器(16位),由高8位和低8位两个寄存器组成TMOD时定时器的工作方式寄存器,确定工作方式和功能,其高4位用来设置定时器1,低4位用来设置定时器0,较为常用的有C/T(定时器模式和计数器模式选择位,为1计数器模式,为0定时器模式,如图5.2)、M1M0工作方式选择位(具体如表5.3)。TMOD通常设为0x01。
图2.2
表2.3
M1 | M0 | 工作方式 |
---|---|---|
0 | 0 | 方式0,为13位定时器/计数器 |
0 | 1 | 方式1,为16位定时器/计数器 |
1 | 0 | 方式2,8位初值自动重装8位定时器/计数器 |
1 | 1 | 方式3,仅适用于T0,分成两个8位计数器,T1停止计数 |
加1计数器输入的记数脉冲有两个来源,一个是由系统的时钟振荡器输出脉冲经12分频后送来。另一个是T0或T1引脚输入的外部脉冲源,每来一个脉冲计数器加1,当加到计数器全为1时,再输入一个脉冲就是计数器回零,且计数器的溢出时TCON寄存器中的TF0或TF1置1,相CPU发出中断请求。如果定时器\计数器工作于定时模式,则表示定时时间已到,如果工作于计数模式,则表示计数值已满。由此可见,溢出时计数器的值减去计数器初值便是加1计数器的计数值。
2.4.1控制寄存器TCON
TCON是控制寄存器,控制T0、T1的启动和停止及设置溢出标志。
位 | 说明 |
---|---|
TF1 | 定时器1溢出标志位,当定时器1记满溢出时,申请中断,进入中断服务程序 |
TR1 | 定时器1运行控制位,置1启动定时器1 |
TF0 | 定时器0溢出标志位,当定时器0记满溢出时,申请中断,进入中断服务程序 |
TR0 | 定时器0运行控制位,置1启动定时器0 |
IE1 | 外部中断1请求标志,为0时电平触发方式,为1时跳变沿触发方式 |
IT1 | 外部中断1触发方式选择位,为0时电平触发方式,为1时跳变沿触发方式 |
IE0 | 外部中断1请求标志,为0时电平触发方式,为1时跳变沿触发方式 |
IT0 | 外部中断1触发方式选择位,为0时电平触发方式,为1时跳变沿触发方式 |
2.4.2计数器初值的设定
设时钟频率为12MHz,时钟周期便为1/12us,12个时钟周期为一个机器周期,故机器周期为1us,即记1个数需要1us。由于该计数器由高8位(TH0)和低8位(TL0)两个寄存器组成,共16位,所以记满TH0和TL0需要2^16-1个数,即65536个数再来一个脉冲计数器溢出,便向CPU申请中断。
因此溢出一次共需65536us约等于65.5ms,如果定时20ms的话,需要先给TH0和TL0赋一个初值,按照上述,即在初值的基础上记20000个数后定时器溢出,也就是说定时20ms中断一次,故而TH0和TL0装入的总数位65536-20000=45536。由于TH0和TL0都是8位寄存器,每个寄存器记满的个数位2^8=256,所以TH0=45536/256,TL0=45536%256。
2.4.3中断服务程序
void 函数名()interrupt 中断号
{中断后需要去做的内容
}
函数名自拟,但要遵从函数命名规则,中断号见表5.1。
在写单片机程序时,一般步骤如下:
(1) 对TMOD赋值,确定T0和T1的工作方式。
(2) 计算初值,并将初值写入TH0、TL0或TH1、TL1。
(3) 开放中断,如表5.2。
(4) 启动定时器/计数器。
2.4.4例程
2.4.4.1、外部中断例程
#include<reg52.h>typedef unsigned int u16;
typedef unsigned char u8;sbit k3=P3^2; //定义一个按键K3
sbit led=P2^0; //定义P20口是ledvoid delay(u16 i)
{while(i--);
}void water_led() //主任务是流水灯程序,且是一个死循环
{unsigned char a;P1=0x01;delay(1000);while(1){for(a=1;a<=8;a++){P1=0x01<<a;delay(3000);}}
}void init() //开启外部中断
{IT0=1;//跳变沿出发方式(下降沿)EX0=1;//打开INT0的中断允许。 EA=1;//打开总中断
}
void main()
{ init();water_led();while(1);
}void Int0() interrupt 0 //外部中断0的中断函数
{int num=0;delay(1000); //延时消抖if(k3==0) //判断按键是否按下{led=~led; //灯的状态取反while(1) //延长中断操作的时间,便于观察{num++;delay(5000);if(num==10){num=0;break;}}}
}
主任务是流水灯程序,在外部中断为触发之前一直执行此操作,当外部中断触发时即按键按下后,另外定义的led这个灯的状态取反,且延时一段时间后外部中断操作结束,继续执行流水灯任务(流水灯的8个灯与led之间独立)。
2.4.4.2、定时器中断例程
#include<reg52.h>typedef unsigned int u16; //对数据类型进行声明定义
typedef unsigned char u8;sbit led=P2^0; //定义P20口是ledvoid init()
{TMOD=0X01;//选择为定时器0模式,工作方式1,仅用TR0打开启动。TH0=(65536-50000)/256; //给定时器赋初值,定时50msTL0=(65536-50000)%256; ET0=1;//打开定时器0中断允许EA=1;//打开总中断TR0=1;//打开定时器
}void main()
{ init(); //定时器0初始化while(1);
}void init() interrupt 1
{static u16 i;TH0=(65536-50000)/256; //给定时器赋初值,定时50msTL0=(65536-50000)%256; i++;if(i==20){i=0;led=~led; }
}
定时器定时50ms,当经过20次计数后,共计时1s,此时小灯的状态取反。故本程序的意思为使小灯进行时间间隔为1s的闪烁。
三、单片机双机通信(TTL电平通信)
3.1、概念: 将单片机A的TXD端接在单片机B的RXD端,单片机A的RXD端接在单片机B的TXD端,如图6.1,另外这两个单片机必须共地,因为数据在传输时必须要有一个回路,即单片机A、B都需要有一个共同的参考低电平。这种通信方式距离越短越可靠。
图3.1
3.2串行口控制寄存器SCON
SCON用以设定串行口的工作方式、接收/发送控制以及设置状态标志等。单片机复位时SCON全部被清零。其各位如表6.1:
表3.1
位序号 | 位符号 | 功能 |
---|---|---|
D7 | SM0 | 工作方式选择位,如表6.2 |
D6 | SM1 | 工作方式选择位,如表6.2 |
D5 | SM2 | 多机通信控制位,主要用于方式2和方式3。当SM2=1时,可利用收到的RB8来控制是否激活R1(RB8=0时不激活R1,收到的信息丢弃。RB8=1时收到的数据进入SBUF并激活R1,进而在中断服务中将数据从SBUF读走。当SM2=0时,不论收到的RB8时0还是1均可以使收到的数据进入SBUF并激活R1,此时RB8不具有控制R1激活的功能) |
D4 | REN | 允许串行接收位,REN=1时允许串行口接收数据;REN=0时禁止串行口接收数据 |
D3 | TB8 | 方式2,3中发送数据的第9位 |
D2 | RB8 | 方式2,3中接收数据的第9位 |
D1 | T1 | 发送中断标志位,当发送数据结束时,由内部硬件使T1置为1,向CPU发出中断请求,在中断服务程序中必须用软件将其清0,取消此中断请求 |
D0 | R1 | 接受中断标志位,由内部硬件使R1置为1,向CPU发出中断请求,在中断服务程序中必须用软件将其清0,取消此中断请求 |
表3.2
SM0 | SM1 | 方式 | 功能 |
---|---|---|---|
0 | 0 | 0 | 同步移位寄存器方式 |
0 | 1 | 1 | 10位异步收发8位数据,波特率可变(由定时器1的溢出率控制) |
1 | 0 | 2 | 11位异步收发9位数据,波特率固定 |
1 | 1 | 3 | 11位异步收发9位数据,波特率可变(由定时器1的溢出率控制) |
3.3例程
主机
#include<reg52.h>typedef unsigned int u16;
typedef unsigned char u8;sbit key_1=P2^1;
sbit key_2=P2^2;
sbit key_3=P2^3;
sbit key_4=P2^4;u16 date=0;void delay(u16 i)
{while(i--);
}void send(u16 a)
{ES=0; //关串口中断SBUF=a; //将数据发给SBUFwhile(TI==0); //等待发送完成TI=0; //清除中断标记ES=1; //开串口中断
}void key()
{if(key_1==0){delay(10);if(key_1==0){date=1;send(date);}}while(!key_1);if(key_2==0){delay(10);if(key_2==0){date=2;send(date);}}while(!key_2);if(key_3==0){delay(10);if(key_3==0){date=3;send(date);}}while(!key_3);if(key_4==0){delay(10);if(key_4==0){date=4;send(date);}}while(!key_4);}void main()
{TMOD=0x20; //设置计数器工作方式2TH1=0xfd; //设置波特率位9600TL1=0xfd;SM0=0; //确定串行口控制寄存器工作方式SM1=1;EA=1; //开总中断ES=1; //开串口接收中断TR1=1; //开启计数while(1){P2=0xff;date=0;key(); //扫描按键}
}
从机
#include<reg52.h>typedef unsigned int u16; //对数据类型进行声明定义
typedef unsigned char u8;sbit a=P1^1;
sbit b=P1^2;
sbit c=P1^3;
sbit d=P1^4;void show_led(u16 num)
{switch(num){case 1:P1=0x00;a=1;break;case 2:P1=0x00;b=1;break;case 3:P1=0x00;c=1;break;case 4:P1=0x00;d=1;break;}
}void main()
{TMOD=0x20; //设置计数器工作模式2TH1=0xfd; //设置波特率9600TL1=0xfd;REN=1; //开串行口接收数据SM0=0; //确定串行口控制寄存器工作方式SM1=1;EA=1; //开总中断ES=1; //开串行口中断TR1=1; //开启计数P1=0x00;while(RI==1); //等待接收}void ser()interrupt 4
{u16 a;if(RI==1) {RI=0; //取消此中断请求a=SBUF; //从SUBUF中得到数据show_led(a);} }
在主机的四个按键中,任意按下一个键都会在从机上点亮一个对应的小灯。
四、按键
4.1、独立按键
4.1.1独立按键原理
独立按键的原理很简单,就是通过判断按键是否按下从而给单片机的I/O口输入高电平或者低电平来确定是否执行目标程序。由于在按键按下时电压并不是瞬间变化的(如图7.1),存在抖动,故而通常在写独立按键程序时加延时作消抖处理。
图4.1
4.1.2、独立按键程序
if(key==0)
{delay(10);if(key==0){执行程序}while(!key);
}
4.2、矩阵按键
4.2.1、矩阵按键原理
矩阵按键一般由16个独立按键组成的(如图7.2),每个按键实质上都与两个I/O口相连,一个I/O口输出高电平,另一个I/O口输出低电平,当按键按下时,输出高电平的I/O口此时便输出低电平,如图7.3。通过判断每个I/O口的状态便可知哪一行哪一列按键被按下。假设矩阵按键连接在单片机的P1口,先初始化P1=0x0f,所以高4位就为0,低4位就为1,图中s1按键分别于P1^ 7、P1^ 3相连,当s1按键按下时,P1^3口就被拉低,此时P1口就变为0x07(如图7.4)。
图4.2
图4.3
图4.4
4.2.2矩阵按键例程:
char a=0;P1=0x0f;if(P1!=0x0f)//读取按键是否按下{delay(1000);//延时10ms进行消抖if(P1!=0x0f)//再次检测键盘是否按下{ //测试列P1=0X0F;switch(P1){case(0X07): …break;case(0X0b): …break;case(0X0d): …break;case(0X0e): …break;
…}//测试行P1=0XF0;switch(P1){case(0X70): …break;case(0Xb0): …break;case(0Xd0): …break;case(0Xe0): …break;…}while((a<50)&&( P1!=0xf0)) //检测按键松手检测{delay(1000);a++;}}}
五、液晶1602
5.1、液晶1602接口(如表8.1)
如表5.1
编号 | 符号 | 引脚说明 | 编号 | 符号 | 引脚说明 |
---|---|---|---|---|---|
1 | Vss | 电源地 | 9 | D2 | 数据口 |
2 | Vcc | 电源正 | 10 | D3 | 数据口 |
3 | VO | 液晶显示对比度调节端 | 11 | D4 | 数据口 |
4 | RS | 数据/命令选择端(H/L) | 12 | D5 | 数据口 |
5 | R/W | 读写选择端(H/L) | 13 | D6 | 数据口 |
6 | E | 使能信号 | 14 | D7 | 数据口 |
7 | D0 | 数据口 | 15 | BLA | 背光电源正 |
8 | D1 | 数据口 | 16 | BLK | 背光电源负 |
5.2基本时序(如表5.2)
如表5.2
操作状态 | 输入 | 输出 |
---|---|---|
读状态 | RS=L,R/W=H,E=H | D0~D7=状态字 |
写状态 | RS=H,R/W=H,E=H | 无 |
写指令 | RS=L,R/W=L, D0~D7=指令码,E=高脉冲 | D0~D7=数据 |
写数据 | RS=H,R/W=L, D0~D7=数据,E=高脉冲 | 无 |
5.3初始化设置,所有指令在编程序时均以十六进制表示(如表8.3)
如表5.3
指令码 | 功能 |
---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 清屏 |
0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 设置162显示,57点阵,8位数据接口 |
0 | 0 | 0 | 0 | 1 | D | C | B | D=1开显示,D=0关显示;C=1显示光标,C=0不显示光标B=1光标闪烁,B=0光标不闪烁 |
0 | 0 | 0 | 0 | 0 | 1 | N | S | N=1当读或写一个字符后地址指针加1,且光标加1,N=0当读或写一个字符后地址指针减1,光标减1;S=1当写一个字符时,整屏显示左移(N=1)或右移(N=0),以得到光标不移动而屏幕移动的效果,S=0当写一个字符时,整屏显示不移动 |
0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 光标左移 |
0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 光标右移 |
0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 整屏左移,同时光标跟随移动 |
0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 整屏右移,同时光标跟随移动 |
5.4例程
#include<reg52.h>#define uchar unsigned char
#define uint unsigned int
#define LCD1602_DATAPINS P0char str1[]="I love you !";
char str2[]="And you ?";sbit RW=P2^5; //读写选择端
sbit RS=P2^6; //数据命令选择端
sbit E=P2^7; //使能端void Delay1ms(uint i) //延时,误差 0us
{uchar j;for (; i>0; i--)for (j=200;j>0;j--);
}void LcdWriteCom(uchar com) //写入命令
{E = 0; //使能RS = 0; //选择发送命令RW = 0; //选择写入LCD1602_DATAPINS = com; //放入命令Delay1ms(1); //等待数据稳定E = 1; //写入时序Delay1ms(5); //保持时间E = 0;
} void LcdWriteData(uchar dat) //写入数据
{E = 0; //使能清零RS = 1; //选择输入数据RW = 0; //选择写 LCD1602_DATAPINS = dat; //写入数Delay1ms(1);E = 1; //写入时序Delay1ms(5); //保持时间E = 0;
}void LcdInit() //LCD初始化子程序
{LcdWriteCom(0x38); //开显示LcdWriteCom(0x0f); //开显示不显示光标LcdWriteCom(0x06); //写一个指针加1LcdWriteCom(0x01); //清屏LcdWriteCom(0x80); //设置数据指针起点
}void delay(uint i)
{while(i--);
}void main(void)
{uint i=0;LcdInit();LcdWriteCom(0x0C); //不显示光标LcdWriteCom(0x80); //显示第一行for(i=0;i<12;i++){LcdWriteData(str1[i]);delay(5);} LcdWriteCom(0x80+0x40); //显示第二行for(i=0;i<9;i++){LcdWriteData(str2[i]);delay(5);}while(1);
}
在1602液晶上第一行显示I love you!,第二行显示And you?且尾部没有光标.
六、单片机的休闲模式
6.1、概念:当单片机进入休闲模式时,外部晶振停振,CPU、定时器、串行口全部停止工作,只有外部中断继续工作。进入休闲模式后,芯片中程序未涉及的数据存储器和特殊功能寄存器中的数据将保持原值。用外部中断低电平触发或下降沿触发中断唤醒单片机,可使程序从原来停止处继续运行;亦可使用硬件复位唤醒单片机,只是程序从头开始执行。进入休闲模式的单片机功耗可降低至0.1uA以下。
6.2、例程(继续引用液晶1602的例子)
#include<reg52.h>#define uchar unsigned char
#define uint unsigned int
#define LCD1602_DATAPINS P0char str1[]="I love you !";
char str2[]="And you ?";
int num=0,sum=0;sbit RW=P2^5; //读写选择端
sbit RS=P2^6; //数据命令选择端
sbit E=P2^7; //使能端void Delay1ms(uint i) //延时,误差 0us
{uchar j;for (; i>0; i--)for (j=200;j>0;j--);
}void LcdWriteCom(uchar com) //写入命令
{E = 0; //使能RS = 0; //选择发送命令RW = 0; //选择写入LCD1602_DATAPINS = com; //放入命令Delay1ms(1); //等待数据稳定E = 1; //写入时序Delay1ms(5); //保持时间E = 0;
} void LcdWriteData(uchar dat) //写入数据
{E = 0; //使能清零RS = 1; //选择输入数据RW = 0; //选择写 LCD1602_DATAPINS = dat; //写入数Delay1ms(1);E = 1; //写入时序Delay1ms(5); //保持时间E = 0;
}void LcdInit() //LCD初始化子程序
{LcdWriteCom(0x38); //开显示LcdWriteCom(0x0f); //开显示不显示光标LcdWriteCom(0x06); //写一个指针加1LcdWriteCom(0x01); //清屏LcdWriteCom(0x80); //设置数据指针起点
}void delay(uint i)
{while(i--);
}void show_1602()
{uint i=0;LcdInit();LcdWriteCom(0x0C); //不显示光标LcdWriteCom(0x80); //显示第一行for(i=0;i<12;i++){LcdWriteData(str1[i]);delay(5);} LcdWriteCom(0x80+0x40); //显示第二行for(i=0;i<9;i++){LcdWriteData(str2[i]);delay(5);}
}void main()
{show_1602();TMOD=0x01;TH0=(65536-50000)/256;TL0=(65536-50000)%256;EA=1;ET0=1;EX0=1;TR0=1;while(1){if(num==20){num=0;sum++;if(sum==5){LcdWriteCom(0x08); //关显示ET0=0; //进入休闲模式前先关闭定时器,因为定时器中断也可唤醒单片机PCON=0x02; //单片机进入休眠}}}
}
void timer0()interrupt 1
{TH0=(65536-50000)/256;TL0=(65536-50000)%256;num++;
}
void ex_init0()interrupt 0
{PCON=0; //唤醒单片机ET0=1; sum=0; LcdWriteCom(0x0f); //开显示LcdWriteCom(0x0C); //不显示光标
}
当定时5s到后,液晶1602原显示内容消失,单片机进入休闲模式,在外部中断0引脚(P3^2)用低电平触发可唤醒单片机,原内容显示。
七、红外通信
7.1原理
红外通信就是利用红外线即光信号转化为电信号的一种通信方式,红外遥控器将遥控信号(二进制脉冲码)调制在38KHz(通常)的载波上,经缓冲放大后送至红外发光二极管,转化为红外信号发射出去,由红外接收头接收后并由单片机解码翻译为电信号。如图10.1。
图7.1
单片机接收到的信号是一段二进制脉冲,按照如图9.2的方式进行解码操作,其共有5个码,起始码是一段判断接收信号正确的码,其余四个码每个码都有8位,共32位,数据码与数据反码相反,即数据反码是数据码的反码。最先接收到的是一段时长为9ms的低电平和时长为4.5ms的高电平组成的起始码,接下来依次分别为用户码、用户码、数据码和数据反码,他们的接受机制一样,但每位有位“0”和位“1”之分(位“0”是指一段长为1.125ms的周期信号,其中低电平长0.56ms; 位“1”是指一段长为2.25ms的周期信号,其中低电平长0.56ms),即判断每个码的每个位的信号为“1”还是“0”,如图9.3,以用户码为例,其有8二进制数据位组成,即C0~C7,在接收数据时是一位一位进行接收的,首先接收低位,按图9.2所示,如果信号是从左向右传递,则最先接收C7位的数据,然而传来的数据信号并不能直接读取,要进行重新识别,如果该信号是“1”信号则该数据位为1,反之为0。
图7.2
图7.3
7.3 51单片机遥控器键码(如图10.4)
图7.4
7.3例程
#include<reg52.h>
#include<intrins.h> typedef unsigned int u16; //对数据类型进行声明定义
typedef unsigned char u8;sbit a=P2^0;
sbit b=P2^1;
sbit c=P2^2;
sbit d=P2^3;sbit IRIN=P3^2;u8 IrValue[6];
u8 Time;
u8 j,k,s,num=0;void delay(u16 i)
{while(i--);
}void init()
{IT0=1;//下降沿触发EX0=1;//打开中断0允许EA=1; //打开总中断IRIN=1;//初始化端口
}void Key()
{switch(IrValue[2]){case 0x0c: P2=0x00;a=1;break; //1键控制灯亮case 0x18: P2=0x00;b=1;break; //2键控制灯灭case 0x5E: P2=0x00;c=1;break; //3键控制灯亮case 0x08: P2=0x00;d=1;break; //4键控制灯灭}
}void main()
{ init();P2=0x00;while(1){Key();}
}void ReadIr() interrupt 0
{u8 j,k;u16 err;Time=0; delay(700); //7msif(IRIN==0) //确认是否真的接收到正确的信号{ err=1000; //1000*10us=10ms,超过说明接收到错误的信号/*当两个条件都为真是循环,如果有一个条件为假的时候跳出循环,免得程序出错的时侯,程序死在这里*/ while((IRIN==0)&&(err>0)) //等待前面9ms的低电平过去 { delay(1);err--;} if(IRIN==1) //如果正确等到9ms低电平{err=500;while((IRIN==1)&&(err>0)) //等待4.5ms的起始高电平过去{delay(1);err--;}for(k=0;k<4;k++) //共有4组数据{ for(j=0;j<8;j++) //接收一组数据{err=60; while((IRIN==0)&&(err>0))//等待信号前面的560us低电平过去{delay(1);err--;}err=500;while((IRIN==1)&&(err>0)) //计算高电平的时间长度。{delay(10); //0.1msTime++;err--;if(Time>30){return;}}IrValue[k]>>=1; //k表示第几组数据if(Time>=8) //如果高电平出现大于565us,那么是1{IrValue[k]|=0x80;}Time=0; //用完时间要重新赋值 }}}if(IrValue[2]!=~IrValue[3]){return;}}
}
从遥控器按1-4键分别对应a-d四个小灯,且每次按下一个键只有一个小灯亮。
八、看门狗
8.1概念:为防止单片机收单外界干扰或者自身某些原因致使程序跑飞或陷入死循环中,造成不可预料的后果,便拓展了一种用于监测单片机运行状态的功能——看门狗。外部看门狗暂且不论,就内部看门狗而言。STC89系列单片机内部自带了看门狗,其内部有一个看门狗定时器寄存器,通过对该寄存器赋值便可实现看门狗功能。
8.2看门狗定时器寄存器(WDT_CONTR)设置
表8.1
位序号 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
位符号 | – | – | EN_WDT | CLR_WDT | IDLE_WDT | PS2 | ps1 | ps0 |
EN_WDT: 看门狗允许位,设置为1时,启动看门狗
CLR_WDT:看门狗清0位,设为1时看门狗定时器重新计数。硬件会自动将其清0
IDLE_WDT:设置为1时,看门狗定时器在单片机“空闲模式”计数,设置为0时,看门狗定时器在单片机“空闲模式”不计数
PS2,PS1,PS0:看门狗预分频值,如表8.2
表8.2
PS2 | PS1 | PS0 | 预分频数 | 看门狗溢出时间 |
---|---|---|---|---|
0 | 0 | 0 | 2 | 65.5ms |
0 | 0 | 1 | 4 | 131.0ms |
0 | 1 | 0 | 8 | 262.1ms |
0 | 1 | 1 | 16 | 524.2ms |
1 | 0 | 0 | 32 | 1.0485s |
1 | 0 | 1 | 64 | 2.0971s |
1 | 1 | 0 | 128 | 4.1943s |
1 | 1 | 1 | 256 | 8.3886 |
看门狗溢出时间=(N预分频数32768)/晶振频率
N表示STC单片机的时钟模式,一种是单倍速,也就是12时钟模式,既12个振荡周期位一个机器周期;另一种时双倍速,又称6时钟模式,该模式下,STC单片机比其他51单片机运行速度快一倍
8.3例程
#include <reg52.h>#define uchar unsigned char
#define uint unsigned intsfr WDT_CONTR=0xe1;//看门狗定时器寄存器,看门狗溢出时间131mssbit led=P1^0;void delay_ms(uint ms)
{uint i=0,j=0;for(i=ms;i>0;i--){for(j=110;j>0;j--);}
}
void main()
{WDT_CONTR=0x35;//设置溢出时间2s//首先以1.6s时间变化一次led=1;delay_ms(500);//延时1.6sled=0;while(1){delay_ms(1000);//延时1sWDT_CONTR=0x35;//喂狗}
}
本程序的实验现象是灯亮后会一直保持熄灭状态,因为程序处于正常喂狗,便一直在while循环中,当改变while循环中的delay_ms函数的参数,当该参数大于2000后,实验现象是小灯会一直闪烁,因为单片机在不断复位。
九、内部EEPROM
9.1 功能:STC89C51,STC89C52单片机内部有2KB的EEPROM,STC单片机时利用IAP计术实现的,擦写次数可达100000次。其用于储存数据,具有掉电保护功能,再次上电时仍可读出原存储的数据。
9.2 ISP_CMD寄存器设置
表9.1
D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | 模式选择 |
---|---|---|---|---|---|---|---|---|
– | – | – | – | – | 0 | 0 | 0 | 待机模式,无ISP操作 |
– | – | – | – | – | 0 | 0 | 1 | 进行字节读 |
– | – | – | – | – | 0 | 1 | 0 | 进行字节写 |
– | – | – | – | – | 0 | 1 | 1 | 进行区擦除 |
9.3 单片机内部EEPROM地址
表9.2
第一扇区 | 第二扇区 | 第三扇区 | 第四扇区 | 第五扇区 | 第六扇区 | 第七扇区 | 第八扇区 | |
---|---|---|---|---|---|---|---|---|
起始地址 | 2000H | 2200H | 2400H | 2600H | 2800H | 2A00H | 2C00H | 2E00H |
9.4例程
LCD1602.h
#ifndef _LCD1602_H
#define _LCD1602_H#include <reg52.h>
#include <stdio.h>
#include <string.h>
#define uchar unsigned char
#define uint unsigned int
#define LCD1602_DATAPINS P0sbit RW=P2^5; //读写选择端
sbit RS=P2^6; //数据命令选择端
sbit E=P2^7; //使能端void Delay_1ms(uint i);
void LcdWriteCom(uchar com);
void LcdWriteData(uchar dat);
void LcdInit();
void LcdShow(uint num);
#endif
LCD1602.c
#include "LCD1602.h"void Delay_1ms(uint i) //延时,误差 0us
{uchar j;for (; i>0; i--)for (j=200;j>0;j--);
}void LcdWriteCom(uchar com) //写入命令
{E = 0; //使能RS = 0; //选择发送命令RW = 0; //选择写入LCD1602_DATAPINS = com; //放入命令Delay_1ms(1); //等待数据稳定E = 1; //写入时序Delay_1ms(5); //保持时间E = 0;
} void LcdWriteData(uchar dat) //写入数据
{E = 0; //使能清零RS = 1; //选择输入数据RW = 0; //选择写 LCD1602_DATAPINS = dat; //写入数Delay_1ms(1);E = 1; //写入时序Delay_1ms(5); //保持时间E = 0;
}void LcdInit() //LCD初始化子程序
{LcdWriteCom(0x38); //开显示LcdWriteCom(0x0f); //开显示不显示光标LcdWriteCom(0x06); //写一个指针加1LcdWriteCom(0x01); //清屏LcdWriteCom(0x80); //设置数据指针起点
}void LcdShow(uint num)
{uchar i=0,str[10]={0};sprintf(str,"%d",num);for(;i<strlen(str);i++){LcdWriteData(str[i]); }
}
EEPROM.h
#ifndef EEPROM_H
#define EEPROM_H#include <intrins.h>
#include <reg52.h>#define uchar unsigned char
#define uint unsigned int#define RdCommand 0x01
#define PrgCommand 0x02
#define EraseCommand 0x03#define SECTOR_1 0x2000
#define SECTOR_2 0x2200
#define SECTOR_3 0x2400
#define SECTOR_4 0x2600
#define SECTOR_5 0x2800
#define SECTOR_6 0x2A00
#define SECTOR_7 0x2C00
#define SECTOR_8 0x2E00#define Error 1
#define Ok 0
#define WaitTime 0x01sfr ISP_DATA=0xe2;
sfr ISP_ADDRH=0xe3;
sfr ISP_ADDRL=0xe4;
sfr ISP_CMD=0xe5;
sfr ISP_TRIG=0xe6;
sfr ISP_CONTR=0xe7;void ISP_IAP_enable(void);
void ISP_IAP_disable(void);
void ISPgoon(void);
uchar byte_read(uint byte_addr);
void SectorErase(uint sector_addr);
void byte_write(uint byte_addr,uchar original_data);#endif
EEPROM.c
#include "EEPROM.h"void ISP_IAP_enable(void) //打开ISP,IAP功能
{EA=0;ISP_CONTR= ISP_CONTR & 0x18;ISP_CONTR= ISP_CONTR | WaitTime;ISP_CONTR= ISP_CONTR | 0x80;
}void ISP_IAP_disable(void) //关闭ISP,IAP功能
{ISP_CONTR= ISP_CONTR & 0x7f;ISP_TRIG = 0x00;EA=1;
}void ISPgoon(void) //公用触发
{ISP_IAP_enable();ISP_TRIG= 0x46;ISP_TRIG= 0xb9;_nop_();
}uchar byte_read(uint byte_addr) //字节读
{ISP_ADDRH= (uchar)(byte_addr>>8);ISP_ADDRL= (uchar)(byte_addr & 0x00ff);ISP_CMD= ISP_CMD & 0xf8;ISP_CMD= ISP_CMD | RdCommand;ISPgoon();ISP_IAP_disable();return (ISP_DATA);
}void SectorErase(uint sector_addr) //扇区擦除
{uint iSectorAddr;iSectorAddr=(sector_addr & 0xfe00);ISP_ADDRH=(uchar)(iSectorAddr>>8);ISP_ADDRL=0x00;ISP_CMD=ISP_CMD & 0xf8;ISP_CMD=ISP_CMD | EraseCommand;ISPgoon();ISP_IAP_disable();
}void byte_write(uint byte_addr,uchar original_data) //original_data<=255
{ISP_ADDRH=(uchar)(byte_addr>>8);ISP_ADDRL=(uchar)(byte_addr & 0x00ff);ISP_CMD=ISP_CMD & 0xf8;ISP_CMD=ISP_CMD | PrgCommand;ISP_DATA= original_data;ISPgoon();ISP_IAP_disable();
}
main.c
#include "LCD1602.h"
#include "EEPROM.h"void main()
{uint num=0;LcdInit();LcdWriteCom(0x0C); //不显示光标num=byte_read(SECTOR_1); //从0x2000处读数据LcdShow(num);SectorErase(SECTOR_1); //擦除0x2000处数据byte_write(SECTOR_1,16);while(1);
}
实验现象:程序烧录后,起初液晶屏不会正常显示数据,断电后再次上电,液晶屏显示16