这一篇在干嘛?
交通灯是「有限状态机」最经典的入门案例:灯的每一种亮灭组合是一个状态,倒计时归零就跳到下一个状态。本实验用 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)
beginif (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; }下载测试
- 将流水灯模块接插在开发板 J1 口上;
- 下载程序之后,可以看到交通灯红绿黄灯切换,切换周期与设计时序表一致。

现象与总结
上板后可以看到:东西方向绿灯亮 20 秒、转黄灯 5 秒,同时南北方向红灯亮满 25 秒;然后东西转红灯、南北转绿灯,如此 50 秒一个周期循环。这个实验把流水灯里「计数 + 翻转」的结构正式升级为有限状态机:state 寄存器编码状态、组合逻辑译码输出、倒计数器控制跳转。这套「状态 + 计数」的模板可以直接套用到自动售货机、电梯控制器、密码锁等一切按时间轮转的场景,是后续所有复杂控制逻辑的地基。
自测
自测
- 本实验的交通灯一个完整周期是多少秒?东西方向和南北方向各时段的灯色如何安排?
- traffic 模块有几个状态?分别对应什么亮灯组合?状态 3 为什么写在 default 分支里?
- state==0(东西绿灯)时装入 count 的值是 yellow_time 还是 green_time?为什么?
- clkdiv 模块如何把 50MHz 时钟变成 1Hz?计数终值为什么是 24999999?
- 如果要把绿灯时间改为 30 秒、黄灯时间改为 3 秒,需要修改哪些 parameter?如果想加入左转相位,状态机需要增加什么?