一、基本概念:

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.0RXD串行输入口
P3.1TXD串行输出口
P3.2INT0外部中断0
P3.3INT1外部中断1
P3.4T0定时器/计数器0外部输入端
P3.5T1定时器/计数器1外部输入端
P3.6WR外部数据存储器写脉冲
P3.7RD外部数据存储器读脉冲

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

中断源中断号说明
INT00外部中断0,由P3.2端口线引入,低电平或下降沿引起
INT12外部中断1,由P3.3端口线引入,低电平或下降沿引起
T01定时器/计数器0中断,由T0计数器记满回零引起
T13定时器/计数器1中断,由T1计数器记满回零引起
T25定时器/计数器2中断,由T2计数器记满回零引起
TI/RI4串行口中断,串行端口完成一帧字符发送/接受后引起

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

M1M0工作方式
00方式0,为13位定时器/计数器
01方式1,为16位定时器/计数器
10方式2,8位初值自动重装8位定时器/计数器
11方式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口是led


void 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口是led

void init()
{
	TMOD=0X01;//选择为定时器0模式,工作方式1,仅用TR0打开启动。

	TH0=(65536-50000)/256;	//给定时器赋初值,定时50ms
	TL0=(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;	//给定时器赋初值,定时50ms
	TL0=(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

位序号位符号功能
D7SM0工作方式选择位,如表6.2
D6SM1工作方式选择位,如表6.2
D5SM2多机通信控制位,主要用于方式2和方式3。当SM2=1时,可利用收到的RB8来控制是否激活R1(RB8=0时不激活R1,收到的信息丢弃。RB8=1时收到的数据进入SBUF并激活R1,进而在中断服务中将数据从SBUF读走。当SM2=0时,不论收到的RB8时0还是1均可以使收到的数据进入SBUF并激活R1,此时RB8不具有控制R1激活的功能)
D4REN允许串行接收位,REN=1时允许串行口接收数据;REN=0时禁止串行口接收数据
D3TB8方式2,3中发送数据的第9位
D2RB8方式2,3中接收数据的第9位
D1T1发送中断标志位,当发送数据结束时,由内部硬件使T1置为1,向CPU发出中断请求,在中断服务程序中必须用软件将其清0,取消此中断请求
D0R1接受中断标志位,由内部硬件使R1置为1,向CPU发出中断请求,在中断服务程序中必须用软件将其清0,取消此中断请求

表3.2

SM0SM1方式功能
000同步移位寄存器方式
01110位异步收发8位数据,波特率可变(由定时器1的溢出率控制)
10211位异步收发9位数据,波特率固定
11311位异步收发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;				//将数据发给SBUF
	while(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;				  //设置计数器工作方式2
	TH1=0xfd;				  //设置波特率位9600
	TL1=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;			  //设置计数器工作模式2
	TH1=0xfd;			  //设置波特率9600
	TL1=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

编号符号引脚说明编号符号引脚说明
1Vss电源地9D2数据口
2Vcc电源正10D3数据口
3VO液晶显示对比度调节端11D4数据口
4RS数据/命令选择端(H/L)12D5数据口
5R/W读写选择端(H/L)13D6数据口
6E使能信号14D7数据口
7D0数据口15BLA背光电源正
8D1数据口16BLK背光电源负

5.2基本时序(如表5.2)
如表5.2

操作状态输入输出
读状态RS=L,R/W=H,E=HD0~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

指令码功能
00000001清屏
00111000设置162显示,57点阵,8位数据接口
00001DCBD=1开显示,D=0关显示;C=1显示光标,C=0不显示光标B=1光标闪烁,B=0光标不闪烁
000001NSN=1当读或写一个字符后地址指针加1,且光标加1,N=0当读或写一个字符后地址指针减1,光标减1;S=1当写一个字符时,整屏显示左移(N=1)或右移(N=0),以得到光标不移动而屏幕移动的效果,S=0当写一个字符时,整屏显示不移动
00010000光标左移
00010100光标右移
00011000整屏左移,同时光标跟随移动
00011100整屏右移,同时光标跟随移动

5.4例程

#include<reg52.h>

#define  uchar unsigned  char
#define  uint unsigned  int
#define  LCD1602_DATAPINS  P0
  
char  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);  //写一个指针加1
    LcdWriteCom(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  P0
  
char  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);  //写一个指针加1
    LcdWriteCom(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);	//7ms
	if(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.1ms
						Time++;
						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

位序号D7D6D5D4D3D2D1D0
位符号EN_WDTCLR_WDTIDLE_WDTPS2ps1ps0

EN_WDT: 看门狗允许位,设置为1时,启动看门狗
CLR_WDT:看门狗清0位,设为1时看门狗定时器重新计数。硬件会自动将其清0
IDLE_WDT:设置为1时,看门狗定时器在单片机“空闲模式”计数,设置为0时,看门狗定时器在单片机“空闲模式不计数
PS2,PS1,PS0:看门狗预分频值,如表8.2
表8.2

PS2PS1PS0预分频数看门狗溢出时间
000265.5ms
0014131.0ms
0108262.1ms
01116524.2ms
100321.0485s
101642.0971s
1101284.1943s
1112568.3886

看门狗溢出时间=(N预分频数32768)/晶振频率
N表示STC单片机的时钟模式,一种是单倍速,也就是12时钟模式,既12个振荡周期位一个机器周期;另一种时双倍速,又称6时钟模式,该模式下,STC单片机比其他51单片机运行速度快一倍
8.3例程

#include <reg52.h>

#define uchar unsigned char
#define uint  unsigned int

sfr WDT_CONTR=0xe1;//看门狗定时器寄存器,看门狗溢出时间131ms

sbit 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.6s
	led=0;
	while(1)
	{
		delay_ms(1000);//延时1s
		WDT_CONTR=0x35;//喂狗
	}
}

本程序的实验现象是灯亮后会一直保持熄灭状态,因为程序处于正常喂狗,便一直在while循环中,当改变while循环中的delay_ms函数的参数,当该参数大于2000后,实验现象是小灯会一直闪烁,因为单片机在不断复位。

九、内部EEPROM

9.1 功能:STC89C51,STC89C52单片机内部有2KB的EEPROM,STC单片机时利用IAP计术实现的,擦写次数可达100000次。其用于储存数据,具有掉电保护功能,再次上电时仍可读出原存储的数据。
9.2 ISP_CMD寄存器设置
表9.1

D7D6D5D4D3D2D1D0模式选择
000待机模式,无ISP操作
001进行字节读
010进行字节写
011进行区擦除

9.3 单片机内部EEPROM地址
表9.2

第一扇区第二扇区第三扇区第四扇区第五扇区第六扇区第七扇区第八扇区
起始地址2000H2200H2400H2600H2800H2A00H2C00H2E00H

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  P0

sbit  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);  //写一个指针加1
    LcdWriteCom(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 0x01

sfr 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

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐