NEC协议解码 + 红外接收器 + 遥控器按键映射 — 用遥控器控制FPGA!
🏆 成就:遥控大师 ✅ Verilator验证通过
NEC是最常见的红外遥控协议——38kHz载波调制,引导码+32位数据,自带校验。你家电视遥控器大概率就是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
endmodulemodule 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 |
| ↑ | 0x46 | ↓ | 0x15 |
| ← | 0x44 | → | 0x43 |
| OK | 0x40 | 0 | 0x16 |
| 1 | 0x0C | 2 | 0x18 |
| 3 | 0x5E | 音量+ | 0x09 |
💡 VS1838B接收器:红外接收模块内置38kHz带通滤波+放大+解调。输出低电平=检测到38kHz红外,高电平=无信号。供电3.3~5V,无需外部元件!
练习1:用遥控器控制VGA精灵移动
练习2:实现音量调节:按↑↓键改变PWM音频占空比
练习3:添加重复码处理:长按连续触发
练习4:学习其他遥控协议(RC5/RC6/SIRC)
练习5:实现红外发送器(NEC编码输出)
步骤1:verilator --lint-only nec_decoder.v
步骤2:verilator --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谐波