1. 项目概述
这个FPGA交通信号灯控制系统项目,本质上是用数字逻辑电路模拟现实中的红绿灯切换逻辑。作为一名电子工程师,我经常用这类项目来验证FPGA设计的可靠性。相比单片机方案,FPGA的并行处理特性特别适合这种多信号协调控制的场景。
核心设计思路是构建一个有限状态机(FSM),通过Verilog硬件描述语言实现四个状态的循环切换。每个状态对应特定的灯光组合和持续时间:
- 状态0:南北绿灯20秒 + 东西红灯20秒
- 状态1:南北黄灯5秒 + 东西红灯5秒
- 状态2:南北红灯20秒 + 东西绿灯20秒
- 状态3:南北红灯5秒 + 东西黄灯5秒
整个系统运行在50MHz时钟基准下,通过分频计数器实现秒级定时。选择Altera Quartus II作为开发环境,既因为其良好的仿真支持,也考虑到与常见FPGA开发板的兼容性。
2. 硬件架构设计
2.1 FPGA选型考量
在这个项目中,我选择了Cyclone IV EP4CE6作为核心芯片,主要基于三点考虑:
- 逻辑资源足够:需要约500LE(逻辑单元)实现状态机和计数器
- 时钟管理:内置PLL可稳定生成50MHz时钟
- I/O需求:至少需要6个输出引脚控制两组信号灯
实际部署时建议选择工业级芯片,如Cyclone V 5CEBA4,其工作温度范围更宽(-40℃~100℃)
2.2 外围电路设计
信号灯驱动电路需要特别注意:
verilog复制module driver(
input [2:0] light_ctrl, // 3位控制信号
output reg R,G,Y // 红绿黄灯
);
always @(*) begin
{R,G,Y} = 3'b000; // 默认全灭
case(light_ctrl)
3'b100: G = 1'b1; // 绿灯亮
3'b010: Y = 1'b1; // 黄灯亮
3'b001: R = 1'b1; // 红灯亮
endcase
end
endmodule
实际硬件连接时:
- 每组信号灯需串联限流电阻(通常220Ω)
- 建议使用光耦隔离(如TLP521)保护FPGA端口
- 大功率LED需外接MOSFET驱动(如IRLZ44N)
3. Verilog核心实现
3.1 状态机设计
状态编码采用二进制顺序编码,便于扩展:
verilog复制parameter S_NG_SR = 2'b00; // 状态0:南北绿,东西红
parameter S_NY_SR = 2'b01; // 状态1:南北黄,东西红
parameter S_NR_SG = 2'b10; // 状态2:南北红,东西绿
parameter S_NR_SY = 2'b11; // 状态3:南北红,东西黄
reg [1:0] current_state;
reg [24:0] counter; // 25位计数器,最大25秒
状态转移逻辑是核心:
verilog复制always @(posedge clk) begin
if(rst) begin
current_state <= S_NG_SR;
counter <= 0;
end else begin
counter <= counter + 1;
case(current_state)
S_NG_SR: if(counter == 20-1) begin // 20秒倒计时
current_state <= S_NY_SR;
counter <= 0;
end
S_NY_SR: if(counter == 5-1) begin // 5秒倒计时
current_state <= S_NR_SG;
counter <= 0;
end
// 其他状态类似...
endcase
end
end
3.2 定时器设计
采用50MHz时钟时,秒级定时的精确实现:
verilog复制reg [25:0] prescaler;
always @(posedge clk) begin
if(prescaler == 50_000_000 - 1) begin
prescaler <= 0;
counter <= counter + 1; // 秒计数器+1
end else begin
prescaler <= prescaler + 1;
end
end
实际项目中建议使用PLL生成1Hz时钟,减少逻辑资源占用
4. Quartus II仿真验证
4.1 测试平台搭建
创建Testbench时需注意:
verilog复制`timescale 1ns/1ps
module tb_traffic_light;
reg clk = 0;
reg rst = 1;
wire [2:0] NS_light, EW_light;
// 时钟生成(50MHz)
always #10 clk = ~clk;
initial begin
#100 rst = 0; // 释放复位
#5000 $finish; // 仿真5us
end
// 实例化被测模块
traffic_light uut(
.clk(clk),
.rst(rst),
.NS_light(NS_light),
.EW_light(EW_light)
);
endmodule
4.2 波形分析要点
在ModelSim中观察关键信号:
- current_state:应呈现00→01→10→11的循环变化
- counter:每个状态内从0递增到设定值-1
- 输出信号:与状态严格对应,无毛刺
典型问题排查:
- 状态不切换:检查counter比较值是否正确
- 输出抖动:添加输出寄存器缓冲
- 计时不准:检查时钟分频逻辑
5. 实际部署与优化
5.1 硬件调试技巧
现场调试时建议:
- 先单独测试每组LED驱动电路
- 用示波器检查FPGA输出信号
- 逐步增加状态复杂度(从2状态开始)
5.2 功能扩展方案
这个基础设计可以轻松扩展:
- 紧急模式:添加外部中断切换全红状态
verilog复制always @(posedge emergency) begin
current_state <= S_EMERGENCY;
counter <= 0;
end
- 时段控制:通过ROM存储不同时段的定时参数
verilog复制reg [7:0] timing_rom [0:3];
initial begin
timing_rom[0] = 20; // 南北绿灯
timing_rom[1] = 5; // 南北黄灯
//...
end
- 车流量检测:添加传感器输入动态调整时长
6. 常见问题解决方案
6.1 状态机卡死
症状:波形显示状态停止切换
排查步骤:
- 检查复位信号是否有效释放
- 验证counter是否正常递增
- 确认状态转移条件判断正确
6.2 输出信号毛刺
解决方法:
- 对输出信号添加寄存器缓冲
verilog复制always @(posedge clk) begin
NS_light_reg <= NS_light_next;
EW_light_reg <= EW_light_next;
end
- 优化状态编码(如使用One-Hot编码)
- 增加时钟约束(set_false_path)
6.3 计时误差过大
校准方法:
- 使用SignalTap II测量实际时钟频率
- 调整分频系数补偿误差
- 换用更精确的晶振(如TCXO)
7. 工程管理建议
7.1 版本控制策略
推荐使用Git管理项目:
code复制/project
├── /rtl // Verilog源代码
├── /sim // 测试平台
├── /constraints // 管脚约束文件
└── /doc // 设计文档
7.2 参数化设计技巧
将关键参数定义为宏:
verilog复制`define NS_GREEN_TIME 20
`define NS_YELLOW_TIME 5
//...
always @(posedge clk) begin
case(current_state)
S_NG_SR: if(counter == `NS_GREEN_TIME-1) begin
//...
end
//...
endcase
end
这种设计在需要调整时长时,只需修改宏定义即可全局生效。