目录
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
如需上述资料私信或留下邮箱!