红外遥控器使用的是红外LED,人眼看不到。
红外遥控器的电路有两种。
**第一种**:有两个输入,第一个输入:控制38MHZ,第二个输入控制高低电平(低电平才会使红外LED发光),发光时LED是以38MHZ闪烁的。这两个输入个通过一个PNP型三极管控制LED的亮灭
第二种:只有一个输入,这个输入包括了频率及高低电平,低电平会有38MHZ的抖动。同样也有一个三极管控制LED的亮灭。这两种就是信号的调制电路。
为什么需要信号的调制呢?
这是因为生活中的干扰红外信号太多了,所以信号需要调制,我们只要找到我们对应频率的信号解调就可以了(感觉可以理解为红外发射器和接收器之间的一种通讯协议)。
当然,对于调制和解调是硬件自动完成的。调制就是上面所说的调制电路,解调是有一个单独的解调模块,我们单片机只需要关注解调模块的输出脚电平即可。解调后的信号和调制前的信号一模一样。
解调模块:
上面的信息不想知道也可,下面的必须 了解嗷。
在解调模块连接了我们的外部中断,原因就是我们不能够使用 if 等语句来判断是什么电平,这样的话就太慢了,所以我们需要中断,这里不使用定时器中断是因为这个中断是突发的不是有规律的。
红外信号就是高电平代表1,低电平代表0嘛?肯定不是
红外信号是使用 NEC编码 来表示0 1 的
以下是NEC编码的内容:
开始(start):
0:
1:
重复(repeat):
数据发送格式:
地址反码和命令反码都是用来校验数据是否准确的。
那么接下来就到了代码思路了:我们可以看到代码当中不仅需要外部中断,还需要定时器来判断是0 是1 是start 还是 repeat。我们的外部中断选用的是下降沿触发,定时器是12MHZ的晶振频率,定时器0,16位计数器,定时器时钟12
我们选择使用状态机的思维,分别给0,1,2三种状态
0状态:空闲状态,进入中断,我们要改为状态1
状态1:根据定时计数器的数值,判断是start还是repeat信号,start信号将状态改为状态2,repeat信号将状态改为0
状态2:我们根据定时计数器的数值,判断是0 还是 1,然后再对应的给上面**数据发送格式**的四个字节,来进行每一位的赋值,在赋值的时候我们需要一个指针,通过这个指针指到对应的某一位进行置0 或 置1的操作,指针是通过移位来完成的(注意:移位的距离不够超过16位,否则可能会出错),这里使用数组存每一个字节。当指针指到了第32位时,就可以返回数据,或者接受错误开始重新接收了。
好了,来看看代码吧
//状态机的代码思路
#include <REGX52.H>
#include "INT0.h"
#include "Timer0.h"
unsigned char IR_RepeatFlag; //重复标志位
unsigned char IR_Data[4]; //四个数据位
unsigned char IR_pData; //当前指针已经走到了哪一位,总共四个字节的数据即32位
unsigned char IR_State; //表明当前的状态
unsigned int IR_Time; //记入此时定时器计数器的值
unsigned char IR_Address; //记入地址
unsigned char IR_Num; //记录接收的值
unsigned char IR_DataFlag; //成功的接收到了数据
/*
红外遥控初始化
*/
void IR_Init(void)
{
INT0_Init();
Timer0_Init();
}
/*
返回我们的地址
*/
unsigned char IR_GetAddress(void)
{
return IR_Address;
}
/*
返回我们的数据
*/
unsigned char IR_GetNum(void)
{
return IR_Num;
}
/*
返回我们的重复标志
*/
unsigned char IR_GetRepeatFlag(void)
{
if(IR_RepeatFlag == 1)
{
IR_RepeatFlag = 0; //将重复标志位清零
return 1; //返回重复
}
return 0; //返回不重复
}
/*
返回我们接收到数据的标志位
*/
unsigned char IR_GetDataFlag(void)
{
if(IR_DataFlag == 1)
{
IR_DataFlag = 0;
return 1;
}
return 0;
}
/*
在外部中断函数当中,根据状态来判断我们代码的执行,这里是外部中断,下降沿执行
0表示空闲状态
1表示进入了判断是开始还是重复的状态
2表示是开始这时候就可以进行对数据的操作了.
*/
//!(这个函数我们全程状态为0的时候才会启动和关闭定时器,数据读取完成之后关闭定时器),!(这里的判断当中口号内没有等号)
void Int0_Routine(void) interrupt 0
{
if(IR_State == 0) //下降沿进入状态1,出中断函数,然后根据下一个下降沿的中断来判断是开始还是重复信号
{
IR_State = 1;
Timer0_SetCounter(0); //初始化我们计数器的值为0
Timer0_Run(1); //开启定时器
}
else if(IR_State == 1)
{
IR_Time = Timer0_GetCounter(); //获取当前计数器的值,根据当前计数器的值来判断是开始信号还是我们的重复信号
Timer0_SetCounter(0);//!(我们只需要清零即可,不需要关掉定时器)
if(IR_Time > 13500 - 500 && IR_Time < 13500 + 500) //因为定时器不一定准确,因此我们可以给一个500的上下浮动范围
{
IR_State = 2; //当为开始的时候只需要将我们的状态改为2,等待下一次下降沿的触发即可
}
else if(IR_Time > 11250 - 500 && IR_Time < 11250 + 500)
{
IR_State = 0; //当为重复的时候再重新开始
Timer0_Run(0); //!此时状态0需要关掉定时器,再等待状态0进中断的时候开启
IR_RepeatFlag = 1; //重复标志位置1,我们可以使用这个标志位再main函数当中用来判断是不是一直再按某个红外遥控器的按键
}
else //!接收出错,可能都不在这两个时间区间,那么就会走到这里
{
IR_State = 1;
}
}
else if(IR_State == 2) //状态2接收数据
{
IR_Time = Timer0_GetCounter();
Timer0_SetCounter(0); //定时器清零
if(IR_Time > 1120 - 500 && IR_Time < 1120 + 500) //表示0,此时我们需要进行将对应位置0的操作
{
IR_Data[IR_pData / 8] &= ~(0x01 << (IR_pData % 8));
IR_pData ++;//!(忘记写了)数据位指针自增
}
else if(IR_pData > 2250 - 500 && IR_pData < 2250 + 500) //表示1,此时我们需要进行将对应位置1
{
IR_Data[IR_pData / 8] |= (0x01 << (IR_pData % 8));
IR_pData ++;//!(忘记写了)数据位指针自增
}
else //!(没判断)接收出错
{
IR_pData=0; //数据位置指针清0
IR_State=1; //置状态为1
}
if(IR_pData == 32) //当我们已经完全的读取完了之后我们就需要再将数据位指针变为0
{
IR_pData = 0;
if(IR_Data[0] == ~IR_Data[1] && IR_Data[2] == ~IR_Data[3]) //地址、数据进行校验
{
IR_DataFlag = 1; //数据接收标志位置1
IR_Address = IR_Data[0]; //地址存储
IR_Num = IR_Data[2]; //数据存储
}
//!(没有写)
Timer0_Run(0); //定时器停止
IR_State=0; //置状态为0
}
}
}