锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

【正点原子FPGA连载】第十六章交通灯实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

时间:2023-05-22 08:07:00 p6三极管三极管g2

1)实验平台:正点原子新起点V2开发板
2)平台采购地址:https://detail.tmall.com/item.htm?id=609758951113
2)全套实验源码 手册 视频下载地址:http://www.openedv.com/thread-300792-1-1.html
3)正点原子FPGA感兴趣的同学可以加群讨论:99424016
4)关注正点原子微信官方账号,获取最新信息更新
在这里插入图片描述

第十六章交通灯实验

交通信号灯是生活中非常常见的公共设施。它在道路交叉口、斑马线等位置起着疏导交通的作用。本章将使用交通信号灯模块再现其功能。
本章包括以下几个部分:
1616.1简介
16.2实验任务
16.3硬件设计
16.4程序设计
16.5下载验证
16.1简介
交通信号灯通常由红、绿、黄三种颜色的灯组成。红灯亮时,禁止通行;绿灯亮时,可通行;黄灯亮时,提示交通时间结束,立即转换为红灯。
本实验要再现的交通信号灯是十字路口的信号灯,由两对信号灯组成。信号灯的实物如图所示 16.1.1所示:

图 16.1.1 交通信号灯
图 16.1.二是十字路口交通信号灯简化示意图:

图 16.1.2 十字路口信号灯示意图
一个方向上的信号灯点亮顺序是:红灯熄灭后,绿灯熄灭后,黄灯熄灭,黄灯熄灭后,红灯熄灭,所以一直在循环。此外,同一方向上的一对信号灯颜色相同,显示时间相同。
制作模拟交通信号灯的功能,制作了表 16.1.1信号灯状态转换表:

表 16.1.1 交通信号灯状态转换表

表 16.1.1.红灯在一个周期内发光30s,绿灯发光27s,黄灯发光3s。以东西信号灯状态为例,红灯的发光时间等于黄灯和绿灯的发光时间。因此,一个完整的状态转换周期是红灯发光时间的两倍,即60s。
红色信号灯在东西方向发光的30盏s内部,南北方向从绿灯切换到黄灯;30个红色信号灯在南北方向发光s在内部,东西方向也从绿灯切换到黄灯。因此,我们将东西方向和南北方向的信号灯同时保持在一个固定的时间段,并将其划分为一个状态。因此,有四种循环状态:
1.东西红灯亮27s,南北绿灯亮27s,然后切换到状态2;
2.东西方向红灯亮3s,南北黄灯亮3s,然后切换到状态3;
3.东西绿灯亮27s,南北红灯亮27s,然后切换到状态4;
4.东西方向黄灯亮3s,南北红灯亮3s,然后切换到状态1。
如图 16.1.三是交通信号灯状态转换图:

图 16.1.3 交通信号灯状态转换图
另外,以东西方向为例:红灯在一个周期内发光30s,绿灯发光27s,黄灯发光3s。红灯发光期间,数字管上显示的数字应从29递减至0;同样,在绿灯发光期间,数字管上显示的数字应从26递减至0;黄灯发光时,数字管上显示的数字应从2递减至0。
16.2实验任务
本节的实验任务是通过新的起点开发板和外部交通信号灯扩展模块再现交通信号灯的功能。
16.3硬件设计
我们的新起点FPGA左边的开发板P6.扩展端口可用于外部交通信号灯扩展模块如图所示 16.3.1所示。

图 16.3.1 交通信号灯原理图
从上图可以看出,交通信号灯扩展模块交通信号灯扩展模块LED我们用六个灯LED控制信号驱动12个LED这是因为东西方向或南北方向LED灯的亮灭状态总是一样的,所以我们在东西方向或南北方向有相同的颜色LED这种设计的优点是减少了交通信号灯扩展模块LED引脚控制信号。
上图中,四个共阳数码管对应四个路口,每个路口用两个数码管显示当前状态的剩余时间。众所周知,在十字路口,东西方向或南北方向的数字管显示时间总是一样的。以东西方向为例,由于两个方向显示时间一致,所以两个方向的数字管,十个可以用同一位置选择信号控制,另一个位置选择信号控制,使两个位置选择信号控制东西方向共四位数字管,南北数字管。这样设计的好处是减少了交通信号灯扩展模块位选信号的引脚。
需要注意的是,数字管理PNP型三极管驱动,当三极管基极为低电平时,选择数字管相应的位置,因此交通信号灯扩展模块的位置信号为低电平有效。
如图所示 16.3.2.本章实验将交通信号灯设定为上北下南、左西右东。

图 16.3.2 交通信号灯实物图
在本实验中,交通信号灯脚分布如下表所示。
表 16.3.1 交通信号灯实验管脚分配

对应的TCL约束文件如下:
set_location_assignment PIN_M2 -to sys_clk
set_location_assignment PIN_M1 -to sys_rst_n
set_location_assignment PIN_R12 -to sel[0]
set_location_assignment PIN_T13 -to sel[1]
set_location_assignment PIN_R11 -to sel[2]
set_location_assignment PIN_G2 -to sel[3]
set_location_assignment PIN_N8 -to seg_led[0]
set_location_assignment PIN_P8 -to seg_led[1]
set_location_assignment PIN_P6 -to seg_led[2]
set_location_assignment PIN_M8 -to seg_led[3]
set_location_assignment PIN_R14 -to seg_led[4]
set_location_assignment PIN_N6 -to seg_led[5]
set_location_assignment PIN_R13 -to seg_led[6]
set_location_assignment PIN_T14 -to seg_led[7]
set_location_assignment PIN_L9 -to led[0]
set_location_assignment PIN_M9 -to led[1]
set_location_assignment PIN_P9 -to led[2]
set_location_assignment PIN_K9 -to led[3]
set_location_assignment PIN_L10 -to led[4]
set_location_assignment PIN_N9 -to led[5]
16.4程序设计
我们可以根据实验任务粗略规划统的控制流程:交通灯控制模块将需要显示的时间数据连接到数码管
显示模块,同时将状态信号连接到led灯控制模块,然后数码管显示模块和led灯控制模块驱动交通信号灯外设工作。系统框图如图 16.4.1所示,

图 16.4.1 交通灯实验系统框图
各模块端口及信号连接如下图所示:

图 16.4.2 交通信号灯顶层模块原理图
由上图可知,FPGA部分包括四个模块,顶层模块(top_traffic)、交通灯控制模块(traffic_light)、数码管显示模块(seg_led)、led灯控制模块(led)。在顶层模块中完成对其它三个模块的例化,并实现各模块之间的数据传递。
顶层模块(top_traffic):顶层模块完成了对其它三个子模块的例化、实现了子模块间的信号连接、并将led灯和数码管的驱动信号输出给外接设备(交通信号灯外设)。
交通灯控制模块(traffic_light):交通灯控制模块是本次实验的核心代码,这个模块控制信号灯的状态转换,将实时的状态信号state[1:0]输出给led灯控制模块(led),同时将东西和南北方向的实时时间数据ew_time[5:0]和sn_time[5:0]输出给数码管显示模块(seg_led)。
数码管显示模块(seg_led):接收交通灯控制模块传递过来的东西和南北方向的实时时间数据ew_time[5:0]和sn_time[5:0],并以此驱动对应的数码管,将数据显示出来。
led灯控制模块(led):根据接收到的实时状态信号state[1:0],驱动东西和南北方向的led发光。
顶层模块的代码如下:

1   module top_traffic( 
2       input                  sys_clk   ,    //系统时钟信号
3       input                  sys_rst_n ,    //系统复位信号
4       
5       output       [3:0]     sel       ,    //数码管位选信号
6       output       [7:0]     seg_led   ,    //数码管段选信号
7       output       [5:0]     led            //LED使能信号
8   );
9   
10  //wire define 
11  wire   [5:0]  ew_time;                    //东西方向状态剩余时间数据
12  wire   [5:0]  sn_time;                    //南北方向状态剩余时间数据 
13  wire   [1:0]  state  ;                    //交通灯的状态,用于控制LED灯的点亮
14  
15  //*****************************************************
16  //** main code 
17  //*****************************************************
18  //交通灯控制模块 
19  traffic_light u0_traffic_light(
20      .sys_clk                (sys_clk),   
21      .sys_rst_n              (sys_rst_n),      
22      .ew_time                (ew_time),
23      .sn_time                (sn_time),
24      .state                  (state)
25  );
26  
27  //数码管显示模块 
28  seg_led    u1_seg_led(
29      .sys_clk                (sys_clk)  ,
30      .sys_rst_n              (sys_rst_n),
31      .ew_time                (ew_time),
32      .sn_time                (sn_time), 
33      .en                     (1'b1),   
34      .sel                    (sel), 
35      .seg_led                (seg_led)
36  );
37  
38  //led灯控制模块
39  led   u2_led(
40      .sys_clk                (sys_clk  ),
41      .sys_rst_n              (sys_rst_n),
42      .state                  (state    ),
43      .led                    (led      )
44  ); 
45  
46  endmodule        

在代码第22行和第23行,将交通灯控制模块输出的ew_time和sn_time实时时间数据信号连接到数码管显示模块;在代码的第24行,将交通灯控制模块输出的state状态连接到led灯控制模块。
交通灯控制模块的代码如下:

1   module  traffic_light(
2       //input
3       input               sys_clk   ,        //系统时钟
4       input               sys_rst_n ,        //系统复位
5   
6       output  reg  [1:0]  state     ,        //交通灯的状态,用于控制LED灯的点亮
7       output  reg  [5:0]  ew_time   ,        //交通灯东西向数码管要显示的时间数据
8       output  reg  [5:0]  sn_time            //交通灯南北向数码管要显示的时间数据
9       );
10  
11  //parameter define
12  parameter  TIME_LED_Y    = 3;              //黄灯发光的时间
13  parameter  TIME_LED_R    = 30;             //红灯发光的时间
14  parameter  TIME_LED_G    = 27;             //绿灯发光的时间
15  parameter  WIDTH         = 25_000_000;     //产生频率为1hz的时钟
16  
17  //reg define
18  reg    [5:0]     time_cnt;                 //产生数码管显示时间的计数器 
19  reg    [24:0]    clk_cnt;                  //用于产生clk_1hz的计数器
20  reg              clk_1hz;                  //1hz时钟
21  
22  //*****************************************************
23  //** main code 
24  //*****************************************************
25  //计数周期为0.5s的计数器 
26  always @ (posedge sys_clk or negedge sys_rst_n)begin
27      if(!sys_rst_n)
28          clk_cnt <= 25'b0;
29      else if (clk_cnt < WIDTH - 1'b1)
30          clk_cnt <= clk_cnt + 1'b1;
31      else 
32          clk_cnt <= 25'b0;
33  end 
34  
35  //产生频率为1hz的时钟
36  always @(posedge sys_clk or negedge sys_rst_n)begin
37      if(!sys_rst_n)
38          clk_1hz <= 1'b0;
39      else  if(clk_cnt == WIDTH - 1'b1)
40          clk_1hz <= ~ clk_1hz;
41      else  
42          clk_1hz <=  clk_1hz;
43  end
44  
45  //切换交通信号灯工作的4个状态,并产生数码管要显示的时间数据
46  always @(posedge clk_1hz or negedge sys_rst_n)begin
47      if(!sys_rst_n)begin        
48          state <= 2'd0;
49          time_cnt <= TIME_LED_G ;            //状态1持续的时间
50      end 
51      else begin
52          case (state)
53              2'b0:  begin                    //状态1
54                  ew_time <= time_cnt + TIME_LED_Y - 1'b1;//东西方向数码管要显示的时间数据
55                  sn_time <= time_cnt - 1'b1;             //南北方向数码管要显示的时间数据
56                  if (time_cnt > 1)begin      //time_cnt等于1的时候切换状态
57                      time_cnt <= time_cnt - 1'b1;
58                      state <= state;
59                  end 
60                  else begin
61                      time_cnt <= TIME_LED_Y; //状态2持续的时间
62                      state <= 2'b01;         //切换到状态2
63                  end 
64              end 
65              2'b01:  begin                   //状态2
66                  ew_time <= time_cnt  - 1'b1;
67                  sn_time <= time_cnt  - 1'b1; 
68                  if (time_cnt > 1)begin
69                      time_cnt <= time_cnt - 1'b1;
70                      state <= state;
71                  end 
72                  else begin
73                      time_cnt <= TIME_LED_G; //状态3持续的时间
74                      state <= 2'b10;         //切换到状态3
75                  end 
76              end 
77              2'b10:  begin                   //状态3
78                  ew_time <= time_cnt  - 1'b1;
79                  sn_time <= time_cnt + TIME_LED_Y - 1'b1; 
80                  if (time_cnt > 1)begin
81                      time_cnt <= time_cnt - 1'b1;
82                      state <= state;
83                  end 
84                  else begin
85                      time_cnt <= TIME_LED_Y; //状态4持续的时间
86                      state <= 2'b11;         //切换到转态4
87                  end 
88              end 
89              2'b11:  begin                   //状态4
90                  ew_time <= time_cnt  - 1'b1;
91                  sn_time <= time_cnt  - 1'b1; 
92                  if (time_cnt > 1)begin
93                      time_cnt <= time_cnt - 1'b1;
94                      state <= state;
95                  end 
96                  else begin
97                      time_cnt <= TIME_LED_G;
98                      state <= 2'b0;          //切换到状态1
99                  end 
100             end         
101             default: begin
102                 state <= 2'b0;
103                 time_cnt <= TIME_LED_G;  
104             end 
105         endcase
106     end 
107 end                 
108 
109 endmodule 

因为交通灯控制模块是以秒为单位计时的,所以在代码第25行到第43行,我们通过分频产生频率为1HZ、周期为1s的时钟。在前面讲解表 16.3.1的时候,我们提到信号灯有4个工作状态,并且每个状态的持续时间已经作了详细的说明。所以在代码第52行至第100行,通过time_cnt计数器来切换这4个工作状态,并且在每个状态里,将东西和南北方向信号灯的状态剩余时间,分别赋值给ew_time、和sn_time寄存器。在讲解表 16.3.1的时候,提到状态1里东西向红灯和南北向绿灯一起发光27s,但是实际上,红灯一共要发光30s,绿灯一共要发光27s。那么,在状态1开始的时候,东西方向红灯对应的数码管显示的初始值应该为29,南北方向绿灯对应的数码管显示的初始值应该为26,然后每秒各个方向数码管显示的值都递减1;状态1结束时,东西方向红灯对应的数码管显示3,南北方向绿灯对应的数码管显示0。在状态2开始的时候,东西方向红灯对应的数码管显示2,南北方向黄灯对应的数码管也显示2。状态结束的时候,两个方向的数码管都显示0。其他状态数码管的显示原理也是类似的。
需要注意的是,在代码第56行、68行、80行、92行,当time_cnt等于1的时候跳转状态,否则下一秒有的数码管会显示错误,比如显示63。这是因为当time_cnt等于0的时候跳转,由于time_cnt是6位寄存器,time_cnt-1则为6’b111111,也就是63。
如图 16.4.3为交通灯控制模块的仿真图。state信号标示当前信号灯所处的工作状态。通过仿真图我们可以看出,在state等于00的时候(表 16.1.1处提到的状态1),ew_time和sn_time的信号变化情况符合设计要求。

图 16.4.3 交通灯控制模块仿真图
数码管显示模块的代码如下:

1   module seg_led(
2       input                  sys_clk     ,     //系统时钟 
3       input                  sys_rst_n   ,     //系统复位 
4       input        [5:0]     ew_time     ,     //东西方向数码管要显示的数值
5       input        [5:0]     sn_time     ,     //南北方向数码管要显示数值
6       input                  en          ,     //数码管使能信号 
7       output  reg  [3:0]     sel         ,     //数码管位选信号
8       output  reg  [7:0]     seg_led           //数码管段选信号,包含小数点
9   );
10  
11  //parameter define
12  parameter  WIDTH = 50_000;                   //计数1ms的计数深度
13  
14  //reg define 
15  reg    [15:0]             cnt_1ms;           //计数1ms的计数器
16  reg    [1:0]              cnt_state;         //用于切换要点亮数码管
17  reg    [3:0]              num;               //数码管要显示的数据
18  
19  //wire define
20  wire   [3:0]              data_ew_0;         //东西方向数码管的十位
21  wire   [3:0]              data_ew_1;         //东西方向数码管的各位
22  wire   [3:0]              data_sn_0;         //南北方向数码管的十位
23  wire   [3:0]              data_sn_1;         //南北方向数码管的各位
24  
25  //*****************************************************
26  //** main code 
27  //*****************************************************
28  assign  data_ew_0   = ew_time / 10;          //取出东西向时间数据的十位
29  assign  data_ew_1   = ew_time % 10;          //取出东西向时间数据的个位
30  assign  data_sn_0   = sn_time / 10;          //取出南北向时间数据的十位
31  assign  data_sn_1   = sn_time % 10;          //取出南北向时间数据的个位
32  
33  //计数1ms
34  always @ (posedge sys_clk or negedge sys_rst_n) begin
35      if (!sys_rst_n)
36          cnt_1ms <= 15'b0;
37      else if (cnt_1ms < WIDTH - 1'b1)
38          cnt_1ms <= cnt_1ms + 1'b1;
39      else
40          cnt_1ms <= 15'b0;
41  end 
42  
43  //计数器,用来切换数码管点亮的4个状态
44  always @ (posedge sys_clk or negedge sys_rst_n) begin
45      if (!sys_rst_n)
46          cnt_state <= 2'd0;
47      else  if (cnt_1ms == WIDTH - 1'b1)
48          cnt_state <= cnt_state + 1'b1;
49      else
50          cnt_state <= cnt_state;
51  end 
52  
53  //先显示东西方向数码管的十位,然后是个位。再显示南北方向数码管的十位,然后个位
54  always @ (posedge sys_clk or negedge sys_rst_n) begin
55      if(!sys_rst_n) begin
56          sel  <= 4'b1111;
57          num  <= 4'b0;
58      end 
59      else if(en) begin       
60          case (cnt_state) 
61              3'd0 : begin     
62                  sel <= 4'b1110;              //驱动东西方向数码管的十位 
63                  num <= data_ew_0;
64              end       
65              3'd1 : begin     
66                  sel <= 4'b1101;              //驱动东西方向数码管的个位
67                  num <= data_ew_1;
68              end 
69              3'd2 : begin 
70                  sel <= 4'b1011;              //驱动南北方向数码管的十位
71                  num  <= data_sn_0;
72              end
73              3'd3 : begin 
74                  sel <= 4'b0111;              //驱动南北方向数码管的个位
75                  num  <= data_sn_1 ;    
76              end
77              default : begin     
78                  sel <= 4'b1111;                     
79                  num <= 4'b0;
80              end 
81          endcase
82      end
83      else  begin
84          sel <= 4'b1111;
85          num <= 4'b0;    
86      end
87  end 
88  
89  //数码管要显示的数值所对应的段选信号 
90  always @ (posedge sys_clk or negedge sys_rst_n) begin
91      if (!sys_rst_n) 
92          seg_led <= 8'b0; 
93      else begin
94          case (num)              
95              4'd0 : seg_led <= 8'b1100_0000;                                                        
96              4'd1 : seg_led <= 8'b1111_1001;                            
97              4'd2 : seg_led <= 8'b1010_0100;                            
98              4'd3 : seg_led <= 8'b1011_0000;                            
99              4'd4 : seg_led <= 8'b1001_1001;                            
100             4'd5 : seg_led <= 8'b1001_0010;                            
101             4'd6 : seg_led <= 8'b1000_0010;                            
102             4'd7 : seg_led <= 8'b1111_1000;      
103             4'd8 : seg_led <= 8'b1000_0000;      
104             4'd9 : seg_led <= 8'b1001_0000;    
105             default :  seg_led <= 8'b1100_0000;
106         endcase
107     end 
108 end
109 
110 endmodule

由动态数码管实验可知,数码管显示刷新速度在毫秒级是比较合适。所以在代码第33行至第41行,产生一个计时周期为1ms的计数器。为了产生数码管位选控制信号,在代码第16行设置了一个2位计数器cnt_state。每经过1ms,计数器累加1次。总共能计数0,1,2,3这四个10进制数。在代码第60行至80行,依据cnt_state的值,通过给sel寄存器赋值来驱动数码管不同的位选,并将选中位要显示的数值赋值给num寄存器。在代码第94行至代码第104行,根据num的值可以产生相应的段选控制信号seg_led。
如图 16.4.4为数码管显示模块的仿真图。在ew_time和sn_time的值分别为24和21时,在这1s期间,数码管位选信号sel循环切换数码管对应的位选信号,然后依据num寄存器产生对应的段选信号seg_led。

图 16.4.4 数码管显示模块仿真图
led灯控制模块的代码如下:

1   module led (
2       input              sys_clk   ,       //系统时钟
3       input              sys_rst_n ,       //系统复位
4       input       [1:0]  state     ,       //交通灯的状态
5       output reg  [5:0]  led               //红黄绿LED灯发光使能 
6   );
7   
8   //parameter define
9   parameter  TWINKLE_CNT = 20_000_000;     //让黄灯闪烁的计数次数
10  
11  //reg define
12  reg    [24:0]     cnt;                   //让黄灯产生闪烁效果的计数器
13  
14  //计数时间为0.2s的计数器,用于让黄灯闪烁 
15  always @(posedge sys_clk or negedge sys_rst_n)begin                                  
16      if(!sys_rst_n)                                                                   
17          cnt <= 25'b0;                                                                
18      else if (cnt < TWINKLE_CNT - 1'b1)                                                                                                        
19          cnt <= cnt + 1'b1;                                                                                                                                                                                                                                                             
20      else                                                                             
21          cnt <= 25'b0;                                                                
22  end                                                                                  
23                                                                                      
24  //在交通灯的四个状态里,使相应的led灯发光 
25  always @(posedge sys_clk or negedge sys_rst_n)begin                                  
26      if(!sys_rst_n)                                                                   
27          led <= 6'b100100;                                                            
28      else begin                                                                       
29          case(state)                                                                   
30              2'b00:led<=6'b100010;        //led寄存器从高到低分别驱动:东西向 
31                                          //红绿黄灯,南北向红绿黄灯 
32              2'b01: begin                                                             
33                  led[5:1]<=5'b10000;                                                  
34                  if(cnt == TWINKLE_CNT - 1'b1)  //计数满0.2秒让黄灯的亮灭状况切换一次
35                                              //产生闪烁的效果 
36                      led[0] <= ~led[0];                                               
37                  else                                                                 
38                      led[0] <= led[0元器件数据手册、IC替代型号,打造电子元器件IC百科大全!
          

相关文章