1.导言
1838一体化红外接收头是我们最常用的红外接收元器件。它被广泛的应用于电视机、空调、电冰箱以及电视机顶盒等需要红外遥控的电器上。当然,所有带红外遥控功能的单片机开发板上,也有它的身影。瑞生的LPC1114开发板上也没有放过它。
2.红外接收头与单片机的硬件连接
上图中,CON3代表红外接收管,1 2 3引脚定义分别为OUT、GND和VCC。其中,VCC可以接5V,也可以接3.3V,主要看与其连接的单片机工作电压是多少。电路中,R12用于给连接OUT引脚的单片机引脚上拉电阻,C16用于电源滤波,提高红外遥控稳定性。电路连接很简单吧。一看就知道,它与单片机之间采用单总线通信方式。
3.接收红外信号的流程
当你拿着一个红外遥控器对准红外接收头按时,红外接收头的OUT引脚将会如何变化?这个是由遥控器决定的,因为它只是起到一个解码的作用。在当下,我国,基本上是用的遥控器都是遵循NEC的编码方式。所以,我们就需要知道NEC红外遥控的编码方式了。
NEC红外遥控编码方式:它的指令格式依次为,码头+定制码高位+定制码低位+数据码+数据反码。码头用于通知红外遥控信号的来临,由9毫秒的低电平加4.5毫秒的高电平组成;定制码高位、定制码低位、数据码、数据反码都是1个字节。定制码高位与定时码低位组成一个16位的定制码,一般用来识别红外遥控器,就是说,假如你的空调的遥控器定制码是88,你家的电视遥控定制码是55,你用电视的遥控对空调就不起作用,如果你家的电视遥控和空调遥控的定制码相同,那你拿上电视的遥控对着空调按一番,说不定就出什么乱子了。数据码用于识别用户的功能,如加减声音、换台等按键发出的数据码都是不一样的。数据反码与数据码是反码的关系,用于校验数据码接收正确与否。
口述一下红外遥控按下以后,接收头OUT引脚的变化:首先是9ms的低电平,然后是4.5ms的高电平,然后将会出现定制码高位,定制码低位,数据码,数据反码,这4个码的逻辑1是560us低电平+1680us高电平,逻辑0是560us低电平+560us高电平。
上图是遥控器发出的脉冲信号,仔细的童鞋会看出,它的电平和我刚才口述的接收头接收到的信号电平刚好是相反的。
此外,用户如果一直按着一个键不放,将会发送重发码。重发码是跟在按键码后面的,它由9ms低电平+2.5ms高电平+560us低电平+97ms高电平组成,由重发码的规律,我们可以看出,当单片机观察到9ms低电平时,后面如果是4.5ms高电平,就是第一次按的按键码,如果9ms后面跟的是2.5ms高电平,就是重发码。
4.单片机如何检测这些脉冲并计算大小?
用下面两个功能:
基本思路:把红外接收头的OUT引脚与单片机中断引脚相连接,用定时器记下每次电平跳变之间的时间,通过判断时间,来获取这些码值。从而知道遥控器按了哪个键。
通过上面的思路,首先,最好用双边沿中断,双边沿中断,就是上升沿和下降沿都会发生中断,然后,我们想到了单片机的捕获功能(CAP),我们现在常用的STC51、LPC1114、STM32单片机都带有CAP捕获功能,而且都可以设置为双边沿中断。下面我们就以LPC1114为例,来写个程序。
4.1单片机CAP捕获初始化配置程序
我们以LPC1114为例,有4个CAP引脚,我们用其中的一个P1.8引脚,如下图:
void IR_Init(void) { LPC_SYSCON->SYSAHBCLKCTRL |= (1<<16); // 打开引脚功能模块IOCON时钟 LPC_IOCON->PIO1_8 &= ~0x07; LPC_IOCON->PIO1_8 |= 0x01; /* CT16B1 CAP0 */ 配置P1.8引脚为CT16B1_CAP引脚 LPC_SYSCON->SYSAHBCLKCTRL &= ~(1<<16); // 关闭IOCON模块时钟,配置完引脚功能了,关闭时钟节省耗电 LPC_SYSCON->SYSAHBCLKCTRL |= (1<<8); //打开CT16B1定时器时钟 LPC_TMR16B1->TCR = 0x02; //复位定时器 LPC_TMR16B1->PR = 49; //配置预分频器,使得1us TC+1 LPC_TMR16B1->IR = 0x10; //中断复位 LPC_TMR16B1->CCR = 0x07; // 配置CAP引脚双边沿中断 LPC_TMR16B1->TCR = 0x01; // 打开定时器,开始计时 NVIC_EnableIRQ(TIMER_16_1_IRQn); // 开启NVCI中断入口 }
上面的函数,用来配置CAP引脚。因为LPC1114的PIN9默认是GPIO功能,即P1.8,所以我们把引脚切换为CAP引脚功能。
4.2需要定义的变量
uint8_t pulse_start=0; // 脉冲开始标志 uint8_t pulse_bnum=0; // 脉冲计数器 uint8_t pulse_ok=0; // 第一次按键码标志 uint8_t key_repeat=0; // 按键的次数 uint16_t tc_buf=0; // 脉冲宽度存储 uint16_t ir_buf[64]; // 64个16位变量,用来存储9ms+4.5ms之后的4个码的电平值 uint8_t user_code_hi; // 定制码高位 uint8_t user_code_lo; // 定制码低位 uint16_t user_code; // 定制码 uint8_t key_code; // 数据码 uint8_t key_code_lo; // 数据码反码 uint8_t ir_sign; //接收到按键标志
当单片机发现9ms低电平之后,pulse_start置1。当发现9ms后面跟着4.5ms高电平之后,pulse_ok置1,当发现9ms后面跟着2.5ms高电平之后,key_repeat置1。之后出现的4个码值,一共4个字节,每个字节8个位,每个位是由一个低电平和高电平组成,这个电平的值有560us和1680us,所以需要16位的值存储,4个字节共32个位,每个位由一低一高电平,所以需要定义一个能放64个16位数据的数组。成功接收一个按键值后,ir_sign置1。
4.3中断函数
void TIMER16_1_IRQHandler(void) { if((LPC_TMR16B1->IR&0x10)==0x10) // 判断是否是CAP中断 { tc_buf=LPC_TMR16B1->TC; // 把TC值给了tc_buf LPC_TMR16B1->TC = 0; j// 把TC值清0,从0开始再计数 if((tc_buf>8500)&&(tc_buf<9500)) // 发现9ms { pulse_start=1; LPC_TMR16B1->IR = 0X10; // 清CAP0中断位 return; } if(pulse_start==1) // 如果发现9ms以后 { if((tc_buf>4000)&&(tc_buf<5000)) // 发现4.5ms { pulse_ok=1; // 标志按下了按键 LPC_TMR16B1->IR = 0X10; // 清CAP0中断位 pulse_start=0; // 清此位,为下次按键做准备 key_repeat=1; // 标志第一次按键 return; } else if((tc_buf>2000)&&(tc_buf<3000)) // 发现2.5ms { key_repeat++; // 增加按键次数 LPC_TMR16B1->IR = 0X10; // 清CAP0中断位 pulse_start=0; // 清0 ir_sign=1; // 标志按键值依然有效 return; } } if(pulse_ok==1) // 如果发现了9ms和4.5ms { ir_buf[pulse_bnum]=tc_buf; //开始存放64个电平值 pulse_bnum++; if(pulse_bnum==64) { pulse_ok=0; pulse_bnum=0; ir_sign=1; // 接收完64个电平值之后,此变量置1,表示接收到一个按键值了 } } } LPC_TMR16B1->IR = 0X10; // 清CAP0中断位 }
当红外接收头的OUT引脚有脉冲信号,发生一次边沿跳变,就会进一次中断,进入中断以后,记录下TC的值,然后再把TC清0,从0开始记。前面在CAP引脚配置函数中得知,TC每1us加1。当发现9ms+4.5ms之后,再把后面的4个码值存放到数据里面。当发现9ms+2.5ms时,就把按键次数+1。这就是这个中断函数完成的工作。
4.4解码函数
在前面的中断函数里面,我们获取了4个码值的电平,现在我们用一个函数,把这4个字节计算出来。函数如下:
uint8_t ir_process(void) { uint8_t i; uint16_t buf; for(i=0;i<16;i++) // 计算定制码高位 { user_code_hi<<=1; buf=ir_buf[i]+ir_buf[++i]; // 把高低电平加起来 if((buf>2100)&&(buf<2450)) // 是否是2.25ms,即是否是逻辑1 { user_code_hi+=1; } } for(i=16;i<32;i++) // 计算定制码低位 { user_code_lo<<=1; buf=ir_buf[i]+ir_buf[++i]; // 把高低电平加起来 if((buf>2100)&&(buf<2450)) // 是否是2.25ms,即是否是逻辑1 { user_code_lo+=1; } } for(i=32;i<48;i++) // 计算数据码 { key_code<<=1; buf=ir_buf[i]+ir_buf[++i]; if((buf>2100)&&(buf<2450)) { key_code+=1; } } for(i=48;i<64;i++) // 计算数据码反码 { key_code_lo<<=1; buf=ir_buf[i]+ir_buf[++i]; if((buf>2100)&&(buf<2450)) { key_code_lo+=1; } } if(key_code==(uint8_t)~key_code_lo) // 判断接收数据是否正确 { user_code = (user_code_hi<<8)+user_code_lo; return 1; } else return 0; }
解码函数,带有返回值。如果返回1代表接收正确,如果返回0代表接收错误。有没有接收正确,是通过判断数据码和数据补码是否成互补的关系得出的。
前面所述,逻辑1是560us低电平+1680us高电平,共2.25ms;逻辑0是560us低电平+560us高电平,共1.125ms。所以我们把前后两个电平加起来判断,如果是2.25ms,就是逻辑1,如果是1.125ms,就是逻辑0。在程序中,省略了判断如果是低电平的程序,如果加上判断低电平的程序,是下面的这个样子:
...... for(i=0;i<16;i++) // 计算定制码高位 { user_code_hi<<=1; buf=ir_buf[i]+ir_buf[++i]; // 把高低电平加起来 if((buf>2100)&&(buf<2450)) // 是否是2.25ms,即是否是逻辑1 { user_code_hi+=1; } else((buf>1100)&&(buf<1300)) // 是否是1.125ms,即是否是逻辑0 { user_code_hi+=0; } } .....
不过,你仔细研究后发现,加上判断低电平的语句,和不加判断低电平的语句,结果是一样的,因为低电平加的是0。
4.5主函数中调用
主函数中,首先需要调用IR_Init()初始化配置函数,然后在while里面可以检测是否有按键按下。
int main() { IR_Init(); while(1) { if((ir_sign==1)&&(ir_process()==1)) // 判断是否有按键按下 { ...... ir_sign=0; } }
初次拿到一个遥控器,可以先把遥控器的定制码和各个按键的数据码用串口输出,然后就可以写程序判断他们用在实际产品中了。
5.总结
STC51、LPC1114、STM32都有CAP功能,即使没有捕获功能,也可以用双边沿中断+定时器的方式实现。上面以LPC1114为例讲解了一下。其它单片机都是触类旁通的。
我是瑞生,毫无保留的给大家透露电子设计经验,不定时分享实用的落地的电子设计技巧,希望能够帮助到大家。