16.1 红外光的基本原理红外线是波长介于微波和可见光之间的电磁波,波长在760纳米到1毫米之间,是波形比红光长的非可见光。自然界中的一切物体,只要它的温度高于绝对零度(-273)就存在分子和原子的无规则运动,其表面就会不停的辐射红外线。当然了,虽然是都辐射红外线,但是不同的物体辐射的红外强度是不一样的,而我们正是利用了这一点把红外技术应用到我们实际开发中。 红外发射管很常用,在我们的遥控器上都可以看到,他类似发光二极管,但是他发射出来的是红外光,是我们肉眼所看不到的。第二课我们学过发光二极管会随着电流的增大亮度逐渐增加,同样的道理,红外发射管会随着电流的增大,红外线的强度越来越强,常见的红外发射管如图16-1所示。 红外接收管内部带了一个具有红外光敏感特征的PN节,属于光敏二极管,但是它只对红外光有反应。无红外光时,光敏管不导通,有红外光时,光敏管导通形成光电流,并且在一定范围内电流随着红外光的强度的增强而增大。典型的红外接收管如图16-2所示。 这种红外发射和接收对管在小车、机器人避障以及红外循迹小车中有所应用,所以暂提供一个原理图给大家作为学习之用,如图16-3所示。 在图16-3这个原理图中,发射控制和接收检测都是接到我们单片机的IO口上。 发射部分:当发射控制输出高电平时,三极管Q1不导通,红外发射管L1不会发射红外信号;当发射控制输出低电平的时候,通过三极管Q1导通让L1发出红外光。 接收部分:R4是一个电位器,也就是“传说”中的滑动变阻器。我们通过调整这个滑动变阻器给LM393的2脚一个阈值电压,这个电压值大小可以根据实际情况来确定。而红外光敏二极管L2收到红外光的时候,会产生电流,并且随着红外光的从弱变强,电流会从小变大。当没有红外光或者说红外光很弱的时候,3脚的电压就会接近VCC,如果3脚比2脚的电压高的话,通过LM393比较器后,接收检测引脚输出一个高电平。当随着光强变大,电流变大,3脚的电压值等于VCC-I*R3,电压就会越来越小,当小到一定程度,比2脚的电压还小的时候,接收检测引脚就会变为低电平。 这个电路用于避障的时候,发射管先发送红外信号,红外信号会随着传送距离的加大逐渐衰减,如果遇到障碍物,就会形成红外反射。当反射回来的信号比较弱时,光敏二极管L2接收的红外光较弱,比较器LM393的3脚电压高于2脚电压,接收检测引脚输出高电平,说明障碍物比较远;当反射回来的信号比较强,接收检测引脚输出低电平,说明障碍物比较近了。 用于小车循迹的时候,必须要有黑色和白色的轨道。当红外信号发送到黑色轨道时,黑色因为吸光能力比较强,红外信号发送出去后就会被吸收掉,反射部分很微弱。白色轨道就会把大部分红外信号返回来。通常情况下的循迹小车,需要应用多个红外模块同时检测,从多个角度判断轨道,根据判断的结果来调整小车使其按照正常循迹前行。 16.2红外遥控通信原理 在实际的通信领域,发出来的信号一般有较宽的频谱,而且都是在比较低的频率段分布大量的能量,所以称之为基带信号,这种信号是不适合直接在信道中传输的。为便于传输、提高抗干扰能力和有效的利用带宽,通常需要将信号调制到适合信道和噪声特性的频率范围内进行传输,这就叫做信号调制。在通信系统的接收端要对接收到的信号进行解调,恢复出原来的基带信号。这部分通信原理的内容,大家了解一下即可。 我们平时用到的红外遥控器里的红外通信,通常是使用38K左右的载波进行调制的,下面我把原理大概给大家介绍一下,了解一下,先看发送部分原理。 调制:就是用待传送信号去控制某个高频信号的幅度、相位、频率等参量变化的过程,即用一个信号去装载另一个信号。比如我们的红外遥控信号要发送的时候,先经过38K调制,如图16-4所示。 原始信号就是我们要发送的一个数据“0”位或者一位数据“1”位,而所谓38K载波就是频率为38K的方波信号,调制后信号就是最终我们发射出去的波形。我们使用原始信号来控制38K载波,当信号是数据“0”的时候,38K载波毫无保留的全部发送出去,当信号是数据“1”的时候,不发送任何载波信号。 那在原理上,我们如何从电路的角度去实现这个功能呢?如图16-5所示。 38K载波,我们可以用455K晶振,经过12分频得到37.91K,也可以由时基电路NE555来产生,或者使用单片机的PWM来产生。当信号输出引脚输出高电平时,Q2截止,不管38K载波信号如何控制Q1,右侧的竖向支路都不会导通,红外管L1不会发送任何信息。当信号输出是低电平的时候,那么38K载波就会通过Q1释放出来,在L1上产生38K的载波信号。这里要说明的是,大多数家电遥控器的38K的占空比是1/3,也有1/2的,但是相对少一些。 正常的通信来讲,接收端要首先对信号通过监测、放大、滤波、解调等等一系列电路处理,然后输出基带信号。但是红外通信的一体化接收头HS0038B,已经把这些电路全部集成到一起了,我们只需要把这个电路接上去,就可以直接输出我们所要的基带信号了,如图16-6所示。 由于红外接收头内部放大器的增益很大,很容易引起干扰,因此在接收头供电引脚上必须加上滤波电容,官方手册给的值是4.7uF,我们这里直接用的10uF,手册里还要求在供电引脚和电源之间串联100欧的电阻,进一步降低干扰。 图16-6所示的电路,用来接收图16-5电路发送出来的波形,当HS0038监测到有38K的红外信号时,就会在OUT引脚输出低电平,当没有38K的时候,OUT引脚就会输出高电平。那我们把OUT引脚接到单片机的IO口上,通过编程,就可以获取红外通信发过来的数据了。 大家想想,OUT引脚输出的数据是不是又恢复成为基带信号数据了呢?那我们单片机在接收这个基带信号数据的时候,如何判断接收到的是什么数据,应该遵循什么协议呢?像我们前边学到的UART、I2C、SPI等通信协议都是基带通信的通信协议,而红外的38K仅仅是对基带信号进行调制解调,让信号更适合在信号中传输。 由于我们的红外调制信号是半双工的,而且同时空间只能允许一个信号源,所以我们红外的基带信号不适合在I2C或者SPI通信协议中进行的,我们前边提到过UART虽然是2条线,但是通信的时候,实际上一条线即可,所以红外可以在UART中进行通信。当然,这个通信也不是没有限制的,比如在HS0038B的数据手册中标明,要想让HS0038B识别到38K的红外信号,那么这个38K的载波必须要大于10个周期,这就限定了我们红外通信的基带信号的比特率必须不能高于3800,那如果把串口输出的信号直接用38K调制的话,波特率也就不能高于3800。当然还有很多其他基带协议可以利用红外来调制,下面我们介绍一种遥控器常用的红外通信协议——NEC协议。 16.3NEC协议红外遥控器 家电遥控器通信距离往往要求不高,而红外的成本比其他无线设备要低的多,所以家电遥控器应用中红外始终占据着一席之地。遥控器的基带通信协议很多,大概有几十种,常用的就有ITT协议、NEC协议、Sharp协议、Philips RC-5协议、Sony SIRC协议等。用的最多的就是NEC协议了,因此我们这节课也以NEC协议标准来讲解一下。 NEC协议的数据格式包括了引导码、用户码、用户码(或者用户码反码)、按键键码和键码反码,最后一个停止位,停止位主要起隔离作用,一般不进行判断,编程时我们也不予理会。其中数据编码总共是4个字节32位,如图16-7所示。第一个字节是用户码,第二个字节可能也是用户码,或者是用户码的反码,具体由生产商决定,第三个字节就是当前按键的键数据码,而第四个字节是键数据码的反码,可用于对数据的纠错。 这个NEC协议,表示数据的方式不像我们之前学过的比如uart那样直观,而是每一位数据本身也需要进行编码,编码后再进行载波调制。 引导码:9ms的载波+4.5ms的空闲。 比特值“0”:560us的载波+560us的空闲。 比特值“1”:560us的载波+1.68ms的空闲。 结合图16-7我们就能看明白了,最前面黑乎乎的一段,是引导码的9ms载波,紧接着是引导码的4.5ms的空闲,而后边的数据码,是众多载波和空闲交叉,它们的长短就由其要传递的具体数据来决定。我们的HS0038B这个红外一体化接收头,当收到有载波的信号的时候,会输出一个低电平,空闲的时候会输出高电平,我们用逻辑分析仪抓出来一个红外按键通过HS0038解码后的图形来了解一下,如图16-8所示。 从图上可以看出,先是9ms载波加4.5ms空闲的起始码,数据码是低位在前,高位在后,数据码第一个字节是8组560us的载波加560us的空闲,也就是0x00,第二个字节是8组560us的载波加1.68ms的空闲,可以看出来是0xFF,这两个字节就是用户码和用户码的反码。按键的键码二进制是0x0B,反码就是0xF3,最后跟了一个560us载波停止位。对于我们的遥控器来说,不同的按键,就是键码和键码反码的区分,用户码是一样的。这样我们就可以通过单片机的程序,把当前的按键的键码给解出来。 我们前边学习中断的时候,学到51单片机有外部中断0和外部中断1这两个外部中断。我们的红外接收引脚接到了P3.3引脚上,这个引脚的第二功能就是外部中断1。在寄存器TCON中的bit3和bit2这两位,是和外部中断1相关的两位。其中IE1是外部中断标志位,当外部中断发生后,这一位被自动置1,和定时器中断标志位TF相似,进入中断后会自动清零,也可以软件清零。bit2位是设置外部中断类型的,如果bit2位为0,那么只要P3.3为低电平就可以触发中断,如果bit2位为1,那么P3.3从高电平到低电平的下降沿发生才可以触发中断。此外,外部中断1使能位是EX1。那下面我们就把程序写出来,使用数码管把遥控器的用户码和键码显示出来。 Infrared.c文件主要是用来检测红外通信的,当发生外部中断后,进入外部中断,通过定时器1定时,首先对引导码判断,而后对数据码的每个位逐位获取高低电平的时间,从而得知每一位是0还是1,最终把数据码解出来。 /***********************infrared.c文件程序源代码*************************/ #include <reg52.h> sbit IR_INPUT = P3^3; //红外接收引脚 bit irflag = 0; //红外接收标志,收到一帧正确数据后置1 unsigned char ircode[4]; //红外代码接收缓冲区 void InitInfrared(void) //红外功能的初始化函数 { TMOD &= 0x0F; //清零T1的控制位 TMOD |= 0x10; //配置T1为模式1 TR1 = 0; //停止T1计数 ET1 = 0; //禁止T1中断 IT1 = 1; //设置INT1为负边沿触发 EX1 = 1; //使能INT1中断 } unsigned int GetHighTime(void) //获取高电平时间 { TH1 = 0; //清零T1计数初值 TL1 = 0; TR1 = 1; //启动T1计数 while (IR_INPUT) //红外输入引脚为1时循环检测等待,变为0时则结束本循环 { if (TH1 >= 0x40) { //当T1计数值大于0x4000,即高电平持续时间超过约18ms时, break; //强制退出循环,是为了避免信号异常时,程序假死在这里。 } } TR1 = 0; //停止T1计数 return (TH1*256 + TL1); //返回T1的计数值 } unsigned int GetLowTime(void) //获取低电平时间 { TH1 = 0; //清零T1计数初值 TL1 = 0; TR1 = 1; //启动T1计数 while (!IR_INPUT) //红外输入引脚为0时循环检测等待,变为1时则结束本循环 { if (TH1 >= 0x40) { //当T1计数值大于0x4000,即低电平持续时间超过约18ms时, break; //强制退出循环,是为了避免信号异常时,程序假死在这里。 } } TR1 = 0; //停止T1计数 return (TH1*256 + TL1); //返回T1的计数值 } void EXINT1_ISR() interrupt 2 //INT1中断服务函数,执行红外接收及解码 { unsigned char i, j; unsigned char byt; unsigned int time;
//接收并判定引导码的9ms低电平 time = GetLowTime(); if ((time<7833) || (time>8755)) //时间判定范围为8.5~9.5ms, { //超过此范围则说明为误码,直接退出 IE1 = 0; //退出前清零INT1中断标志 return; } //接收并判定引导码的4.5ms高电平 time = GetHighTime(); if ((time<3686) || (time>4608)) //时间判定范围为4.0~5.0ms, { //超过此范围则说明为误码,直接退出 IE1 = 0; return; } //接收并判定后续的4字节数据 for (i=0; i<4; i++) //循环接收4个字节 { for (j=0; j<8; j++) //循环接收判定每字节的8个bit { //接收判定每bit的560us低电平 time = GetLowTime(); if ((time<313) || (time>718)) //时间判定范围为340~780us, { //超过此范围则说明为误码,直接退出 IE1 = 0; return; } //接收每bit高电平时间,判定该bit的值 time = GetHighTime(); if ((time>313) && (time<718)) //时间判定范围为340~780us, { //在此范围内说明该bit值为0 byt >>= 1; //因低位在先,所以数据左移,高位为0 } else if ((time>1345) && (time<1751)) //时间判定范围为1460~1900us, { //在此范围内说明该bit值为1 byt >>= 1; //因低位在先,所以数据左移, byt |= 0x80; //高位置1 } else //不在上述范围内则说明为误码,直接退出 { IE1 = 0; return; } } ircode[i] = byt; //接收完一个字节后保存到缓冲区 } irflag = 1; //接收完毕后设置标志 IE1 = 0; //退出前清零INT1中断标志 } 大家在阅读这个文件里的代码时,会发现我们在获取高低电平时间的时候做了超时判断if (TH1 >= 0x40),这个超时判断一方面是应对空间突发的红外干扰信号,如果我们不做超时判断,程序有可能会一直等待下一个跳变才会停止检测,造成程序假死。另外一个方面,遥控器的单按按键和持续按住按键发出来的信号是不同的。我们先来对比一下两种按键方式的信号状态,如图16-9和16-10所示。 ![]() 单次按键的结果16-9和我们之前的图16-8是一样的,这个不需要再解释。而持续按键,首先会发出一个和单次按键一样的波形出来,经过大概40ms后,会产生一个9ms载波加2.25ms空闲,再跟一个停止位的波形,而后只要你还在按住按键,每经过大概96ms就会产生9ms载波加2.25ms空闲加停止位这样的重复波形。我们人为按下按键的时候,很难控制按下的时间,因此后边的很容易出现这种延续波形,我们加上超时判断也可以有效的避免进入延续波形的死循环中去。 /***********************main.c文件程序源代码*************************/ #include <reg52.h> sbit ADDR3 = P1^3; //LED选择地址线3 sbit ENLED = P1^4; //LED总使能引脚 unsigned char code LedChar[] = { //数码管显示字符转换表 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E }; unsigned char LedBuff[6] = { //数码管显示缓冲区 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; unsigned char T0RH = 0; //T0重载值的高字节 unsigned char T0RL = 0; //T0重载值的低字节 extern bit irflag; extern unsigned char ircode[4]; void ConfigTimer0(unsigned int ms); extern void InitInfrared(void); void main () { P0 = 0xFF; //P0口初始化 ADDR3 = 1; //选择数码管 ENLED = 0; //LED总使能 InitInfrared(); //初始化红外功能 ConfigTimer0(1); //配置T0定时1ms EA = 1; //开总中断 //PT0 = 1; //配置T0中断为高优先级
while(1) { if (irflag) //接收到红外数据时刷新显示 { irflag = 0; LedBuff[5] = LedChar[ircode[0] >> 4]; //用户码显示 LedBuff[4] = LedChar[ircode[0]&0x0F]; LedBuff[1] = LedChar[ircode[2] >> 4]; //键码显示 LedBuff[0] = LedChar[ircode[2]&0x0F]; } } } void ConfigTimer0(unsigned int ms) //T0配置函数 { unsigned long tmp;
tmp = 11059200 / 12; //定时器计数频率 tmp = (tmp * ms) / 1000; //计算所需的计数值 tmp = 65536 - tmp; //计算定时器重载值 tmp = tmp + 15; //修正中断响应延时造成的误差
T0RH = (unsigned char)(tmp >> 8); //定时器重载值拆分为高低字节 T0RL = (unsigned char)tmp; TMOD &= 0xF0; //清零T0的控制位 TMOD |= 0x01; //配置T0为模式1 TH0 = T0RH; //加载T0重载值 TL0 = T0RL; ET0 = 1; //使能T0中断 TR0 = 1; //启动T0 } void InterruptTimer0() interrupt 1 //T0中断服务函数 { static unsigned char iled = 0;
TH0 = T0RH; //定时器重新加载重载值 TL0 = T0RL;
//LED数码管动态扫描 P0 = 0xFF; //关闭所有段选位,显示消隐 P1 = (P1 & 0xF8) | iled; //位选索引值赋值到P1口低3位 P0 = LedBuff[iled]; //相应显示缓冲区的值赋值到P0口 if (iled < 5) //位选索引0-5循环,因有6个数码管 iled++; else iled = 0; } main.c文件程序的主要功能就是把获取到的红外遥控器的用户码和键码信息,传送到数码管上显示出来,并且通过定时器0的1ms中断进行数码管的动态刷新。不知道大家经过试验发现没有,当我们按下遥控器按键的时候,数码管显示的数字会闪烁,这是什么原因呢?单片机的程序都是顺序执行的,一旦我们按下遥控器按键,我们的程序就会进入遥控器解码段,而这个解码段的时间比较长,要几十个毫秒,而我们的数码管动态刷新间隔超过了10ms后就会有闪烁的感觉了,因此这个闪烁主要是由于我们程序执行红外解码时,延误了数码管动态刷新造成的。 如何解决?前边我们讲过中断优先级问题,如果设置了中断优先级,就会产生中断嵌套。中断嵌套的原理,我们在前边讲中断的时候已经讲过一次了,大家可以回头再复习一下。那么这个程序中,有2个中断程序,一个是外部中断程序,一个是定时器中断程序。如果设置外部中断优先级比较高的话,由于在外部中断中要接收红外信号,耗时几十毫秒,会耽误数码管的动态刷新。而在定时器中断程序中,执行时间只有几十个us,即使进入中断,也不会干扰到红外信号的正常接收,因此这个地方我们把定时器0的中断优先级设置为高优先级。在主程序main函数中,大家把这句注释掉的程序 “//PT0 = 1;”取消注释,再编译一下,下载到单片机里,然后再试试发送按键,是不是没有任何闪烁了呢?而中断嵌套的意义也有所体会了吧。 |
|