基于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;
}
}
}
四、反思和总结
现在整体写下来竟然觉得不是很难,但做的过程中确实是遇到很多的问题,比如有:
- 江科大给的LCD1602数据显示函数是有bug的,显示139*256时会在结果最前面加上20,害我找了好久
- 数据是获取到了,但是出现了乱码,这是为什么?可能是因为在状态1的时候接收到了01数据帧,发生问题后又没有正确地将state设置为1,导致后续接收数据时出现了位置错乱。
- 连续按好几下也没有反应,数据正确与否全靠缘分?可能是因为部分代码正确,但是处理错误代码发生问题,比如start&repeat位出错,获取数据出错的时候没有解决好。