FPGA- 红外遥控(附代码)

该文详细介绍了使用FPGA实现红外遥控信息的接收和解码过程,重点讲解了红外接收模块的设计,包括状态机的运用和NEC协议的解析。文中还提到了LED灯控制模块,用于根据接收到的重复码控制LED灯的闪烁。整个系统通过检测红外信号的高低电平时间间隔来识别NEC编码协议,并在检测到有效数据时显示在数码管上。
摘要由CSDN通过智能技术生成

目录

1. 理论学习

2. 实操

2.1 整体说明

2.2 红外接收模块

2.2.1 模块框图

2.2.2 状态机

2.2.3 波形图绘制

2.2.4  RTL代码

2.3 LDE灯控制模块

2.3.1 模块框图

2.3.2 波形绘制

2.3.3 RTL代码

2.3.4 仿真代码

2.4 顶层模块

2.4.1 模块框图 

2.4.2 RTL代码

3.上板验证

4. 总结

1. 理论学习

 红外遥控简介:

      红外遥控是一种无线、非接触控制技术,具有抗干扰能力强,信息传输可靠,功耗低,成本低,易实现等显著优点, 被诸多电子设备特别是家用电器广泛采用。本文讲述如何使用FPGA技术实现红外信息的编码以及接收到红外信息后的解码方式。

    红外线遥控是利用近红外光传送遥控指令的,波长为 0.76um~1.5um。用近红外作为遥控光源,是因为目前红外发射器件(红外发光管)与红外接收器件(光敏二极管、三极管及光电池)的发光与受光峰值波长一般为 0.8um~0.94um, 在近红外光波段内,二者的光谱正好重合,能够很好地匹配,可以获得较高的传输效率及较高的可靠性。

 红外遥控系统组成:

     由上图可知系统分为三部分:发射部分、接收部分、FPGA解码部分。

 红外遥控的编码协议:   

        红外遥控的编码协议种类繁多,如: NEC、 Philips RC-5、 Philips RC-6、 Sony SIRC
等,而使用最多的是 NEC 协议。NEC 协议采用的是  PPM(Pulse Position Modulation,脉冲位置调制)进行编码。当我们按下遥控器的一个按键时,会发送一帧的数据。这一帧数据由引导码、地址码、地址反码、数据码、数据反码以及一位结束位(可忽略)组成。地址码、地址反码、数据码、数据反码都是8位二进制数使用逻辑“0”和逻辑“1”表示,低位在前高位在后,如下图所示。 

    下面介绍逻辑“0”和逻辑“1”以及一直按着按键不放的重复码的编码方式。

 逻辑“0”和逻辑“1”编码方式

       由上图可知:逻辑“1”和逻辑“0”是根据脉冲之间的时间间隔来区分的。 逻辑“1”由 560us 的高脉冲加上1.69ms 的低电平组成,而逻辑“0”由 560us 的高脉冲加上 560us 的低电平组成。


       长按时,先发送数据,然后每隔 110ms 会发送一个重复码,重复码由9ms 的高电平和2.25ms 的低电平以及 560us 的高电平(结束标志)组成。

      由于使用的是一体化接收头(HS0038B)接收到信号后输出到 FPGA 的波形刚好与发送的波形相反。发送高电平,接收后输出就为低电平;发送低电平,接收后输出就为高电平。 FPGA 芯片接收的波形如下图所示,以下的波形的持续时间是判断红外输入数据是否是按照 NEC 协议发送的关键。

    引导码接收波形图

 “1” /“0”数据接收波形图

 重复码接收波形图

综上,红外遥控按下时发送的数据的顺序是

2. 实操

       实验目标当按下红外遥控器时,对应按键的键值可以稳定显示在数码管上。另外如果一直按住按键,则小灯闪烁一次。

2.1 整体说明

 红外遥控系统框图

       本实验工程包括 4 个模块,其中 seg_dynamic 模块可直接调用,之前写过。

     红外遥控实验的工作流程:一体化接收头接收到红外遥控发来的红外信息后,将红外信息传入 FPGA 芯片内使用红外接收模块( infrared_rcv)进行解码,若接收的信息与协议一致,则让接收到的数据传入数码管显示模块显示数据,若接收到重复码,则让重复码使能信号传入 led 控制模块让 led 闪烁。

2.2 红外接收模块

2.2.1 模块框图

       红外接收模块功能:判断红外输入数据是否是按照 NEC 协议发送的,如果是按照协议进行发送的,则输出发送的数据码和重复码使能信号。NEC 协议就是前面讲的数据由引导码、地址码、地址反码、数据码、数据反码以及一位结束位(可忽略)组成的,其数据之间间隔时间是不同的。

2.2.2 状态机

       由于数据组成较为复杂,使用状态机来标记其各个过程。

2.2.3 波形图绘制

2.2.4  RTL代码

`timescale  1ns/1ns

module  infrared_rcv
(
    input   wire        sys_clk     ,  
    input   wire        sys_rst_n   ,  
    input   wire        infrared_in ,   //红外接受信号

    output  reg         repeat_en   ,   //重复码使能信号
    output  reg [19:0]  data            //接收的控制码
);

parameter   CNT_0_56MS_L  =   20000 ,  
            CNT_0_56MS_H  =   35000 ,
            CNT_1_69MS_L  =   80000 ,  
            CNT_1_69MS_H  =   90000 ,
            CNT_2_25MS_L  =   100000,  
            CNT_2_25MS_H  =   125000,
            CNT_4_5MS_L   =   175000,  
            CNT_4_5MS_H   =   275000,
            CNT_9MS_L     =   400000,  
            CNT_9MS_H     =   490000;
//state
parameter   IDLE        =   5'b0_0001,  //空闲状态
            S_T9        =   5'b0_0010,  //监测同步码低电平
            S_JUDGE     =   5'b0_0100,  //判断重复码和同步码高电平
            S_IFR_DATA  =   5'b0_1000,  //接收数据
            S_REPEAT    =   5'b1_0000;  //重复码


wire            ifr_in_rise ; 
wire            ifr_in_fall ; 

reg         infrared_in_d1  ; 
reg         infrared_in_d2  ; 
reg [18:0]  cnt             ; 
reg         flag_0_56ms     ; 
reg         flag_1_69ms     ; 
reg         flag_2_25ms     ; 
reg         flag_4_5ms      ; 
reg         flag_9ms        ; 
reg [4:0]   state           ; 
reg [5:0]   data_cnt        ; 
reg [31:0]  data_tmp        ; 

//检测红外信号的上升沿和下降沿
assign  ifr_in_rise = (~infrared_in_d2) & (infrared_in_d1) ;
assign  ifr_in_fall = (infrared_in_d2) & (~infrared_in_d1) ;

//对infrared_in信号打拍
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
            infrared_in_d1  <=  1'b0;
            infrared_in_d2  <=  1'b0;
        end
    else
        begin
            infrared_in_d1  <=  infrared_in;
            infrared_in_d2  <=  infrared_in_d1;
        end

//cnt
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt <=  19'd0;
    else
        case(state)
            IDLE:   cnt <=  19'd0;
            S_T9:   if((ifr_in_rise==1'b1) && (flag_9ms==1'b1))
                        cnt <=  19'd0;
                    else
                        cnt <=  cnt + 1;
            S_JUDGE:if((ifr_in_fall==1'b1) && (flag_2_25ms==1'b1 || flag_4_5ms==1'b1))
                        cnt <=  19'd0;
                    else
                        cnt <=  cnt + 1;
            S_IFR_DATA: if((flag_0_56ms == 1'b1) && (ifr_in_rise==1'b1))
                            cnt <=  19'd0;
                        else    if(((flag_0_56ms==1'b1) || (flag_1_69ms==1'b1)) && (ifr_in_fall==1'b1))
                            cnt <=  19'd0;
                        else
                            cnt <=  cnt + 1;
            default:cnt <=  19'd0;
        endcase

//flag_0_56ms:计数到0.56ms范围拉高标志信号
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        flag_0_56ms <=  1'b0;
    else    if((state == S_IFR_DATA) && (cnt >= CNT_0_56MS_L) && (cnt <= CNT_0_56MS_H))
        flag_0_56ms <=  1'b1;
    else
        flag_0_56ms <=  1'b0;

//flag_1_69ms:计数到1.69ms范围拉高标志信号
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        flag_1_69ms <=  1'b0;
    else    if((state == S_IFR_DATA) && (cnt >= CNT_1_69MS_L) && (cnt <= CNT_1_69MS_H))
        flag_1_69ms <=  1'b1;
    else
        flag_1_69ms <=  1'b0;

//flag_2_25ms:计数到2.25ms范围拉高标志信号
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        flag_2_25ms <=  1'b0;
    else    if((state == S_JUDGE) && (cnt >= CNT_2_25MS_L) && (cnt <= CNT_2_25MS_H))
        flag_2_25ms <=  1'b1;
    else
        flag_2_25ms <=  1'b0;

//flag_4_5ms:计数到4.5ms范围拉高标志信号
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        flag_4_5ms <=  1'b0;
    else    if((state == S_JUDGE) && (cnt >= CNT_4_5MS_L) && (cnt <= CNT_4_5MS_H))
        flag_4_5ms <=  1'b1;
    else
        flag_4_5ms <=  1'b0;

//flag_9ms:计数到9ms范围拉高标志信号
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        flag_9ms <=  1'b0;
    else    if((state == S_T9) && (cnt >= CNT_9MS_L) && (cnt <= CNT_9MS_H))
        flag_9ms <=  1'b1;
    else
        flag_9ms <=  1'b0;

//状态机:状态跳转
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        state   <=  IDLE;
    else
        case(state)
    //若检测到红外信号下降沿到来跳转到S_T9状态
        IDLE:
            if(ifr_in_fall == 1'b1)
                state   <=  S_T9;
            else    //若没检测到红外信号的下降沿,则让其保持在IDLE状态
                state   <=  IDLE;
        S_T9:   //若检测到红外信号上升沿到来,则判断flag_9ms是否为1
                //若检测到时间接近9ms,则跳转到S_judje状态
            if((ifr_in_rise == 1'b1) && (flag_9ms ==  1'b1))
                state   <=  S_JUDGE;
            else    if((ifr_in_rise == 1'b1) && (flag_9ms ==  1'b0))
                state   <=  IDLE;
            else
                state   <=  S_T9;
        S_JUDGE:  //若检测到红外信号下降沿到来,则判断flag_2_25ms是否为1
                  //若检测到时间接近2.25ms,则跳转重复码状态
            if((ifr_in_fall == 1'b1) && (flag_2_25ms == 1'b1))
                state   <=  S_REPEAT;
            else    if((ifr_in_fall == 1'b1) && (flag_4_5ms == 1'b1))
                state   <=  S_IFR_DATA;
            else    if((ifr_in_fall == 1'b1) && (flag_2_25ms == 1'b0) && (flag_4_5ms == 1'b0))
                state   <=  IDLE;
            else
                state   <=  S_JUDGE;
        S_IFR_DATA:
            //若上升沿到来,低电平保持时间不满足编码协议,则回到空闲状态
            if(ifr_in_rise == 1'b1 && flag_0_56ms == 1'b0)
                state   <=  IDLE;
            //若下降沿到来,高电平保持时间不满足编码0或1,则回到空闲状态
            else    if(ifr_in_fall == 1'b1 && (flag_0_56ms == 1'b0 &&
                                                    flag_1_69ms == 1'b0))
                state   <=  IDLE;
            //数据接收完毕之后回到空闲状态,等待下一个指令的到来
            else    if(ifr_in_rise == 1'b1 && data_cnt == 6'd32)
                state   <=  IDLE;
        S_REPEAT:
            /*若上升沿到来,无论时间是否到了0.56ms,
            状态机都跳回IDLE状态等待下一数据码或重复码的到来*/
            if(ifr_in_rise == 1'b1)
                state   <=  IDLE;
            else
                state   <=  S_REPEAT;
        default:
                state   <=  IDLE;
        endcase

//data_tmp
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        data_tmp    <=  32'b0;
    else    if(state == S_IFR_DATA && ifr_in_fall == 1'b1 &&
                                                    flag_0_56ms  == 1'b1)
        data_tmp[data_cnt]  <=  1'b0;
    else    if(state == S_IFR_DATA && ifr_in_fall == 1'b1 &&
                                                    flag_1_69ms  == 1'b1)
        data_tmp[data_cnt]  <=  1'b1;
    else
        data_tmp    <=  data_tmp;

//data_cnt
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        data_cnt    <=  1'b0;
    else    if(ifr_in_rise == 1'b1 && data_cnt == 6'd32)
        data_cnt    <=  1'b0;
    else    if(ifr_in_fall == 1'b1 && state == S_IFR_DATA)
        data_cnt    <=  data_cnt + 1'b1;
    else
        data_cnt    <=  data_cnt;

//repeat_en
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        repeat_en  <=  1'b0;
    else    if(state == S_REPEAT && (data_tmp[23:16] == 
                                        ~data_tmp[31:24]))
        repeat_en  <=  1'b1;
    else
        repeat_en  <=  1'b0;

always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        data    <=  20'b0;
    //数据接收完之后若数据校验正确,则输出数据码的数据
    else    if(data_tmp[23:16] == ~data_tmp[31:24] && data_tmp[7:0] ==
                                    ~data_tmp [15:8] && data_cnt==6'd32)
        data   <=  {12'b0,data_tmp[23:16]};

endmodule

2.3 LDE灯控制模块

2.3.1 模块框图

 

       模块功能:使用红外接收模块产生的重复码使能信号去对 led 进行0.5ms点亮。

2.3.2 波形绘制

       为什么不直接在repent_en高电平时同时拉高LED信号?因为repent_en高电平时间只有560us,低电平时间有11ms左右效果是不明显的。

2.3.3 RTL代码

`timescale  1ns/1ns

module  led_ctrl
(
    input   wire    sys_clk     ,   
    input   wire    sys_rst_n   ,   
    input   wire    repeat_en   ,   //重复码使能信号
    
    output  reg     led             //输出led灯信号
);

parameter   CNT_MAX =   2500_000;

//wire  define
wire    repeat_en_rise  ;   //重复码使能信号上升沿

//reg   define
reg         repeat_en_d1;   //重复码使能信号打一拍
reg         repeat_en_d2;   //重复码使能信号打两拍
reg         cnt_en      ;   //计数器使能信号
reg [21:0]  cnt         ;   //计数器

//获得repeat_en上升沿信号
assign  repeat_en_rise  =   repeat_en_d1 &  ~repeat_en_d2;

//对reeat_en打两拍
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
            repeat_en_d1    <=  1'b0;
            repeat_en_d2    <=  1'b0;
        end
    else
        begin
            repeat_en_d1    <=  repeat_en;
            repeat_en_d2    <=  repeat_en_d1;
        end

//当重复码使能信号上升沿来到,拉高计数器使能信号,计到50ms后拉低
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
            cnt_en <=  1'b0;
    else    if(cnt == CNT_MAX - 1)
            cnt_en <=  1'b0;
    else    if(repeat_en_rise == 1'b1)
            cnt_en <=  1'b1;
            
//当计数器使能信号为高时让计数器开始计数,为低时计数器清零
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
            cnt <=  22'b0;
    else    if(cnt_en == 1'b1)
            cnt <=  cnt + 1;
    else
            cnt <=  22'b0;

//当计数器大于0时,点亮led灯,也就是当使能信号到来,led灯会亮50ms
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        led <=  1'b1;
    else    if(cnt > 0)
        led <=  1'b0;
    else
        led <=  1'b1;

endmodule

2.3.4 仿真代码

`timescale  1ns/1ns

module  tb_top_infrared_rcv();

wire            led    ;
wire    [5:0]   sel    ;
wire    [7:0]   seg    ;

reg     sys_clk     ;
reg     sys_rst_n   ;
reg     infrared_in ;

initial
    begin
        sys_clk     =   1'b1;
        sys_rst_n   <=  1'b0;
        infrared_in <=  1'b1;
        #100
        sys_rst_n   <=  1'b1;
//引导码
        #1000
        infrared_in <=  1'b0; #9000000
        infrared_in <=  1'b1; #4500000
//地址码(发送地址码8’h99)
//数据1
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #1690000
//数据0
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #560000
//数据0
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #560000
//数据1
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #1690000
//数据1
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #1690000
//数据0
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #560000
//数据0
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #560000
//数据1
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #1690000
//地址反码(地址反码为8’h66)
//数据0
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #560000
//数据1
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #1690000
//数据1
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #1690000
//数据0
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #560000
//数据0
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #560000
//数据1
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #1690000
//数据1
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #1690000
//数据0
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #560000
//数据码(发送数据码8’h22)
//数据0
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #560000
//数据1
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #1690000
//数据0
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #560000
//数据0
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #560000
//数据0
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #560000
//数据1
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #1690000
//数据0
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #560000
//数据0
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #560000
//数据反码(数据反码为8’hdd)
//数据1
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #1690000
//数据0
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #560000
//数据1
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #1690000
//数据1
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #1690000
//数据1
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #1690000
//数据0
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #560000
//数据1
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #1690000
//数据1
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #1690000
//重复码
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1; #42000000
        infrared_in <=  1'b0; #9000000
        infrared_in <=  1'b1; #2250000
        infrared_in <=  1'b0; #560000
        infrared_in <=  1'b1;
    end

//clk:产生时钟
always  #10 sys_clk <=  ~sys_clk;


top_infrared_rcv    top_infrared_rcv_inst
(
    .sys_clk     (sys_clk    ),   
    .sys_rst_n   (sys_rst_n  ),   
    .infrared_in (infrared_in),   
    
    .sel         (sel        ),   
    .seg         (seg        ),   
    .led         (led        )    

);

endmodule

2.4 顶层模块

2.4.1 模块框图 

2.4.2 RTL代码

`timescale  1ns/1ns

module  top_infrared_rcv
(
    input   wire            sys_clk     ,   
    input   wire            sys_rst_n   ,   
    input   wire            infrared_in ,   //红外接收信号

    output  wire    [5:0]   sel ,   
    output  wire    [7:0]   seg ,   
    output  wire            led     
);

wire            repeat_en   ;   //重复码使能信号
wire    [19:0]  data        ;   //接收的控制码

infrared_rcv    infrared_rcv_inst
(
    .sys_clk     (sys_clk    ),   
    .sys_rst_n   (sys_rst_n  ),   
    .infrared_in (infrared_in),   

    .repeat_en   (repeat_en  ),   
    .data        (data       )    
);

led_ctrl    led_ctrl_inst
(
    .sys_clk     (sys_clk  ) ,   
    .sys_rst_n   (sys_rst_n) ,   
    .repeat_en   (repeat_en) ,   

    .led         (led      )
);


seg_dynamic seg_dynamic_inst
(
    .sys_clk     (sys_clk  ), 
    .sys_rst_n   (sys_rst_n), 
    .data        (data     ), 
    .point       (6'd0     ), 
    .seg_en      (1'b1     ), 
    .sign        (1'b0     ), 

    .sel         (sel      ), 
    .seg         (seg      )  

);

endmodule

3.上板验证

4. 总结

 *1. 实验核心部分在于红外接收模块,要理解何时进行状态跳转以及判断其跳转的条件。

*2. 代码编写上有些新的知识点,注意学习。

说明:

本人使用的是野火家Xilinx Spartan6系列开发板及配套教程,以下内容如有疑惑或错误欢迎评论区指出。

开发软件:ise14.7     仿真:modelsim 10.5 

如需上述资料私信或留下邮箱!

  • 6
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咖啡0糖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值