红外遥控解码设计与验证VHDL

实验器材

Quartus ii 18.0
小梅哥AC620开发板
红外遥控器

实验背景

红外线遥控器利用波长为0.76μm ~ 1.5 μm 的近红外线来传送信号。红外线的特点是不干扰其他电器设备工作,也不会影响周边环境。比如不同的电视遥控器只能控制对应的电视,这是因为不同的电视遥控器对应一个不同的地址。不同的家用电器有不同的编码方式,统称为红外遥控器编码传输协议,使用最多的为NEC协议。

实验原理

红外发射部分电路包括矩阵键盘,红外发光二极管,编码以及调制电路,这些都已经集成在红外遥控器上(遥控器示意图如下图所示)。接收部分包括光敏二极管,解调以及解码电路。在本实验中,我们仅需要对其输出的信号进行解码操作。
遥控

NEC协议

HT6221为Holtek公司生产的一款基于NEC红外通信协议的遥控编码芯片,采用Pulse Position Modulation(PPM)进行编码。HT6221芯片的红外遥控发送一次数据的数据帧定义如下图所示:一帧数据:帧头,16位地址码,8位数据码,8位数据反码以及1bit结束位(可忽略)组成。
数据帧

数据发送:

引导码由9ms高电平和4.5ms的低电平构成,代码一个数据帧的帧头;
地址码由16位地址构成,低8位在前,高8位在后(对于一个红外遥控器,地址为固定值)。所以NEC协议理论上能最多支持65536个不同的用户。
数据码由数据原码和数据反码构成(可以通过比较这两个8位数据检测数据接收是否正确)。所以理论上支持256种指令。

数据发送0/1:
数据发送1: 560μs高电平+1.69ms低电平
数据发送0: 560μs高电平+560μs低电平
0/1

数据接收:

对于数据接收端,接收到信号之后再输出的波形和输入端发送的波形反相。因此对于FPGA中接收端输出的波形为: 9ms低+4.5ms高 + 0.56ms 低+ 1.69ms高(1)/ 0.56ms高(0)。本模块就是检测此波形然后输出数据(由于红外传感器的品质不同,因此本模块的检测时间为一个区间范围

接收01
当按键长按时输出的波形如下图所示,本模块只进行短按时数据波形设计。
long

VHDL代码实现

由于红外遥控器按键的时序和接收端不同,因此需要先对接收到的红外信号进行时钟同步(采用两级FF):

process(clk,nrst)
begin
	if(nrst = '0')then
		IR_temp1	<= '0';
		IR_temp2	<= '0';
	elsif( rising_edge(clk)) then
		IR_temp1 <= iIR;
		IR_temp2	<= IR_temp1;
	end if;
end process;

对同步的输入信号进行边沿检测(和上面代码一致):

process(clk,nrst)
begin
	if(nrst = '0')then
		edge_temp1	<= '0';
		edge_temp2	<= '0';
	elsif( rising_edge(clk)) then
		edge_temp1 <= IR_temp2;
		edge_temp2 <= edge_temp1;
	end if;
end process;

pose_edge <= (not edge_temp2) and edge_temp1;
fall_edge <= edge_temp2 and (not edge_temp1);

时钟计数器(用于9ms, 4.5ms, 1.69ms, 0.56ms的计数)

process(clk,nrst)
begin
	if(nrst = '0')then
		cnt <= 0;
	elsif(rising_edge(clk))then
		if(cnt_en = '1') then
			cnt <= cnt + 1;
		else
			cnt <= 0;
		end if;
	end if;
end process;

计数时间超时(当计数器的时间已经超过10ms说明出现错误)timeout标志信号

process(clk,nrst)
begin
	if(nrst = '0')then
		timeout <= '0';
	elsif(rising_edge(clk))then
		if(cnt > 500000) then			-- cnt >10 ms超时信号置1
			timeout <= '1';
		else
			timeout <= '0';
		end if;
	end if;
end process;

9ms计数完成信号:当计数器 6.5 ms < cnt < 10 ms 标志着9ms计数完成。

process(clk,nrst)
begin
	if(nrst = '0')then
		T9ms_ok <= '0';
	elsif(rising_edge(clk)) then
		if((cnt > 325000) and (cnt < 500000)) then	-- 6.5 ms < cnt < 10 ms
				T9ms_ok <= '1';
		else
				T9ms_ok <= '0';
		end if;
	end if;
end process;

4.5ms 计数完成标志:当计数器 3.5 ms < cnt < 5.5 ms 时标志着4.5 ms计数完成

process(clk,nrst)
begin
	if(nrst = '0')then
		T4_5ms_ok <= '0';
	elsif(rising_edge(clk)) then
		if((cnt > 175000) and (cnt < 275000)) then	-- 3.5 ms < cnt < 5.5 ms
				T4_5ms_ok <= '1';
		else
				T4_5ms_ok <= '0';
		end if;
	end if;
end process;

1.69 ms计数完成标志: 当计数器 1.5 ms < cnt < 1.8 ms 时标志着1.69 ms计数完成

process(clk,nrst)
begin
	if(nrst = '0')then
		T1_69ms_ok <= '0';
	elsif(rising_edge(clk)) then
		if((cnt > 75000) and (cnt < 90000)) then	-- 1.5 ms < cnt < 1.8 ms
				T1_69ms_ok <= '1';
		else
				T1_69ms_ok <= '0';
		end if;
	end if;
end process;

0.56 ms计数完成标志: 当计数器 0.4 ms < cnt < 0.7 ms 时标志着 0.56 ms计数完成

process(clk,nrst)
begin
	if(nrst = '0')then
		T_56ms_ok <= '0';
	elsif(rising_edge(clk)) then
		if((cnt > 20000) and (cnt < 35000)) then	-- 0.4 ms < cnt < 0.7 ms
				T_56ms_ok <= '1';
		else
				T_56ms_ok <= '0';
		end if;
	end if;
end process;

状态机:
idle: 空闲态,当检测下降沿开始计数,转到wait9ms_low状态

wait9ms_low: 等待9ms低电平计数,当上升沿没到来的时候,计数器一直计数。当检测到上升沿时,如果9ms计时完成,将计数器使能无效(计数器清零),跳到wait4_5ms_high状态,否则说明接受错误,回到idle状态。

wait4_5ms_high: 等待4.5ms高电平计数。如果下降沿未到来,那么计数使能,一直计数等待下降沿的到来。当检测到下降沿,如果T4_5ms_ok=1,说明4.5ms计数完成,将计数器使能无效,跳转到data_get_56ms_low状态;如果检测到下降沿但是4.5ms计数未完成,说明接受错误,回到idle态。

data_get_56ms_low: 等待0.56ms低电平计数完成。 当未检测到上升沿,计数使能,一直计数,等待上升沿。 当检测到上升沿,并且0.56ms计时完成,将计数器使能无效,跳到data_get_01状态。;如果0.56ms计时没有完成,跳到idle态。

data_get_01: 等到1.69 ms或者0.56 ms高电平计数完成。 当没检测到下降沿,一直计时。检测到下降沿之后,如果还没有接收到32位数据(地址+数据+反码),并且0.56ms/1.69ms 高电平接收完成,回到data_get_56ms_low状态继续检测下一个数据。 当检测到下降沿,但是0.56/1.69ms 计数都未完成,或者当32位数据接收完成,回到idle态。
在这里插入图片描述
当timeout=0的时候才能进入状态机,当timeout=1意味着计时器超出10ms了,此时将state置为idle并且计数器使能无效。

if( timeout = '0') then
			case(state) is
				when	idle					=>	if(fall_edge = '1')then
														state  <= wait9ms_low;
														cnt_en <= '1';
													else
														cnt_en <= '0';
														state  <= idle;
													end if;
				when 	wait9ms_low			=>	if(pose_edge = '1')then
														if(T9ms_ok = '1') then
															cnt_en <= '0';
															state	 <= wait4_5ms_high;
														else
															state	 <= idle;
														end if;
													else
														state	 <= wait9ms_low;
													end if;
				when	wait4_5ms_high		=>	if(fall_edge = '1')then
														if(T4_5ms_ok = '1') then
															cnt_en <= '0';
															state	 <= data_get_56ms_low;
														else
															state	 <= idle;
														end if;
													else
														state	 <= wait4_5ms_high;
														cnt_en <= '1';
													end if;								
				when	data_get_56ms_low	=>	if(pose_edge = '1')then
														if(T_56ms_ok = '1')then
															cnt_en <= '0';
															state	 <= data_get_01;
														else
															state	 <= idle;
														end if;
													else
														state  <= data_get_56ms_low;
														cnt_en <= '1';
													end if;
				when	data_get_01			=>	if(fall_edge = '1')then
														if(data_get_done = '1')then
															state	 <= idle;
														else
															if( (T_56ms_ok = '1') or (T1_69ms_ok = '1')) then
																state	 <= data_get_56ms_low;
																cnt_en <= '0';
															else
																state  <= idle;
															end if;
														end if;
													else
														state <= data_get_01;
														cnt_en <= '1';
													end if;
				when	others				=> state <= idle;
			end case;
else
	state <= idle;
	cnt_en <= '0';
end if;

一帧数据传输完成标志data_get_done:
由于每个传输的数据0/1都需要在下降沿才能判断是0/1,所以对于最后一位数据,在下降沿拉低后0.56ms之后信号又会重新拉高,然后等待下一次9ms的拉低(红外接收器输出波形iIR默认高电平)。

32个数据有32个下降沿,33个上升沿(最后的拉高),因此当检测到第33个上升沿的时候,表明数据接收完成。在最后data_get_01状态检测到下降沿之后将数据赋给寄存器,最后一个上升沿的时候将寄存器的数据输出。

process(clk, nrst)
begin
	if(nrst = '0')then
		data_get_done <= '0';
		data_cnt <= 0;
		data_tem <= (others => '0');
	elsif(rising_edge(clk)) then
		if( (state = data_get_56ms_low) and (data_cnt = 32) and ( pose_edge = '1'))then
			data_cnt <= 0;
			irdata	<= data_tem( 31 downto 16);
			iraddr	<= data_tem( 15 downto 0);
			data_get_done <= '1';
		elsif( (state = data_get_01) and (fall_edge = '1'))then
			data_cnt <= data_cnt + 1;
			if( T_56ms_ok = '1') then
				data_tem(data_cnt) <= '0';
			elsif( T1_69ms_ok = '1') then
				data_tem(data_cnt) <= '1';
			end if;
		else
			data_get_done <= '0';
		end if;
	end if;
end process;

仿真

编写testbench代码如下所示:
bit_send procedure模块用来发送数据0/1。
send_data procedure模块调用bit_send 模块发送一帧数据
最后插入assert语句让testbench运行完成之后马上停止。

gencom: process
procedure bit_send(one_bit : in std_logic) is
	begin
		iIR <= '0';
		wait for 560000 ns;
		
		iIR <= '1';
		if( one_bit = '1') then
			wait for 1690000 ns;
		else
			wait for 560000 ns;
		end if;
	end procedure;

procedure send_data( addr : in std_logic_vector(15 downto 0);
							data : in std_logic_vector(7 downto 0)) is
	begin
		iIR <= '0';
		wait for 9000000 ns;
		
		iIR <= '1';
		wait for 4500000 ns;
		
		for i in 0 to 15 loop
			bit_send( addr(i));
		end loop;
		
		for i in 0 to 7 loop
			bit_send( data(i));
		end loop;
		
		for i in 0 to 7 loop
			bit_send( not data(i));
		end loop;
		iIR <= '0';
		wait for 560000 ns;
		
		iIR <= '1';
	end procedure;
						
begin
	nrst <= '0';
	iIR  <= '1';
	wait for 201 ns;
		
	nrst <= '1';
	wait for 2000 ns;
	
	send_data( X"0100", X"78");		-- 0111_1000 -> 1000_0111
	
	wait for 60000000 ns;			-- 保证两次数据传输间隔大于108 ms
	
	send_data( X"1100", X"bd");		-- 1011_1101 -> 0100_0010

	wait for 60000000 ns;
	
	assert (false)
   report "Test run done!"
   severity failure;

end process;

仿真结果:
simulation
当数据接收完成之后,irdata和iraddr信号才被赋值,get_flag信号拉高。地址和数据输出的值和testbench中设置的值一致。

板级验证

将VHDL代码下载到AC620开发板之后通过ISSP工具中的probe探针检测 iraddr 和 irdata数据。
(PS: 此处probe赋值的时候将addr赋为了高16位,数据和反码赋给了低16位)
按下按键0之后红外接收器输出的数据: (按键0 对应 16)
0
按下按键6之后红外接收器输出的数据:(按键6 对应5A)
6
输出的数据和理论一致,地址位保持不变。低16位为反码和数据,和仿真匹配。

红外遥控VHDL代码资源


版权声明:本文为Somnus_z原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。