项目名称
HT6221红外遥控解码设计
具体要求
接收红外按键的数据在ISSP上观察
设计说明
下图为红外遥控器及按键图。红外接收头有三个引脚,电源、地和信号输出。
HT6221芯片的红外遥控发送数据帧定义,一帧数据由引导码、地址码、数据码及数据反码组成。地址码共16位,低位在前,高位在后,8位数据码及其反码也是低位在前,高位在后。
HT6221芯片是一款基于NEC红外通信协议的遥控编码芯片,该协议采用脉冲之间不同时长的时间间隔来区分“1”和“0”,下图为其编码协议中“1”和“0”的编码波形,而在实际接收时,接收头接收到信号后输出的波形刚好与此波形反相。
数据1是0.56ms的低电平和1.69ms的高电平,数据0是0.56ms的低电平和0.56ms的高电平。
代码设计
本次将代码拆开说明,先定义红外解码模块端口列表。iIR为接收头输出数据,irdata为解码后的数据码,iraddr为解码后的地址码,get_flag为解码完成标志信号。
module ir_decode(
input clk,
input rst_n,
input iIR,
output [15:0]irdata,
output [15:0]iraddr,
output get_flag
);
对接收头输出的数据进行同步,并定义一个上升沿和下降沿信号。
reg iIR_s0;
reg iIR_s1;
always@(posedge clk or negedge rst_n)
if(!rst_n)begin
iIR_s0<=0;
iIR_s1<=0;
end
else begin
iIR_s0<=iIR;
iIR_s1<=iIR_s0;
end
wire nedge=iIR_s1 && ~iIR_s0;
wire pedge=~iIR_s1 && iIR_s0;
在使能计数的时候开始计数,计数超过10ms,计数器就开始清零。
reg en_cnt;
reg [18:0] cnt;
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt<=0;
else if(en_cnt)
cnt<=cnt+1;
else
cnt<=0;
reg timeout;
always@(posedge clk or negedge rst_n)
if(!rst_n)
timeout<=0;
else if(cnt>=500000)
timeout<=1;
else
timeout<=0;
定义一个计数到9ms、4.5ms、560us、1690us的标志信号。因为有一定的延迟或者其他原因取这些时间的一个范围。
reg flag_9ms;
reg flag_4_5ms;
reg flag_5_6us;
reg flag_1_6_9ms;
always@(posedge clk or negedge rst_n)
if(!rst_n)
flag_9ms<=0;
else if(cnt>400000 && cnt<495000)
flag_9ms<=1;
else
flag_9ms<=0;
always@(posedge clk or negedge rst_n)
if(!rst_n)
flag_4_5ms<=0;
else if(cnt>152500 && cnt<277500)
flag_4_5ms<=1;
else
flag_4_5ms<=0;
always@(posedge clk or negedge rst_n)
if(!rst_n)
flag_5_6us<=0;
else if(cnt>20000 && cnt<35000)
flag_5_6us<=1;
else
flag_5_6us<=0;
always@(posedge clk or negedge rst_n)
if(!rst_n)
flag_1_6_9ms<=0;
else if(cnt>75000 && cnt<90000)
flag_1_6_9ms<=1;
else
flag_1_6_9ms<=0;
根据帧头数据与数据波形对接收波形采用状态机进行实现.
s_idle为空闲状态,等待接收信号的下降沿。
s_flag_9ms为识别9ms的低电平引导码。
s_flag_4_5ms为识别4.5ms的低电平引导码。
s_get_data为读码状态
reg [3:0] state;
localparam s_idle =4'b0001;
localparam s_flag_9ms =4'b0010;
localparam s_flag_4_5ms=4'b0100;
localparam s_get_data =4'b1000;
reg get_flag_done;
在空闲状态检测到接收信号下降沿跳转下一个状态,并开始使能计数,否则保持在空闲状态。第二个状态开始的时候使能计数,检测到上升沿的时候并且9ms的标志信号为高就开始跳转下一个状态,并让计数器清零,下一个状态重新开始计数,如果没有检测到9ms的标志信号继续等待。第三个状态类似。最后一个状态刚开始的时候使能计数,如果检测到上升沿并且560us的标志信号为低,或者检测到下降沿并且560us的标志信号与1690us的表示信号为低,表明接收数据有误,返回初始状态,数据接收成功后也返回初始状态。如果检测到上升沿并且560us的标志信号为高,或者如果检测到下降沿并且560us的标志信号与1690us的标志信号都为高,计数器清零。根据下图就可以很轻松设计出来。
always@(posedge clk or negedge rst_n)
if(!rst_n)begin
state<=s_idle;
en_cnt<=0;
end
else if(!timeout)
case(state)
s_idle : if(nedge)begin
state<=s_flag_9ms;
en_cnt<=1;
end
else begin
state<=state;
en_cnt<=0;
end
s_flag_9ms : if(pedge)begin
if(flag_9ms)begin
state<=s_flag_4_5ms;
en_cnt<=0;
end
else
state<=s_idle;
end
else begin
state<=state;
en_cnt<=1;
end
s_flag_4_5ms: if(nedge)begin
if(flag_4_5ms)begin
state<=s_get_data;
en_cnt<=0;
end
else
state<=s_idle;
end
else begin
state<=state;
en_cnt<=1;
end
s_get_data : if(pedge && !flag_5_6us)
state<=s_idle;
else if(nedge && (!flag_5_6us && !flag_1_6_9ms))
state<=s_idle;
else if(get_flag_done)
state<=s_idle;
else if(pedge && flag_5_6us)
en_cnt<=0;
else if(nedge && (flag_1_6_9ms || flag_5_6us))
en_cnt<=0;
else
en_cnt<=1;
endcase
else
begin
en_cnt<=0;
state<=s_idle;
end
最后对数据进行接收,在读码状态如果数据计数到32,解码完成标志信号拉高,数据计数清零,根据上图,如果检测到下降沿,数据计数加一,在检测到下降沿的同时如果1690us的标志信号为高表明解码到数据1,如果560us的标志信号为高表明解码到数据0。
data_temp为总共的数据位数,iraddr为地址,irdata为数据,协议规定先发送低位。所以现将地址位从低位到高位开始发送,再将数据位从低位到高位开始发送。
reg [5:0] data_cnt;
reg [31:0] data_temp;
assign irdata=data_temp[31:16];
assign iraddr=data_temp[15:0];
always@(posedge clk or negedge rst_n)
if(!rst_n)begin
data_cnt<=0;
data_temp<=0;
get_flag_done<=0;
end
else if(state==s_get_data)begin
if( data_cnt==32)begin
get_flag_done<=1;
data_cnt<=0;
end
else begin
if(nedge)
data_cnt<=data_cnt+1;
if(nedge && flag_5_6us)
data_temp[data_cnt]<=0;
else if(nedge && flag_1_6_9ms)
data_temp[data_cnt]<=1;
get_flag_done<=0;
end
end
assign get_flag=get_flag_done;
仿真验证
将模块进行例化
`timescale 1ns/1ns
`define clk_period 20
module ir_decode_tb;
reg clk;
reg rst_n;
reg iIR;
wire [15:0]irdata;
wire [15:0]iraddr;
wire get_flag;
ir_decode ir_decode(
.clk(clk),
.rst_n(rst_n),
.iIR(iIR),
.irdata(irdata),
.iraddr(iraddr),
.get_flag(get_flag)
);
iIR空闲状态为1,发送地址为1数据为 32和地址为2数据为22
initial clk=0;
always #(`clk_period/2) clk=~clk;
initial begin
rst_n=0;iIR=1;
#(`clk_period*10+1)
rst_n=1;
#2000;
send_data(1,8'h32);
#60000000;
send_data(2,8'h22);
#60000000;
$stop;
end
这里先说明一个小语法,task的用法,定义一个任务。
task task_demo; //任务定义结构开头,命名为 task_demo
input [7:0] x,y; //输入端口说明
output [7:0] tmp; //输出端口说明begin
end
endtask
任务调用方法,其中端口1,端口2为参数。
task_demo(端口1, 端口 2, ........, 端口 N);
发送引导码, 9ms的低电平和4.5ms的高电平。然后发送数据发送,560us的低电平之后拉高,之后如果iIR是1就延时1690us,否则延迟560us。
integer i;
task send_data;
input [15:0] addr;
input [7:0] data;
begin
iIR=0;#9000000;
iIR=1;#4500000;
for(i=0;i<=15;i=i+1)
begin
iIR=0;#560000;
iIR=1;
if(addr[i])
#1690000;
else
#560000;
end
for(i=0;i<=7;i=i+1)
begin
iIR=0;#560000;
iIR=1;
if(data[i])
#1690000;
else
#560000;
end
for(i=0;i<=7;i=i+1)
begin
iIR=0;#560000;
iIR=1;
if(~addr[i])
#1690000;
else
#560000;
end
iIR=0;#560000;iIR=1;
end
endtask
仿真结果
issp调试
新建issp的ip核,在顶层模块中例化,将工程下载到板子上。
module decode_top(
input clk,
input rst_n,
input iIR,
output [15:0]irdata,
output [15:0]iraddr,
output get_flag
);
issp issp(
.probe({irdata,iraddr}),
.source()
);
ir_decode ir_decode(
.clk(clk),
.rst_n(rst_n),
.iIR(iIR),
.irdata(irdata),
.iraddr(iraddr),
.get_flag(get_flag)
);
endmodule
打开issp,将数据改为16进制,选择连续抓取,按下按键0,可以看到抓取的数据。