FPGA交通灯实战:用Verilog在Quartus里实现可调时序的十字路口控制(附仿真代码)

十字路口交通信号控制是数字电路设计的经典案例,也是FPGA初学者掌握状态机设计的绝佳实践项目。本文将带你从零开始,用Verilog HDL在Quartus Prime开发环境中实现一个具备可调时序参数的交通灯控制器,并通过ModelSim完成功能验证。

1. 项目需求分析与设计思路

现代交通灯控制系统需要满足三个核心需求:安全性、灵活性和可扩展性。我们的设计将围绕这三点展开:

  • 基础功能:东西方向绿灯30秒+黄灯2秒,南北方向红灯32秒;随后切换为南北方向绿灯30秒+黄灯2秒,东西方向红灯32秒
  • 扩展功能:各方向红绿灯时间可通过参数灵活配置
  • 特殊状态:紧急情况下可手动切换为全方向黄灯闪烁模式

1.1 状态机设计

采用Moore型有限状态机(FSM)作为控制核心,定义四个状态:

parameter S_EW_GREEN = 2'b00;  // 东西绿灯,南北红灯
parameter S_EW_YELLOW = 2'b01; // 东西黄灯,南北红灯 
parameter S_NS_GREEN = 2'b10;  // 南北绿灯,东西红灯
parameter S_NS_YELLOW = 2'b11; // 南北黄灯,东西红灯

状态转移图如下:

        +---------------+
        |               |
        v               |
S_EW_GREEN -> S_EW_YELLOW
    ^               |
    |               v
S_NS_YELLOW <- S_NS_GREEN

1.2 模块划分

系统由五个核心模块构成:

  1. 时钟分频模块:将板载高频时钟分频为1Hz基准信号
  2. 状态控制模块:实现状态机及定时逻辑
  3. 灯控输出模块:根据状态生成灯控信号
  4. 倒计时显示模块:驱动数码管显示剩余时间
  5. 参数配置模块:存储可调时间参数

2. Verilog实现详解

2.1 顶层模块设计

module traffic_light(
    input clk,          // 板载时钟(如50MHz)
    input reset_n,      // 复位信号(低有效)
    input emergency,    // 紧急模式开关
    output reg [2:0] ew_light, // 东西方向灯控[红,黄,绿]
    output reg [2:0] ns_light, // 南北方向灯控[红,黄,绿]
    output [6:0] seg,   // 七段数码管段选
    output [3:0] an     // 数码管位选
);

// 参数化设计,便于修改时序
parameter EW_GREEN_TIME = 30;  // 东西绿灯时间
parameter EW_YELLOW_TIME = 2;  // 东西黄灯时间
parameter NS_GREEN_TIME = 30;  // 南北绿灯时间  
parameter NS_YELLOW_TIME = 2;  // 南北黄灯时间

wire clk_1Hz;  // 1Hz时钟信号
wire [5:0] count_val; // 当前倒计时值

// 实例化各子模块
clock_divider clk_div(.clk(clk), .reset_n(reset_n), .clk_out(clk_1Hz));

state_controller ctrl(
    .clk(clk_1Hz),
    .reset_n(reset_n),
    .emergency(emergency),
    .ew_green_time(EW_GREEN_TIME),
    .ew_yellow_time(EW_YELLOW_TIME),
    .ns_green_time(NS_GREEN_TIME),
    .ns_yellow_time(NS_YELLOW_TIME),
    .current_state(state),
    .count(count_val)
);

light_driver lights(
    .state(state),
    .emergency(emergency),
    .ew_light(ew_light),
    .ns_light(ns_light)
);

segment_display disp(
    .clk(clk),
    .reset_n(reset_n),
    .value(count_val),
    .seg(seg),
    .an(an)
);

endmodule

2.2 状态控制模块

这是系统的核心逻辑,采用三段式状态机写法:

module state_controller(
    input clk,
    input reset_n,
    input emergency,
    input [5:0] ew_green_time,
    input [5:0] ew_yellow_time,
    input [5:0] ns_green_time, 
    input [5:0] ns_yellow_time,
    output reg [1:0] current_state,
    output reg [5:0] count
);

// 状态定义
parameter S_EW_GREEN = 2'b00;
parameter S_EW_YELLOW = 2'b01;
parameter S_NS_GREEN = 2'b10;
parameter S_NS_YELLOW = 2'b11;

// 状态寄存器
reg [1:0] state, next_state;

// 第一段:状态寄存器更新
always @(posedge clk or negedge reset_n) begin
    if(!reset_n)
        state <= S_EW_GREEN;
    else if(!emergency)  // 非紧急模式下才更新状态
        state <= next_state;
end

// 第二段:状态转移逻辑
always @(*) begin
    case(state)
        S_EW_GREEN: 
            next_state = (count == 1) ? S_EW_YELLOW : S_EW_GREEN;
        S_EW_YELLOW:
            next_state = (count == 1) ? S_NS_GREEN : S_EW_YELLOW;
        S_NS_GREEN:
            next_state = (count == 1) ? S_NS_YELLOW : S_NS_GREEN;
        S_NS_YELLOW:
            next_state = (count == 1) ? S_EW_GREEN : S_NS_YELLOW;
        default:
            next_state = S_EW_GREEN;
    endcase
end

// 第三段:计数器逻辑
always @(posedge clk or negedge reset_n) begin
    if(!reset_n) begin
        count <= ew_green_time;
        current_state <= S_EW_GREEN;
    end else if(emergency) begin
        count <= 0;  // 紧急模式下停止计数
        current_state <= state;
    end else begin
        current_state <= state;
        case(state)
            S_EW_GREEN: 
                count <= (count == 1) ? ew_yellow_time : count - 1;
            S_EW_YELLOW:
                count <= (count == 1) ? ns_green_time : count - 1;
            S_NS_GREEN:
                count <= (count == 1) ? ns_yellow_time : count - 1;
            S_NS_YELLOW:
                count <= (count == 1) ? ew_green_time : count - 1;
        endcase
    end
end

endmodule

2.3 灯控输出模块

module light_driver(
    input [1:0] state,
    input emergency,
    output reg [2:0] ew_light, // [red, yellow, green]
    output reg [2:0] ns_light
);

// 灯控编码
parameter LIGHT_OFF = 3'b000;
parameter RED_ON    = 3'b100;
parameter YELLOW_ON = 3'b010;
parameter GREEN_ON  = 3'b001;

always @(*) begin
    if(emergency) begin
        // 紧急模式:双黄灯闪烁
        ew_light = YELLOW_ON;
        ns_light = YELLOW_ON;
    end else begin
        case(state)
            2'b00: begin  // S_EW_GREEN
                ew_light = GREEN_ON;
                ns_light = RED_ON;
            end
            2'b01: begin  // S_EW_YELLOW
                ew_light = YELLOW_ON;
                ns_light = RED_ON;
            end
            2'b10: begin  // S_NS_GREEN
                ew_light = RED_ON;
                ns_light = GREEN_ON;
            end
            2'b11: begin  // S_NS_YELLOW
                ew_light = RED_ON;
                ns_light = YELLOW_ON;
            end
            default: begin
                ew_light = LIGHT_OFF;
                ns_light = LIGHT_OFF;
            end
        endcase
    end
end
endmodule

3. 关键实现技巧

3.1 参数化设计

通过Verilog参数实现时间可配置:

module traffic_light #(
    parameter EW_GREEN = 30,
    parameter EW_YELLOW = 2,
    parameter NS_GREEN = 30,
    parameter NS_YELLOW = 2
) (
    // 端口定义
);

实例化时可覆盖默认值:

traffic_light #(
    .EW_GREEN(45),
    .NS_GREEN(60) 
) my_traffic_light(
    // 端口连接
);

3.2 数码管动态扫描

实现四位倒计时显示:

module segment_display(
    input clk,
    input reset_n,
    input [5:0] value,
    output reg [6:0] seg,
    output reg [3:0] an
);

reg [1:0] scan_cnt;
reg [3:0] digit;
reg [15:0] refresh_cnt;

// 刷新率约1kHz (50MHz/2^16)
always @(posedge clk or negedge reset_n) begin
    if(!reset_n)
        refresh_cnt <= 0;
    else
        refresh_cnt <= refresh_cnt + 1;
end

always @(posedge clk) begin
    if(refresh_cnt == 0)
        scan_cnt <= scan_cnt + 1;
end

// 数码管位选
always @(*) begin
    case(scan_cnt)
        2'b00: begin
            an = 4'b1110;
            digit = value % 10; // 个位
        end
        2'b01: begin
            an = 4'b1101; 
            digit = value / 10; // 十位
        end
        default: begin
            an = 4'b1111;
            digit = 0;
        end
    endcase
end

// 七段译码
always @(*) begin
    case(digit)
        0: seg = 7'b1000000;
        1: seg = 7'b1111001;
        // ... 其他数字编码
        default: seg = 7'b1111111;
    endcase
end

endmodule

4. ModelSim仿真验证

4.1 测试平台搭建

`timescale 1ns/1ps

module tb_traffic_light;

reg clk;
reg reset_n;
reg emergency;
wire [2:0] ew_light;
wire [2:0] ns_light;

traffic_light dut (
    .clk(clk),
    .reset_n(reset_n),
    .emergency(emergency),
    .ew_light(ew_light),
    .ns_light(ns_light)
);

// 时钟生成 (50MHz)
initial begin
    clk = 0;
    forever #10 clk = ~clk;
end

// 测试流程
initial begin
    reset_n = 0;
    emergency = 0;
    #100 reset_n = 1;
    
    // 观察正常模式
    #2000000; // 观察2ms仿真时间
    
    // 测试紧急模式
    emergency = 1;
    #500000;
    emergency = 0;
    
    #1000000;
    $stop;
end

endmodule

4.2 仿真结果分析

正常模式下应观察到:

  • 东西绿灯亮30个时钟周期
  • 东西黄灯亮2个时钟周期
  • 南北绿灯亮30个时钟周期
  • 南北黄灯亮2个时钟周期

紧急模式下:

  • 双方向黄灯持续闪烁
  • 计数器停止计数

5. Quartus工程实现

5.1 引脚分配示例(以DE10-Standard开发板为例)

FPGA引脚外设功能
PIN_AF1450MHz时钟输入
PIN_AA15复位按钮
PIN_AB12紧急模式开关
PIN_AA24东西方向红灯
PIN_AB23东西方向黄灯
PIN_AA23东西方向绿灯
......

5.2 时序约束

# 时钟约束
create_clock -name clk -period 20 [get_ports clk]

# 输出延迟约束
set_output_delay -clock clk 5 [get_ports {ew_light[*] ns_light[*]}]

6. 进阶优化方向

  1. 车流量自适应:通过传感器输入动态调整各方向绿灯时长
  2. 夜间模式:低流量时段切换为黄灯闪烁
  3. 倒计时显示优化:增加秒数十位显示
  4. 多路口协同:多个FPGA通过UART通信实现联动控制

以下是一个车流量检测的状态机改进示例:

// 在状态控制模块中添加
input [3:0] ew_traffic; // 东西方向车流量检测
input [3:0] ns_traffic; // 南北方向车流量检测

// 动态调整绿灯时间
wire [5:0] dynamic_ew_green = EW_GREEN_TIME + ew_traffic*2;
wire [5:0] dynamic_ns_green = NS_GREEN_TIME + ns_traffic*2;

通过这个完整的FPGA交通灯项目实践,你不仅能掌握Verilog状态机设计方法,还能学习到参数化设计、仿真验证等工程化开发技巧。建议在基础版本实现后,尝试挑战进阶功能以深化理解。

更多推荐