正点原子 发表于 2019-7-27 11:34:12

【正点原子FPGA连载】第四十九章 交通灯实验--摘自【正点原子】开拓者 FPGA 开发指南

本帖最后由 正点原子 于 2020-10-23 17:15 编辑

1)实验平台:正点原子开拓者FPGA开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=579749209820
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-281143-1-1.html
4)本实例源码下载:
5)对正点原子FPGA感兴趣的同学可以加群讨论:712557122点击加入:
6)关注正点原子公众号,获取最新资料更新


第四十九章 交通灯实验


交通信号灯是一种生活中非常常见的公共设施, 它在马路上的路口、 斑马线等位置起着疏导交通的作用。 本章我们将使用交通信号灯模块来再现它的功能。
本章包括以下几个部分:
49.1 交通信号灯简介
49.2 实验任务
49.3 硬件设计
49.4 程序设计
49.5 下载验证
49.1 交通信号灯简介
交通信号灯往往由红、 绿、 黄三种颜色的灯组成。 红灯亮的时候, 禁止通行; 绿灯亮的时候, 可以通行; 黄灯亮的时候, 提示通行时间已经结束, 马上要转换为红灯。本次实验要再现的交通信号灯为十字路口处的信号灯, 它由两对信号灯组成。 信号灯实物
如图 49.1.1所示:


图 49.1.1 交通信号灯


图 49.1.2为十字路**通信号灯简化示意图:


图 49.1.2 十字路口信号灯示意图

单独一个方向上的信号灯点亮顺序是: 红灯熄灭后绿灯亮, 绿灯熄灭后黄灯亮, 黄灯熄灭后红灯亮, 这样一直循环下去。 另外, 同一方向上的一对信号灯亮的颜色一致, 且显示的时间是一样的。
为了模拟交通信号灯的功能, 制作了表 49.1.1信号灯状态转换表:


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

表 49.1.1中, 设定一个周期内, 红灯发光30s, 绿灯发光27s, 黄灯发光3s。 以东西方向信号灯状态为例, 红灯发光的时间等于黄灯与绿灯发光的时间和。 所以一个完整的状态转换周期是红灯发光的时间的两倍, 也就是60s。
在东西方向红色信号灯发光的30s内, 南北方向由绿灯切换到了黄灯; 在南北方向红色信号灯发光的30s内, 东西方向也由绿灯切换到了黄灯。 因此我们将东西和南北方向信号灯同时保持在固定状态的时间段, 划为一个状态。 由此产生了循环往复的4个状态:
1、 东西方向红灯亮27s, 南北方向绿灯亮27s, 然后切换到状态2;
2、 东西方向红灯亮3s, 南北方向黄灯亮3s, 然后切换到状态3;
3、 东西方向绿灯亮27s, 南北方向红灯亮27s, 然后切换到状态4;
4、 东西方向黄灯亮3s, 南北方向红灯亮3s, 然后切换到状态1。


图 49.1.3 交通信号灯状态转换图

另外, 以东西方向为例: 一个周期内, 红灯发光30s, 绿灯发光27s, 黄灯发光3s。 那么在红灯发光期间, 数码管上显示的数字要从29递减到0; 同理, 绿灯发光期间, 数码管上显示的数字要从26递减到0; 黄灯发光的时候, 数码管上显示的数字要从2递减到0。
49.2 实验任务
本节实验任务是通过FPGA开发板和外接的交通信号灯扩展模块, 再现交通信号灯的功能。
49.3 硬件设计
我们的开拓者FPGA开发板上左边的P6扩展口可以用来外接交通信号灯扩展模块, 交通信号灯扩展模块的原理图如图 49.3.1所示。


图 49.3.1 交通信号灯原理图

由上图可知, 交通信号灯扩展模块四个方向共12个LED灯, 而我们使用6个LED控制信号来驱动12个LED灯, 这是因为东西方向或者南北方向LED灯的亮灭状态总是一致的, 所以我们将东西方向或者南北方向颜色相同的LED灯并联在一起, 这样设计的好处是减少了交通信号灯扩展
模块LED控制信号的引脚。上图中四个共阳型数码管分别对应四个路口, 每个路口用两位数码管显示当前状态的剩余时间。 我们知道, 在十字路口中, 东西方向或者南北方向数码管显示的时间总是一样的。 以东西方向为例, 正因为这两个方向显示的时间一致, 所以这两个方向的数码管, 它们的十位可以用同一个位选信号来控制, 个位用另一个位选信号来控制, 这样的话, 就可以实现两个位选信号控制东西方向共4位数码管的亮灭, 南北方向的数码管同理。 这样设计的好处是减少了交通信号灯扩展模块位选信号的引脚。需要注意的是, 数码管由PNP型三极管驱动, 当三极管的基极为低电平时, 数码管相应的位被选通, 所以交通信号灯扩展模块的位选信号是低电平有效的。交通信号灯实物图如图 49.3.2所示, 我们本章实验设定交通信号灯的方向为上北下南,左西右东。


图 49.3.2 交通信号灯实物图

本实验中, 交通信号灯管脚分配如下表所示。
表 49.3.1 交通信号灯实验管脚分配






49.4 程序设计
根据实验任务, 我们可以大致规划出系统的控制流程: 交通灯控制模块将需要显示的时间数据连接到数码管显示模块, 同时将状态信号连接到led灯控制模块, 然后数码管显示模块和led灯控制模块驱动交通信号灯外设工作。 系统框图如图 49.4.1所示,


图 49.4.1 交通灯实验系统框图

各模块端口及信号连接如下图所示:


图 49.4.2 交通信号灯顶层模块原理图

由上图可知, FPGA部分包括四个模块, 顶层模块(top_traffic) 、 交通灯控制模块(traffic_light) 、 数码管显示模块(seg_led) 、 led灯控制模块(led) 。 在顶层模块中完成对其它三个模块的例化, 并实现各模块之间的数据传递。
顶层模块(top_traffic) : 顶层模块完成了对其它三个子模块的例化、 实现了子模块间的信号连接、 并将led灯和数码管的驱动信号输出给外接设备(交通信号灯外设) 。交通灯控制模块(traffic_light) : 交通灯控制模块是本次实验的核心代码, 这个模块
控制信号灯的状态转换, 将实时的状态信号state输出给led灯控制模块(led) ,同时将东西和南北方向的实时时间数据ew_time和sn_time输出给数码管显示模块(seg_led) 。数码管显示模块(seg_led) : 接收交通灯控制模块传递过来的东西和南北方向的实时时
间数据ew_time和sn_time, 并以此驱动对应的数码管, 将数据显示出来。led灯控制模块(led) : 根据接收到的实时状态信号state, 驱动东西和南北方向的led发光。
顶层模块的代码如下:
1 module top_traffic(
2 input sys_clk , //系统时钟信号
3 input sys_rst_n , //系统复位信号
4 5
output sel , //数码管位选信号
6 output seg_led , //数码管段选信号
7 output led //LED使能信号
8 );
9 1
0 //wire define
11 wire ew_time; //东西方向状态剩余时间数据
12 wire sn_time; //南北方向状态剩余时间数据
13 wire 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 state , //交通灯的状态, 用于控制LED灯的点亮
7 output reg ew_time , //交通灯东西向数码管要显示的时间数据
8 output reg 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 time_cnt; //产生数码管显示时间的计数器
19 reg 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的时钟。 在前面讲解表 49.3.1的时候, 我们提到信号灯有4个工作状态,并且每个状态的持续时间已经作了详细的说明。 所以在代码第52行至第100行, 通过
time_cnt计数器来切换这4个工作状态, 并且在每个状态里, 将东西和南北方向信号灯的状态剩余时间, 分别赋值给ew_time、 和sn_time寄存器。 在讲解表 49.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。如图 49.4.3为交通灯控制模块的仿真图。 state信号标示当前信号灯所处的工作状态。 通
过仿真图我们可以看出,在state等于00的时候(表 49.1.1处提到的状态1),ew_time和sn_time的信号变化情况符合设计要求。


图 49.4.3 交通灯控制模块仿真图

数码管显示模块的代码如下:
1 module seg_led(
2 input sys_clk , //系统时钟
3 input sys_rst_n , //系统复位
4 input ew_time , //东西方向数码管要显示的数值
5 input sn_time , //南北方向数码管要显示数值
6 input en , //数码管使能信号
7 output reg sel , //数码管位选信号
8 output reg seg_led //数码管段选信号,包含小数点
9 );
10
11 //parameter define
12 parameter WIDTH = 50_000; //计数1ms的计数深度
13
14 //reg define
15 reg cnt_1ms; //计数1ms的计数器
16 reg cnt_state; //用于切换要点亮数码管
17 reg num; //数码管要显示的数据
18
19 //wire define
20 wire data_ew_0; //东西方向数码管的十位
21 wire data_ew_1; //东西方向数码管的各位
22 wire data_sn_0; //南北方向数码管的十位
23 wire 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。
如图 49.4.4为数码管显示模块的仿真图。 在ew_time和sn_time的值分别为24和21时, 在这1s期间, 数码管位选信号sel循环切换数码管对应的位选信号, 然后依据num寄存器产生对应的段选信号seg_led。


图 49.4.4 数码管显示模块仿真图

led灯控制模块的代码如下:
1 module led (
2 input sys_clk , //系统时钟
3 input sys_rst_n , //系统复位
4 input state , //交通灯的状态
5 output reg led //红黄绿LED灯发光使能
6 );
7 8
//parameter define
9 parameter TWINKLE_CNT = 20_000_000; //让黄灯闪烁的计数次数
10
11 //reg define
12 reg 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'b10000;
34 if(cnt == TWINKLE_CNT - 1'b1) //计数满0.2秒让黄灯的亮灭状况切换一次
35 //产生闪烁的效果
36 led <= ~led;
37 else
38 led <= led;
39 end
40 2'b10:led<=6'b010100;
41 2'b11: begin
42 led<=2'b00;
43 led<=3'b100;
44 if(cnt == TWINKLE_CNT - 1'b1)
45 led <= ~led;
46 else
47 led <= led;
48 end
49 default:led<=6'b100100;
50 endcase
51 end
52 end
53
54 endmodule
在代码的第25行至第48行, 根据所处的状态值state, 驱动相应的led灯发光。 输出的高3位led从高到低分别驱动东西方向的红、 黄、 绿三个LED灯, 输出的低3位led分别驱动南北方向红、 黄、 绿灯三个LED灯。在黄灯亮的状态期间, 为了产生闪烁的效果, 需要在一段时间内使其发光, 在另一段时间内使其熄灭, 这样循环直到黄灯发光的状态结束。 在代码第14行至代码第22行, 产生一个计数周期为0.2s的计数器, 每0.2s让黄灯的亮灭状态切换一次, 实现闪烁的效果。
图 49.4.5为led控制模块的仿真图。 可以看到, 在state的值为01和11时(东西方向或者南北方向有黄灯发光的状态) , led寄存器的值在一直在变化。


图 49.4.5 led控制模块仿真图

仿真文件代码如下所示, :
1 `timescale 1ns/1ns
2 module tb_top_traffic ;
3 reg sys_clk ; //系统时钟
4 reg sys_rst_n ; //系统复位
5 6
wire sel ; //数码管位选信号
7 wire seg_led ; //数码管段选信号
8 wire led ; //led灯控制信号
9 1
0 initial begin
11 sys_clk <= 1'b0;
12 sys_rst_n <= 1'b0;
13 # 20 sys_rst_n <= 1'b1;
14 end
15
16 always # 10 sys_clk = ~sys_clk; //产生频率为50Mhz的时钟
17
18 //例化交通灯顶层模块
19 top_traffic u_top_traffic(
20 .sys_clk (sys_clk ),
21 .sys_rst_n (sys_rst_n ),
22 .sel (sel ),
23 .seg_led (seg_led ),
24 .led (led )
25 );
26
27 endmodule
49.5 下载验证
首先我们打开交通灯实验工程, 在工程所在的路径下打开top_traffic/par文件夹, 在里面找到“top_traffic.qsf” 并双击打开。 注意工程所在的路径名只能由字母、 数字以及下划线组成, 不能出现中文、 空格以及特殊字符等。 工程打开后如图 49.5.1所示:


图 49.5.1 交通信号灯工程

将交通信号灯模块按照排母上丝印标识插在开发板上左边的P6扩展口上, 然后将下载器一端连接电脑, 另一端与开发板上对应端口连接, 最后连接电源线并打开电源开关(如图 49.5.3所示) 。
接下来我们下载程序, 验证交通灯实验的功能。 工程打开后通过点击工具栏中的“Programmer” 图 标 打 开 下 载 界 面 , 通 过 “Add File” 按 钮 选 择top_traffic/par/output_files目录下的“top_traffic.sof” 文件。 开发板电源打开后, 在程序 下载 界面 点击 “Hardware Setup” , 在 弹出 的对 话框 中选 择当前 的硬 件连 接为“USB-Blaster” 。然后点击“Start” 将工程编译完成后得到的sof文件下载到开发板中, 如图 49.5.2所示:


图 49.5.2 程序下载完成界面

程序下载完成后, 可以观察到交通信号灯开始工作, 说明交通灯实验验证成功, 实验结果如下图所示:


图 49.5.3 实验效果



页: [1]
查看完整版本: 【正点原子FPGA连载】第四十九章 交通灯实验--摘自【正点原子】开拓者 FPGA 开发指南