21. 红外遥控

红外遥控是一种无线、非接触控制技术,具有抗干扰能力强,信息传输可靠,功耗低,成本低,易实现等显著优点。被诸多电子设备特别是家用电器广泛采用,并越来越多地应用到计算机系统中。本章节将为大家介绍红外信息的编码格式以及接收到红外信息后的解码方式。

21.1. 理论学习

21.1.1. 红外遥控简介

人的眼睛能看到的可见光若按波长排列,则依次(从长到短)为红、橙、黄、绿、青、蓝、紫。红光的波长范围为620nm~720nm,波长比红光波长还长的的光叫红外线。红外线又称红外光波,红外光波按波长范围可分为近红外、中红外、远红外、极红外4类。红外线遥控是利用近红外光传送遥控指令的,波长为0.76um~1 .5um。用近红外作为遥控光源,是因为目前红外发射器件(红外发光管)与红外接收器件(光敏二极管、三极管及光电池)的发光与受光峰值波长一般为0.8um~0.94um,在近红外光波段内,二者的光谱正好重合,能够很好地匹配,可以获得较高的传输效率及较高的可靠性。

21.1.2. 红外遥控系统组成

IFR002

图 27‑1 红外遥控系统组成

如图 27‑1所示,红外遥控发射部分由遥控按键、编码以及调制电路、红外发光二极管等组成。红外遥控接收部分由光敏二极管、解调电路等组成。最后将解调的信号输入FPGA内进行解码输出。

使用一体化接收头时只需对其输出的信号进行解码操作。我们使用的开发板板载的一体化红外接收头的型号为HS0038B,外形如图 27‑2所示。需要注意的是,该接收头收到信号后输出的波形刚好与发送的波形相反。

IFR003

图 27‑2 一体化接收头实物图

本实验使用的红外遥控实物及各个按键对应的键码图如图 27‑3所示。

IFR004

图 27‑3 红外遥控键码图

21.1.3. 红外遥控编码协议

红外遥控的编码协议种类繁多,如:NEC、Philips RC-5、Philips RC-6、Sony SIRC等,而使用最多的是NEC协议,我们开发板配套的遥控器使用的也是NEC协议。下面为大家介绍NEC协议的编码格式。

NEC协议采用的是PPM(Pulse Position Modulation,脉冲位置调制)进行编码。当我们按下遥控器的一个按键时,会发送一帧的数据。这一帧数据由引导码、地址码、地址反码、数据码、数据反码以及一位结束位(可忽略)组成。如图 27‑4所示。

IFR005

图 27‑4 数据帧定义

由上图所示:9ms的高电平脉冲和之后的4.5ms的低电平组成了引导码。接着后面是地址码及数据码,低位在前,高位在后。最后是562.5um脉冲突发以表示消息传输的结束。其中地址码加上地址反码的时间和数据码加上数据反码的时间是固定不变的(如数据码为00000111,则数据反码则为11111000),总是 由8个“1”和8个“0”组成。地址码和数据码都是由“0”、“1”组成的,下面介绍逻辑“0”和逻辑“1”的编码方式,如图 27‑5所示。

IFR006

图 27‑5 逻辑电平编码格式

由上图可知:逻辑“1”和逻辑“0”是根据脉冲之间的时间间隔来区分的。逻辑“1”由560us的高脉冲加上1.69ms的低电平组成,而逻辑“0”由560us的高脉冲加上560us的低电平组成。

上面已经介绍了逻辑“1”和逻辑“0”的编码方式以及一帧数据的组成。那如果我们按着按键一直不放发送完一帧数据之后又会发送什么呢?如图 27‑6所示。

IFR007

图 27‑6 长按时的输出波形

如上图所示:长按时,当发送完数据后,每隔110ms会发送一个重复码,重复码由9ms的高脉冲和2.25ms的低电平以及560us的高脉冲(结束标志)组成。如图 27‑7所示:

IFR008

图 27‑7 重复码数据格式

上面已经说到我们使用的一体化接收头接收到信号后输出到FPGA的波形刚好与发送的波形相反。即发送的高脉冲,接收后输出就为低电平;发送的低电平,接收后输出就为高电平。FPGA芯片接收的波形如下图所示:

IFR009

图 27‑8 引导码接收波形图

IFR010

图 27‑9 “1”/“0”数据接收波形图

IFR011

图 27‑10 重复码接收波形图

最后再对我们所用的红外遥控按下时发送的数据的顺序做个总结:如图 27‑11所示。

IFR012

图 27‑11 红外遥控发送码顺序图

各个码的编码方式上面已经做了详细的讲解,下面进入我们的实战演练,通过具体的实验设计来进一步了解。

21.2. 实战演练

21.2.1. 实验目标

实验目标:使用FPGA开发板配套的红外遥控器发送红外信号,FPGA开发板上的一体化接收头接收到红外信号后传入FPGA芯片内,FPGA芯片接收到信号后进行解码,将解码后的按键码显示在数码管上。若检测到发送了重复码,则让led闪烁显示,一个重复码闪烁一次。

21.2.2. 硬件资源

我们使用开发板上的红外遥控、红外接收头、led灯以及数码管去进行该实验的验证,如图 27‑12、图 27‑13、图 27‑14所示。

IFR013

图 27‑12 开发板配套红外遥控

IFR014

图 27‑13 硬件资源

IFR015

图 27‑14 led灯

由原理图可知,红外接收头的型号为HS0038B,只有一个输入信号(红外输入);led灯为低电平点亮。原理图如图 27‑15、图 27‑16所示。

IFR016

图 27‑15 红外接收头原理图

IFR017

图 27‑16 led灯原理图

本次实验还用到了数码管,数码管显示部分的硬件原理图这里就不再列举说明了,大家可回到“数码管的静态显示”章节硬件部分进行了解。

21.2.3. 程序设计

硬件资源介绍完毕,我们开始实验工程的程序设计。在本小节,我们采用先整体概括,再局部说明的方式对实验工程的各个模块进行讲解,详细内容如下。

21.2.3.1. 整体说明

在本小节,我们先对整个实验工程有一个整体认识,首先来看一下红外遥控实验工程的整体框图,具体见图 27‑17。

IFR018

图 27‑17 红外遥控系统框图

本实验工程包括6个模块,其中seg_595_dynamic模块我们可直接调用,内部还有两个子模块。各模块简介,具体见表格 27‑1。

表格 27‑1 红外遥控工程模块简介

模块名称

功能描述

top_infrared_rcv

顶层模块

seg_595_dynamic

数码管显示模块

infrared_rcv

红外接收模块

led_ctrl

led灯控制模块

结合图 27‑17和表格 27‑1,我们来说一下红外遥控实验的工作流程:一体化接收头接收到红外遥控发来的红外信息后,将红外信息传入FPGA芯片内使用红外接收模块(infrared_rcv)进行解码,若接收的信息与协议一致,则让接收到的数据传入数码管显示模块显示数据,若接收到重复码,则让重复码使能信号 传入led控制模块让led闪烁。

21.2.3.2. 数码管显示模块

在“数码管的动态显示”章节中我们已经对数码管动态显示模块做了详细的讲解,在此就不再讲解了,在这里我们直接调用这个模块即可。需要注意的是该模块下还有子模块我们没有在整体框图中画出,该模块框图如图 27‑18所示。

IFR019

图 27‑18 数码管显示模块

21.2.3.3. 红外接收模块

本小节中我们开始对红外接收模块时序关系进行详细的讲解,这个模块也是我们红外遥控的核心模块,望大家认真学习。

模块框图

IFR020

图 27‑19 红外接收模块框图

表格 27‑2 红外接收模块输入输出信号描述

信号

位宽

类型

功能描述

sys_clk

1bit

Input

系统时钟,频率50MHz

sys_rst_n

1bit

Input

复位信号,低有效

infrared_in

1bit

Input

红外输入信号

data

20bit

Output

输出数据

repeat_en

1bit

Output

重复码使能信号

根据实验任务,我们是要将红外信息的数据码显示在数码管上,而我们协议的数据码是8bit的位宽,而为什么这里要定义成20bit的位宽呢?那是因为我们调用的是数码管动态显示章节的显示模块,而在那个模块我们定义的数据位宽是20位的,所以在这里我们也定义成20位的位宽与之相匹配,防止出现未知的错误。

红外接收模块功能:判断红外输入数据是否是按照NEC协议发送的,如果是按照协议进行发送的,则输出发送的数据码和重复码使能信号。而如何判断输入数据是否是按照协议发送的呢?在前面的理论学习中我们已经知道接收的数据与红外发送的数据是相反的,根据图 27‑8、图 27‑9、图 27‑10可以发现我们解码其实就 是判断接收的引导码是不是9ms的低电平和4.5ms的高电平,判断的是高低电平的持续时间,接下来的地址码和数据码“1”和“0”都是560us的低电平开始的,不同的是高电平的持续时间,逻辑“1”是持续1.69ms的高电平,逻辑“0”是持续560us的高电平。同样的重复码也是如此判断,先判断是不是持续了9 ms的低电平,再判断是不是持续了2.25ms的高电平,最后的560us的低电平是标志重复码结束的结束位,可以不用判断。

状态机

下面根据状态机转换图来帮助大家理解,如图 27‑20所示。

IFR021

图 27‑20 红外驱动状态跳转图

IDLE:状态机初始状态,等待红外信号的到来。当检测到下降沿来临时,表示9ms的低电平已经开始发送,这时我们从IDLE跳转到S_T9状态。

S_T9:等待9ms低电平状态。当上升沿到来的时候,电平从低电平变为高电平,此时我们判断低电平是不是保持了9ms,若是保持了9ms则跳转到下一状态,若保持时间不是9ms则说明发送的信息有误,回到IDLE状态等待下一帧的数据到来。

S_JUDGE:判断是重复码还是地址码状态。当下降沿到来的时候,我们判断高电平的持续时间,若持续了4.5ms则说明下面接收的是地址码、地址反码和数据码、数据反码,则跳转到S_IFR_DATA状态;若持续了2.25ms则说明这一段数据是重复码,让其跳转到S_REPEAT状态。若保持时间不是4.5ms和 2.25ms则说明发送的信息有误,回到IDLE状态等待下一帧的数据到来。

S_IFR_DATA:红外地址码、地址反码和数据码、数据反码接收状态。逻辑“1”和“0”都是从560us的低电平开始的,所以上升沿来到时先判断低电平是否持续了560us。接下来是判断高电平,下降沿到来时持续了1.69ms高电平的是逻辑“1”,持续了560us的是逻辑“0”。当接收完32位数据后回到初 始状态等待重复码的到来或者下一帧数据的到来。如果都不满足则说明数据帧发送错误状态回到初始状态。

S_REPEAT:重复码状态。当上升沿到来的时候可以不用判断结束位,让其直接回到初始状态即可,等待下一个重复码或下一帧数据。

通过状态机的描述,相信大家对怎么判断接收的红外信息是否正确都有了基本的思路,下面通过波形图来说明具体的操作。

波形图绘制

在模块框图部分,我们对该怎么判断接收的红外信息已经做了一定的描述。通过之前的描述我们可以知道主要是判断高低电平的持续时间去判断红外信息的正确性,所以我们需要几个计数器来产生9ms、4.5ms、2.25ms、1.69ms、0.56ms持续时间的时间标志信号。另外,由于不同遥控器厂家晶振精度不一致,因此 不一定严格按照时序图的时序参数来产生波形,这里给每个计数器的值的一定范围来实现,这样可消除环境等干扰因素带来的误差。下面给出产生时间信号的波形图,如图 27‑21所示。

IFR022

图 27‑21 时间信号产生波形图

如波形图所示:时钟是50MHz,一个时钟周期就是20ns,计数器计到27999即为0.56ms,84499即为1.69ms,112499即为2.25ms,224999即为4.5ms,449999即为9ms。我们只需要在其一定范围内拉高标志信号即可。

我们是在红外信号的上升沿下降沿到来时去判断高低电平的保持时间的,所以我们需要对红外信号采沿。具体方法如图 27‑22所示。

IFR023

图 27‑22 红外信号上升沿下降沿信号产生波形图

前面触摸按键控制led灯我们已经讲到过产生信号上升沿的方法,产生下降沿的方法也是一样的。对需要采沿的信号打拍后经过取反、相与操作即可获得上升沿或下降沿标志信号,详细可参考“触摸按键控制LED灯”章节中的信号沿产生方法。上图中ifr_in_rise为红外信号上升沿,ifr_in_fall为红外信号下降 沿。

上面波形图是标志信号的产生方法,下面将分三个波形图来讲解状态机的跳转时序。首先是引导码跳转的波形图,如图 27‑23所示。

IFR024

图 27‑23 引导码状态跳转波形图

在前面状态机跳转中说到,当检测到下降沿到来时,让其跳转到S_T9状态。如上波形图所示,当检测到下降沿标志信号(ifr_in_fall)为高时,状态机的状态(state)跳转到S_T9状态,并且让计数器开始计数,用该计数器来判断低电平的保持时间。

当状态机跳转到S_T9状态时让cnt开始计数,当检测到红外信号上升沿来到时,我们判断计数器是否计到了9ms范围,若是计到了说明发送正确,同时让计数器归0以及让状态机跳转到S_JUDGE状态。

当状态机在S_JUDGE状态时,让计数器开始计数。当下一个下降沿到来时,若是计数器计到了4.5ms范围说明发送的是同步码,则状态机跳转到地址码数据码接收状态,同时让计数器归0。

以上是同步码的跳转逻辑关系,而状态在S_JUDGE状态跳转到S_REPEAT(重复码)状态的情况,将在后面的重复码波形图中讲到。

引导码之后接收的是地址码、地址反码、数据码和数据反码,它们的状态跳转波形图如图 27‑24所示。

IFR025

图 27‑24 数据帧状态跳转波形图

接着引导码状态跳转波形图后是数据帧状态跳转波形图,如图 27‑24所示当状态机跳转到了数据帧接收状态(S_IFR_DATA)。我们又得开始计数判断了,同前面讲的一样,当状态机到了一个新的状态时,就让计数器清零并重新计数。

状态机跳转到S_IFR_DATA状态时,当上升沿到来时无论是“0”还是“1”首先都是持续0.56ms的低电平,所以我们先对0.56ms的低电平进行检测判断。当检测到其持续了0.56ms时让计数器清零并开始对下一个电平时间进行计数。当下降沿到来时判断高电平的持续时间,若是持续了0.56ms则说明接收的 数为“0”;若是持续了1.69ms则说明接收的数为“1”。

因为后面是需要将数据帧的数据码显示出来,所以我们需要将接收的32位数据帧寄存起来(data_tmp)。为了将输入的值寄存在寄存器的相应位数,我们需要借助一个位数计数器(data_cnt)来完成,当状态机在数据帧接收状态且下降沿到来时(每发送完一位数据,就会产生一个下降沿)让位数计数器加1,并且给da ta_tmp相应的值。这样寄存器当前寄存的位数就是寄存时采到的位数计数器的值,后面的位数用同样的方法去判断即可。

若是都不满足“0”和“1”的时序要求,则说明发送的数据有误,让状态机回到初始状态。整个数据的接收都是判断是“0”还是“1”,当接收完最后一个数据时,也就是当下降沿到来时计数器的值加到了31;0~31即为32位的数据帧(8位地址码、8位地址反码、8位数据码、8位数据反码)由低位到高位排列。这个时候32 位寄存器(data_tmp)都寄存了相应的值,当地址码与地址反码,数据码与数据反码相匹配时,将数据码的值给data输出。当最后一个值接收完了之后,也就是data_cnt等于32时让状态机跳转到下一状态。

当数据帧接收完之后,如果我们按键没有松开,则会继续发送重复码,波形图如图 27‑25所示。

IFR026

图 27‑25 重复码状态跳转波形图

重复码的状态跳转与引导码的状态跳转大致相同。不同的是9ms的低电平之后的高电平的持续时间,如果高电平持续了2.25ms说明发送的是重复码,则状态机跳转到重复码状态。当下一个上升沿到来时可以不用判断结束位的低电平持续时间,直接让状态机跳转到初始状态即可。同时当状态机处于重复状态(S_REPEAT)且我 们前面接收的数据码正确时,拉高重复码使能信号(repeat_en)表示发送了一个重复码。其余信号的逻辑变化关系跟前面是一样的。

红外接收的数据如何判断,显示的数据码如何产生,控制led灯的重复码使能信号如何产生都已经做了详细的讲述。下面给出红外接收模块的参考代码。

代码编写

参照绘制波形图,编写模块代码。模块参考代码,具体见代码清单 27‑1。

代码清单 27‑1 红外接收模块参考代码(infrared_rcv.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
module infrared_rcv
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低有效
input wire infrared_in , //红外接受信号

output reg repeat_en , //重复码使能信号
output reg [19:0] data //接收的控制码
);
////
//\* Parameter and Internal Signal \//
////
//parameter define
parameter CNT_0_56MS_L = 20000 , //0.56ms计数为0-27999
CNT_0_56MS_H = 35000 ,
CNT_1_69MS_L = 80000 , //1.69ms计数为0-84499
CNT_1_69MS_H = 90000 ,
CNT_2_25MS_L = 100000, //2.25ms计数为0-112499
CNT_2_25MS_H = 125000,
CNT_4_5MS_L = 175000, //4.5ms计数为0-224999
CNT_4_5MS_H = 275000,
CNT_9MS_L = 400000, //9ms计数为0-449999
CNT_9MS_H = 490000;
//state
parameter IDLE = 5'b0_0001, //空闲状态
S_T9 = 5'b0_0010, //监测同步码低电平
S_JUDGE = 5'b0_0100, //判断重复码和同步码高电平
S_IFR_DATA = 5'b0_1000, //接收数据
S_REPEAT = 5'b1_0000; //重复码
//wire define
wire ifr_in_rise ; //检测红外信号的上升沿
wire ifr_in_fall ; //检测红外信号的下降沿
//reg define
reg infrared_in_d1 ; //对infrared_in信号打一拍
reg infrared_in_d2 ; //对infrared_in信号打两拍
reg [18:0] cnt ; //计数器
reg flag_0_56ms ; //0.56ms计数完成标志信号
reg flag_1_69ms ; //1.69ms计数完成标志信号
reg flag_2_25ms ; //2.25ms计数完成标志信号
reg flag_4_5ms ; //4.5ms计数完成标志信号
reg flag_9ms ; //0.56ms计数完成标志信号
reg [4:0] state ; //状态机状态
reg [5:0] data_cnt ; //数据计数器
reg [31:0] data_tmp ; //数据寄存器
////
//\* Main Code \//
////
//检测红外信号的上升沿和下降沿
assign ifr_in_rise = (~infrared_in_d2) & (infrared_in_d1);
assign ifr_in_fall = (infrared_in_d2) & (~infrared_in_d1);
//对infrared_in信号打拍
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
infrared_in_d1 <= 1'b0;
infrared_in_d2 <= 1'b0;
end
else
begin
infrared_in_d1 <= infrared_in;
infrared_in_d2 <= infrared_in_d1;
end
//cnt
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt <= 19'd0;
else
case(state)
IDLE: cnt <= 19'd0;
S_T9: if((ifr_in_rise==1'b1) && (flag_9ms==1'b1))
cnt <= 19'd0;
else
cnt <= cnt + 1;
S_JUDGE:if((ifr_in_fall==1'b1) && (flag_2_25ms==1'b1 \|\|
flag_4_5ms==1'b1))
cnt <= 19'd0;
else
cnt <= cnt + 1;
S_IFR_DATA: if((flag_0_56ms == 1'b1) && (ifr_in_rise==1'b1))
cnt <= 19'd0;
else if(((flag_0_56ms==1'b1) \|\|
(flag_1_69ms==1'b1)) && (ifr_in_fall==1'b1))
cnt <= 19'd0;
else
cnt <= cnt + 1;
default:cnt <= 19'd0;
endcase
//flag_0_56ms:计数到0.56ms范围拉高标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
flag_0_56ms <= 1'b0;
 else if((state == S_IFR_DATA) && (cnt >= CNT_0_56MS_L) &&
 (cnt <= CNT_0_56MS_H))
 flag_0_56ms <= 1'b1;
 else
 flag_0_56ms <= 1'b0;

 //flag_1_69ms:计数到1.69ms范围拉高标志信号
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 flag_1_69ms <= 1'b0;
 else if((state == S_IFR_DATA) && (cnt >= CNT_1_69MS_L) &&
 (cnt <= CNT_1_69MS_H))
 flag_1_69ms <= 1'b1;
 else
 flag_1_69ms <= 1'b0;

 //flag_2_25ms:计数到2.25ms范围拉高标志信号
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 flag_2_25ms <= 1'b0;
 else if((state == S_JUDGE) && (cnt >= CNT_2_25MS_L) &&
 (cnt <= CNT_2_25MS_H))
 flag_2_25ms <= 1'b1;
 else
 flag_2_25ms <= 1'b0;

 //flag_4_5ms:计数到4.5ms范围拉高标志信号
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 flag_4_5ms <= 1'b0;
 else if((state == S_JUDGE) && (cnt >= CNT_4_5MS_L) &&
 (cnt <= CNT_4_5MS_H))
 flag_4_5ms <= 1'b1;
 else
 flag_4_5ms <= 1'b0;

 //flag_9ms:计数到9ms范围拉高标志信号
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 flag_9ms <= 1'b0;
 else if((state == S_T9) && (cnt >= CNT_9MS_L) &&
 (cnt <= CNT_9MS_H))
 flag_9ms <= 1'b1;
 else
 flag_9ms <= 1'b0;

 //状态机:状态跳转
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 state <= IDLE;
 else
 case(state)
 //若检测到红外信号下降沿到来跳转到S_T9状态
 IDLE:
 if(ifr_in_fall == 1'b1)
 state <= S_T9;
 else //若没检测到红外信号的下降沿,则让其保持在IDLE状态
 state <= IDLE;
 S_T9: //若检测到红外信号上升沿到来,则判断flag_9ms是否为1
 //若检测到时间接近9ms,则跳转到S_judje状态
 if((ifr_in_rise == 1'b1) && (flag_9ms == 1'b1))
 state <= S_JUDGE;
 else if((ifr_in_rise == 1'b1) && (flag_9ms == 1'b0))
 state <= IDLE;
 else
 state <= S_T9;
 S_JUDGE: //若检测到红外信号下降沿到来,则判断flag_2_25ms是否为1
 //若检测到时间接近2.25ms,则跳转重复码状态
 if((ifr_in_fall == 1'b1) && (flag_2_25ms == 1'b1))
 state <= S_REPEAT;
 else if((ifr_in_fall == 1'b1) && (flag_4_5ms == 1'b1))
 state <= S_IFR_DATA;
 else if((ifr_in_fall == 1'b1) && (flag_2_25ms == 1'b0) &&
 (flag_4_5ms == 1'b0))
 state <= IDLE;
 else
 state <= S_JUDGE;
 S_IFR_DATA:
 //若上升沿到来,低电平保持时间不满足编码协议,则回到空闲状态
 if(ifr_in_rise == 1'b1 && flag_0_56ms == 1'b0)
 state <= IDLE;
 //若下降沿到来,高电平保持时间不满足编码0或1,则回到空闲状态
 else if(ifr_in_fall == 1'b1 && (flag_0_56ms == 1'b0 &&
 flag_1_69ms == 1'b0))
 state <= IDLE;
 //数据接收完毕之后回到空闲状态,等待下一个指令的到来
 else if(ifr_in_rise == 1'b1 && data_cnt == 6'd32)
 state <= IDLE;
 S_REPEAT:
 /*若上升沿到来,无论时间是否到了0.56ms,
 状态机都跳回IDLE状态等待下一数据码或重复码的到来*/
 if(ifr_in_rise == 1'b1)
 state <= IDLE;
 else
 state <= S_REPEAT;
 default:
 state <= IDLE;
 endcase

 //data_tmp
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 data_tmp <= 32'b0;
 else if(state == S_IFR_DATA && ifr_in_fall == 1'b1 &&
 flag_0_56ms == 1'b1)
 data_tmp[data_cnt] <= 1'b0;
 else if(state == S_IFR_DATA && ifr_in_fall == 1'b1 &&
 flag_1_69ms == 1'b1)
 data_tmp[data_cnt] <= 1'b1;
 else
 data_tmp <= data_tmp;

 //data_cnt
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 data_cnt <= 1'b0;
 else if(ifr_in_rise == 1'b1 && data_cnt == 6'd32)
 data_cnt <= 1'b0;
 else if(ifr_in_fall == 1'b1 && state == S_IFR_DATA)
 data_cnt <= data_cnt + 1'b1;
 else
 data_cnt <= data_cnt;

 //repeat_en
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 repeat_en <= 1'b0;
 else if(state == S_REPEAT && (data_tmp[23:16] ==
 ~data_tmp[31:24]))
 repeat_en <= 1'b1;
 else
 repeat_en <= 1'b0;

 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 data <= 20'b0;
 //数据接收完之后若数据校验正确,则输出数据码的数据
 else if(data_tmp[23:16] == ~data_tmp[31:24] && data_tmp[7:0] ==
 ~data_tmp [15:8] && data_cnt==6'd32)
 data <= {12'b0,data_tmp[23:16]};

 endmodule

代码是根据所画的波形图进行编写的,在波形图介绍中对各个信号的关系也进行了说明,代码上对关键信号关系也进行了注释,在此就不再一一说明。

21.2.3.4. led灯控制模块

模块框图

led控制模块的目的是为了验证我们红外遥控是否发出了重复码,一直按是不是一直在重复发送。模块框图,具体见图 27‑26。

IFR027

图 27‑26 led控制模块框图

表格 27‑3 led控制模块输入输出端口功能描述

信号

位宽

类型

功能描述

repeat_en

1Bit

Input

重复码使能信号

sys_clk

1Bit

Input

系统时钟,频率50MHz

sys_rst_n

1Bit

Input

复位信号,低电平有效

led

1Bit

output

led输出信号

根据上面的图和表格所示,我们是用红外接收模块产生的重复码使能信号去对led进行点亮的。下面根据波形图详细了解如何去控制。

波形图绘制

IFR028

图 27‑27 led控制波形图

实验任务是来一次重复码,让led闪烁一次。一个使能信号高电平的时间是很短的,如果我们让使能信号高的时候led灯亮,低的时候led灯不亮,这样led灯亮的时间就太短了,效果不明显。从理论部分中我们知道,如果长按红外遥控按键那么每110ms会发送一次重复码,那么我们能不能让led灯在110ms中亮50m s呢,显然这样设计会使led的显示效果比较好。如何实现呢?首先需要一个50ms的计数器,当重复码使能信号的上升沿来临时(产生上升沿信号的方法与前面一致),先产生一个计数器使能信号(cnt_en),让这个计数器使能信号去控制计数器的状态。

如上波形图所示:当重复码使能信号上升沿到来时,拉高计数器使能信号(cnt_en),当计数器使能信号为高时,让计数器开始计数,当计数器计到2499999时拉低计数器使能信号,同时当使能信号为低时让计数器清零。这样当计数器的值大于0时给led点亮即可实现点亮50ms了。

代码编写

模块波形图绘制完毕后,参照绘制波形图进行参考代码的编写。模块参考代码,具体见代码清单 27‑2。

代码清单 27‑2 led控制参考代码(led_ctrl.v)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
module led_ctrl
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低有效
input wire repeat_en , //重复码使能信号

output reg led //输出led灯信号
);

 ////
 //\* Parameter and Internal Signal \//
 ////
 //parameter define
 parameter CNT_MAX = 2500_000;

 //wire define
 wire repeat_en_rise ; //重复码使能信号上升沿

 //reg define
 reg repeat_en_d1; //重复码使能信号打一拍
 reg repeat_en_d2; //重复码使能信号打两拍
 reg cnt_en ; //计数器使能信号
 reg [21:0] cnt ; //计数器

 ////
 //\* Main Code \//
 ////

 //获得repeat_en上升沿信号
 assign repeat_en_rise = repeat_en_d1 & ~repeat_en_d2;

 //对reeat_en打两拍
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 begin
 repeat_en_d1 <= 1'b0;
 repeat_en_d2 <= 1'b0;
 end
 else
 begin
 repeat_en_d1 <= repeat_en;
 repeat_en_d2 <= repeat_en_d1;
 end

 //当重复码使能信号上升沿来到,拉高计数器使能信号,计到50ms后拉低
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 cnt_en <= 1'b0;
 else if(cnt == CNT_MAX - 1)
 cnt_en <= 1'b0;
 else if(repeat_en_rise == 1'b1)
 cnt_en <= 1'b1;

 //当计数器使能信号为高时让计数器开始计数,为低时计数器清零
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 cnt <= 22'b0;
 else if(cnt_en == 1'b1)
 cnt <= cnt + 1;
 else
 cnt <= 22'b0;

 //当计数器大于0时,点亮led灯,也就是当使能信号到来,led灯会亮50ms
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 led <= 1'b1;
 else if(cnt > 0)
 led <= 1'b0;
 else
 led <= 1'b1;

 endmodule

模块参考代码是参照绘制波形图进行编写的,在波形图绘制小节已经对模块各信号有了详细的说明,对各信号介绍不再过多叙述。

21.2.3.5. 顶层模块

模块框图

top_infrared_rcv顶层模块主要是对各个子功能模块的实例化,以及对应信号的连接,模块框图如图 27‑28所示。

IFR029

图 27‑28 红外遥控顶层模块框图

模块各输入输出信号描述如表格 27‑4所示。

表格 27‑4 顶层模块输入输出信号描述

信号

位宽

类型

功能描述

sys_clk

1bit

input

系统时钟,50MHz

sys_rst_n

1bit

input

复位信号,低有效

infrared_in

1bit

input

红外输入信号

stcp

1bit

output

存储寄存器时钟

shcp

1bit

output

移位寄存器时钟

ds

1bit

output

串行数据

oe

1bit

output

输出使能,低有效

led

1bit

output

输出led信号

代码编写

顶层模块代码编写较为容易,无需波形图的绘制,顶层参考代码,具体见代码清单 27‑3。

代码清单 27‑3 红外遥控顶层参考代码(top_infrared_rcv.v)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
module top_infrared_rcv
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire infrared_in , //红外接收信号

output wire stcp , //输出数据存储器时钟
output wire shcp , //移位寄存器的时钟输入
output wire ds , //串行数据输入
 output wire oe , //输出使能信号
 output wire led //led灯控制信号

 );

 ////
 //\* Parameter And Internal Signal \//
 ////

 //wire define
 wire repeat_en ; //重复码使能信号
 wire [19:0] data ; //接收的控制码

 ////
 //\* Main Code \//
 ////

 //-------------infrared_rcv_inst--------------
 infrared_rcv infrared_rcv_inst
 (
 .sys_clk (sys_clk ), //系统时钟,频率50MHz
 .sys_rst_n (sys_rst_n ), //复位信号,低有效
 .infrared_in (infrared_in), //红外接受信号

 .repeat_en (repeat_en ), //重复码使能信号
 .data (data ) //接收的控制码

 );

 //-------------led_ctrl_inst--------------
 led_ctrl led_ctrl_inst
 (
 .sys_clk (sys_clk ) , //系统时钟,频率50MHz
 .sys_rst_n (sys_rst_n) , //复位信号,低有效
 .repeat_en (repeat_en) , //重复码使能信号

 .led (led )

 );

 //-------------seg_595_dynamic_inst--------------
 seg_595_dynamic seg_595_dynamic_inst
 (
 .sys_clk (sys_clk ), //系统时钟,频率50MHz
 .sys_rst_n (sys_rst_n), //复位信号,低有效
 .data (data ), //数码管要显示的值
 .point (6'd0 ), //小数点显示,高电平有效
 .seg_en (1'b1 ), //数码管使能信号,高电平有效
 .sign (1'b0 ), //符号位,高电平显示负号

 .stcp (stcp ), //输出数据存储寄时钟
 .shcp (shcp ), //移位寄存器的时钟输入
 .ds (ds ), //串行数据输入
 .oe (oe ) //输出使能信号

 );

 endmodule

代码56行是数码管的小数信号连接,本次实现显示的数值没有小数,我们给其为0即可。同理seg_en信号和sign信号的值也是根据我们的实际需求给即可。

21.2.3.6. RTL视图

顶层模块介绍完毕,使用Quartus II软件对实验工程进行编译,工程通过编译后查看实验工程RTL视图。工程RTL视图,具体见图 27‑29。由图可知,实验工程的RTL视图与实验整体框图相同,各信号线均已正确连接。

IFR030

图 27‑29 实验工程RTL视图

21.2.3.7. 仿真验证

仿真代码编写

顶层模块参考代码介绍完毕,开始对顶层模块进行仿真,对顶层模块的仿真就是对实验工程的整体仿真。顶层模块仿真参考代码,具体见代码清单 27‑4。

代码清单 27‑4 顶层仿真参考代码(tb_top_infrared_rcv.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
\`timescale 1ns/1ns
module tb_top_infrared_rcv();

////
//\* Parameter and Internal Signal \//
////

//wire define
wire led ;
wire stcp ;
wire shcp ;
wire ds ;
wire oe ;
//reg define
reg sys_clk ;
reg sys_rst_n ;
reg infrared_in ;
////
//\* Main Code \//
////
//对sys_clk,sys_rst_n,infrared_in赋值
initial
begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
infrared_in <= 1'b1;
#100
sys_rst_n <= 1'b1;
//引导码
#1000
infrared_in <= 1'b0; #9000000
infrared_in <= 1'b1; #4500000
//地址码(发送地址码8’h99)
//数据1
infrared_in <= 1'b0; #560000
infrared_in <= 1'b1; #1690000
//数据0
infrared_in <= 1'b0; #560000
infrared_in <= 1'b1; #560000
//数据0
infrared_in <= 1'b0; #560000
infrared_in <= 1'b1; #560000
//数据1
infrared_in <= 1'b0; #560000
infrared_in <= 1'b1; #1690000
//数据1
infrared_in <= 1'b0; #560000
infrared_in <= 1'b1; #1690000
//数据0
infrared_in <= 1'b0; #560000
infrared_in <= 1'b1; #560000
//数据0
infrared_in <= 1'b0; #560000
infrared_in <= 1'b1; #560000
//数据1
infrared_in <= 1'b0; #560000
infrared_in <= 1'b1; #1690000
//地址反码(地址反码为8’h66)
//数据0
infrared_in <= 1'b0; #560000
infrared_in <= 1'b1; #560000
//数据1
infrared_in <= 1'b0; #560000
infrared_in <= 1'b1; #1690000
//数据1
infrared_in <= 1'b0; #560000
infrared_in <= 1'b1; #1690000
//数据0
infrared_in <= 1'b0; #560000
infrared_in <= 1'b1; #560000
//数据0
infrared_in <= 1'b0; #560000
infrared_in <= 1'b1; #560000
//数据1
infrared_in <= 1'b0; #560000
infrared_in <= 1'b1; #1690000
//数据1
infrared_in <= 1'b0; #560000
infrared_in <= 1'b1; #1690000
//数据0
infrared_in <= 1'b0; #560000
infrared_in <= 1'b1; #560000
//数据码(发送数据码8’h22)
//数据0
infrared_in <= 1'b0; #560000
infrared_in <= 1'b1; #560000
//数据1
infrared_in <= 1'b0; #560000
infrared_in <= 1'b1; #1690000
//数据0
infrared_in <= 1'b0; #560000
infrared_in <= 1'b1; #560000
//数据0
infrared_in <= 1'b0; #560000
infrared_in <= 1'b1; #560000
//数据0
 infrared_in <= 1'b0; #560000
 infrared_in <= 1'b1; #560000
 //数据1
 infrared_in <= 1'b0; #560000
 infrared_in <= 1'b1; #1690000
 //数据0
 infrared_in <= 1'b0; #560000
 infrared_in <= 1'b1; #560000
 //数据0
 infrared_in <= 1'b0; #560000
 infrared_in <= 1'b1; #560000
 //数据反码(数据反码为8’hdd)
 //数据1
 infrared_in <= 1'b0; #560000
 infrared_in <= 1'b1; #1690000
 //数据0
 infrared_in <= 1'b0; #560000
 infrared_in <= 1'b1; #560000
 //数据1
 infrared_in <= 1'b0; #560000
 infrared_in <= 1'b1; #1690000
 //数据1
 infrared_in <= 1'b0; #560000
 infrared_in <= 1'b1; #1690000
 //数据1
 infrared_in <= 1'b0; #560000
 infrared_in <= 1'b1; #1690000
 //数据0
 infrared_in <= 1'b0; #560000
 infrared_in <= 1'b1; #560000
 //数据1
 infrared_in <= 1'b0; #560000
 infrared_in <= 1'b1; #1690000
 //数据1
 infrared_in <= 1'b0; #560000
 infrared_in <= 1'b1; #1690000
 //重复码
 infrared_in <= 1'b0; #560000
 infrared_in <= 1'b1; #42000000
 infrared_in <= 1'b0; #9000000
 infrared_in <= 1'b1; #2250000
 infrared_in <= 1'b0; #560000
 infrared_in <= 1'b1;
 end

 //clk:产生时钟
 always #10 sys_clk <= ~sys_clk;

 ////
 //\* Instantiation \//
 ////

 top_infrared_rcv top_infrared_rcv_inst
 (
 .sys_clk (sys_clk ), //系统时钟,频率50MHz
 .sys_rst_n (sys_rst_n ), //复位信号,低电平有效
 .infrared_in (infrared_in), //红外接收信号

 .stcp (stcp ), //输出数据存储寄时钟
 .shcp (shcp ), //移位寄存器的时钟输入
 .ds (ds ), //串行数据输入
 .oe (oe ), //输出使能信号
 .led (led ) //led灯控制信号

 );

 endmodule

仿真波形分析

使用ModelSim软件对代码进行仿真,仿真波形如下所示。

IFR031

图 27‑30 红外接收仿真总体图

IFR032

图 27‑31 红外接收数据图

IFR033

图 27‑32 红外接收重复码图

根据上面三个仿真波形图可以看到仿真文件产生的红外信号与理论上发射的红外信号是相同的。可以看到data的数据就是我们发送的数据码(仿真文件产生),同时当重复码到来时,对应的重复码使能信号(repeat_en)也拉高了,与我们所画的波形图是一样的。

IFR034

图 27‑33 led灯控制仿真波形图

由图 27‑33仿真波形图可以看到,当重复码使能信号到来时,led灯会点亮一段时间,这与我们设计的波形图也是一样的。

数码管模块我们在数码管的动态显示中已经验证过了,这次就不在验证了。根据以上仿真波形图可以得出我们设计的工程满足我们的实验要求。

21.3. 上板调试

21.3.1. 引脚约束

仿真验证通过后,准备上板验证,上板验证之前先要进行引脚约束。工程中各输入输出信号与开发板引脚对应关系如表格 27‑5所示。

表格 27‑5 引脚分配表

信号名

信号类型

对应引脚

备注

sys_clk

input

E1

时钟

sys_rst_n

input

M15

复位

infrared_in

input

G16

红外输入

stcp

output

K9

存储寄存器时钟

shcp

output

B1

移位寄存器时钟

ds

output

R1

串行数据

oe

output

L11

输出使能,低有效

led

output

L7

led灯

下面进行管脚分配,管脚的分配方法在前面章节已有所讲解,在此就不再过多叙述,管脚的分配如下图 27‑34所示。

IFR035

图 27‑34 管脚分配

21.3.1.1. 结果验证

管脚配置完成之后重新进行编译,编译完之后就可以进行下载验证了,在下载之前首先将电源与下载线与开发板连接好,连接好之后上电,如图 27‑35所示。

IFR036

图 27‑35 下载连线图

打开下载界面后,当检测到下载器(USB-Blaster)已连接之后,即可点击“Add File…”添加sof文件,添加好后点击“start”开始下次,随后界面会显示下载成功,如图 27‑36所示。

IFR037

图 27‑36 下载成功界面

下载成功后即可以开始验证了。首先需先把红外遥控尾部的绝缘隔片拔出,让红外遥控通电。拔出后即可开始使用,此时我们可对着红外接收头按下任意按键,此时若数码管中显示的值与按下的按键相匹配,则验证正确。对应的红外遥控键码图详见图 27‑3。

21.4. 章末总结

到这里,本章节讲解完毕,通过实验,相信读者对于我们使用的红外遥控的编码协议(NEC协议)有了很清晰的认知,当然更重要的是我们学会了这种接收信息的方式,相信大家以后遇到相类似的信息发送协议,学起来能起到事半功倍的效果。

21.5. 拓展训练

编写工程,使用遥控器和红外接收器控制LED灯、蜂鸣器,按下某个按键控制LED灯亮灭或蜂鸣器鸣叫。