4.4 NEC红外线遥控器解码
4.4.1 接收头原理图介绍
实验板上的红外线接收头是接在单片机的P3.2 IO口上,要使用红外线接收功能,需要将红外线接收头的跳线帽接上。
4.4.2 NEC红外线协议介绍
红外线遥控是目前使用最广泛的一种通信和遥控手段。由于红外线遥控装置具有体积小、功耗低、功能强、成本低等特点,因而,继彩电、录像机之后,在录音机、音响设备、空凋机以及玩具等其它小型电器装置上也纷纷采用红外线遥控。工业设备中,在高压、辐射、有毒气体、粉尘等环境下,采用红外线遥控不仅完全可靠而且能有效地隔离电气干扰。
红外线是波长介于微波和可见光之间的电磁波,波长在 760 纳米到 1 毫米之间,是波形比红光长的非可见光。
家电遥控器通信距离往往要求不高,而红外的成本比其它无线设备要低的多,所以家电遥控器应用中红外始终占据着一席之地。遥控器的基带通信协议很多,大概有几十种,常用的就有 ITT 协议、 NEC 协议、 Sharp 协议、 Philips RC-5 协议、 Sony SIRC 协议等。用的最多的就是 NEC 协议了,本小节以 NEC协议标准进行讲解,实验板自带的遥控器也是NEC协议的。
NEC协议,通常是使用 38K左右的载波进行调制,再发送的;这里的调制就是用待传送信号去控制某个高频信号的幅度、相位、频率等参量变化的过程,即用一个信号去装载另一个信号。比如:我们的红外遥控信号要发送的时候,先经过 38K 调制。
原始信号就是要发送的一个数据“0”位或者一位数据“1”位,而38K 载波就是频率为 38K 的方波信号,调制后信号就是最终我们发射出去的波形。使用原始信号来控制 38K 载波,当信号是数据“0”的时候, 就发送38K载波,当信号是数据“1”的时候,就不发送任何载波信号,那么数据接收方只需要检测是否收到38KHZ的载波就行判断是否收到了数据。实验板上带了一个一体化接收头HS0038B,可以识别38KHZ的载波信号,当HS0038B收到38KHZ载波就输出低电平,没有收到38KHZ载波就输出高电平;程序就可以判断HS3008的引脚电平配合指定的协议格式来分析收到的数据。
NEC协议的数据格式包括了引导码、用户码、用户码反码、按键码、按键反码。
其中数据编码总共是4个字节 32 位(除了引导码外就是数据位),数据格式中的反码用于数据校验,可以判断数据在传输过程中有没有丢包。
NEC协议解码总结:
引导码:9ms的低电平+4.5ms的高空闲。
逻辑 1: 560us 低+1680us 高电平
逻辑 0: 应该是 560us 低+560us 高电平。
遥控接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平。
红外线遥控器向红外线接收头按下一个按键之后,红外线接收头先收到的是9ms的高电平脉冲,接着是4.5ms的低电平,之后就是接收8bit的用户码、8bit的用户码的反码,8bit 的按键码,8bit 的按键码的反码。如果一直按着1个键,这样遥控器发送的是以110ms为周期的重复码,就是说,发了一次按键码之后,不会再发送按键码,而是每隔110ms时间,发送一段重复码,重复码由9ms高电平和2.25ms的低电平以及560us的高电平组成。
4.4.3 采集NEC协议的信号
为了更加直观的理解NEC协议格式,可以使用逻辑分析仪或者示波器采集HS0038红外线接收头收到的数据,再通过软件分析收到的数据。
逻辑分析仪软件下载地址: Logic analyzer software from Saleae
4.4.4 NEC红外线协议解码示例
下面代码采用定时器+外部中断的方式解码红外线数据,外部中断采用外部中断0,接在P3.2口上,配置外部中断0的触发方式为下降沿触发。
解码思路: 红外线接收头没有收到38KHZ方波时,默认输出高电平,当收到38KHZ方波时输出低电平,这时就触发了下降沿,接着就进入到外部中断0 的中断服务函数;解码的过程就在中断服务函数里实现,高低电平的持续时间通过定时器0进行计数,当前实验板的晶振是12MHZ,刚好定时器的计数器+1的时间就是1us,配置定时器0的工作方式为16位模式,最大计数可以到65535,NEC协议最长的一段时间也就9000us,完全满足需求;解码完成之后设置标志位,在主函数里将解码的数据通过串口打印出去。
(硬件平台说明:CPU是STC90C516RD 、晶振频率12MHZ 、工作在12T模式下、一个机器周期为1us时间)
示例代码:
#include <reg51.h> u8 Infrared_RX_Flag=0; //红外接收标志,收到一帧正确数据后置1 u8 Infrared_RX_Buff[4];//红外代码接收缓冲区 sbit Infrared_GPIO=P3^2;//红外接收引脚--外部中断0 /* 函数功能: 开始红外线解码之前的相关初始化 实验板的晶振频率是12MHZ 51单片机标准架构下一个机器周期是12个时钟周期,如果晶振频率是12MHZ,那一个机器周期的时间就是12/12微秒。 也就是说定时器的计数器+1的时间就是12/12=1us。 */ void Infrared_Init(void) { Infrared_GPIO=1;//红外接收引脚默认保持高电平输出 TMOD&=0xF0; //清除配置 TMOD|=0x01; //配置定时器0,工作在16位计数模式 TR0=0; //停止定时器0计数 ET0=0; //禁止定时器0中断 IT0=1; //开启外部中断0,下降沿触发 EX0=1; //允许外部中断0中断 } /* 函数功能: 检测高电平持续的时间 */ u16 Infrared_GetTimeH(void) { TH0=0; //定时器0重装值为0 TL0=0; //定时器0重装值为0 TR0=1; //启动定时器0开始计数 while(Infrared_GPIO)//等待高电平结束 { if(TH0>0x40)//防止超时 { break; } } TR0=0;//停止定时器0计数 return TH0<<8|TL0;//T0计数值合成为16位整数返回 } /* 检测低电平持续的时间 */ u16 Infrared_GetTimeL(void) { TH0=0;//定时器0的高8位重装值 TL0=0;//定时器0的低8位重装值 TR0=1;//开启定时器0 while(Infrared_GPIO==0)//等待低电平结束 { if(TH0>0x40)//防止超时 { break; } } TR0=0;//停止定时器0计数 return TH0<<8|TL0;//T0计数值合成为16位整数返回 } /* 外部中断0中断服务函数 */ void EXTI0_IRQHandler() interrupt 0 { u8 i, j; u16 time; u8 byte; time=Infrared_GetTimeL(); //获取出现低电平的时间 if((time<7800)||(time>9300))//判断低电平时间是否符合9ms范围 { //超过此范围则说明为误码,直接退出 IE0=0; //清除外部中断0中断标志 return; } time=Infrared_GetTimeH(); //获取出现高电平的时间 if((time<3500)||(time>4700))//高电平是否符合4.5ms范围 { //超过此范围则说明为误码,直接退出 IE0=0; //清除外部中断0中断标志 return; } //接收32位数据位 for(i=0;i<4;i++) { for(j=0;j<8;j++) { time=Infrared_GetTimeL(); //获取低电平持续时间,标准的间隔时间为560us范围 if((time<300)||(time>700)) //判断范围是否合理 { IE0=0;//清除外部中断0中断标志 return; } //1和0是靠高电平持续的长短来区分的 time=Infrared_GetTimeH(); //获取高电平持续时间 if(time>300&&time<700) //0的标准时间为560us { byte>>=1; } else if(time>1400&&time<1800) //1的标准时间是1680us { byte>>=1; byte|=0x80; } else //不在上面的判断范围内说明是错误码,直接退出 { IE0=0;//清除外部中断0中标 return; } } Infrared_RX_Buff[i]=byte;//接收完一个字节后保存到缓冲区 } Infrared_RX_Flag=1;//接收完毕后设置标志 IE0=0;//退出前清除外部中断0中断标志 } int main() { UART_Init(); //初始化串口波特率为4800 Infrared_Init(); //初始化红外功能 while(1) { if(Infrared_RX_Flag) //接收到红外数据 { Infrared_RX_Flag=0; //清楚标志 printf("user1:%d,user2:%d\r\n",(int)Infrared_RX_Buff[0],(int)((u8)(~Infrared_RX_Buff[1]))); printf("key1:%d,key2:%d\r\n",(int)Infrared_RX_Buff[2],(int)((u8)(~Infrared_RX_Buff[3]))); } } }