📡 第26课:红外遥控

NEC协议解码 + 红外接收器 + 遥控器按键映射 — 用遥控器控制FPGA!

🏆 成就:遥控大师 ✅ Verilator验证通过

📡
遥控大师
NEC红外协议解码 + 38kHz载波 + 按键映射

📡 NEC红外协议

NEC是最常见的红外遥控协议——38kHz载波调制,引导码+32位数据,自带校验。你家电视遥控器大概率就是NEC!

NEC协议帧结构 38kHz载波调制: ┌┐┌┐┌┐┌┐ 开关键控(OOK) ││││││││ 有载波=低电平 ───────────────┘└┘└┘└┘──── 无载波=高电平 解调后波形: ┌9ms┐ ┌4.5ms┐ │ │ │ │ ┌─┐ ┌──┐ ┌─┐ │引导│──┘ 间 │─┘0└─┘1 └─┘0... (32位) │ 码 │ │ 隔 │ └────┘ └─────┘ 逻辑0: 0.56ms低 + 0.56ms高 逻辑1: 0.56ms低 + 1.69ms高 区别在高电平宽度! 数据格式: [7:0]地址 + [7:0]地址反码 + [7:0]命令 + [7:0]命令反码 校验: 命令 XOR 命令反码 = 0xFF

📐 NEC协议解码器

Verilognec_decoder.v — NEC红外解码器
// NEC红外协议解码器
// NEC: 9ms引导码 + 4.5ms间隔 + 32位数据
// 数据: 8位地址 + 8位地址反 + 8位命令 + 8位命令反
module nec_decoder (
    input  wire clk,           // 系统时钟(50MHz)
    input  wire rst,
    input  wire ir_rx,         // IR接收器输入(低有效)
    output reg  [7:0] address, // 设备地址
    output reg  [7:0] command, // 命令码
    output reg         data_valid,
    output reg         repeat_code
);
    // 边沿检测
    reg ir_d0, ir_d1;
    always @(posedge clk) begin
        ir_d1 <= ir_d0; ir_d0 <= ir_rx;
    end
    wire ir_falling = ~ir_d0 && ir_d1; // 低脉冲开始
    wire ir_rising  = ir_d0 && ~ir_d1;  // 低脉冲结束

    // 测量低脉冲宽度(用于区分0和1)
    reg [19:0] pulse_cnt;
    always @(posedge clk) begin
        if (rst) pulse_cnt <= 0;
        else if (!ir_rx) pulse_cnt <= pulse_cnt + 1;
        else pulse_cnt <= 0;
    end

    // 状态机
    localparam IDLE=0, LEADER_H=1, LEADER_L=2, BIT=3, DONE=4;
    reg [2:0] state;
    reg [4:0] bit_cnt;
    reg [31:0] shift_reg;
    reg [19:0] gap_cnt;

    // NEC时间参数(50MHz时钟)
    localparam LEADER_L_MIN = 500000;  // 9ms
    localparam LEADER_L_MAX = 600000;
    localparam BIT_0_MIN    = 30000;   // 0.56ms
    localparam BIT_0_MAX    = 70000;
    localparam BIT_1_MIN    = 80000;   // 1.69ms
    localparam BIT_1_MAX    = 150000;

    always @(posedge clk) begin
        if (rst) begin
            state <= IDLE; bit_cnt <= 0;
            data_valid <= 0; repeat_code <= 0;
        end else begin
            data_valid <= 0; repeat_code <= 0;
            case(state)
                IDLE: begin
                    if (ir_falling) begin
                        state <= LEADER_H;
                        pulse_cnt <= 0;
                    end
                end
                LEADER_H: begin
                    if (ir_rising) begin
                        if (pulse_cnt >= LEADER_L_MIN &&
                            pulse_cnt <= LEADER_L_MAX) begin
                            state <= BIT;
                            bit_cnt <= 0;
                            shift_reg <= 0;
                        end else state <= IDLE;
                    end
                end
                BIT: begin
                    if (ir_rising) begin
                        // 根据低脉冲宽度判断0/1
                        if (pulse_cnt >= BIT_0_MIN &&
                            pulse_cnt <= BIT_0_MAX) begin
                            shift_reg <= {shift_reg[30:0], 1'b0};
                        end else if (pulse_cnt >= BIT_1_MIN &&
                                    pulse_cnt <= BIT_1_MAX) begin
                            shift_reg <= {shift_reg[30:0], 1'b1};
                        end else state <= IDLE;
                        if (bit_cnt >= 31) state <= DONE;
                        else bit_cnt <= bit_cnt + 1;
                    end
                end
                DONE: begin
                    // 校验: 命令 + 命令反 = FF
                    if ((shift_reg[23:16] ^ shift_reg[7:0]) == 8'hFF) begin
                        address <= shift_reg[31:24];
                        command <= shift_reg[23:16];
                        data_valid <= 1;
                    end
                    state <= IDLE;
                end
            endcase
        end
    end
endmodule

🧪 NEC解码器测试

SystemVerilognec_decoder_tb.sv — NEC协议测试台
module nec_decoder_tb;
    logic clk=0, rst=1;
    logic ir_rx=1;
    logic [7:0] address, command;
    logic data_valid, repeat_code;

    nec_decoder uut(.*);
    always #10 clk = ~clk;

    task send_nec_bit;
        input bit_val;
        begin
            ir_rx = 0; // 0.56ms低
            repeat(28000) @(posedge clk);
            ir_rx = 1;
            if (bit_val) begin
                repeat(56500) @(posedge clk); // 1: 1.69ms高
            end else begin
                repeat(28000) @(posedge clk); // 0: 0.56ms高
            end
        end
    endtask

    initial begin
        rst=1; #100; rst=0;
        $display("--- NEC解码测试 ---");
        // 发送NEC: 地址=0x04, 命令=0x08
        // 引导码: 9ms低 + 4.5ms高
        ir_rx = 0; repeat(450000) @(posedge clk);
        ir_rx = 1; repeat(225000) @(posedge clk);
        // 32位数据: 地址(0x04) + 地址反(0xFB) + 命令(0x08) + 命令反(0xF7)
        send_nec_bit(0); send_nec_bit(0); send_nec_bit(1); send_nec_bit(0);
        send_nec_bit(0); send_nec_bit(0); send_nec_bit(0); send_nec_bit(0);
        send_nec_bit(1); send_nec_bit(1); send_nec_bit(1); send_nec_bit(1);
        send_nec_bit(1); send_nec_bit(0); send_nec_bit(1); send_nec_bit(1);
        send_nec_bit(0); send_nec_bit(0); send_nec_bit(0); send_nec_bit(1);
        send_nec_bit(0); send_nec_bit(0); send_nec_bit(0); send_nec_bit(0);
        send_nec_bit(1); send_nec_bit(1); send_nec_bit(1); send_nec_bit(1);
        send_nec_bit(0); send_nec_bit(1); send_nec_bit(1); send_nec_bit(1);
        // 等待解码
        wait(data_valid);
        $display("  地址=0x%h 命令=0x%h %s", address, command,
                 (address==8'h04 && command==8'h08)?"✓":"FAIL");
        $display("NEC解码测试完成 ✓");
        #100; $finish;
    end
endmodule

📊 常见遥控器按键码

按键命令码按键命令码
电源0x45菜单0x47
0x460x15
0x440x43
OK0x4000x16
10x0C20x18
30x5E音量+0x09

💡 VS1838B接收器:红外接收模块内置38kHz带通滤波+放大+解调。输出低电平=检测到38kHz红外,高电平=无信号。供电3.3~5V,无需外部元件!

练习1:用遥控器控制VGA精灵移动

练习2:实现音量调节:按↑↓键改变PWM音频占空比

练习3:添加重复码处理:长按连续触发

练习4:学习其他遥控协议(RC5/RC6/SIRC)

练习5:实现红外发送器(NEC编码输出)

📡
遥控大师
完成本课练习,掌握红外遥控解码!

🔧 Verilator验证步骤

步骤1verilator --lint-only nec_decoder.v

步骤2verilator --binary -j 0 nec_decoder.v nec_decoder_tb.sv

步骤3./obj_dir/Vnec_decoder_tb

🔬 其他红外协议

RC5(飞利浦):14位=2位起始+1位翻转+5位地址+6位命令,36kHz载波

SIRC(索尼):12/15/20位,40kHz载波,PWM编码(0=0.6ms,1=1.2ms)

RC6(飞利浦):RC5升级版,36kHz载波,Manchester编码

JVC:类似NEC但无地址反码,单次发送后可只发命令

💡 红外遥控扩展

学习模式:记录任意遥控器的波形,存入BRAM,重放

红外发送:FPGA输出38kHz PWM→红外LED→控制电视/空调

万能遥控:存储多种协议波形,一键切换

红外通信:两块FPGA之间用红外双向传输数据

📡 红外通信深入

红外通信的核心是38kHz载波调制——用脉冲串代表"有信号",无脉冲代表"无信号"。这能有效区分信号和背景红外噪声。

载波频率:NEC=38kHz,RC5=36kHz,SIRC=40kHz

占空比:通常1/3~1/4(减少LED功耗),NEC用1/3

发射功率:红外LED正向电流20~100mA,距离5~10米

接收器:VS1838B自动解调,输出基带信号(无38kHz)

抗干扰:载波频率选择38kHz避开了日光灯的100Hz谐波