前言
最近学51单片机学习到红外遥控解码与发送部分,开发板的相关教程只有NEC协议的解码,基本的解码套路是1838接收头输出管脚接单片机外部中断0,当接收到红外信号时产生下降沿触发中断,在中断函数中先延时9ms判断电平再延时4.5ms判断电平,从而跳过引导码;再分别延时560us、1690us左右不等的时间判断电平来解码“0”或“1”,直到结束;红外发送思路就是根据NEC协议及红外码值的二进制码分别控制高低电平,并延时相应的时间。但存在这么几个问题:
1. 解码逻辑写死在中断处理函数中,不方便扩展、移植;
2. 只能解码NEC协议的红外遥控信号,如果拿一款别的遥控器来,编码协议未知,整个程序就无能为力了;
3. 接收到信号时实时解码,没有保存红外波形信息,不能输出波形进行分析;
4. 只能发送NEC协议的红外遥控信号。
恰逢外地出差,带了开发板却没带NEC的红外遥控器,手边只有空调、电视遥控器和一个带红外遥控功能的手机,于是就想利用51单片机做一个通用的红外遥控信号录波、解码、发送为一体的程序,直接录制红外波形,发送时也是直接按原波形发送,这样就做到了万能红外信号的学习与发送。同时将录制的波形数据发送到上位机进行显示、分析,这样就算拿到一款未知红外协议的遥控器,也可以做它的协议分析了。PS:本人没有示波器、逻辑分析仪,有这些装备的同学请随便看看。
红外遥控基本原理
红外发射和接收的原理就不细说了,网上很多,也可以参见这篇文章《全面了解红外遥控(中文版)》,这是一个歪果仁写的,网友翻译,讲了基本原理,也介绍了各种常见的协议。
需注意的是通常的介绍协议时说的表示逻辑“0”或“1”的高低电平是针对发射端的,而常用的一体化红外接收头如HS0038、VX1838等在无红外信号是输出高电平,有红外信号时输出低电平,也就是与发射端时相反的——发射端高电平发射红外线,接收端接收后产生低电平。这在解码时必须注意。
红外遥控录波硬件系统
为了尽可能的提高录波时的分辨率,采用了1T模式的STC12C5A60S2单片机,之所以用STC12系列而没用更快的STC15系列时因为12系列DIP40封装与传统8051完全兼容,直接插51开发板上就能用。红外遥控接收头为HS0038,输出管脚接P3.2口(原理图中红外接收头只是随便找了个相近的元件做示意)。采用LCD1602做简单显示。原理图如下:
红外遥控录波程序实现
本文章内只贴出关键程序,完整程序请点击下载,编译环境Keil4。
原理:HS0038输出管脚接INT0中断,下降沿触发。当接收到红外信号后,HS0038输出管脚为低电平,进入中断处理函数,立即启动定时器0,等待红外输出管脚变为高电平,记录低电平时间;然后重置定时器0,等待红外输出管脚变为低电平,记录高电平时间;如此往复,直到某次等待超时或记录时间的数组已用完。
录制的波形数据保存到一个unsigned char数组中,两两一组,以低电平开始(针对接收端而言),交替表示低电平、高电平的持续时间。格式为:
0x04, 0x24, //低字节在前,实际数据为0x2404,低电平持续时间的计数值 0x84, 0x11, //低字节在前,实际数据为0x1184,高电平持续时间的计数值 ...
该段程序不仅可以录制红外波形,还可以做简易的逻辑分析仪使用。录制波形时定时器0的计时时间为1us,所以该段程序的录波理论最小分辨率为1us,但由于中间计算过程等耗时会产生误差,所以最好用来录制电平持续时间大于10us的脉冲波形。
录波的流程图如下:
//硬件 //@单片机 : STC12C5A60S2 //@晶振 : 12.0MHz
void InitTimer0() //定时器0初始化 { ET0 = 1; AUXR &= 0x7f; //定时器时钟12T模式,1us TMOD &= 0xF0; //设置定时器模式 TMOD |= 0x01; //设置16位定时器 TL0 = 0x00; //设置定时器初值 TH0 = 0x00; //设置定时器初值 TF0 = 0; //清除TF0标志 TR0 = 0; //暂不开启定时器0计时 PT0 = 1; //高优先级,必须,否则在外部中断0中就不能执行定时器0是否超时溢出 } void Timer0Interrupt(void) interrupt 1 using 1 { timer0Overflow = true; //超时溢出标志 } void InitINT0() { EX0 = 1; //打开中断0 IT0 = 1; //1——下降沿触发;0——低电平触发 } void INT0Interrupt() interrupt 0 using 2 { UINT8 i; UINT8 cH, cL; TR0 = 1; //定时器0开始计数 EX0 = 0; //关闭外部中断0的中断响应 usedLength = 1; //如果没有接收到有效信号,串口发送1次共4字节数据,用来跟串口收发失败的情况区分 for (i = 0; i < MAX_BUFFER_LENGTH / 4; i++) { while (!IR_In) { if (timer0Overflow) { usedLength = i; goto endfor; //65ms,超时,跳出循环 } } TR0 = 0; cL = TL0; //取定时数据 cH = TH0; TL0 = 0x00; //初始化 TH0 = 0x00; timer0Overflow = false; TR0 = 1; //定时器0开始计数 waveData[4 * i + 0] = cL; waveData[4 * i + 1] = cH; while (IR_In) { if (timer0Overflow) { usedLength = i; goto endfor; //65ms,超时,跳出循环 } } TR0 = 0; cL = TL0; //取定时数据 cH = TH0; TL0 = 0x00; //初始化 TH0 = 0x00; timer0Overflow = false; TR0 = 1; //定时器0开始计数 waveData[4 * i + 2] = cL; waveData[4 * i + 3] = cH; usedLength = i; } endfor: TR0 = 0; //关闭定时器0 timer0Overflow = false; TL0 = 0x00; TH0 = 0x00; if (usedLength > 2) //至少录制了一组有效数据,显示录制的数据长度 { Lcd1602Clear(); setPos(0, 0); writeData('L'); writeData(':'); writeData((usedLength + 1) * 4 / 100 + '0'); writeData((usedLength + 1) * 4 / 10 % 10 + '0'); writeData((usedLength + 1) * 4 % 10 + '0'); } IE0 = 0; //若接收信号过程中产生了下降沿,IE0则为1,此处需清除外部中断0的中断标志 EX0 = 1; //打开外部中断0的中断响应 }
按键发送数据,同时添加了一个按键做清空缓存数组用,程序如下:
void main() { UINT16 n; InitSys(); while (1) { Key_Send = 1; if ( Key_Send != 1) { DelayX10ms(1); Key_Send = 1; if (Key_Send != 1) { for (n = 0; n < usedLength; n++) { //将波形数据串口发送到上位机 UartSendByte(waveData[4 * n + 0]); UartSendByte(waveData[4 * n + 1]); UartSendByte(waveData[4 * n + 2]); UartSendByte(waveData[4 * n + 3]); } while (!Key_Send); //等待弹起 } } Key_Clear = 1; if ( Key_Clear != 1) { DelayX10ms(1); Key_Clear = 1; if (Key_Clear != 1) { //清空波形缓存数组 for (n = 0; n < MAX_BUFFER_LENGTH; n++) { waveData[n] = 0; } SystemReady(); while (!Key_Send); } } } }
上位机红外波形分析
录制完波形后,波形数据通过串口发送到上位机,得到类似下图的十六进制数据,进行数据处理后就可以进行分析解码了。
为方便分析,我用C#简单写了个小程序,可以很方便的绘制波形,并将每帧的2字节数据直接转换为时间长度,方便对照各种红外协议分析。如下图,该段红外信号已9220us的高电平开始,紧随一个4484us的低电平,与NEC协议中“9ms高电平+4.5ms低电平”的引导码格式相符,分析其后面的电平持续时间,可知这段红外信号为NEC格式信号。
欢迎关注本人的个人博客YoungCoding.top