FPGA 红外接收器解码

Verilog 的 NEC 协议解码模块网上有很多,但普遍比较冗长。我写了一个很简洁的模块,能稳定地达到解码效果。

 

硬件:

图1:红外接收头

  1. 接收端:需要一个红外接收头,如图1,它的信号引脚只有一个,数据是串行的,因此我们只需要对该引脚时序进行解码即可。
  2. 发送端:可以用家电遥控器,必须是NEC协议的遥控器,可以试试各种空调遥控器,电视遥控器,一般总有几个是NEC协议的

 

时序分析:

先分析NEC协议时序:

图2:NEC 协议的完整数据帧时序

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

静默时数据引脚是高电平,接收到红外信号后会有一段数据帧到来,如图2是一个数据帧,它包括这几个部分:

  1. 引导码:9ms的低电平脉冲 + 4.5ms的高电平脉冲,如图3。
  2. 32位的数据:每一位由一段低电平和一段高电平组成,低电平固定为0.56ms,高电平的持续时间决定了这一位是0还是1,若该位是0,则高电平持续0.56ms,若该位是1,则高电平持续1.69ms,如图3。

 

在32位数据中,前8位是地址码,紧接着8位是地址码的反码,再紧接着的8位是数据码,最后的8位是数据码的反码。反码用于校验。我写的时序只提取8位的数据码。

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

 

Verilog实现:

首先,设红外信号引脚为ir,使用的时钟为clk,频率4KHz,clk可以用分频器实现。另外准备一个寄存器reg [51:0] shift; 。在每个clk上升沿,把shift左移一位,把红外引脚的读值存入shift[0],这样,shift寄存器始终都存放的是红外引脚的前52个状态。若shift==51’b000000000000000000000000000000000011111111111111111,说明此时引导码刚好结束,那么把一个计数寄存器cnt置26,开始读取数据,在以后的clk上升沿,判断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。

 

代码:

模块引脚定义:

  • clk: 4KHz 时钟信号
  • ir: 红外接收头输入信号
  • done: 平常为低电平,红外帧接收完成后会产生一个clk周期的高电平脉冲,每当clk的上升沿,如果done=1,表示可以从output reg [7:0] code中取数据了。
  • code: 解码的结果,8位的数据h
module IrDecode(
    input  clk,           // required 4kHz clock
    input  ir,            // input ir signal
    output reg done,      // decode done
    output reg [7:0]code  // decode 
);
reg [51:0] shift = 52'h0;
reg [4:0]  cnt   = 5'h0;
reg [7:0]  tmp   = 8'h0;
initial begin done = 1'b0, code = 8'h0; end

always @ (posedge clk) 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