1. 红外遥控简介
红外遥控是一种无线、非接触控制技术,具有抗干扰能力强,信息传输可靠,功耗低,成本低,易实现等显著优点,被诸多电子设备特别是家用电器广泛采用,并越来越多的应用到计算机系统中。
由于它不能穿过障碍物去控制被控对象的能力,可以有相同的遥控频率或编码,而不会出现遥控信号“串门”的情况。
红外遥控器发射出的红外光波长范围在760nm到1nm之间,而人眼可见光的波长范围一般在400nm到760nm之间,所以看不到红外遥控器发出的红外光。
红外遥控器和红外接收头外形
2. 红外遥控器协议
红外遥控器传输协议的编码目前广泛使用的是:NEC协议和Philips RC-5协议。
主流的调制方式有两种:
PPM(Pulse Position Modulation,脉冲位置调制)
PWM(Pulse Width Modulation,脉冲宽度调制)
NEC协议特征
- 8位地址和8位指令长度
- 地址和命令2次传输(确保可靠性)
- 脉冲位置调制
- 载波频率为38KHz
- 位时间为1.125ms或2.25ms(高电平持续时间来区分)
NEC码位定义
NEC协议采用PPM调制的形式进行编码,数据的每一位(Bit)脉冲长度为560us,由38KHz的载波脉冲进行调制,有载波脉冲的地方,其宽度都为560us,而载波脉冲的间隔时间是不同的,逻辑“1”的载波脉冲+载波脉冲时间间隔为2.25ms;逻辑“0”的脉冲+载波脉冲间隔时间为逻辑“1”的一般,也就是1.125ms。
NEC协议数据传输格式
传输数据时地位在前;9ms的AGC(自动增益控制)载波脉冲开始;紧接着是4.5ms的空闲信号;随后是地址码和控制码。地址码和控制码分别传输了两次,第二次传输的地址码和控制码都是反码;
NEC协议数据重复码(连发码)
当红外遥控器上的按键一直被按下时,红外遥控器只会发送一次完整的信息,其后会每隔110ms发送一次重复码(也叫连发码)。重复码的数据格式比较简单,同样是9ms的AGC(自动增益控制)载波脉冲开始;紧接着是2.25ms的空闲信号;随后是560us的载波脉冲。
红外接收头
一体化红外接收头,内部集成了红外监测二极管、自动增益放大器(AGC)、带通滤波器(Band Pass)、解调器(Demodulator)等电路。
红外接收解码
3. 试验任务
使用开发板接收红外遥控器发出的红外信号,并将数据显示在数码管上;如果检测到重复码,则通过LED灯显示。
系统框图
程序设计
代码
module top_remote_rcv(
input sys_clk , //系统时钟
input sys_rst_n, //系统复位信号,低电平有效
input remote_in, //红外接收信号
output [5:0] sel , //数码管位选信号
output [7:0] seg_led , //数码管段选信号
output led //led灯
);
//wire define
wire [7:0] data ;
wire repeat_en ;
//*****************************************************
//** main code
//*****************************************************
//数码管显示模块
seg_led u_seg_led(
.clk (sys_clk),
.rst_n (sys_rst_n),
.seg_sel (sel),
.seg_led (seg_led),
.data (data), //红外数据
.point (6'd0), //无小数点
.en (1'b1), //使能数码管
.sign (1'b0) //无符号显示
);
//HS0038B驱动模块
remote_rcv u_remote_rcv(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.remote_in (remote_in),
.repeat_en (repeat_en),
.data_en (),
.data (data)
);
led_ctrl u_led_ctrl(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.repeat_en (repeat_en),
.led (led)
);
endmodule
module remote_rcv(
input sys_clk , //系统时钟
input sys_rst_n , //系统复位信号,低电平有效
input remote_in , //红外接收信号
output reg repeat_en , //重复码有效信号
output reg data_en , //数据有效信号
output reg [7:0] data //红外控制码
);
//parameter define
parameter st_idle = 5'b0_0001; //空闲状态
parameter st_start_low_9ms = 5'b0_0010; //监测同步码低电平
parameter st_start_judge = 5'b0_0100; //判断重复码和同步码高电平(空闲信号)
parameter st_rec_data = 5'b0_1000; //接收数据
parameter st_repeat_code = 5'b1_0000; //重复码
//reg define
reg [4:0] cur_state ;
reg [4:0] next_state ;
reg [11:0] div_cnt ; //分频计数器
reg div_clk ; //分频时钟
reg remote_in_d0 ; //对输入的红外信号延时打拍
reg remote_in_d1 ;
reg [7:0] time_cnt ; //对红外的各个状态进行计数
reg time_cnt_clr ; //计数器清零信号
reg time_done ; //计时完成信号
reg error_en ; //错误信号
reg judge_flag ; //检测出的标志信号 0:同步码高电平(空闲信号) 1:重复码
reg [15:0] data_temp ; //暂存收到的控制码和控制反码
reg [5:0] data_cnt ; //对接收的数据进行计数
//wire define
wire pos_remote_in ; //输入红外信号的上升沿
wire neg_remote_in ; //输入红外信号的下降沿
//*****************************************************
//** main code
//*****************************************************
assign pos_remote_in = (~remote_in_d1) & remote_in_d0;
assign neg_remote_in = remote_in_d1 & (~remote_in_d0);
//时钟分频,50Mhz/(2*(3124+1))=8khz,T=0.125ms
always @(posedge sys_clk or negedge sys_rst_n ) begin
if (!sys_rst_n) begin
div_cnt <= 12'd0;
div_clk <= 1'b0;
end
else if(div_cnt == 12'd3124) begin
div_cnt <= 12'd0;
div_clk <= ~div_clk;
end
else
div_cnt = div_cnt + 12'b1;
end
//对红外的各个状态进行计数
always @(posedge div_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
time_cnt <= 8'b0;
else if(time_cnt_clr)
time_cnt <= 8'b0;
else
time_cnt <= time_cnt + 8'b1;
end
//对输入的remote_in信号延时打拍
always @(posedge div_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
remote_in_d0 <= 1'b0;
remote_in_d1 <= 1'b0;
end
else begin
remote_in_d0 <= remote_in;
remote_in_d1 <= remote_in_d0;
end
end
//状态机
always @ (posedge div_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cur_state <= st_idle;
else
cur_state <= next_state ;
end
always @(*) begin
next_state = st_idle;
case(cur_state)
st_idle : begin //空闲状态
if(remote_in_d0 == 1'b0)
next_state = st_start_low_9ms;
else
next_state = st_idle;
end
st_start_low_9ms : begin //监测同步码低电平
if(time_done)
next_state = st_start_judge;
else if(error_en)
next_state = st_idle;
else
next_state = st_start_low_9ms;
end
st_start_judge : begin //判断重复码和同步码高电平(空闲信号)
if(time_done) begin
if(judge_flag == 1'b0)
next_state = st_rec_data;
else
next_state = st_repeat_code;
end
else if(error_en)
next_state = st_idle;
else
next_state = st_start_judge;
end
st_rec_data : begin //接收数据
if(pos_remote_in && data_cnt == 6'd32)
next_state = st_idle;
else
next_state = st_rec_data;
end
st_repeat_code : begin //重复码
if(pos_remote_in)
next_state = st_idle;
else
next_state = st_repeat_code;
end
default : next_state = st_idle;
endcase
end
always @(posedge div_clk or negedge sys_rst_n ) begin
if (!sys_rst_n) begin
time_cnt_clr <= 1'b0;
time_done <= 1'b0;
error_en <= 1'b0;
judge_flag <= 1'b0;
data_en <= 1'b0;
data <= 8'd0;
repeat_en <= 1'b0;
data_cnt <= 6'd0;
data_temp <= 32'd0;
end
else begin
time_cnt_clr <= 1'b0;
time_done <= 1'b0;
error_en <= 1'b0;
repeat_en <= 1'b0;
data_en <= 1'b0;
case(cur_state)
st_idle : begin
time_cnt_clr <= 1'b1;
if(remote_in_d0 == 1'b0)
time_cnt_clr <= 1'b0;
end
st_start_low_9ms : begin //9ms/0.125ms = 72
if(pos_remote_in) begin
time_cnt_clr <= 1'b1;
if(time_cnt >= 69 && time_cnt <= 75)
time_done <= 1'b1;
else
error_en <= 1'b1;
end
end
st_start_judge : begin
if(neg_remote_in) begin
time_cnt_clr <= 1'b1;
//重复码高电平2.25ms 2.25/0.125 = 18
if(time_cnt >= 15 && time_cnt <= 20) begin
time_done <= 1'b1;
judge_flag <= 1'b1;
end
//同步码高电平4.5ms 4.5/0.125 = 36
else if(time_cnt >= 33 && time_cnt <= 38) begin
time_done <= 1'b1;
judge_flag <= 1'b0;
end
else
error_en <= 1'b1;
end
end
st_rec_data : begin
if(pos_remote_in) begin
time_cnt_clr <= 1'b1;
if(data_cnt == 6'd32) begin
data_en <= 1'b1;
data_cnt <= 6'd0;
data_temp <= 16'd0;
if(data_temp[7:0] == ~data_temp[15:8]) //校验控制码和控制反码
data <= data_temp[7:0];
end
end
else if(neg_remote_in) begin
time_cnt_clr <= 1'b1;
data_cnt <= data_cnt + 1'b1;
//解析控制码和控制反码
if(data_cnt >= 6'd16 && data_cnt <= 6'd31) begin
if(time_cnt >= 2 && time_cnt <= 6) begin //0.565/0.125 = 4.52
data_temp <= {1'b0,data_temp[15:1]}; //逻辑“0”
end
else if(time_cnt >= 10 && time_cnt <= 15) //1.69/0.125 = 13.52
data_temp <= {1'b1,data_temp[15:1]}; //逻辑“1”
end
end
end
st_repeat_code : begin
if(pos_remote_in) begin
time_cnt_clr <= 1'b1;
repeat_en <= 1'b1;
end
end
default : ;
endcase
end
end
endmodule
module seg_led(
input clk , // 时钟信号
input rst_n , // 复位信号
input [19:0] data , // 6位数码管要显示的数值
input [5:0] point , // 小数点具体显示的位置,从高到低,高电平有效
input en , // 数码管使能信号
input sign , // 符号位(高电平显示“-”号)
output reg [5:0] seg_sel, // 数码管位选,最左侧数码管为最高位
output reg [7:0] seg_led // 数码管段选
);
//parameter define
localparam CLK_DIVIDE = 4'd10 ; // 时钟分频系数
localparam MAX_NUM = 13'd5000 ; // 对数码管驱动时钟(5MHz)计数1ms所需的计数值
//reg define
reg [ 3:0] clk_cnt ; // 时钟分频计数器
reg dri_clk ; // 数码管的驱动时钟,5MHz
reg [23:0] num ; // 24位bcd码寄存器
reg [12:0] cnt0 ; // 数码管驱动时钟计数器
reg flag ; // 标志信号(标志着cnt0计数达1ms)
reg [2:0] cnt_sel ; // 数码管位选计数器
reg [3:0] num_disp ; // 当前数码管显示的数据
reg dot_disp ; // 当前数码管显示的小数点
//wire define
wire [3:0] data0 ; // 个位数
wire [3:0] data1 ; // 十位数
wire [3:0] data2 ; // 百位数
wire [3:0] data3 ; // 千位数
wire [3:0] data4 ; // 万位数
wire [3:0] data5 ; // 十万位数
//*****************************************************
//** main code
//*****************************************************
//提取显示数值所对应的十进制数的各个位
assign data0 = data % 4'd10; // 个位数
assign data1 = data / 4'd10 % 4'd10 ; // 十位数
assign data2 = data / 7'd100 % 4'd10 ; // 百位数
assign data3 = data / 10'd1000 % 4'd10 ; // 千位数
assign data4 = data / 14'd10000 % 4'd10; // 万位数
assign data5 = data / 17'd100000; // 十万位数
//对系统时钟10分频,得到的频率为5MHz的数码管驱动时钟dri_clk
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
clk_cnt <= 4'd0;
dri_clk <= 1'b1;
end
else if(clk_cnt == CLK_DIVIDE/2 - 1'd1) begin
clk_cnt <= 4'd0;
dri_clk <= ~dri_clk;
end
else begin
clk_cnt <= clk_cnt + 1'b1;
dri_clk <= dri_clk;
end
end
//将20位2进制数转换为8421bcd码(即使用4位二进制数表示1位十进制数)
always @ (posedge dri_clk or negedge rst_n) begin
if (!rst_n)
num <= 24'b0;
else begin
if (data5 || point[5]) begin //如果显示数据为6位十进制数,
num[23:20] <= data5; //则依次给6位数码管赋值
num[19:16] <= data4;
num[15:12] <= data3;
num[11:8] <= data2;
num[ 7:4] <= data1;
num[ 3:0] <= data0;
end
else begin
if (data4 || point[4]) begin //如果显示数据为5位十进制数,则给低5位数码管赋值
num[19:0] <= {data4,data3,data2,data1,data0};
if(sign)
num[23:20] <= 4'd11; //如果需要显示负号,则最高位(第6位)为符号位
else
num[23:20] <= 4'd10; //不需要显示负号时,则第6位不显示任何字符
end
else begin //如果显示数据为4位十进制数,则给低4位数码管赋值
if (data3 || point[3]) begin
num[15: 0] <= {data3,data2,data1,data0};
num[23:20] <= 4'd10; //第6位不显示任何字符
if(sign) //如果需要显示负号,则最高位(第5位)为符号位
num[19:16] <= 4'd11;
else //不需要显示负号时,则第5位不显示任何字符
num[19:16] <= 4'd10;
end
else begin //如果显示数据为3位十进制数,则给低3位数码管赋值
if (data2 || point[2]) begin
num[11: 0] <= {data2,data1,data0};
//第6、5位不显示任何字符
num[23:16] <= {2{4'd10}};
if(sign) //如果需要显示负号,则最高位(第4位)为符号位
num[15:12] <= 4'd11;
else //不需要显示负号时,则第4位不显示任何字符
num[15:12] <= 4'd10;
end
else begin //如果显示数据为2位十进制数,则给低2位数码管赋值
if (data1 || point[1]) begin
num[ 7: 0] <= {data1,data0};
//第6、5、4位不显示任何字符
num[23:12] <= {3{4'd10}};
if(sign) //如果需要显示负号,则最高位(第3位)为符号位
num[11:8] <= 4'd11;
else //不需要显示负号时,则第3位不显示任何字符
num[11:8] <= 4'd10;
end
else begin //如果显示数据为1位十进制数,则给最低位数码管赋值
num[3:0] <= data0;
//第6、5位不显示任何字符
num[23:8] <= {4{4'd10}};
if(sign) //如果需要显示负号,则最高位(第2位)为符号位
num[7:4] <= 4'd11;
else //不需要显示负号时,则第2位不显示任何字符
num[7:4] <= 4'd10;
end
end
end
end
end
end
end
//每当计数器对数码管驱动时钟计数时间达1ms,输出一个时钟周期的脉冲信号
always @ (posedge dri_clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
cnt0 <= 13'b0;
flag <= 1'b0;
end
else if (cnt0 < MAX_NUM - 1'b1) begin
cnt0 <= cnt0 + 1'b1;
flag <= 1'b0;
end
else begin
cnt0 <= 13'b0;
flag <= 1'b1;
end
end
//cnt_sel从0计数到5,用于选择当前处于显示状态的数码管
always @ (posedge dri_clk or negedge rst_n) begin
if (rst_n == 1'b0)
cnt_sel <= 3'b0;
else if(flag) begin
if(cnt_sel < 3'd5)
cnt_sel <= cnt_sel + 1'b1;
else
cnt_sel <= 3'b0;
end
else
cnt_sel <= cnt_sel;
end
//控制数码管位选信号,使6位数码管轮流显示
always @ (posedge dri_clk or negedge rst_n) begin
if(!rst_n) begin
seg_sel <= 6'b111111; //位选信号低电平有效
num_disp <= 4'b0;
dot_disp <= 1'b1; //共阳极数码管,低电平导通
end
else begin
if(en) begin
case (cnt_sel)
3'd0 :begin
seg_sel <= 6'b111110; //显示数码管最低位
num_disp <= num[3:0] ; //显示的数据
dot_disp <= ~point[0]; //显示的小数点
end
3'd1 :begin
seg_sel <= 6'b111101; //显示数码管第1位
num_disp <= num[7:4] ;
dot_disp <= ~point[1];
end
3'd2 :begin
seg_sel <= 6'b111011; //显示数码管第2位
num_disp <= num[11:8];
dot_disp <= ~point[2];
end
3'd3 :begin
seg_sel <= 6'b110111; //显示数码管第3位
num_disp <= num[15:12];
dot_disp <= ~point[3];
end
3'd4 :begin
seg_sel <= 6'b101111; //显示数码管第4位
num_disp <= num[19:16];
dot_disp <= ~point[4];
end
3'd5 :begin
seg_sel <= 6'b011111; //显示数码管最高位
num_disp <= num[23:20];
dot_disp <= ~point[5];
end
default :begin
seg_sel <= 6'b111111;
num_disp <= 4'b0;
dot_disp <= 1'b1;
end
endcase
end
else begin
seg_sel <= 6'b111111; //使能信号为0时,所有数码管均不显示
num_disp <= 4'b0;
dot_disp <= 1'b1;
end
end
end
//控制数码管段选信号,显示字符
always @ (posedge dri_clk or negedge rst_n) begin
if (!rst_n)
seg_led <= 8'hc0;
else begin
case (num_disp)
4'd0 : seg_led <= {dot_disp,7'b1000000}; //显示数字 0
4'd1 : seg_led <= {dot_disp,7'b1111001}; //显示数字 1
4'd2 : seg_led <= {dot_disp,7'b0100100}; //显示数字 2
4'd3 : seg_led <= {dot_disp,7'b0110000}; //显示数字 3
4'd4 : seg_led <= {dot_disp,7'b0011001}; //显示数字 4
4'd5 : seg_led <= {dot_disp,7'b0010010}; //显示数字 5
4'd6 : seg_led <= {dot_disp,7'b0000010}; //显示数字 6
4'd7 : seg_led <= {dot_disp,7'b1111000}; //显示数字 7
4'd8 : seg_led <= {dot_disp,7'b0000000}; //显示数字 8
4'd9 : seg_led <= {dot_disp,7'b0010000}; //显示数字 9
4'd10: seg_led <= 8'b11111111; //不显示任何字符
4'd11: seg_led <= 8'b10111111; //显示负号(-)
default:
seg_led <= {dot_disp,7'b1000000};
endcase
end
end
endmodule
module led_ctrl(
input sys_clk , //系统时钟
input sys_rst_n , //系统复位信号,低电平有效
input repeat_en , //重复码触发信号
output reg led //LED灯
);
//reg define
reg repeat_en_d0 ; //repeat_en信号打拍采沿
reg repeat_en_d1 ;
reg [22:0] led_cnt ; //LED灯计数器,用于控制LED灯亮灭
//wire define
wire pos_repeat_en;
//*****************************************************
//** main code
//*****************************************************
assign pos_repeat_en = ~repeat_en_d1 & repeat_en_d0;
repeat_en信号打拍采沿
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
repeat_en_d0 <= 1'b0;
repeat_en_d1 <= 1'b0;
end
else begin
repeat_en_d0 <= repeat_en;
repeat_en_d1 <= repeat_en_d0;
end
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
led_cnt <= 23'd0;
led <= 1'b0;
end
else begin
if(pos_repeat_en) begin
led_cnt <= 23'd5_000_000; //单次重复码:亮80ms 灭20ms
led <= 1'b1; //led亮的时间:4_000_000*20ns=80ms
end
else if(led_cnt != 23'd0) begin
led_cnt <= led_cnt - 23'd1;
if(led_cnt < 23'd1_000_000) //led灭的时间:1_000_000*20ns=20ms
led <= 1'b0;
end
end
end
endmodule