FPGA红外解码

Verilog的NEC协议解码时序,在网上有很多,但是都太冗长。自己写一个20多行的,测试发现能稳定地达到解码效果。

NEC协议经常用于家电遥控器,这个程序可以用家电遥控器测试。

 

硬件:

图:红外接收头

红外接收头的数据引脚只有一个,数据是串行的,因此我们只需要对一个引脚时序进行解码即可。

时序分析:

先分析分析NEC协议时序:

图:整体时序

图:引导码,重复引导码,数据1,数据0 的时序

如图,平常数据引脚是高电平,接收到红外信号后会有一段引导码:9ms的低电平脉冲,然后是一段4.5ms的高电平脉冲。紧接着是32位的数据,每一位由一段低电平和一段高电平组成,先是一段0.56ms的低电平脉冲,然后是一段高电平脉冲,若该位是0,则高电平持续0.56ms,若该位是1,则高电平持续1.69ms。

 

在32位数据中,前8位是地址码,紧接着8位是地址码的反码,再紧接着的8位是数据码,最后的8位是数据码的反码。反码用于校验。但是我写的时序不考虑校验。只提取8位的数据码,地址码也不提取,因为对于每个遥控器,地址码都是一样的,并无提取的意义。

另外,NEC协议规定,若你长按遥控器的一个键,则发送完第一个完整的时序后,会持续发送重复的引导码。与按下键时第一次发送的引导码不同,这个引导码的高电平持续2.25ms,我们可以依此区分。我的程序不考虑长按按钮,屏蔽掉了重复码。

可见这是一个比较简单的时序,我只写了20多行,综合出来不到100个逻辑单元。网上好多写170行代码,综合出来占用了300个逻辑单元的,也是醉了。

 

Verilog实现:

首先,设红外信号引脚为ir,使用的时钟为cp,频率4KHz,也就是每0.25ms有一个上升沿,cp可以用FPGA的其它模块提供。另外准备一个寄存器reg [51:0] shift; 。在每个cp上升沿,把shift左移一位,把红外引脚的读值存入shift[0],这样,shift寄存器始终都存放的是红外引脚的前52个状态。若shift==51’b000000000000000000000000000000000011111111111111111,说明此时引导码刚好结束,那么把一个计数寄存器cnt置26,开始读取数据,在以后的cp上升沿,判断shift[1:0]是否为2’b10,若是,说明刚刚遇到了一个ir的下降沿,那么我们可以通过shift[4]知道上一个高电平脉冲是长的还是短的,(若shift[4]=1,说明上一个高电平脉冲比较长,反正比较短)。通过判断shift[4],我们获得了一位数据,把它放入一个8位的缓存寄存器tmp的最高位,同时把tmp右移一位。,每获得一位,就让cnt-1,直到cnt降到1,此时ir信号刚好前进到了数据码的第24位,tmp里存的刚好是8位数据码。后面的数据反码我们不再关心。那么就可以把tmp放入输出结果寄存器code。

 

代码:

模块引脚定义:

  • cp: 4KHz 时钟信号
  • ir: 红外接收头输入信号
  • done: 平常为低电平,红外接收完成后会产生一个cp周期的高电平脉冲,每当cp上升时,表示可以从code中取数据了。
  • code: 解码的结果,8位的数据
module ir(
    input  cp,ir,
    output reg done,
    output reg [7:0]code
);
reg [51:0] shift;
reg [4:0]  cnt;
reg [7:0]  tmp;

always @ (posedge cp) begin
    done <= 0;
    if( cnt>0 && shift[1:0]==2'b10 ) begin
        tmp <= tmp>>1;
        tmp[7] <= (shift[4]) ? 1 : 0;
        cnt <= cnt-1;
        if(cnt==1) begin
            done <= 1;
            code <= tmp;
        end
    end
    shift <= shift<<1;
    shift[0] <= ir;
    if(shift[51:0]==51'b11111111111111111)
        cnt <= 26;
end

endmodule