基于51单片机和LCD1602显示红外遥控器的键值

基于51单片机和LCD1602显示红外遥控器的键值

一、背景介绍

最近在学习51单片机,一直下来都比较顺利,唯独红外控制这部分花了我两天的时间,因此用blog记录一下写程序的过程,便于以后复习。

二、功能介绍以及原理介绍

如题,使用红外遥控器向51单片机上的红外接收模块发送信号,模块能读取信号并自动将其转换成普通电信号的形式。(红外遥控器发出的信号波形为:低电平是38kHz的波形,高电平就是一条直线)
一帧信号中包括了开始位、地址位、地址反码位、数据位和数据反码位,不同的位占用的时间不同,因此可以通过分析不同位的时间来分析各个位的实际意义。
那么怎么计算一个位所占用的时间呢?这里可以使用外部中断和计时器,将外部中断设置为下降沿触发。第一次触发中断后,计时器开始计时,等到第二次外中断发生,计算其时间,如果正好是13.5ms,那么可以知道第一位数据是Start,以此类推就可以得到各个位的实际含义了。

在这里插入图片描述

三、代码编写

首先编写好定时器0的函数,功能是在接收到下降沿并进入外部中断时让计时器开始计时,在接收到下一个下降沿时获取到这段时间并将定时器0归零。

// 首先是计时器初始化
void Timer0_Init()
{
	// 设置计时器的模式以及哪个计时器
	TMOD = TMOD & 0xF0;
	TMOD = TMOD | 0x01;
	TR0 = 0; // 设置定时器工作位,1表示开始工作
	TL0 = 0;				//设置定时初始值
	TH0 = 0;				//设置定时初始值
}

// 让计时器开始计时或停止计时
void Timer0_StartStop(unsigned char flag)
{
	TR0 = flag;
}

// 设置定时器的值
void Timer0_setCounter(unsigned int counter)
{
	TH0 = counter / 256;
	TL0 = counter % 256; 
}

// 获取当前计时器的值
unsigned int Timer0_getCounter()
{
	return TH0 * 256 + TL0;
}

然后新建一个IR模块,该模块完成对外部中断的初始化,并对外部中断进行处理,也就是对收到的红外信号进行处理。

// 首先是外部中断初始化
void Int0_Init()
{
	IT0 = 1; // 下降沿接收中断
	EX0 = 1; // 中断允许为1
	EA = 1; // 中断允许为1
	PX0 = 1; // 将外中断设置为高优先级,避免响应不及时导致编码错误
}

// 接收到下降沿就进入一次这个中断,一次只处理一个下降沿!
void Int0_Routine() interrupt 0
{
	// IR_State表示当前红外模块接收并转化后得到的信号状态
    // 0表示要做好接收信号的准备,例如将计时器赋初值并令其开始计时。同时改变信号状态为1
    // 1表示即将接收Start信号或者Repeat信号
    // 2表示即将接收32位数据信号
	if(IR_State == 0)
	{
        // 设置计时器的值为0并开始计时,同时设置当前信号状态为1
		Timer0_setCounter(0);
		Timer0_StartStop(1);
 		IR_State = 1;
        // IR_pData用来统计收到的数据的位数,等于32时表示该数据已经接收完毕,如果没有Repeat信号的话,该帧算结束。
		IR_pData = 0;
	}
	
    // 当信号状态为1时,表示即将接收Start或Repeat,具体是什么要通过计时器的值来统计。
	else if(IR_State == 1)
	{
        // 获取计时器的值
		time = Timer0_getCounter();
        // 据统计,在STC89C52上,大约是0.93us计时器加1,而Start帧时间长度为13.5ms,所以误差在九折的情况下是可以接受的。
		if(time > 12150 && time < 13500)
		{
            // 若接收到Start帧,说明要准备接收数据了,设State为2,同时将计时器置零
			IR_State = 2;
			Timer0_setCounter(0);
            // 以下牵涉到Repeat帧问题,可以先不管。
			IR_RepeatFlag = 0;
             getData=0;
		}
		// 该时间范围表示接收到Repeat帧,Repeat的思路是,如果是在接收完数据帧之后收到Repeat帧,那么		此时getData位一定为1,且数据已经被保存好,那么我们直接在其中执行一些操作即可。
        // 只有当收到Start帧时,我们才将getData位置为0,表示新的一帧开始
		else if(time > 10000 && time < 12000)
		{
			// 上次数据成功接收之后再允许+
			if(getData == 1 && (Command0 == 0x09 || Command0 == 0x15))
			{
				err++;
			}
            // Repeat帧处于一个信号的末尾,因此当接收到Repeat帧后,需要将State置零,并将计时器置零且			停止计时。
			IR_State = 0;
			Timer0_setCounter(0);
			Timer0_StartStop(0);
		}
		// 如果接收到的数据帧即不是Start也不是Repeat,那么认为该帧出错,将State置1,表示持续等待			Start帧和Repeat帧。(如果下一个是Start帧,那么就可以顺利执行;如果下一个是Repeat帧,那么会		使State置零,重新等待信号输入,而且由于上一帧结束后已经将数据部分清零,所以该Repeat帧无效。)
		else 
		{
			IR_State = 1;
            // s1e用来记录出错帧的次数,如果该帧出错,那么告诉用户,以防用户按下按钮但是没有响应
			s1e++;
			Timer0_setCounter(0);
		}
	}
	// 开始接收数据
	else if(IR_State == 2)
	{
		time = Timer0_getCounter();
		// 表示发送了一个0
		if(time < 1300 && time > 900)
		{
            // 接收到数据后IR_pData加1,并将计时器清零重新计算
			IR_pData++;
			Timer0_setCounter(0);
		}
		// 表示发送了一个1
		else if(time < 2200 && time > 1800)
		{
			IR_Data[IR_pData/8] |= (0x01 << (IR_pData%8));
			IR_pData++;
			Timer0_setCounter(0);
		}
        // 表示该数据帧出错
		else
		{
            // 将已经获取到的数据全部清除,并将State置零,相当于丢弃该帧
			IR_pData=0;
			IR_Data[0] = 0;
			IR_Data[1] = 0;
			IR_Data[2] = 0;
			IR_Data[3] = 0;
			IR_State = 0;
		}
		// 表示数据接收完成
		if(IR_pData == 32)
		{
            // 接受完一帧后将数据全部清除,准备接收下一帧或者根据Repeat来做一些操作
			IR_State = 0;
			IR_pData = 0;
			Timer0_setCounter(0);
			Timer0_StartStop(0);
            // getData表示数据被正确接收,便于其他函数根据该标志位执行一些其他操作
			getData = 1;
            // 保存获取到的数据
			Address0 = IR_Data[0];
			Address1 = IR_Data[1];
			Command0= IR_Data[2];
			Command1= IR_Data[3];
            // 保存数据后将缓存清零
			IR_Data[0] = 0;
			IR_Data[1] = 0;
			IR_Data[2] = 0;
			IR_Data[3] = 0;		
		}
	}
}

四、反思和总结

现在整体写下来竟然觉得不是很难,但做的过程中确实是遇到很多的问题,比如有:

  1. 江科大给的LCD1602数据显示函数是有bug的,显示139*256时会在结果最前面加上20,害我找了好久
  2. 数据是获取到了,但是出现了乱码,这是为什么?可能是因为在状态1的时候接收到了01数据帧,发生问题后又没有正确地将state设置为1,导致后续接收数据时出现了位置错乱。
  3. 连续按好几下也没有反应,数据正确与否全靠缘分?可能是因为部分代码正确,但是处理错误代码发生问题,比如start&repeat位出错,获取数据出错的时候没有解决好。
  • 14
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是基于51单片机,使用Lu90614红外传感LCD1602显示屏和蜂鸣的报警代码示例: ```C #include <reg52.h> #include <intrins.h> #define uchar unsigned char #define uint unsigned int sbit beep = P2^3; // 蜂鸣控制引脚 sbit RS = P0^0; // LCD1602命令/数据选择引脚 sbit RW = P0^1; // LCD1602读/写选择引脚 sbit EN = P0^2; // LCD1602使能引脚 sbit temp = P3^7; // 红外传感数据引脚 void delay(uint z); // 延时函数声明 void Init_Lcd1602(); // LCD1602初始化函数声明 void LcdWriteCom(uchar com); // LCD1602写命令函数声明 void LcdWriteData(uchar dat); // LCD1602数据函数声明 void LcdShowStr(uchar x,uchar y,uchar *str); // LCD1602显示字符串函数声明 void main() { uchar str1[]=" FIRE!"; // 显示的报警信息 uchar str2[]=" WARNING!"; uchar str3[]="No Fire."; uchar flag = 0; // 报警标志位,0表示无报警,1表示有报警 Init_Lcd1602(); // 初始化LCD1602 LcdShowStr(0,0,str3); // 初始化显示"NO FIRE." while(1) { if(temp == 0) // 如果红外传感探测到火焰 { if(flag == 0) // 如果之前无报警 { flag = 1; // 设置报警标志位 beep = 1; // 开启蜂鸣 LcdWriteCom(0x01); // 清屏 LcdShowStr(0,0,str1); // 显示报警信息 LcdShowStr(0,1,str2); } } else // 如果红外传感未探测到火焰 { if(flag == 1) // 如果之前有报警 { flag = 0; // 清除报警标志位 beep = 0; // 关闭蜂鸣 LcdWriteCom(0x01); // 清屏 LcdShowStr(0,0,str3); // 显示"NO FIRE." } } } } void delay(uint z) // 延时函数 { uint x,y; for(x=z;x>0;x--) for(y=110;y>0;y--); } void Init_Lcd1602() // LCD1602初始化函数 { LcdWriteCom(0x38); // 显示模式设置,8位数据总线,2行显示,5*7点阵字符 LcdWriteCom(0x0c); // 显示开关控制,显示开,光标关,光标闪烁关 LcdWriteCom(0x06); // 光标/显示移位,光标右移,字符不移动 LcdWriteCom(0x01); // 清屏,光标回到起始位置 } void LcdWriteCom(uchar com) // LCD1602写命令函数 { RS = 0; // 命令模式 RW = 0; // 写入模式 P2 = com; // 写入数据 EN = 1; // 使能 _nop_(); // 空操作 EN = 0; // 禁能 delay(1); // 延时 } void LcdWriteData(uchar dat) // LCD1602数据函数 { RS = 1; // 数据模式 RW = 0; // 写入模式 P2 = dat; // 写入数据 EN = 1; // 使能 _nop_(); // 空操作 EN = 0; // 禁能 delay(1); // 延时 } void LcdShowStr(uchar x,uchar y,uchar *str) // LCD1602显示字符串函数 { uchar i=0; if(y==0) LcdWriteCom(0x80+x); // 第1行 else if(y==1) LcdWriteCom(0xc0+x); // 第2行 while(str[i] != '\0') // 循环显示字符串 { LcdWriteData(str[i]); i++; } } ``` 在上述代码中,红外传感数据引脚接在P3.7上,蜂鸣控制引脚接在P2.3上,LCD1602显示屏的命令/数据选择引脚、读/写选择引脚和使能引脚分别接在P0.0、P0.1和P0.2上。在主函数中,程序会不断检测红外传感的状态,如果探测到火焰,就会开启蜂鸣显示报警信息到LCD1602上,如果未探测到火焰,则关闭蜂鸣显示"NO FIRE."。需要注意的是,本示例中的报警信息和警告信息都是静态的,实际应用中可以根据具体需求进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值