FPGA 24 红外遥控(NEC协议)解码
主要功能 :设计了一个红外 NEC协议的解码模块
实现(设计)流程:通过遥控器发送的红外信号,外围红外信号接收传感器对数据进行接收,得到一个在基频上的高低电平的(非方波)的输入信号IIR,fpga内部设计该模块,实现对该信号进行解码,设计一个状态机,最终判断数据解析是否完整接收,进而判断数据是否正确。接收正确以后,数据解析出来的地址和数据,以及本次解析完成的标志信号。
实验目的 : ①解决非方波信号协议的数据解码 ② 状态机判断和时间计数器在状态机中的灵活使用。③标志信号和always 块的相互分离,本次实验中,状态机数据接收的32个bit信号,我们是在外面用了一个always 块来处理数据接收的情况的,时间计数器的使能和不使能在实验中用的也是非常的灵活,这个是我们需要掌握的一些比较好的设计方法,让自己的代码更具有可读性.
实验现象 : 按下红外遥控上的按键,然后在QuartusⅡ软件中使用Iln system sources and probeseditor中观察解码结果,根据解码结果与红外遥控实际发出的数值进行比较从而判断解码的正确性。
HT6221芯片是一款基于NEC红外通信协议的遥控编码芯片,基于HT6221芯片的红外遥控发送一次数据的数据帧定义如下图所示:
一帧数据由引导码、地址码和数据码以及数据反码组成.
其中,引导码由9ms的载波波形和4.5ms的低电平组成,代表一个数据帧的帧头;
地址码共16位,低位在前,高位在后,因此,该协议理论上支持最局65536个个同的用户;
8位数据码及其反码也是低位在前,高位在后。因此,理论上该协议支持高达256个用户指令。
该协议采用脉冲之间不同时长的时间间隔来区分“1”和“O”,下图为其编码协议中“1”.
但是(注这里很重要): 而在实际接收时,接收头接收到信号后【输出的波形刚好与此波形反相】。
所以:这里有两种处理方式,①在输入的fpga信号端加入一个非门,最后来回改发送的信号编写状态机的解析协议
② 对原始信号取反,直接对其反相信号进行解码(本次实验使用的是这种方式.)
即实际的接收数据会变成如下所示:
实际接收到信号如下所示:
使用signal_tap-II 真实采样到的数据波形:
IDEL : 空闲状态(IDEL),等待IR接收信号下降沿的到来。表示引导码信号开始到来.进入到LEADER_T9 状态.
LEADER_T9 : 识别9ms 的低电平引导码,识别成功则继续识别4.5ms的高电平引导码状态LEADER_T4_5 ,否则返回空闲态。
LEADER_T4_5 : 识别4.5ms的高电平引导码,识别成功则进入读码(DATE_GET )状态,否则返回空闲态。
DATE_GET : 读码状态,若32个码字已经读完或者读取过程中发生了错误,则返回空闲态(IDEL)。
ir_decode.文件
module ir_decode(
Clk,
Rst_n,
iIR,
Get_Flag,
irData,
irAddr
);
input Clk; //时钟
input Rst_n; //复位
input iIR; //红外模块信号输入
output Get_Flag; //解码成功标志信号
output [15:0]irData; //接收的数据 数据核数据反码
output [15:0]irAddr; //接收的地址 低8位核高8位
reg [18:0]cnt;//time counter
reg [3:0]state;
// 定义一个时钟到达的计数器标志位
reg T9ms_ok;
reg T4_5ms_ok;
reg T_56ms_ok;
reg T1_69ms_ok;
reg Get_Data_Done;
reg Cnt_en;
reg timeout;
reg [5:0]data_cnt;
reg [31:0]data_tmp;
assign irAddr = data_tmp[15:0];
assign irData = data_tmp[31:16];
//状态机状态参数
localparam
IDEL = 4'b0001,
LEADER_T9 = 4'b0010,
LEADER_T4_5 = 4'b0100,
DATE_GET = 4'b1000;
reg s_IR0,s_IR1;
//消除亚稳态
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
s_IR0 <= 1'b0;
s_IR1 <= 1'b0;
end
else begin
s_IR0 <= iIR;
s_IR1 <= s_IR0;
end
reg s_IR0_Temp,s_IR1_Temp;
// 获取上升沿和下降沿
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
s_IR0_Temp <= 1'b0;
s_IR1_Temp <= 1'b0;
end
else begin
s_IR0_Temp <= s_IR1;
s_IR1_Temp <= s_IR0_Temp;
// s_IR1_Temp:当前时刻t0 s_IR0_Temp:后一个时刻t1
end
wire ir_pedge,ir_nedge;
assign ir_pedge = !s_IR1_Temp && s_IR0_Temp; //上升沿:当前时刻是低电平,后一时刻是高电平
assign ir_nedge = s_IR1_Temp && !s_IR0_Temp; //下降沿:当前时刻是高电平,后一时刻是低电平
//时间计数器 cnt_en 使能信号
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
cnt <= 19'd0;
else if(Cnt_en == 1'b1)
cnt <= cnt + 1'b1;
else
cnt <= 19'd0;
// T9ms_ok 标志位
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
T9ms_ok <= 1'b0;
else if(cnt > 19'd325000 && cnt <19'd495000) // 8.5ms ~ 9.5ms
T9ms_ok <= 1'b1;
else
T9ms_ok <= 1'b0;
// T4_5ms_ok标志位 4ms ~ 5ms
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
T4_5ms_ok <= 1'b0;
else if(cnt > 19'd152500 && cnt <19'd277500) //4ms ~ 5ms
T4_5ms_ok <= 1'b1;
else
T4_5ms_ok <= 1'b0;
// T_56ms_ok标志位 0.5ms ~ 0.6ms
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
T_56ms_ok <= 1'b0;
else if(cnt > 19'd20000 && cnt <19'd35000) // 0.5ms ~ 0.6ms
T_56ms_ok <= 1'b1;
else
T_56ms_ok <= 1'b0;
// T1_69ms_ok标志位 1.6ms ~ 1.8ms
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
T1_69ms_ok <= 1'b0;
else if(cnt > 19'd75000 && cnt <19'd90000)
T1_69ms_ok <= 1'b1;
else
T1_69ms_ok <= 1'b0;
//计数器 timeout 超时处理
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
timeout <= 1'b0;
else if(cnt >= 19'd500000)
timeout <= 1'b1;
else
timeout <= 1'b0;
//
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
state <= IDEL; //默认空闲状态
Cnt_en <= 1'b0;
end
else if(!timeout //没有发出超时信号
begin
case(state)
IDEL:
if(ir_nedge)begin // ir_nedge :下降沿到来,根据引导码的编码方式
// 它是先是高电平,随后变成低电平的一个信号,所以检测下降沿
Cnt_en <= 1'b1; // 如果是检测到下降沿,则Cnt_en=1,计时器开始计时
state <= LEADER_T9; // 开始计数,进入 LEADER_T9 (9ms 低电平信号)状态
end
else begin //位检测到上升沿,保持该状态不变
state <= IDEL;
Cnt_en <= 1'b0;
end
LEADER_T9:
if(ir_pedge)begin // 判断是否检测到上升沿,当检测到上升沿时,表示要么进入下一状态,
// 要么就是数据从出错
if(T9ms_ok)begin //检测到帧头信号9ms的低电平时间在某个区间内 T9ms_ok 就会等于 1
Cnt_en <= 1'b0; // Cnt_en=0 ,主要目的是计数器清零
state <= LEADER_T4_5; //进入到下一个状态
end
else begin //否则没数据检测错误
state <= IDEL;
end
end
else begin //还未检测到上升沿
state <= LEADER_T9; //状态保持
Cnt_en <= 1'b1; //计数器继续计数
end
LEADER_T4_5:
if(ir_nedge)begin // 判断是否检测到下降沿,当检测到下降沿时,表示要么进入下一状态(数据接收状态),
if(T4_5ms_ok)begin//检测到帧头信号接收正确的标志位
Cnt_en <= 1'b0;// Cnt_en=0 ,计数器清零
state <= DATE_GET;//进入到下一个状态(数据接收状态)
end
else begin
state <= IDEL; //否则没数据检测错误
end
end
else begin //还未检测到上升沿
state <= LEADER_T4_5;
Cnt_en <= 1'b1;
end
DATE_GET:
if(ir_pedge && !T_56ms_ok) //当我们接收到上升沿(也就是说是之前是低电平560ms),且低电平时间不等于560us,那么数据接收错误
state <= IDEL;
else if(ir_nedge && (!T_56ms_ok && !T1_69ms_ok)) //高电平时间 既不是高电平560us、1.69ms ,那么数据接收错误
state <= IDEL;
else if(Get_Data_Done) //数据接收完成,进行空闲状态
state <= IDEL;
else if(ir_pedge && T_56ms_ok)begin // ir_pedge && T_56ms_ok 表示前面发送的是560us 低电平信号,进入高电平信号的判断
Cnt_en <= 1'b0; //正确接受到560us的低电平信号,时间计数器清零.
end
else if(ir_nedge && (T_56ms_ok || T1_69ms_ok))begin //ir_nedge && (T_56ms_ok || T1_69ms_ok) 高电平信号,且正常接收到 560us 的高电平或者 1.69ms 的高电平
Cnt_en <= 1'b0; //本次bit数据接收完成,时间计数器清零
end
else
Cnt_en <= 1'b1;
default:;
endcase
end
else begin //超时状态的话,让状态机复位
Cnt_en <= 1'b0;
state <= IDEL;
end
// 数据计数器模块
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin//复位信号
Get_Data_Done <= 1'b0;
data_cnt <= 6'd0;
data_tmp <= 32'd0;
end
else if(state == DATE_GET)begin //状态机状态 DATE_GET 时候 ,进入数据判断任务状态
if(ir_pedge && (data_cnt == 6'd32))begin //检测到上升沿的时候,且数据计数到33个上升沿的时候,表明数据已经接收完成
data_cnt <= 6'd0;
Get_Data_Done <= 1'b1; //发送 Get_Data_Done
end
else begin //注:下降沿到来,表示数据的高电平时间发送完成
if(ir_nedge) //只要检测到一个下降沿,数据计数器位+1 ,接收最后一个(第33个)下降沿的时候,变成 data_cnt = 32
data_cnt <= data_cnt + 1'b1;
if(ir_nedge && T_56ms_ok) //检测到下降沿,且计数器时间位 0.56us ,数据为0
data_tmp[data_cnt] <= 1'b0;
else if(ir_nedge && T1_69ms_ok) //检测到下降沿,且计数器时间位 0.56us ,数据为0
data_tmp[data_cnt] <= 1'b1;
Get_Data_Done <= 1'b0;
end
end
assign Get_Flag = Get_Data_Done;
endmodule
ir_decode_tb.v 仿真测试文件
`timescale 1ns/1ns
`define clk_period 20
module ir_decode_tb;
reg Clk;
reg Rst_n;
reg iIR;
wire Get_Flag;
wire [15:0]irData;
wire [15:0]irAddr;
integer i;
ir_decode ir_decode(
.Clk(Clk),
.Rst_n(Rst_n),
.iIR(iIR),
.Get_Flag(Get_Flag),
.irData(irData),
.irAddr(irAddr)
);
initial Clk = 1'b1;
always#(`clk_period/2)Clk = ~Clk;
initial begin
Rst_n = 1'b0;
iIR = 1'b1;
#(`clk_period*10+1'b1);
Rst_n = 1'b1;
#2000;
iIR = 1'b1;
send_data(1,8'h12); //调用 send_data任务
#60000000;
send_data(3,8'heb); //调用 send_data任务
#60000000;
$stop ;
end
task send_data;
input [15:0]addr;
input [7:0]data;
begin
iIR = 0;#9000000; //发送帧头保持9ms
iIR = 1;#4500000; //发送高电平保持4.5ms
for(i=0;i<=15;i=i+1)begin //发送地址,调用 bit_send task任务
bit_send(addr[i]);
end
for(i=0;i<=7;i=i+1)begin //发送数据
bit_send(data[i]);
end
for(i=0;i<=7;i=i+1)begin //发送数据反码
bit_send(~data[i]);
end
iIR = 0;#560000; //发送停止位数延时0.56ms
iIR = 1; //发送停止位的高电平
end
endtask
task bit_send;
input one_bit;
begin
iIR = 0; #560000; //发送数据0,延时0.56ms
iIR = 1;
if(one_bit) //发送1,高电平维持1.69ms
#1690000;
else //发送0,高电平维持0.56ms
#560000;
end
endtask
endmodule