实验4 交通信号灯控制系统设计与Verilog实现
一、实验目的
- 掌握多位计数器的设计方法;
- 掌握分频电路的设计方法;
- 掌握控制器的设计方法;
- 掌握8位数码管的动态扫描显示的设计方法;
- 掌握FPGA的层次化设计方法。
二、实验主要仪器设备
- FPGA实验板。
- 软件为Vivado2018.3,ModelSim 10.6c
三、设计任务与要求
1. 基本任务及要求
十字路口由一条主道和支道构成。主道和支道均有红、绿、黄3种信号灯。
通常保持主道绿灯、支道红灯。只有当支道有车时,且主道计时的时间到,才转为主道红灯,支道绿灯。
绿灯转红灯过程中,先由绿灯转为黄灯,3秒钟后再由黄灯转为红灯;同时对方红灯转绿灯。
当两个方向同时有车时,红、绿灯应每隔30秒变换一次,应扣除绿灯转红灯过程中有3秒黄灯过渡,绿灯实际只亮27秒;如图1所示。
若仅一个方向有车时,处理方法是:该方向原来为红灯时,另一个方向立即由绿灯变为黄灯,3秒钟后再由黄灯变为红灯,同时本方向由红灯变为绿灯。该方向原为绿灯时,继续保持绿灯。当另一方向有车来时,作两个方向均有车处理。
图1 主道与支道信号灯与时间关系
2. 扩展任务及要求
- 当两个方向同时有车时,主道红、绿灯每隔30秒变换一次,支道每隔20秒变换一次。
图2主道与支道信号灯时间不相等状态图
- 图2采用置最大时间、减计时;例如置27后每隔1秒减1。
- 当两个方向增加左转信号灯。
- 主道是直行绿灯为27秒,转为黄灯3秒,再转为左转绿灯12秒,再转为黄灯3秒,所消耗的时间一共为45秒。
- 支道是直行绿灯为17秒,转为黄灯3秒,再转为左转绿灯7秒,再转为黄灯3秒,所消耗的时间一共为30秒。
图3 增加左转信号灯状态图
- 四、实验内容与步骤
1. 基本任务
(1) 系统设计
根据交通信号灯控制系统的功能,车辆传感器及交通信号灯如图1所示,具体方案如下:
- 在4个方向各装1个车辆传感器,有车用1表示,无车用0表示。主道(A道)分别为AS1和AS2,只要AS1或AS2中有一个为1,就说明A道有车,令AS=AS1+AS2。支道(B道)分别为BS1和BS2,只要BS1或BS2中有一个为1,就说明B道有车,令BS=BS1+BS2。
- 设黄灯3秒时间到时T3=1,时间未到时T3=0;设绿灯27秒时间到时,T27=1,时间未到时T27=0。
- 设主道由绿灯转为黄灯的条件为AK,当AK=0时绿灯继续,当AK=1时立即由绿灯转为黄灯。设支道由绿灯转为黄灯的条件为BK,当BK=0时绿灯继续,当BK=1时立即由绿灯转为黄灯。
- 设主道两侧的绿灯、黄灯、红灯分别为AG1、AY1、AR1,AG2、AY2、AR2;AG1、AG2,AY1、AY2,AR1、AR2分别并联,即它们同时点亮或熄灭,分别用AG、AY、AR表示。
- 设支道的两侧绿灯、黄灯、红灯分别为BG1、BY1、BR1,BG2、BY2、BR2。BG1、BG2,BY1、BY2,BR1、BR2分别并联,即它们同时点亮或熄灭,分别用BG、BY、BR表示。用0表示灭、1表示亮,则两个方向的交通信号灯有4种输出状态,如表1所示。
表1 交通灯输出状态
输出状态 |
AG |
AY |
AR |
BG |
BY |
BR |
S0(00) |
1 |
0 |
0 |
0 |
0 |
1 |
S1(01) |
0 |
1 |
0 |
0 |
0 |
1 |
S2(10) |
0 |
0 |
1 |
1 |
0 |
0 |
S3(11) |
0 |
0 |
1 |
0 |
1 |
0 |
图2 十字路口交通信号灯控制系统结构图
由上述分析,得到总体结构如图2所示,其中C3、C27为计数器控制信号;当C3=1时,3秒定时器,也就是3进制计数器开始计数;当C27=1时,27秒定时器,也就是27进制计数器开始计数。CP为1Hz时钟输入信号;RESET为复位信号,低有效。
(2) 各功能模块设计、仿真和调试
(a) 工作原理
本实验的目的是通过设计一个交通灯控制系统,来控制十字路口的交通信号灯,并在LCD上显示交通灯的状态。主要任务包括正常交通灯控制和根据传感器信号的变化调整交通灯状态。
1. 交通灯控制逻辑
基本任务及要求
信号灯状态定义:
主道(A)和支道(B)各有红、黄、绿三种灯光。
主道和支道信号灯的初始状态:主道绿灯亮,支道红灯亮。
当支道有车时(BS = 1),且主道计时器计时结束(T30),主道转为红灯,支道转为绿灯。
信号灯从绿灯转为红灯时,先变黄灯持续3秒,再转为红灯。黄灯期间,对方方向的红灯开始计时。
特殊处理:
当主道和支道同时有车时(AS = 1 且 BS = 1),红、绿灯每隔30秒变换一次,实际绿灯亮27秒,黄灯3秒。
当仅一个方向有车时,该方向原为绿灯时,保持绿灯;原为红灯时,另一方向由绿转黄3秒,再转红灯,同时当前方向转为绿灯。
2. 状态机设计
状态机使用5个状态来表示交通灯的不同状态:
S0:主道绿灯,支道红灯
S1:主道黄灯,支道红灯
S2:主道红灯,支道绿灯
S3:主道红灯,支道黄灯
S4:主道红灯,支道红灯(过渡状态)
3. 计时器设计
不同状态下使用不同的计时器:
timer_30:用于主道和支道均有车时,主道绿灯亮27秒,主道黄灯亮3秒,计时30秒。
timer_27:主道绿灯实际亮27秒。
timer_20:主道和支道同时有车时,支道计时20秒。
timer_17:支道绿灯亮17秒。
timer_3:黄灯亮3秒。
4. 主时钟分频
为了简化设计,使用一个分频器将50MHz时钟信号分频为1Hz,用于控制计时器的计时。
5. 状态转换逻辑
初始状态:进入S0,主道绿灯亮,支道红灯亮。
状态转换:
S0到S1:如果BS = 1 且主道计时结束(T27或T30),转到S1。
S1到S2:3秒后(T3),转到S2。
S2到S3:支道计时结束(T17),转到S3。
S3到S0:3秒后(T3),转到S0。
S4:如果T20时间到,转到S0。
6. 输出逻辑
根据当前状态,设置交通灯的输出:
S0:AG = 1, AY = 0, AR = 0;BG = 0, BY = 0, BR = 1(主道绿灯,支道红灯)
S1:AG = 0, AY = 1, AR = 0;BG = 0, BY = 0, BR = 1(主道黄灯,支道红灯)
S2:AG = 0, AY = 0, AR = 1;BG = 1, BY = 0, BR = 0(主道红灯,支道绿灯)
S3:AG = 0, AY = 0, AR = 1;BG = 0, BY = 1, BR = 0(主道红灯,支道黄灯)
S4:AG = 0, AY = 0, AR = 1;BG = 0, BY = 0, BR = 1(主道红灯,支道红灯)
(b) Verilog源程序
module traffic_lcd (
// 50MHz 时钟输入
input wire clk_50M,
// 复位按钮
input wire reset_btn,
// A方向信号
input wire AS,
// B方向信号
input wire BS,
// A方向绿灯
output wire AG,
// A方向黄灯
output wire AY,
// A方向红灯
output wire AR,
// B方向绿灯
output wire BG,
// B方向黄灯
output wire BY,
// B方向红灯
output wire BR,
// 红色视频信号(3位)
output wire [2:0] video_red,
// 绿色视频信号(3位)
output wire [2:0] video_green,
// 蓝色视频信号(2位)
output wire [1:0] video_blue,
// 水平同步信号
output wire video_hsync,
// 垂直同步信号
output wire video_vsync,
// 视频时钟信号
output wire video_clk,
// 数据使能信号
output wire video_de
);
// 主颜色信号(2位)
// 用于控制主方向(A或B)的灯的颜色
wire [1:0] maincolor;
// 辅助颜色信号(2位)
// 用于控制辅助方向(非主方向)的灯的颜色
wire [1:0] sidecolor;
// 主方向灯的持续时间(5位)
wire [4:0] maintime;
// 辅助方向灯的持续时间(5位)
wire [4:0] sidetime;
// 交通灯控制器实例
// 根据输入的时钟、复位信号、AS、BS控制主颜色、辅助颜色、主方向和辅助方向的持续时间
// 并输出到AG, AY, AR, BG, BY, BR
traffic_light_controller traffic_controller_inst (
.clk(clk_50M),
.reset_n(~reset_btn), // 注意:这里假设traffic_light_controller使用低电平复位
.AS(AS),
.BS(BS),
.maincolor(maincolor),
.sidecolor(sidecolor),
.maintime(maintime),
.sidetime(sidetime),
.AG(AG),
.AY(AY),
.AR(AR),
.BG(BG),
.BY(BY),
.BR(BR)
);
// LCD控制器实例
// 根据输入的时钟、复位信号、主颜色、辅助颜色、主方向和辅助方向的持续时间
// 控制LCD的显示,包括颜色(video_red, video_green, video_blue)和同步信号(video_hsync, video_vsync)
lcd_top lcd_controller_inst (
.clk_50M(clk_50M),
.reset_btn(reset_btn),
.maintime(maintime),
.sidetime(sidetime),
.maincolor(maincolor),
.sidecolor(sidecolor),
.video_red(video_red),
.video_green(video_green),
.video_blue(video_blue),
.video_hsync(video_hsync),
.video_vsync(video_vsync),
.video_clk(video_clk),
.video_de(video_de)
);
Endmodule
module traffic_light_controller (
input wire clk, // 1Hz 时钟信号
input wire reset_n, // 复位信号,低有效
input wire AS, // 主道车辆传感器输入
input wire BS, // 支道车辆传感器输入
output reg[1:0]maincolor, // 主道交通灯颜色:0红,1黄,2绿
output reg[1:0]sidecolor, // 支道交通灯颜色:0红,1黄,2绿
output reg [4:0] maintime, // 主道计时器
output reg [4:0] sidetime, // 支道计时器
output reg AG, AY, AR, // 主道交通灯输出:绿,黄,红
output reg BG, BY, BR // 支道交通灯输出:绿,黄,红
);
parameter S0 = 3'b000; // 主道绿灯,支道红灯
parameter S1 = 3'b001; // 主道黄灯,支道红灯
parameter S2 = 3'b010; // 主道红灯,支道绿灯
parameter S3 = 3'b011; // 主道红灯,支道黄灯
parameter S4 = 3'b100; // 主道红灯,支道红灯(过渡状态)
// 定义变量
reg [2:0] state, next_state; // 当前状态和下一个状态
reg [4:0] timer_30; // 30秒计时器
reg [4:0] timer_27; // 27秒计时器
reg [4:0] timer_20; // 20秒计时器
reg [4:0] timer_17; // 17秒计时器
reg [1:0] timer_3; // 3秒计时器
reg [24:0] counter; // 分频计数器
reg clk_1Hz; // 1Hz 时钟信号
wire T30 = (timer_30 == 5'd0); // 30秒时间到
wire T27 = (timer_27 == 5'd0); // 27秒时间到
wire T20 = (timer_20 == 5'd0); // 20秒时间到
wire T17 = (timer_17 == 5'd0); // 17秒时间到
wire T3 = (timer_3 == 2'd0); // 3秒时间到
// 初始状态
initial begin
maincolor <= 0;
sidecolor <= 0;
state <= S0;
counter <= 0;
clk_1Hz <= 1;
timer_30 <= 5'd30;
timer_27 <= 5'd27;
timer_20 <= 5'd20;
timer_17 <= 5'd17;
timer_3 <= 2'd3;
end
// 分频器:50MHz -> 1Hz
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
counter <= 25'd0;
clk_1Hz <= 1;
end else if (counter == 25'd2) begin
counter <= 25'd0;
clk_1Hz <= ~clk_1Hz; // 产生1Hz的时钟信号
end else begin
counter <= counter + 1;
end
end
// 计时器逻辑
always @(posedge clk_1Hz or negedge reset_n) begin
if (!reset_n) begin
timer_30 <= 5'd30;
timer_27 <= 5'd27;
timer_20 <= 5'd20;
timer_17 <= 5'd17;
timer_3 <= 2'd3;
end else begin
// 30秒计时器
if (state == S2 || state == S0) begin
if (timer_30 > 0)
timer_30 <= timer_30 - 1;
else
timer_30 <= 5'd30;
end else
timer_30 <= 5'd30;
// 27秒计时器
if (state == S0) begin
if (timer_27 > 0)
timer_27 <= timer_27 - 1;
else
timer_27 <= 5'd27;
end else
timer_27 <= 5'd27;
// 20秒计时器
if (state == S4 || (state == S0 && AS && BS)) begin
if (timer_20 > 0)
timer_20 <= timer_20 - 1;
else
timer_20 <= 5'd20;
end else
timer_20 <= 5'd20;
// 17秒计时器
if (state == S2) begin
if (timer_17 > 0)
timer_17 <= timer_17 - 1;
else
timer_17 <= 5'd17;
end else
timer_17 <= 5'd17;
// 3秒计时器
if (state == S1 || state == S3) begin
if (timer_3 > 0)
timer_3 <= timer_3 - 1;
else
timer_3 <= 2'd3;
end else
timer_3 <= 2'd3;
end
end
// 状态转移
always @(posedge clk or negedge reset_n) begin
if (!reset_n)
state <= S0;
else
state <= next_state;
end
// 下一个状态逻辑
always @(*) begin
case (state)
S0: begin
if (AS && BS) begin
if (T27)
next_state = S1;
else
next_state = S0;
end else if (BS && (T27 || !AS))
next_state = S1;
else if (AS && BS && T27)
next_state = S1;
else if (T27)
next_state = S1;
else
next_state = S0;
end
S1: begin
if (T3)
next_state = S2;
else
next_state = S1;
end
S2: begin
if (T17)
next_state = S3;
else if (T30)
next_state = S4;
else
next_state = S2;
end
S3: begin
if (T3)
next_state = S0;
else
next_state = S3;
end
S4: begin
if (T20)
next_state = S0;
else
next_state = S4;
end
default: next_state = S0;
endcase
end
// 输出逻辑
always @(*) begin
case (state)
S0: begin
AG = 1; AY = 0; AR = 0;
BG = 0; BY = 0; BR = 1;
maincolor <= 2'd2;
sidecolor <= 2'd0;
maintime <= (AS && BS) ? timer_27 : timer_30;
sidetime <= timer_30;
end
S1: begin
AG = 0; AY = 1; AR = 0;
BG = 0; BY = 0; BR = 1;
maincolor <= 2'd1;
sidecolor <= 2'd0;
maintime <= timer_3;
sidetime <= timer_30;
end
S2: begin
AG = 0; AY = 0; AR = 1;
BG = 1; BY = 0; BR = 0;
maincolor <= 2'd0;
sidecolor <= 2'd2;
maintime <= timer_30;
sidetime <= timer_17;
end
S3: begin
AG = 0; AY = 0; AR = 1;
BG = 0; BY = 1; BR = 0;
maincolor <= 2'd0;
sidecolor <= 2'd1;
maintime <= timer_30;
sidetime <= timer_3;
end
S4: begin
AG = 0; AY = 0; AR = 1;
BG = 0; BY = 0; BR = 1;
maincolor <= 2'd0;
sidecolor <= 2'd0;
maintime <= timer_20;
sidetime <= timer_30;
end
default: begin
AG = 1; AY = 0; AR = 0;
BG = 0; BY = 0; BR = 1;
end
endcase
end
endmodule
module lcd_top (
input wire clk_50M, //50MHz 閺冨爼鎸撴潏鎾冲弳
input wire reset_btn, //BTN6閹靛濮╂径宥勭秴閹稿鎸冲?閸忕绱濈敮锔界Х閹舵牜鏁哥捄顖ょ礉閹稿绗呴弮鏈佃礋1
input wire [4:0] maintime,
input wire [4:0] sidetime,
input wire [1:0]maincolor,
input wire [1:0]sidecolor,
//閸ユ儳鍎氭潏鎾冲毉娣団?冲娇
output wire [2:0] video_red, //缁俱垼澹婇崓蹇曠閿??3娴??
output wire [2:0] video_green, //缂佽儻澹婇崓蹇曠閿??3娴??
output wire [1:0] video_blue, //閽冩繆澹婇崓蹇曠閿??2娴??
output wire video_hsync, //鐞涘苯鎮撳銉礄濮樻潙閽╅崥灞绢劄閿涘淇婇崣?
output wire video_vsync, //閸﹀搫鎮撳銉礄閸ㄥ倻娲块崥灞绢劄閿涘淇婇崣?
output wire video_clk, //閸嶅繒绀岄弮鍫曟寭鏉堟挸鍤?
output wire video_de //鐞涘本鏆熼幑顔芥箒閺佸牅淇婇崣鍑ょ礉閻€劋绨崠鍝勫瀻濞戝牓娈i崠?
);
// generate pixel clock
logic clk_pix;
logic clk_pix_locked;
clock_divider clock_div_inst (
.clk(clk_50M),
.rst(reset_btn),
.clk_div(clk_pix)
);
wire [15:0] line_2_ascii;
wire [7:0]main_bcd;
wire [7:0]side_bcd;
bin2bcd m(
.bin(maintime),
.bcd(main_bcd)
);
bin2bcd s(
.bin(sidetime),
.bcd(side_bcd)
);
wire [79:0] line_0; // 娑撹桨绨¢弬閫涚┒鐠у?纭风礉娑??娑撶寴scii閸楃姷鏁?8bit
wire [79:0] line_1; // 56-bit line buffer, 7 bit per ascii character
assign line_0 = (maincolor==0)?{8'h20,main_bcd[7:4]+8'h30,main_bcd[3:0]+8'h30,8'h20,8'h20,8'h20,8'h20,8'h20}
:(maincolor==1)?{8'h20,8'h20,8'h20,main_bcd[7:4]+8'h30,main_bcd[3:0]+8'h30,8'h20,8'h20,8'h20}
:{8'h20,8'h20,8'h20,8'h20,8'h20,main_bcd[7:4]+8'h30,main_bcd[3:0]+8'h30,8'h20};
assign line_1 = (sidecolor==0)?{8'h20,side_bcd[7:4]+8'h30,side_bcd[3:0]+8'h30,8'h20,8'h20,8'h20,8'h20,8'h20}
:(sidecolor==1)?{8'h20,8'h20,8'h20,side_bcd[7:4]+8'h30,side_bcd[3:0]+8'h30,8'h20,8'h20,8'h20}
:{8'h20,8'h20,8'h20,8'h20,8'h20,side_bcd[7:4]+8'h30,side_bcd[3:0]+8'h30,8'h20};
reg [25:0] counter; // 璁℃暟鍣紝鐢ㄤ簬璁℃暟50,000,000涓椂閽熷懆鏈?
reg [5:0] number;
wire cnt_eq_1s;
assign cnt_eq_1s= counter==(4999_9999);
always @(posedge clk_50M or posedge reset_btn)
begin
if (reset_btn)
begin
counter <= 26'd0; // 閲嶇疆璁℃暟鍣?
number <= 6'd0; // 閲嶇疆number
end
else if (counter == 26'd49_999_999) // 妫?鏌ヨ鏁板櫒鏄惁鍒拌揪50,000,000
begin
counter <= 26'd0; // 閲嶇疆璁℃暟鍣?
number <= number + 6'd1; // number鍔?1
end
else
begin
counter <= counter + 26'd1; // 璁℃暟鍣ㄥ姞1
end
end
dvi_module dvi_inst (
.clk(clk_pix),
.clk_locked(reset_btn),
.maincolor(maincolor),
.sidecolor(sidecolor),
.video_red(video_red),
.video_green(video_green),
.video_blue(video_blue),
.video_hsync(video_hsync),
.video_vsync(video_vsync),
.video_clk(video_clk),
.video_de(video_de),
.line_0_ascii(line_0),
.line_1_ascii(line_1),
.line_2_number(number)
);
reg [7:0] ascii;
always @(posedge clk_50M)
begin
if (reset_btn)
begin
ascii <= 8'd0;
end
else if (cnt_eq_1s)
begin
if (ascii == 8'd9)
begin
ascii <= 8'd0;
end
else
begin
ascii <= ascii + 1'b1;
end
end
end
endmodule
module ascii_rom_async (
input wire [10:0] addr,
output reg [7:0] data
);
always @(*) begin
case (addr)
// code x30 (0)
11'h300: data = 8'b00000000; //