正点原子 发表于 2021-10-10 14:49:40

《新起点V2之FPGA开发指南》第二十六章 红外遥控实验

本帖最后由 正点原子 于 2021-10-30 10:36 编辑

1)实验平台:正点原子新起点V2FPGA开发板
2)章节摘自【正点原子】《新起点之FPGA开发指南 V2.1》
3)购买链接:https://detail.tmall.com/item.htm?id=609758951113
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-328002-1-1.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流QQ群:712557122








第二十六章 红外遥控实验
       红外遥控是一种无线、非接触控制技术,具有抗干扰能力强、信息传输可靠、功耗低、易实现等显著特点,被诸多电子设备特别是家用电器广泛采用,并越来越多的应用到计算机系统中。本章我们将使用新起点FPGA开发板接收红外遥控器发出的红外信号,并将数据显示在数码管上,如果监测到重复码,则通过LED灯闪烁指示。
       本章分为以下几个章节:
       1.1简介
       1.2实验任务
       1.3硬件设计
       1.4程序设计
       1.5下载验证

1.1简介
       红外遥控是一种无线、非接触控制技术,由于它不具有像无线电遥控那样可以穿过障碍物去控制被控对象的能力,所以同类产品的红外遥控器,可以有相同的遥控频率或编码,而不会隔墙控制或干扰邻居的家用电器,这对于大批量生产以及在家用电器上普及红外遥控器提供了极大的方便。红外遥控器发射出的实际上是一种红外光(红外线),其波长范围在1mm到760nm之间,而人眼可见光的波长范围一般在400nm到760nm之间,所以我们并不能看到红外遥控器发出的红外光,因此对环境的影响很小,也不会影响临近的无线电设备。
       红外遥控器的编码目前广泛使用的是:NEC协议和Philips RC-5协议。新起点FPGA开发板配套的遥控器使用的是NEC协议,其逻辑电平编码格式如图 26.1.1所示。

图 26.1.1 NEC协议逻辑电平编码格式
       NEC协议采用PPM调制(Pulse Position Modulation,脉冲位置调制)的形式进行编码,数据的每一位(Bit)脉冲长度为560us,由38KHz的载波脉冲(carrier burst)进行调制,推荐的载波占空比为1/3至1/4。由上图可知,有载波脉冲的地方,其宽度都为560us,而载波脉冲的间隔时间是不同的。逻辑“1”的载波脉冲+载波脉冲间隔时间为2.25ms;逻辑“0”的载波脉冲+载波脉冲间隔时间约为逻辑“1”的一半,也就是1.12ms。

图 26.1.2 NEC协议的数据传输格式
       图 26.1.2为NEC协议的数据传输格式。由图可知,传输数据时低位在前,图中的地址码(Address)为0x59,控制码(Command)为0x16。一个信息的发送由9ms的AGC(自动增益控制)载波脉冲开始,用于在早期的IR红外接收器中设置增益;紧接着是4.5ms的空闲信号;随后是地址码和控制码。地址码和控制码分别传输了两次,第二次传输的地址码和控制码都是反码,用于对地址码和控制码做校验,当然,也可以直接忽略地址码反码和控制码反码。每次信息都是按照同步码(9ms载波脉冲+4.5ms空闲信号)、地址码、地址反码、控制码和控制反码的格式进行传输,因此,单次信息传输的时间是固定不变的。
       当红外遥控器上的按键被一直按下时,红外遥控器只会发送一次完整的信息,其后会每隔110ms发送一次重复码(也叫连发码)。重复码的数据格式比较简单,同样是由9ms的AGC(自动增益控制)载波脉冲开始,紧接着是2.25ms的空闲信号,随后是560us的载波脉冲,重复码的数据格式如图 26.1.3和图 26.1.4所示。

图 26.1.3 重复码的数据格式

图 26.1.4 一直发送重复码
       以上部分是对NEC协议的介绍,也就是红外遥控器发送数据时所遵循的协议规范,接下来我们了解下开发板板载的红外接收头,其型号为HS0038B,实物图和结构框图如图 26.1.5和图 26.1.6所示。

图 26.1.5HS0038B实物图

图 26.1.6 HS0038B结构框图
       红外接收头通常被厂家集成在一个元件中,成为一体化红外接收头。内部集成了红外监测二极管、自动增益放大器(AGC)、带通滤波器(Band Pass)、解调器(Demodulator)等电路。红外遥控器发出的信息经38KHz的载频进行二级调制以提高发射效率,达到降低电源功率的目的,然后再经过红外发射二极管产生红外线向空间中发射。红外接收头通过红外监测二极管,将光信号转换成电信号,经过电路调制之后,最终输出可以被FPGA采集的TTL电平信号。这里要注意的一点是,红外接收头内部的三极管电路具有信号反向的功能,也就是将1变为0,0变为1,那么上面的整个协议则电平反过来接收。9ms本来是高电平,那么将变为低电平,以此类推如图 26.1.7所示,接收解码对应的波形是FPGA最终接收到的红外信号。

图 26.1.7 红外接收解码接收图
      下图为红外解码接收到的完整波形。

图 26.1.8 红外解码接收到的完整波形
       从图 26.1.8可以看到,地址码为0,控制码为0x15。在一段时间之后,我们还可以收到几个脉冲,这就是NEC协议规定的重复码(连发码),如果一帧数据发送完毕之后,按键仍然没有放开,则发射重复码,可以通过统计重复码来标记按键按下的长短/次数。
下图是红外遥控的按键对应的键码值。
         
图 26.1.9 红外遥控对应的键码值
       需要注意的是,上图中右侧的键码值是以十进制数进行显示的。
1.2实验任务
       本节实验任务是使用新起点FPGA开发板接收红外遥控器发出的红外信号,并将数据显示在数码管上;如果监测到重复码,则通过LED灯闪烁指示。
1.3硬件设计
       HS0038电路原理图如图 26.3.1所示,图中REMOTE_IN信号为红外接收头的电平输出端。

图 26.3.1 HS0038B电路原理图
       本实验的管脚分配如下表所示

表 26.3.1 红外遥控数码管显示实验管脚分配
       因为本节实验用到的管脚基本都是数码管的引脚,在数码管实验章节已经给出了,这里就不再提供TCL约束文件了。
1.4程序设计
       根据实验任务可大致规划出控制流程,红外驱动模块解析红外数据,将控制码输出至数码管驱动模块,重复码有效信号输出至LED控制模块。数码管驱动模块将对应的位选和段选信号发送至数码管,使相应的数字显示在数码管上,LED控制模块根据重复码信号控制LED灯的亮灭。系统框图如下所示。

图 26.4.1 红外遥控实验系统框图
      顶层模块原理图如下所示

图 26.4.2 顶层模块原理图
       FPGA顶层(top_remote_rcv)例化了以下两个模块:红外驱动模块(remote_rcv)和数码管动态显示模块(seg_led),实现各模块间信号的交互。
       顶层模块代码如下:
1module top_remote_rcv(
2      input             sys_clk,    //系统时钟
3      input             sys_rst_n,    //系统复位信号,低电平有效
4      input             remote_in,    //红外接收信号
5      output   sel      ,    //数码管位选信号
6      output   seg_led,    //数码管段选信号
7      output            led         //led灯
8);
9
10 //wire define
11 wire   data       ;
12 wire          repeat_en;
13
14 //*****************************************************
15 //**                  main code
16 //*****************************************************
17
18 //数码管显示模块
19 seg_led u_seg_led(
20   .clk            (sys_clk),   
21   .rst_n          (sys_rst_n),
22   .sel            (sel),   
23   .seg_led      (seg_led),
24   .data         (data),         //红外数据
25   .point          (6'd0),         //无小数点
26   .en             (1'b1),         //使能数码管
27   .sign         (1'b0)            //无符号显示
28   );
29
30 //HS0038B驱动模块
31 remote_rcv u_remote_rcv(               
32   .sys_clk      (sys_clk),
33   .sys_rst_n      (sys_rst_n),   
34   .remote_in      (remote_in),
35   .repeat_en      (repeat_en),               
36   .data_en      (),
37   .data         (data)
38   );
39
40 led_ctrlu_led_ctrl(
41   .sys_clk       (sys_clk),
42   .sys_rst_n   (sys_rst_n),
43   .repeat_en   (repeat_en),
44   .led         (led)
45   );
46
47 endmodule
       顶层模块完成对其他模块的例化,红外驱动模块输出的控制码(data)连接至数码管显示模块,输出的repeat_en(重复码有效信号)连接至LED控制模块。
       由本章简介部分介绍的红外传输时序可以发现,红外传输时序非常适合使用状态机来编写。红外驱动模块状态跳转图如下图所示。

图 26.4.3 红外驱动模块状态跳转图
       红外驱动模块使用三段式状态机来解析红外遥控信号,从上图可以比较直观的看到每个状态实现的功能以及跳转都下一个状态的条件。由于一次完整的红外信息和重复码都是以同步码(9ms的低电平)开始,其空闲信号高电平的时间是不一样的,一次完整的红外信息空闲信号高电平时间是4.5ms,而重复码的空闲信号高电平时间是2.25ms。所以我们在st_start_judge状态判断空闲信号高电平的时间,如果时间是4.5ms,则跳转到st_rec_data状态;如果时间是2.25ms,则跳转到st_repeat状态。
       红外驱动模块部分代码如下:
1   module remote_rcv(
2       input                  sys_clk   ,//系统时钟
3       input                  sys_rst_n ,//系统复位信号,低电平有效
4      
5       input                  remote_in ,//红外接收信号
6       output    reg          repeat_en ,//重复码有效信号
7       output    reg          data_en   ,//数据有效信号
8       output    reg   data         //红外控制码
9       );
10
11//parameter define
12parameterst_idle         = 5'b0_0001;//空闲状态
13parameterst_start_low_9ms= 5'b0_0010;//监测同步码低电平
14parameterst_start_judge    = 5'b0_0100;//判断重复码和同步码高电平(空闲信号)
15parameterst_rec_data       = 5'b0_1000;//接收数据
16parameterst_repeat_code    = 5'b1_0000;//重复码
17
18//reg define
19reg        cur_state      ;
20reg        next_state   ;
21
22reg       div_cnt      ;//分频计数器
23reg             div_clk      ;//分频时钟
24reg             remote_in_d0   ;//对输入的红外信号延时打拍
25reg             remote_in_d1   ;
26reg        time_cnt       ;//对红外的各个状态进行计数
27
28reg             time_cnt_clr   ;//计数器清零信号
29reg             time_done      ;//计时完成信号
30reg             error_en       ;//错误信号
31reg             judge_flag   ;//检测出的标志信号 0:同步码高电平(空闲信号)1:重复码
32reg       data_temp      ;//暂存收到的控制码和控制反码
33reg        data_cnt       ;//对接收的数据进行计数      
34
35//wire define
36wire            pos_remote_in;//输入红外信号的上升沿
37wire            neg_remote_in;//输入红外信号的下降沿
38
39//*****************************************************
40//**                  main code
41//*****************************************************
42
43assignpos_remote_in = (~remote_in_d1) & remote_in_d0;
44assignneg_remote_in = remote_in_d1 & (~remote_in_d0);
45
46//时钟分频,50Mhz/(2*(3124+1))=8khz,T=0.125ms
47always @(posedge sys_clk or negedge sys_rst_n) begin
48      if (!sys_rst_n) begin
49          div_cnt <= 12'd0;
50          div_clk <= 1'b0;
51      end   
52      else if(div_cnt == 12'd3124) begin
53          div_cnt <= 12'd0;
54          div_clk <= ~div_clk;
55      end   
56      else
57          div_cnt = div_cnt + 12'b1;
58end
59
60//对红外的各个状态进行计数
61always @(posedge div_clk or negedge sys_rst_n) begin
62      if(!sys_rst_n)
63          time_cnt <= 8'b0;
64      else if(time_cnt_clr)
65          time_cnt <= 8'b0;
66      else
67          time_cnt <= time_cnt + 8'b1;
68end
69
70//对输入的remote_in信号延时打拍
71always @(posedge div_clk or negedge sys_rst_n) begin
72      if(!sys_rst_n) begin
73          remote_in_d0 <= 1'b0;
74          remote_in_d1 <= 1'b0;
75      end
76      else begin
77          remote_in_d0 <= remote_in;
78          remote_in_d1 <= remote_in_d0;
79      end
80end
81
82//状态机
83always @ (posedge div_clk or negedge sys_rst_n) begin
84      if(!sys_rst_n)
85          cur_state <= st_idle;
86      else
87          cur_state <= next_state ;
88end
89
90always @(*) begin
91      next_state = st_idle;
92      case(cur_state)
93          st_idle : begin                           //空闲状态
94            if(remote_in_d0 == 1'b0)
95                  next_state = st_start_low_9ms;
96            else
97                  next_state = st_idle;            
98          end
99          st_start_low_9ms : begin                  //监测同步码低电平
100             if(time_done)
101               next_state = st_start_judge;
102             else if(error_en)
103               next_state = st_idle;
104             else
105               next_state = st_start_low_9ms;
106         end
107         st_start_judge : begin                  //判断重复码和同步码高电平(空闲信号)
108             if(time_done) begin
109               if(judge_flag == 1'b0)
110                     next_state = st_rec_data;
111               else
112                     next_state = st_repeat_code;
113             end
114             else if(error_en)
115               next_state = st_idle;
116             else
117               next_state = st_start_judge;
118         end
119         st_rec_data : begin                     //接收数据
120             if(pos_remote_in && data_cnt == 6'd32)
121               next_state = st_idle;
122             else
123               next_state = st_rec_data;               
124         end
125         st_repeat_code : begin                  //重复码
126             if(pos_remote_in)
127               next_state = st_idle;
128             else
129               next_state = st_repeat_code;   
130         end   
131         default : next_state = st_idle;
132   endcase
133 end
134
135 always @(posedge div_clk or negedge sys_rst_n ) begin
136   if (!sys_rst_n) begin
137         time_cnt_clr <= 1'b0;
138         time_done <= 1'b0;
139         error_en <= 1'b0;
140         judge_flag <= 1'b0;
141         data_en <= 1'b0;
142         data <= 8'd0;
143         repeat_en <= 1'b0;
144         data_cnt <= 6'd0;
145         data_temp <= 32'd0;
146   end
147   else begin
148         time_cnt_clr <= 1'b0;
149         time_done <= 1'b0;
150         error_en <= 1'b0;
151         repeat_en <= 1'b0;
152         data_en <= 1'b0;
153         case(cur_state)
154             st_idle         : begin
155               time_cnt_clr <= 1'b1;
156               if(remote_in_d0 == 1'b0)
157                     time_cnt_clr <= 1'b0;
158             end   
159             st_start_low_9ms: begin                           //9ms/0.125ms = 72
160               if(pos_remote_in) begin
161                     time_cnt_clr <= 1'b1;                  
162                     if(time_cnt >= 69 && time_cnt <= 75)
163                         time_done <= 1'b1;
164                     else
165                         error_en <= 1'b1;
166               end   
167             end
168             st_start_judge : begin
169               if(neg_remote_in) begin   
170                     time_cnt_clr <= 1'b1;   
171                     //重复码高电平2.25ms 2.25/0.125 = 18      
172                     if(time_cnt >= 15 && time_cnt <= 20) begin
173                         time_done <= 1'b1;
174                         judge_flag <= 1'b1;
175                     end   
176                     //同步码高电平4.5ms 4.5/0.125 = 36
177                     else if(time_cnt >= 33 && time_cnt <= 38) begin
178                         time_done <= 1'b1;
179                         judge_flag <= 1'b0;                        
180                     end
181                     else
182                         error_en <= 1'b1;
183               end                     
184             end
185             st_rec_data : begin                                 
186               if(pos_remote_in) begin
187                     time_cnt_clr <= 1'b1;
188                     if(data_cnt == 6'd32) begin
189                         data_en <= 1'b1;
190                         data_cnt <= 6'd0;
191                         data_temp <= 16'd0;
192                         if(data_temp == ~data_temp)    //校验控制码和控制反码
193                           data <= data_temp;
194                     end
195               end
196               else if(neg_remote_in) begin
197                     time_cnt_clr <= 1'b1;
198                     data_cnt <= data_cnt + 1'b1;   
199                     //解析控制码和控制反码      
200                     if(data_cnt >= 6'd16 && data_cnt <= 6'd31) begin
201                         if(time_cnt >= 2 && time_cnt <= 6) begin//0.56/0.125 = 4.48
202                           data_temp <= {1'b0,data_temp};//逻辑“0”
203                         end
204                         else if(time_cnt >= 10 && time_cnt <= 15) //1.69/0.125 = 13.52
205                           data_temp <= {1'b1,data_temp};//逻辑“1”
206                     end
207               end
208             end
209             st_repeat_code : begin                              
210               if(pos_remote_in) begin                           
211                     time_cnt_clr <= 1'b1;
212                     repeat_en <= 1'b1;
213               end
214             end
215             default : ;
216         endcase
217   end
218 end
219
220 endmodule
       在代码第47行开始的always语句块中,我们对输入的50MHz的时钟进行分频,得到一个周期为0.125ms(8KHz)的时钟,即以8Khz的时钟对红外信号进行采样。这里之所以对时钟进行分频,是因为红外信号接收的过程用时较长,如果使用50Mhz的时钟采样,内部定义的计数器位宽会比较大,所以我们对输入的时钟做了分频的处理,当然分频得到其它频率的时钟也是可以的。
       代码中使用三段式状态机对红外信号进行解析。状态机默认是在st_idle(空闲)状态,并且此时time_cnt_clr的值为1,即time_cnt计数器停止计时;当监测到remote_in_d0为低电平之后,time_cnt_clr的值为0,time_cnt计数器开始计时,此时状态机跳转到st_start_low_9ms状态,在这里主要向大家介绍下程序是如何对9ms低电平的同步码进行计数的。在代码的第160行,当检测到pos_remote_in(红外信号上升沿)为高电平时,说明此时红外信号拉高,即同步码低电平结束,此时判断time_cnt的值是否接近9ms,如果接近9ms,此时开始跳转到st_start_judge状态,否则跳转到空闲状态。程序后面对空闲信号、重复码以及数据的检测方法类似,在此不再赘述。
       图 26.4.4为SignalTap抓取的波形图,从图中可以清晰的看到红外驱动模块各个状态跳转的波形图。可以观察到空闲状态时总线为高电平,按下遥控器按键后,发出9ms低电平的同步码和4.5ms高电平的空闲信号,然后发出00000000的地址码和11111111的地址反码;接下来发送10100010的控制码和01011101的控制反码。需要注意的是,红外遥控先发送的是数据的低位,所以控制码为8’b01000101(8’d69),和图中的data(控制码)保持一致。在波形图的最后,接收到了红外遥控器发出的重复码,当程序检测到重复码之后,repeat_en发出一次脉冲信号。

图 26.4.4 SignalTap抓取的波形图
         LED控制模块代码如下:
1   module led_ctrl(
2       input             sys_clk   ,//系统时钟
3       input             sys_rst_n ,//系统复位信号,低电平有效
4      
5       input             repeat_en ,//重复码触发信号
6       output    reg   led          //LED灯
7       );
8   
9   //reg define
10reg            repeat_en_d0 ;      //repeat_en信号打拍采沿
11reg            repeat_en_d1 ;
12reg    led_cnt      ;      //LED灯计数器,用于控制LED灯亮灭
13
14//wire define
15wire         pos_repeat_en;
16
17//*****************************************************
18//**                  main code
19//*****************************************************
20
21assignpos_repeat_en = ~repeat_en_d1 & repeat_en_d0;
22
23////repeat_en信号打拍采沿
24always @(posedge sys_clk or negedge sys_rst_n) begin
25      if(!sys_rst_n) begin
26          repeat_en_d0 <= 1'b0;
27          repeat_en_d1 <= 1'b0;
28      end
29      else begin
30          repeat_en_d0 <= repeat_en;
31          repeat_en_d1 <= repeat_en_d0;
32      end
33end   
34
35always @(posedge sys_clk or negedge sys_rst_n) begin
36      if(!sys_rst_n) begin
37          led_cnt <= 23'd0;
38          led <= 1'b0;
39      end
40      else begin
41          if(pos_repeat_en) begin
42            led_cnt <= 23'd5_000_000;            //单次重复码:亮80ms 灭20ms
43            led <= 1'b1;                           //led亮的时间:4_000_000*20ns=80ms
44          end   
45          else if(led_cnt != 23'd0) begin
46            led_cnt <= led_cnt - 23'd1;
47            if(led_cnt < 23'd1_000_000)            //led灭的时间:1_000_000*20ns=20ms
48                  led <= 1'b0;
49          end   
50      end   
51end
52
53endmodule
       LED控制模块代码比较简单,首先检测repeat_en信号的上升沿(如代码的第24行开始的always所示),pos_repeat_en拉高之后,计数器赋值为5_000_000,随后计数器每个周期开始递减1,直到计数到0;在计数器在1_000_000~5_000_000范围内,点亮LED灯,其它情况熄灭LED灯,从而指示红外遥控模块是否检测到重复码。
1.5下载验证
       首先将下载器一端连电脑,另一端与开发板上对应端口连接,然后连接电源线并打开电源开关,然后将sof文件下载板子中。
下载完成后,按下遥控器上任意按键,就可以观察数码管上显示的数据了;长按按键的话,可以观察到LED在不停地闪烁。需要注意的是,使用遥控器之前需要先将遥控器后部的塑料绝缘片拔出,否则遥控器无法正常使用。遥控器的实物图如下所示:

图 26.5.1 遥控器的实物图
         按下遥控器的按键,开发板上的数码管就会显示对应的键码值,如下图所示:

图 26.5.2 显示键码值

页: [1]
查看完整版本: 《新起点V2之FPGA开发指南》第二十六章 红外遥控实验