这一篇在干嘛?

交通灯是「有限状态机」最经典的入门案例:灯的每一种亮灭组合是一个状态,倒计时归零就跳到下一个状态。本实验用 HX4S20 开发板外接扩展板,实现最简单的十字路口交通灯——东西、南北两个方向直行轮放,红黄绿按 25 秒一个半周期严格轮转。

实验目的

使用 HX4S20 开发板上接流水灯和交通灯扩展板,设计一个简单的交通灯:东西路口和南北路口分别由红、黄、绿控制,了解交通灯的亮灭规律。

实验原理:时序表与硬件接法

交通灯的显示方式有很多种,如十字路口、丁字路口等;同一个路口也有不同的显示要求。十字路口如果车子只需要东西和南北方向直行就很简单,如果允许左右转弯就比较复杂。本实验仅针对最简单的南北和东西直行的情况。

设计要求:从一个方向红灯切换到绿灯需要 25S;绿灯转黄灯需 20S,黄灯占 5S,加上黄灯到绿灯的时间共计 25S。东西与南北两个方向互补轮放,时序如下表:

方向信号时间安排
东西绿灯20S
东西黄灯5S
东西红灯25S
南北红灯25S
南北绿灯20S
南北黄灯5S

即 50 秒一个完整周期:前 25 秒东西绿(20S)转黄(5S)、南北红;后 25 秒东西红、南北绿(20S)转黄(5S)。控制要求时序图如下:

交通灯控制要求

根据开发板的原理图,可得到以下资料:

根据硬件原理图所示,发光二极管的阳极都接通 3.3V 正电压(高电平),所以要使发光二极管导通,需要在阴极接低电平,LED 才亮起来——和流水灯实验一样是低电平点亮。

实验程序

顶层由两个模块组成:traffic 状态机模块与 clkdiv 分频模块。traffic 模块中 r1/y1/g1 与 r2/y2/g2 各是 2 位输出(对应一位方向的两组灯):

dule traffic(clk, r1, y1, g1, r2, y2, g2);
input clk;
output reg [1:0] r1, y1, g1;
output reg [1:0] r2, y2, g2;
parameter yellow_time = 5;
parameter green_time = 20;
reg [1:0] state;
reg [4:0] count;
always @(posedge clk)

输出译码:四种状态对应四种亮灯组合

state 为 0 时东西绿、南北红;state 为 1 时东西黄、南北红;state 为 2 时东西红、南北绿;state 为 3(default)时东西红、南北黄。输出为 2’b00 的那位灯点亮:

case (state)
0 :
begin
    r1 <= 2'b11;
    y1 <= 2'b11;
    g1 <= 2'b00;
    r2 <= 2'b00;
y2 <= 2'b11;
g2 <= 2'b11;
end
1 :
begin
r1 <= 2'b11;
y1 <= 2'b00;
g1 <= 2'b11;
r2 <= 2'b00;
y2 <= 2'b11;
g2 <= 2'b11;
end
2 :
begin
r1 <= 2'b00;
y1 <= 2'b11;
g1 <= 2'b11;
r2 <= 2'b11;
y2 <= 2'b11;
g2 <= 2'b00;
end
default :
begin
r1 <= 2'b00;
y1 <= 2'b11;
g1 <= 2'b11;
r2 <= 2'b11;
y2 <= 2'b00;
g2 <= 2'b11;
end
endcase

倒计时与状态跳转

count 由状态切换时装入的定时值倒计数,减到 0 就切换状态:0→1 装入 yellow_time,1→2 装入 green_time,2→3 装入 yellow_time,3→0 装入 green_time,循环往复:

always @(posedge clk)
begin
if (count == 0)
begin
    if (state == 3)
    state <= 0;
    else
    state <= state + 1;
    case (state)
    2'b00:
    count <= yellow_time;
    2'b01:
    count <= green_time;
    2'b10:
    count <= yellow_time;
    default:
    count <= green_time;
    endcase
    end
    else
    count = count - 1;
    end
endmodule

定时值为什么反着装

仔细看 case(state):state==0(东西绿)时装入的却是 yellow_time——因为 case 判断的是「当前状态」,而 count 是「离开当前状态」前的倒计时,跳到下一个状态前的等待时长由下一个状态的灯色决定。state 0(绿)结束时进入的是黄灯,所以装 yellow_time。这个「当前状态决定离开它的时间」的对应关系,是本例最容易看岔的地方。

分频器:50MHz 变 1Hz

状态机里的 count 每次减 1 代表 1 秒,所以给它的 clk 必须是 1Hz。clkdiv 模块对 50MHz 输入时钟计数 25_000_000 次翻转一次输出,得到 1Hz(50000000 分频,占空比 50%):

module clkdiv(clkin, clk1Hz); //分频器
input clkin; //输入时钟频率50MHz
output clk1Hz; //1Hz
reg clk1Hz;
reg [24:0]cnt1;
 
always@(posedge clkin)
    if(cnt1==24999999) //50000000 分频
    begin
    clk1Hz<=~clk1Hz;
    cnt1<=0;
    end
    else cnt1<=cnt1+1;
 
endmodule

仿真时先改小定时参数

yellow_time=5、green_time=20 是按 1Hz 节拍设计的,直接仿真要等 50 秒才能看完一个周期。仿真时可以把两个 parameter 和 clkdiv 的 24999999 临时改小,先验证状态跳转与输出译码正确,再恢复原值上板。

实验仿真

根据描述语言进行仿真测试,分析波形:重点检查 state 是否按 0→1→2→3→0 循环、count 是否按对应时间归零跳转、每个状态下 r/y/g 的 2’b00 位是否点亮正确的灯。

管脚约束

扩展板上共有东西(E)、南(S)、西(W)、北(N)四个路口的灯,每个路口各红(R)、黄(Y)、绿(G)三根线,加上时钟共 13 个引脚:

set_pin_assignment { EG } { LOCATION = K16; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { ER } { LOCATION = M16; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { EY } { LOCATION = H16; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { NG } { LOCATION = N14; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { NR } { LOCATION = P16; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { NY } { LOCATION = N16; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { SG } { LOCATION = G14; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { SR } { LOCATION = K15; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { SY } { LOCATION = G16; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { WG } { LOCATION = D16; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { WR } { LOCATION = E15; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { WY } { LOCATION = M10; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { clk } { LOCATION = R7; IOSTANDARD = LVCMOS33; PULLTYPE = PULLUP; }

下载测试

  1. 将流水灯模块接插在开发板 J1 口上;
  2. 下载程序之后,可以看到交通灯红绿黄灯切换,切换周期与设计时序表一致。

现象与总结

上板后可以看到:东西方向绿灯亮 20 秒、转黄灯 5 秒,同时南北方向红灯亮满 25 秒;然后东西转红灯、南北转绿灯,如此 50 秒一个周期循环。这个实验把流水灯里「计数 + 翻转」的结构正式升级为有限状态机:state 寄存器编码状态、组合逻辑译码输出、倒计数器控制跳转。这套「状态 + 计数」的模板可以直接套用到自动售货机、电梯控制器、密码锁等一切按时间轮转的场景,是后续所有复杂控制逻辑的地基。

自测