红外遥控解码

1.导言

1838一体化红外接收头是我们最常用的红外接收元器件。它被广泛的应用于电视机、空调、电冰箱以及电视机顶盒等需要红外遥控的电器上。当然,所有带红外遥控功能的单片机开发板上,也有它的身影。瑞生的LPC1114开发板上也没有放过它。

红外遥控解码_第1张图片

2.红外接收头与单片机的硬件连接

红外遥控解码_第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高电平。

红外遥控解码_第3张图片

上图是遥控器发出的脉冲信号,仔细的童鞋会看出,它的电平和我刚才口述的接收头接收到的信号电平刚好是相反的。

此外,用户如果一直按着一个键不放,将会发送重发码。重发码是跟在按键码后面的,它由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引脚,如下图:

红外遥控解码_第4张图片

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为例讲解了一下。其它单片机都是触类旁通的。


我是瑞生,毫无保留的给大家透露电子设计经验,不定时分享实用的落地的电子设计技巧,希望能够帮助到大家。

你可能感兴趣的:(电子技术)