简单介绍
红外遥控技术是一种基于红外线通信的无线遥控技术,具有体积小、传输距离远、传输速率快等特点,广泛应用于家用电器、车载电子、智能家居等领域。
单片机作为控制中心,可以很好地控制红外遥控的发送和接收。红外遥控技术利用红外线通信技术进行信息传输。遥控器将按键信息编码后,通过红外LED发射出去,接收器接收到信号后进行解码并执行相应的命令。红外遥控编码格式有多种,常见的有NEC码、RC5码、RC6码等。本篇只介绍NEC码。
单片机可以通过外部中断、定时器等方式进行红外遥控的接收和解码。接收到的红外信号需要进行解码,得到对应的按键信息,从而实现红外遥控功能。
注意:通信方式为单工,异步。
原理
模式1:只有当两个信号波同时为低电平,才可以导通三极管,相当于两种波混合在一起,进行调制红外波形。
为什么要这样做?答案是太阳光会影响,这是为了提高我们信号的抗干扰性。
之后将收到的信号波在右边的矩形框里进行解调(具体可以百度)!
可以将模式2理解为模式1将波形处理后的结构,将IN引脚用于外部中断进行处理。
时序可以用这张表作为参考!
采集两个下降沿之间的时间,并按照上表进行模拟!
代码分析
上面这幅思维导图就是我们的代码逻辑,开始吧。
外部中断
void Int3_Init(void)
{
IT3=1;
IE3=0;
EX3=1;
EA=1;
PX3=1;
}
配置外部中断3(可以随便选)为下降沿触发,IE3也可以选择不写。
定时器
void Timer0_Init(void)
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0; //设置定时初值
TH0 = 0; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 0; //定时器0不计时
}
可以直接用软件生成代码,要注意选16位不自动重装载(所以TF0 = 0可以不加),因位普通51没有16位重装载模式,我们要用下降沿计数来模拟红外遥控时序,并且不开启定时器中断。因此我们要把定时器一些参数初值全初始化为0!
红外NEC时序模拟
void IR_Init(void)
{
Timer0_Init();
Int3_Init();
}
相信初始化就没必要说了。
#include "main.h"
uint Time_Count = 0;
bit Recv_Repeat_Dat_Flag = 0;//数据接受标志位
bit Recv_Dat_Flag = 0;//数据帧写入标志位
uchar Dat[4] = {0};
uchar Dat_Index = 0;// 0 - 31
uchar IR_Cmd = 0;//红外按键命令
uchar IR_Address = 0;//红外按键地址
enum myenum
{
idle_state, //空闲状态
buffer_state, //缓冲状态
collect_state, //采集状态
};
//初始化红外
void IR_Init()
{
Int3_Init();
Timer0_Init();
}
void Int3_Isr(void) interrupt 7
{
static uchar IR_State = idle_state;
switch(IR_State)
{
case idle_state :
Timer0_InitCount(0); //数据从0开始
Is_Timer0_Interrupt(1); //开启定时器
IR_State = buffer_state; //切到缓冲状态
break;
case buffer_state :
Time_Count = Timer0_GetCount(); //获取上一次中断到此次中断的时间
Timer0_InitCount(0); //复位时间值
//Start 如果计时为13.5ms,则接收到了Start信号
if(Time_Count >= 13500 - 400 && Time_Count <= 13500 + 400)
IR_State = collect_state; //切到采集状态
//Repeat 如果计时为11.25ms,则接收到了Repeat信号
else if(Time_Count >= 11250 - 400 && Time_Count <= 11250 + 400)
{
Recv_Repeat_Dat_Flag = 1; //置收到连发帧标志位为1
IR_State = idle_state; //回到空闲状态
Is_Timer0_Interrupt(0); //关闭定时器
}
//!Start &&!Repeat
else
IR_State = buffer_state; //在缓冲状态里,继续判断Start || Repeat
break;
case collect_state :
Time_Count = Timer0_GetCount(); //获取上一次中断到此次中断的时间
Timer0_InitCount(0); //复位时间值
//写0
if(Time_Count >= 1120 - 400 && Time_Count <= 1120 + 400)
{
Dat[Dat_Index / 8] &= ~(0x01 << (Dat_Index % 8));
Dat_Index++;
}
//写1
else if(Time_Count >= 2250 - 400 && Time_Count <= 2250 + 400)
{
Dat[Dat_Index / 8] |= (0x01 << (Dat_Index % 8));
Dat_Index++;
}
//写入数据错误
else
{
Dat_Index = 0;//清除索引值
IR_State = buffer_state;//还是在缓冲状态继续写数据
}
if(Dat_Index >= 32) //说明一个指令发完
{
Dat_Index = 0; //复位索引值
if((Dat[0] == ~Dat[1]) && (Dat[2] == ~Dat[3])) //数据有效,校验成功
{
Recv_Dat_Flag = 1;//数据写入成功
IR_Cmd = Dat[2];//写入命令
IR_Address = Dat[0];//写入地址
}
Is_Timer0_Interrupt(0); //关闭定时器
IR_State = idle_state; //回到空闲状态
}
break;
}
}
分了三个状态:
1、空闲状态:启动定时器开始做准备
2、缓冲状态:获取两个下降沿时间,检测Start 或 Repeat 如果是Start,则进入采集状态,写数据;如果是Repeat状态,则回到空闲状态,沿用上一次的数据
3、采集状态:写0 或 1,并直到校验完32位数据后,回到空闲状态,如果校验成功(地址码等于地址反码 且命令码等于命令反码)采集数据,校验失败,重新开始状态循环。
注意:时序判断要给定一个范围,不然条件太苛刻,时间刚好在那个时刻,很难进入if,同时给定的范围只能保证一个时序,不可与其他判断的时序重叠。
这就是底层逻辑!
完善
为了方便应用层调用,我们可以进行完善!
具体如下:
//判断是否重复接收数据
uchar IS_Recv_Repeat_Dat()
{
if(Recv_Repeat_Dat_Flag)
{
Recv_Repeat_Dat_Flag = 0;
return 1;
}
return 0;
}
//判断是否接收到数据
uchar IS_Recv_Dat()
{
if(Recv_Dat_Flag)
{
Recv_Dat_Flag = 0;
return 1;
}
return 0;
}
//获取地址
uchar IR_GetAddress()
{
return IR_Address;
}
//获取命令
uchar IR_GetCommand()
{
return IR_Cmd;
}
这些就是底层接口,我们只需在应用层用这些函数即可,这就不用关心底层如何实现的了。
调用
就这么用,也可以以此拓展更多业务。