[牛人杂谈] 几种比较流行和成熟的红外解码程序做一下研究和总结

[复制链接]
4516|33
zhuomuniao110 发表于 2016-6-29 20:49 | 显示全部楼层
本帖最后由 zhuomuniao110 于 2016-6-29 20:54 编辑

4.单片机如何检测这些脉冲并计算大小?

用下面两个功能:

  • 定时器
  • 中断

基本思路:把红外接收头的OUT引脚与单片机中断引脚相连接,用定时器记下每次电平跳变之间的时间,通过判断时间,来获取这些码值。从而知道遥控器按了哪个键。

通过上面的思路,首先,最好用双边沿中断,双边沿中断,就是上升沿和下降沿都会发生中断,然后,我们想到了单片机的捕获功能(CAP),我们现在常用的STC51、LPC1114、STM32单片机都带有CAP捕获功能,而且都可以设置为双边沿中断。下面我们就以LPC1114为例,来写个程序。

4.1单片机CAP捕获初始化配置程序

我们以LPC1114为例,有4个CAP引脚,我们用其中的一个P1.8引脚,如下图:

  1. void IR_Init(void)
  2. {
  3.     LPC_SYSCON->SYSAHBCLKCTRL |= (1<<16);  // 打开引脚功能模块IOCON时钟
  4.     LPC_IOCON->PIO1_8 &= ~0x07;
  5.     LPC_IOCON->PIO1_8 |= 0x01;    /* CT16B1 CAP0 */ 配置P1.8引脚为CT16B1_CAP引脚
  6.     LPC_SYSCON->SYSAHBCLKCTRL &= ~(1<<16);  // 关闭IOCON模块时钟,配置完引脚功能了,关闭时钟节省耗电
  7.     LPC_SYSCON->SYSAHBCLKCTRL |= (1<<8);    //打开CT16B1定时器时钟
  8.     LPC_TMR16B1->TCR = 0x02;        //复位定时器
  9.     LPC_TMR16B1->PR  = 49;        //配置预分频器,使得1us TC+1
  10.     LPC_TMR16B1->IR  = 0x10;        //中断复位
  11.     LPC_TMR16B1->CCR = 0x07;     // 配置CAP引脚双边沿中断
  12.     LPC_TMR16B1->TCR = 0x01; // 打开定时器,开始计时
  13.     NVIC_EnableIRQ(TIMER_16_1_IRQn);    // 开启NVCI中断入口
  14. }

上面的函数,用来配置CAP引脚。因为LPC1114的PIN9默认是GPIO功能,即P1.8,所以我们把引脚切换为CAP引脚功能。

4.2需要定义的变量

  1. uint8_t pulse_start=0;     // 脉冲开始标志
  2. uint8_t pulse_bnum=0;         // 脉冲计数器
  3. uint8_t pulse_ok=0;         // 第一次按键码标志
  4. uint8_t key_repeat=0;         // 按键的次数
  5. uint16_t tc_buf=0;     // 脉冲宽度存储
  6. uint16_t ir_buf[64];         // 64个16位变量,用来存储9ms+4.5ms之后的4个码的电平值
  7. uint8_t user_code_hi;  // 定制码高位
  8. uint8_t user_code_lo; // 定制码低位
  9. uint16_t user_code; // 定制码
  10. uint8_t key_code; // 数据码
  11. uint8_t key_code_lo; // 数据码反码
  12. 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。


zhuomuniao110 发表于 2016-6-29 20:50 | 显示全部楼层
本帖最后由 zhuomuniao110 于 2016-6-29 20:53 编辑

4.3中断函数

  1. <div class="blockcode"><blockquote>void TIMER16_1_IRQHandler(void)
  2. {
  3.     if((LPC_TMR16B1->IR&0x10)==0x10) // 判断是否是CAP中断
  4.     {
  5.         tc_buf=LPC_TMR16B1->TC; // 把TC值给了tc_buf
  6.         LPC_TMR16B1->TC = 0; j// 把TC值清0,从0开始再计数
  7.         if((tc_buf>8500)&&(tc_buf<9500))          // 发现9ms
  8.         {
  9.             pulse_start=1;
  10.             LPC_TMR16B1->IR = 0X10; // 清CAP0中断位
  11.             return;
  12.         }
  13.         if(pulse_start==1)  // 如果发现9ms以后
  14.         {
  15.           if((tc_buf>4000)&&(tc_buf<5000))    // 发现4.5ms
  16.             {
  17.                 pulse_ok=1;  // 标志按下了按键
  18.                 LPC_TMR16B1->IR = 0X10; // 清CAP0中断位
  19.                 pulse_start=0; // 清此位,为下次按键做准备
  20.                 key_repeat=1; // 标志第一次按键
  21.                 return;
  22.             }
  23.             else if((tc_buf>2000)&&(tc_buf<3000))    // 发现2.5ms
  24.             {
  25.                 key_repeat++; // 增加按键次数
  26.                 LPC_TMR16B1->IR = 0X10; // 清CAP0中断位
  27.                 pulse_start=0; // 清0
  28.                 ir_sign=1; // 标志按键值依然有效
  29.                 return;
  30.             }
  31.         }
  32.         if(pulse_ok==1) // 如果发现了9ms和4.5ms
  33.         {
  34.             ir_buf[pulse_bnum]=tc_buf; //开始存放64个电平值
  35.             pulse_bnum++;
  36.             if(pulse_bnum==64)
  37.             {
  38.                 pulse_ok=0;
  39.                 pulse_bnum=0;
  40.                 ir_sign=1; // 接收完64个电平值之后,此变量置1,表示接收到一个按键值了
  41.             }   
  42.         }   
  43.     }
  44.     LPC_TMR16B1->IR = 0X10; // 清CAP0中断位
  45. }


当红外接收头的OUT引脚有脉冲信号,发生一次边沿跳变,就会进一次中断,进入中断以后,记录下TC的值,然后再把TC清0,从0开始记。前面在CAP引脚配置函数中得知,TC每1us加1。当发现9ms+4.5ms之后,再把后面的4个码值存放到数据里面。当发现9ms+2.5ms时,就把按键次数+1。这就是这个中断函数完成的工作。


zhuomuniao110 发表于 2016-6-29 20:51 | 显示全部楼层

4.4解码函数

在前面的中断函数里面,我们获取了4个码值的电平,现在我们用一个函数,把这4个字节计算出来。函数如下:

  1. uint8_t ir_process(void)
  2. {
  3.     uint8_t i;
  4.     uint16_t buf;
  5.    
  6.     for(i=0;i<16;i++)  // 计算定制码高位
  7.     {
  8.         user_code_hi<<=1;
  9.         buf=ir_buf[i]+ir_buf[++i]; // 把高低电平加起来
  10.         if((buf>2100)&&(buf<2450)) // 是否是2.25ms,即是否是逻辑1
  11.         {
  12.             user_code_hi+=1;
  13.         }
  14.     }
  15.     for(i=16;i<32;i++) // 计算定制码低位
  16.     {
  17.         user_code_lo<<=1;
  18.         buf=ir_buf[i]+ir_buf[++i]; // 把高低电平加起来
  19.         if((buf>2100)&&(buf<2450)) // 是否是2.25ms,即是否是逻辑1
  20.         {
  21.             user_code_lo+=1;
  22.         }
  23.     }
  24.     for(i=32;i<48;i++) // 计算数据码
  25.     {
  26.         key_code<<=1;
  27.         buf=ir_buf[i]+ir_buf[++i];
  28.         if((buf>2100)&&(buf<2450))
  29.         {
  30.             key_code+=1;
  31.         }
  32.     }
  33.     for(i=48;i<64;i++) // 计算数据码反码
  34.     {
  35.         key_code_lo<<=1;
  36.         buf=ir_buf[i]+ir_buf[++i];
  37.         if((buf>2100)&&(buf<2450))
  38.         {
  39.             key_code_lo+=1;
  40.         }
  41.     }

  42.     if(key_code==(uint8_t)~key_code_lo) // 判断接收数据是否正确
  43.     {
  44.         user_code = (user_code_hi<<8)+user_code_lo;
  45.         return 1;     
  46.     }
  47.     else return 0;
  48. }

解码函数,带有返回值。如果返回1代表接收正确,如果返回0代表接收错误。有没有接收正确,是通过判断数据码和数据补码是否成互补的关系得出的。

前面所述,逻辑1是560us低电平+1680us高电平,共2.25ms;逻辑0是560us低电平+560us高电平,共1.125ms。所以我们把前后两个电平加起来判断,如果是2.25ms,就是逻辑1,如果是1.125ms,就是逻辑0。在程序中,省略了判断如果是低电平的程序,如果加上判断低电平的程序,是下面的这个样子:

    ......   
  1. for(i=0;i<16;i++)  // 计算定制码高位
  2.     {
  3.         user_code_hi<<=1;
  4.         buf=ir_buf[i]+ir_buf[++i]; // 把高低电平加起来
  5.         if((buf>2100)&&(buf<2450)) // 是否是2.25ms,即是否是逻辑1
  6.         {
  7.             user_code_hi+=1;
  8.         }
  9.         else((buf>1100)&&(buf<1300)) // 是否是1.125ms,即是否是逻辑0
  10.         {
  11.             user_code_hi+=0;
  12.         }
  13.     }

    .....

不过,你仔细研究后发现,加上判断低电平的语句,和不加判断低电平的语句,结果是一样的,因为低电平加的是0。


zhuomuniao110 发表于 2016-6-29 20:52 | 显示全部楼层

4.5主函数中调用

主函数中,首先需要调用IR_Init()初始化配置函数,然后在while里面可以检测是否有按键按下。

  1.   int main()
  2.    {
  3.      IR_Init();
  4.      while(1)
  5.      {
  6.         if((ir_sign==1)&&(ir_process()==1))      // 判断是否有按键按下
  7.         {
  8.             ......
  9.             ir_sign=0;
  10.         }
  11.      }

初次拿到一个遥控器,可以先把遥控器的定制码和各个按键的数据码用串口输出,然后就可以写程序判断他们用在实际产品中了。

5.总结

STC51、LPC1114、STM32都有CAP功能,即使没有捕获功能,也可以用双边沿中断+定时器的方式实现。上面以LPC1114为例讲解了一下。其它单片机都是触类旁通的。


dongnanxibei 发表于 2016-6-29 21:14 | 显示全部楼层
我国,基本上是用的遥控器都是遵循NEC的编码方式。
稳稳の幸福 发表于 2016-6-30 16:38 | 显示全部楼层
如果程序溢出了有一种可能性就是往一个固定长度的数组中存入了大于其长度的数据
 楼主| mintspring 发表于 2016-6-30 22:49 | 显示全部楼层
红外解码就好比单线的通信协议,一般是单工通信
734774645 发表于 2016-6-30 23:46 | 显示全部楼层
相比上面的那个没有时间延迟的程序还是有一点瑕疵啊!所以说上面那个程序,也就是我编的那处程序才是最好的算法和程序.
neeringstu 发表于 2016-7-2 22:04 | 显示全部楼层

有没有170M的无线传输的程序呢?
 楼主| mintspring 发表于 2016-7-3 23:26 | 显示全部楼层
neeringstu 发表于 2016-7-2 22:04
有没有170M的无线传输的程序呢?

频率只是介质,我们做的就是通过串口来控制而已,你随便买这个模块,都是串口或者SPI。I2C的接口,操作方式一样
 楼主| mintspring 发表于 2016-7-3 23:27 | 显示全部楼层
红外的方式这两年会越来越流行的,因为控制简单,成本低廉,而且可以很方便集成到手机上,从前手机上是都有的,现在好多都没了,而小米手机开始逐渐增加这一共呢,让手机成为万能的遥控器。
Jessicakjdsl 发表于 2016-7-5 14:32 | 显示全部楼层
一般都是用中断模式实现的吧,这样才能实时
 楼主| mintspring 发表于 2016-7-10 16:17 | 显示全部楼层
接收确实是中断实现,这样更节能。
shang21ic 发表于 2021-5-26 09:10 | 显示全部楼层
红外编解码芯片可以选YiRTX05E,SOP8封装,自学习所有红外遥控,串口控制,简单易用。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 在线客服 返回列表 返回顶部