实验器材
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低电平
数据接收:
对于数据接收端,接收到信号之后再输出的波形和输入端发送的波形反相。因此对于FPGA中接收端输出的波形为: 9ms低+4.5ms高 + 0.56ms 低+ 1.69ms高(1)/ 0.56ms高(0)。本模块就是检测此波形然后输出数据(由于红外传感器的品质不同,因此本模块的检测时间为一个区间范围)
当按键长按时输出的波形如下图所示,本模块只进行短按时数据波形设计。
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;
仿真结果:
当数据接收完成之后,irdata和iraddr信号才被赋值,get_flag信号拉高。地址和数据输出的值和testbench中设置的值一致。
板级验证
将VHDL代码下载到AC620开发板之后通过ISSP工具中的probe探针检测 iraddr 和 irdata数据。
(PS: 此处probe赋值的时候将addr赋为了高16位,数据和反码赋给了低16位)
按下按键0之后红外接收器输出的数据: (按键0 对应 16)
按下按键6之后红外接收器输出的数据:(按键6 对应5A)
输出的数据和理论一致,地址位保持不变。低16位为反码和数据,和仿真匹配。