51 红外解码

标 签:

    本教程是基于NEC红外协议的解码程序。其它的协议自己去找资料,我也不知道怎么玩。

    先来说说NEC协议吧,NEC 协议的数据格式包括了引导码、用户码、用户码(或者用户码反码)、按键键码和键码反码,最后一个停止位。停止位主要起隔离作用,一般不进行判断,编程时我们也不予理会。其中数据编码总共是 4 个字节 32 位。第一个字节是用户码,第二个字节可能也是用户码,或者是用户码的反码,具体由生产商决定,第三个字节就是当前按键的键数据码,而第四个字节是键数据码的反码,可用于对数据的纠错。下图就是NEC发射的编码

2017032118041762684.png

引导码:9ms 的载波+4.5ms 的空闲。
比特值“0”:560us 的载波+560us 的空闲。
比特值“1”:560us 的载波+1.68ms 的空闲。

    现在知道了NEC协议了,代码就好写了,但也只是相对低级的单片机,比如(stc89系列的)用传统的解码方式方法,就是用单片机计时器一位一位的计算高低电平的时间。但由于现在的单片机越来越快,因些计时器最高计时越来越短了,用传统的解码方法就不行了。至于传统方法的解码程序自己上网去找,多的是。

    下面说的一种方法是阿莫论坛上看到有人发了一种特别的解码方式,他不管协议头的高低电平时间长短,只关注高低电平脉冲的时间周期,周期超过3ms的脉冲,他就认为是错误数据放弃,周期小于3ms的,他就尝试解码。周期为1.12ms的组合表示二进制的“0“, 周期为2.25ms的组合表示二进制的“1”,他发现周期小于3ms的脉冲,进而判断周期是否大于1.875ms,大于1.875ms就是数据1,否则是数据0。这种解码方式非常高效,而且代码很简单。唯一的缺点就是占用CPU率很高。定时器0要125us中断一次,非常占系统资源。

    硬件列表就不上了,也没有什么硬件,HS0038B一体化接收头,stc12系列及以上的都可以。算了也是上个HS0038B外部接线图吧:

2017032215225336729.png

   下面直接上代码,自己改里面的IO定义就好:

#include <reg51.h>
#include <intrins.h>

#define u8 unsigned char
#define u16  unsigned int

//* STC15W404AS寄存器补充 
sfr AUXR1 = 0XA2;
sfr AUXR = 0X8E;
sfr TH2 = 0XD6;
sfr TL2 = 0XD7;
sfr P4 = 0xc0;
sfr P5 = 0xc8;
sfr P3M0=0xB2;
sfr SPSTAT      =   0xCD;   //
sfr SPCTL       =   0xCE;   //
sfr SPDAT       =   0xCF;   //

//定时器2
sfr T2H=0xd6;
sfr T2L=0xd7;
sfr IE2=0xaf;


sbit IR_IO = P3^2;          // IR管脚 任意IO
bit Irprot_LastState = 0;   // 端口状态位
u8 codeCnt = 0;          // 数据码位计数
u8 irTime;                   // 码时间,用于以125us时间计时
u8 IR_data[4]={48,49,50,51}; // 接收数据缓存

void Delay1ms()        //@12.000MHz
{
    unsigned char i, j;

    i = 12;
    j = 169;
    do
    {
        while (--j);
    } while (--i);
}


void delay_ms(unsigned int i)
{
   while(i--)Delay1ms();
}


void Timer0Init(void)        //125微秒@12.000MHz
{
    AUXR |= 0x80;        //定时器时钟1T模式
    TMOD &= 0xF0;        //设置定时器模式
    TL0 = 0x24;        //设置定时初值
    TH0 = 0xFA;        //设置定时初值
    TF0 = 0;        //清除TF0标志
    TR0 = 1;        //定时器0开始计时
    ET0=1;            //打开定时器0中断
    EA=1;            //打开全部中断
}


void UartInit(void)        //4800bps@12.000MHz
{
    SCON = 0x50;        //8位数据,可变波特率
    AUXR |= 0x01;        //串口1选择定时器2为波特率发生器
    AUXR |= 0x04;        //定时器2时钟为Fosc,即1T
    T2L = 0x8F;        //设定定时初值
    T2H = 0xFD;        //设定定时初值
    AUXR |= 0x10;        //启动定时器2
}

void SendData(u8 data_buf) //发送一个字符
{
    SBUF = data_buf;
    while(!TI);//TI是发送成功标志
    TI = 0;
}



void main(void)
{
    u8 i;
    delay_ms(200); //上电延时200ms等待系统稳定
    UartInit();     //串口1初始化
    Timer0Init();//定时器0初始化
    while(1)
    {
        if(codeCnt==31)    //数据接收完成
        {
            if(IR_data[2]==~IR_data[3])//校验一下反码
            {
                for(i=0;i<4;i++) SendData(IR_data[i]);//输出4位hex
                delay_ms(30);//延时30ms等待数据超时,遥控器一次发3组相同的数据,这里用延时只接收1组就好了。
            }
        }
    }
}

//定时器0中断函数 125us
void Timer0() interrupt 1
{
   irTime++; //计时增加125us
   if(irTime==240) {irTime--;  codeCnt=0x3f;} // ir解码后码值存放时间, 240*125us = 30ms  0x3f=64
   if(IR_IO)   Irprot_LastState=1; // 记录IO状态
   else if(Irprot_LastState)       // 有下降沿,并且上个状态是高电平,表示红外管收到数据
   {
      Irprot_LastState = 0;        // 下降沿后IO状态记录为0
      if(irTime<24) // 小于24*125us=3ms的间隔才进行处理    因为红外线的0的周期1.125ms,1的周期2.25ms
      {
         codeCnt++; //数据码位计数+1
         codeCnt &= 0x1f; //等效if(codeCnt>0x1f) codeCnt=0x00; 这种操作比if判断更节约时间 0x1f=31
         IR_data[codeCnt>>3] <<= 1; //codeCnt>>3的范围(0~3),等效于IR_data[i]向左移动1位 (i范围0~3)
         if( irTime>15 )  IR_data[codeCnt>>3]++;  //大于15*125us=1.875ms的间隔为数据1 ,1就+1,0就不变。
      }
      irTime = 0;                  // 下降沿处理完成,将时间清0
   }    
}

以上代码为@浅雪大神友情提供

51-STC / 评 论 (0) / 热度 (834℃) / 2017-04-04 / 阅读全文  / MaWei