红外遥控系统简介
红外遥控系统是利用红外光进行通信的设备,通常由发射和接收两大部分组成,即:由红外LED将调制后的信号发出,再由专门的红外接收头进行解调输出。
红外LED:外表与普通的LED没有什么不同,发射940nm的红外光,人眼看不到。也有850nm的红外LED,可以看见微微的红光。
通讯方式:单工,只能一方发送一方接受,不能反过来;异步,没有单独的时钟线,通信需要双方约定时间。
通信协议标准:NEC标准(我国常用)、RC5、SONY、REC80、SAMSWNG等,主要是欧洲和日本生产厂家所使用的编码格式。
生活中,红外LED被广泛应用,比如在电视、空调和电扇的遥控里都有找到红外LED的身影。这些设备之所以能被控制,是因为内部有与之匹配的红外接收二极管,红外接收二极管的颜色一般是黑色的,当红外接收二极管被红外光照射阻值会变小。
硬件电路
1、红外发送部分
主要由如下两种电路结构所示:
对于第一个电路,只有当两个三极管开关同时打开时,红外LED才发光。其中第一个接口的38KHz为调制频率,在发送信号时需要一直给这个接口输入一个频率为38KHz的方波;第二个接口就输入我们想要传输的波形。
将两个接口互相联系起来,会发现:在高电平时,红外LED不会发光;在低电平时,红外LED以38KHz频率闪着亮。(这是为了抵抗干扰,在自然界中有很多红外光,例如太阳光,而调制的目的就是为了区分自然光与作为信号的红外光。)
对于第二个电路,没有了38KHz接口,只是一个纯粹的三极管开关(给低电平亮、给高电平不亮),在发送的时候需要程序来实现38KHz调制的功能。
2、红外接收部分
接收部分有单独的一个红外接收LED(黑色),如果直接用红外接收管,则接受到的信号就会有很多成分(自然光等),因此后面还需要进行电路操作(例如滤除自然光、放大发送的信号、滤除38KHz部分的信号,使其输出信号看上去和IN端输入信号一致)。一体化红外接收管就包含了红外接收LED和这些功能电路。
注意,需要将输出信号OUT接在外部中断上。
基本发送与接收
空闲状态:红外LED不亮,接收头输出高电平。
发送低电平:红外LED以38KHz频率闪烁发光,接收头输出低电平。
发送高电平:红外LED不亮,接收头输出高电平。
接收装置如何区分空闲状态和发送高电平?
接收装置以第一个下降沿为开始,在此之后的一段时间内其输出波形都是发送装置所发送的波形(此时红外LED不亮为高电平输出),波形结束后又一直输出高电平(此时红外LED不亮为空闲状态)。
注意,此处的高电平和低电平都不代表逻辑1或0,仅仅是指物理现象本身。
NEC编码
NEC编码是一种红外遥控协议,常用于遥控器与设备之间的通信。它是一种常用的编码格式;将遥控发送过来的信号进行一定形式的编码,转换为对应的信息。
如上所示,这是一帧完整的红外信号(注意是红外信号,即这是从发送装置来看,对应输出信号波形高低电平与之相反),其中黄色区域既不是高电平也不是低电平,而是38KHz的高频脉冲信号。
可以看出,一帧完整的红外信号包括:起始位、地址码、地址反码、数据码、数据反码、结束位。
1、起始位:持续低电平(高频脉冲)9ms后持续高电平(不亮)4.5ms,表示要开始传输数据了。
2、地址码:由8位0或1表示,用于确定(选定)设备(比如家里面有很多红外设备,不同的红外设备其地址码是不同的。)其逻辑0或1表示如下:
逻辑0是由562.5us的高频脉冲和562.5us的不亮表示;逻辑1是由562.5us的高频脉冲和1687.5us的不亮表示。对于上上图,其地址码为:11110000。
3、地址反码:对地址码进行取反,因而地址反码为:00001111。地址反码是为了保证传输的准确性,一旦有一个地址反码和地址码对不上号,这这一帧数据都将作废。
4、数据码:同样由8个0或1组成,对于上上图,其数据码为:1111 1111。其包含的信息为所匹配红外设备需要执行的功能。通过8个0或1的排列组合,有256个不同的结果,也就是说红外遥控器最多有256个按键。
5、数据反码:对数据码进行取反。
6、结束位:持续高频脉冲562.5us,表示数据传输结束。
7、重复码:持续低电平9ms后持续不亮4.5ms,在持续562.5us的高频脉冲,在然后又是持续不亮。每个一帧数据的时间约是110ms。(重复码通常用于调音量时的长按)
51单片机的外部中断
以STC89C52为例,有4个外部中断(传统51单片机只有两个)。其外部中断有两种触发方式:下降沿触发和低电平触发。
其外部中断0(INT0)和外部中断1(INT1)外部中断号分别为0、2。
代码设计
Timer0.c
#include <REGX52.H>
//定时器初始化
void Timer0_Init()
{
TMOD&=0xF0; //设置定时器模式
TMOD|=0x01; //设置定时器模式
TL0=0x18; //设置定时器初值
TH0=0xFC; //设置定时器初值
TF0=0; //清除TF0标志
TR0=0; //定时器0不计时
}
//设置定时器初始值
void Timer0_SetCounter(unsigned int Value)
{
TH0=Value/256;
TL0=Value%256;
}
//把定时器里变化后的数据拿出来,得到时间差
unsigned int Timer0_GetCounter()
{
return (TH0<<8)|TL0;
}
//定时器启动开关
void Timer0_Run(unsigned char Flag)
{
TR0=Flag; //Flag为1,开始计时;为0,停止计时
}
Int0.c
#include <REGX52.H>
//外部中断0初始化
void Int0_Init()
{
IT0=1;
IE0=0;
EX0=1;
EA=1;
PX0=1; //优先级设为高优先级
}
此处需要将外部中断0的中断优先级设置为高优先级,保证在同时触发多个中断时优先执行此中断,使红外信号被及时接收。
IR.c
#include <REGX52.H>
#include "Timer0.h"
#include "Int0.h"
unsigned int IR_Time; //计时(时间差)
unsigned char IR_State; //状态
unsigned char IR_Data[4]; //存储数据
unsigned char IR_pData; //0~31,即一个完整的信号有32位
unsigned char IR_DataFlag; //数据帧标志位
unsigned char IR_RepeatFlag; //连发帧标志位
unsigned char IR_Address; //地址码
unsigned char IR_Command; //数据码
//红外遥控初始化
void IR_Init()
{
Timer0_Init();
Int0_Init();
}
/**
*功能:红外遥控获取收到数据帧标志位
*参数:无
*返回值:是否收到数据帧,1为收到,0为未收到
*/
unsigned char IR_GetDataFlag()
{
if(IR_DataFlag)
{
IR_DataFlag=0;
return 1;
}
return 0;
}
/**
*功能:红外遥控获取收到连发帧标志位
*参数:无
*返回值:是否收到连发帧,1为收到,0为未收到
*/
unsigned char IR_GetRepeatFlag()
{
if(IR_RepeatFlag)
{
IR_RepeatFlag=0;
return 1;
}
return 0;
}
/**
*功能:红外遥控获取收到的命令数据
*参数:无
*返回值:收到的命令数据
*/
unsigned char IR_GetCommand()
{
return IR_Command;
}
/**
*功能:红外遥控获取收到的地址数据
*参数:无
*返回值:收到的地址数据
*/
unsigned char IR_GetAddress()
{
return IR_Address;
}
//外部中断0函数,下降沿触发
void Int0_Routine() interrupt 0
{
if(IR_State==0) //状态为0,说明是空闲状态
{
Timer0_SetCounter(0); //设置定时器初始值
Timer0_Run(1); //开始计时
IR_State=1; //进入状态1,即准备接收信号
}
else if(IR_State==1)
{
IR_Time=Timer0_GetCounter(); //读取计时的时间(时间差)
Timer0_SetCounter(0); //定时器清0,重新计时间差
//计时为13.5ms,则收到了起始位(注意,11.0592MHz下为12442)
if(IR_Time>12442-500&&IR_Time<12442+500) //如果是起始位
{
IR_State=2; //进入状态2,即开始接收数据
}
//计时为11.25ms,则收到了重复位(同样,11.0592MHz下为10368)
else if(IR_Time>10368-500&&IR_Time<10368+500)
{
IR_RepeatFlag=1; //说明这一帧已经结束
Timer0_Run(0); //结束计时
IR_State=0; //回到空闲状态
}
//接收出错
else
{
IR_State=1;
}
}
else if(IR_State==2)
{
IR_Time=Timer0_GetCounter(); //读取计时的时间(时间差)
Timer0_SetCounter(0); //定时器清0
//计时为1120us,则收到了逻辑0(注意,11.0592MHz下为1032us)
if(IR_Time>1032-500&&IR_Time<1032+500)
{
IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8)); //数据对应位 置0
IR_pData++;
}
//计时为2250us,则收到了逻辑1(注意,11.0592MHz下为2074us)
else if(IR_Time>2074-500&&IR_Time<2074+500)
{
IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8)); //数据对应位 置1
IR_pData++;
}
//如果接受失败,这一帧作废
else
{
IR_pData=0;
IR_State=1;
}
if(IR_pData>=32) //表示32位数据接受完毕
{
IR_pData=0; //重新置0
if((IR_Data[0]==~IR_Data[1])&&(IR_Data[2]==~IR_Data[3]))
{
IR_Address=IR_Data[0]; //数据转移
IR_Command=IR_Data[2]; //数据转移
IR_DataFlag=1;
}
Timer0_Run(0); //定时器停止
IR_State=0; //置状态为0
}
}
}
此代码主要通过下降沿之间的时间差来判断逻辑0、逻辑1、起始位、重复位。
对于重复位直接返回状态0即可(不需要再进入状态2中改变地址码和数据码),并且在下一次下降沿到来时在进行判断时起始位还是重复位。
对于起始位,则让其进入状态2,记录地址码与数据码。