【正点原子FPGA连载】 第三十一章 交通灯实验-摘自【正点原子】领航者ZYNQ之FPGA开发指南_V2.0
时间:2022-09-23 11:00:00
1)实验平台:正点原子领导者ZYNQ开发板
2)平台采购地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码 手册 视频下载地址:http://www.openedv.com/thread-301505-1-1.html
4)正点原子FPGA感兴趣的同学可以加群讨论:99424016
5)关注正点原子微信官方账号,获取最新信息更新
第三十一章 交通灯实验
交通信号灯是生活中非常常见的公共设施。它在道路交叉口、斑马线等位置起着疏导交通的作用。本章将使用交通信号灯模块再现其功能。
本章包括以下几个部分:
1.1 简介
1.2 实验任务
1.3 硬件设计
1.4 程序设计
1.5 下载验证
1.1 简介
交通信号灯通常由红、绿、黄三种颜色的灯组成。红灯亮时,禁止通行;绿灯亮时,可通行;黄灯亮时,提示交通时间结束,立即转换为红灯。
本实验要再现的交通信号灯是十字路口的信号灯,由两对信号灯组成。信号灯的实物如图所示 7.5.13.1所示:
图 7.5.13.1 交通信号灯
图 7.5.13.二是十字路口交通信号灯简化示意图:
图 7.5.13.2 十字路口信号灯示意图
一个方向上的信号灯点亮顺序是:红灯熄灭后,绿灯熄灭后,黄灯熄灭,黄灯熄灭后,红灯熄灭,所以一直在循环。此外,同一方向上的一对信号灯颜色相同,显示时间相同。
制作模拟交通信号灯的功能,制作了表 31.1.1信号灯状态转换表:
表 31.1.1 交通信号灯状态转换表
表 31.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。
如图 7.5.13.三是交通信号灯状态转换图:
图 7.5.13.3 交通信号灯状态转换图
另外,以东西方向为例:红灯在一个周期内发光30s,绿灯发光27s,黄灯发光3s。红灯发光期间,数字管上显示的数字应从29递减至0;同样,在绿灯发光期间,数字管上显示的数字应从26递减至0;黄灯发光时,数字管上显示的数字应从2递减至0。
1.2 实验任务
本节的实验任务是通过试点ZYNQ开发板和外部交通信号灯扩展模块,再现交通信号灯的功能。
1.3 硬件设计
我们的领航员ZYNQ左边的开发板J3扩展端口可用于外部交通信号灯扩展模块,如图所示 7.5.13.1所示。
图 7.5.13.1 交通信号灯原理图
从上图可以看出,交通信号灯扩展模块交通信号灯扩展模块LED我们用六个灯LED控制信号驱动12个LED这是因为东西方向或南北方向LED灯的亮灭状态总是一样的,所以我们在东西方向或南北方向有相同的颜色LED这种设计的优点是减少了交通信号灯扩展模块LED引脚控制信号。
上图中,四个共阳数码管对应四个路口,每个路口用两个数码管显示当前状态的剩余时间。众所周知,在十字路口,东西方向或南北方向的数字管显示时间总是一样的。以东西方向为例,由于两个方向显示时间一致,所以两个方向的数字管,十个可以用同一位置选择信号控制,另一个位置选择信号控制,使两个位置选择信号控制东西方向共四位数字管,南北数字管。这种设计的优点是减少了交通信号灯扩展模块位选信号的引脚。
需要注意的是,数字管理PNP型三极管驱动,当三极管基极为低电平时,选择数字管相应的位置,因此交通信号灯扩展模块的位置信号为低电平有效。
如图所示 7.5.13.2所示,我们本章实验设定交通信号灯的方向为上北下南,左西右东。
图 7.5.13.2 交通信号灯实物图
在本实验中,交通信号灯脚分布如下表所示。
表 31.3.1 交通信号灯实验管脚分配
信号名 方向 管脚 端口说明
sys_clk input U18 系统时钟50MHz
sys_rst_n input N16 系统复位,低电平有效
sel[0] output P15 选择数字管位信号sel[0]
sel[1] output P16 数码管位选信号sel[1]
sel[2] output W18 选择数字管位信号sel[2]
sel[3] output W19 选择数字管位信号sel[3]
seg_led[0] output T16 数字管a段选择信号seg_led[0]
seg_led[1] output U17 选择数码管b段的信号seg_led[1]
seg_led[2] output V17 选择数字管c段的信号seg_led[2]
seg_led[3] output V18 数字管d段选择信号seg_led[3]
seg_led[4] output T17 选择数字管e段的信号seg_led[4]
seg_led[5] output R18 选择数码管f段的信号seg_led[5]
seg_led[6] output Y18 选择数字管g段的信号seg_led[6]
seg_led[7] output Y19 选择数码管h段的信号seg_led[7]
led[0] output Y17 南北向黄色LED使能
led[1] output T14 南北向绿色LED使能
led[2] output V16 南北向红色LED使能
led[3] output Y16 东西向黄色LED使能
led[4] output T15 东西向绿色LED使能
led[5] output W16 东西向红色LED使能
对应的XDC约束文件如下:
set_property -dict {
PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {
PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
set_property -dict {
PACKAGE_PIN T16 IOSTANDARD LVCMOS33} [get_ports {
seg_led[0]}]
set_property -dict {
PACKAGE_PIN U17 IOSTANDARD LVCMOS33} [get_ports {
seg_led[1]}]
set_property -dict {
PACKAGE_PIN V17 IOSTANDARD LVCMOS33} [get_ports {
seg_led[2]}]
set_property -dict {
PACKAGE_PIN V18 IOSTANDARD LVCMOS33} [get_ports {
seg_led[3]}]
set_property -dict {
PACKAGE_PIN T17 IOSTANDARD LVCMOS33} [get_ports {
seg_led[4]}]
set_property -dict {
PACKAGE_PIN R18 IOSTANDARD LVCMOS33} [get_ports {
seg_led[5]}]
set_property -dict {
PACKAGE_PIN Y18 IOSTANDARD LVCMOS33} [get_ports {
seg_led[6]}]
set_property -dict {
PACKAGE_PIN Y19 IOSTANDARD LVCMOS33} [get_ports {
seg_led[7]}]
set_property -dict {
PACKAGE_PIN Y17 IOSTANDARD LVCMOS33} [get_ports {
led[0]}]
set_property -dict {
PACKAGE_PIN T14 IOSTANDARD LVCMOS33} [get_ports {
led[1]}]
set_property -dict {
PACKAGE_PIN V16 IOSTANDARD LVCMOS33} [get_ports {
led[2]}]
set_property -dict {
PACKAGE_PIN Y16 IOSTANDARD LVCMOS33} [get_ports {
led[3]}]
set_property -dict {
PACKAGE_PIN T15 IOSTANDARD LVCMOS33} [get_ports {
led[4]}]
set_property -dict {
PACKAGE_PIN W16 IOSTANDARD LVCMOS33} [get_ports {
led[5]}]
set_property -dict {
PACKAGE_PIN P15 IOSTANDARD LVCMOS33} [get_ports {
sel[0]}]
set_property -dict {
PACKAGE_PIN P16 IOSTANDARD LVCMOS33} [get_ports {
sel[1]}]
set_property -dict {
PACKAGE_PIN W18 IOSTANDARD LVCMOS33} [get_ports {
sel[2]}]
set_property -dict {
PACKAGE_PIN W19 IOSTANDARD LVCMOS33} [get_ports {
sel[3]}]
1.4 程序设计
根据实验任务,我们可以大致规划出系统的控制流程:交通灯控制模块将需要显示的时间数据连接到数码管显示模块,同时将状态信号连接到led灯控制模块,然后数码管显示模块和led灯控制模块驱动交通信号灯外设工作。系统框图如图 7.5.13.1所示,
图 7.5.13.1 交通灯实验系统框图
各模块端口及信号连接如下图所示:
图 7.5.13.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的时钟。在前面讲解表 31.3.1的时候,我们提到信号灯有4个工作状态,并且每个状态的持续时间已经作了详细的说明。所以在代码第52行至第100行,通过time_cnt计数器来切换这4个工作状态,并且在每个状态里,将东西和南北方向信号灯的状态剩余时间,分别赋值给ew_time、和sn_time寄存器。在讲解表 31.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。
如图 7.5.13.3为交通灯控制模块的仿真图。state信号标示当前信号灯所处的工作状态。通过仿真图我们可以看出,在state等于00的时候(表 31.1.1处提到的状态1),ew_time和sn_time的信号变化情况符合设计要求。
图 7.5.13.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;
元器件数据手册、IC替代型号,打造电子元器件IC百科大全!