2025寒假练 - 基于小脚丫FPGA实现交通灯控制系统
该项目使用了WebIDE图形化、Diamond、Verilog语言,实现了交通灯控制系统的设计,它的主要功能为:使用WebIDE的图形化编程,在数码管上显示计时信息并控制蜂鸣器发出提示音,使用Diamond完成接近传感器对人员走近的检测,并感知环境光,自动点亮路灯。
标签
FPGA
2025寒假练
交通灯控制系统
taotie
更新2025-03-17
南京信息工程大学
16

1.项目需求

本次项目的主要需求是基于小脚丫FPGA套件STEP BaseBoard V4.0实现交通灯控制系统。

2.需求分析

本项目共需完成四种功能,将其分为两部分完成,前两种功能为:在数码管上显示计时信息蜂鸣器报警。本项目将完全使用WebIDE的图形化编程并用到了常规的74系列的元器件来实现这部分的功能。后两种功能为:接近传感器检测人员走近环境光感知,自动点亮路灯。这部分的功能将使用Vreilog语言和Diamond完成。

3.硬件分析

STEP小脚丫FPGA学习平台是苏州思得普信息科技公司专门针对FPGA初学者打造的一系列性价比最高、学习门槛最低的学习模块系列。板上选用的芯片兼具了FPGA和CPLD的优点,瞬时上电启动,无需外部重新配置FPGA,是学习数字逻辑绝佳的选择。系列中所有板子的大小兼容标准的DIP40封装,尺寸只有52mm x 18mm,非常便于携带,而且能够直接插在面包板上或以模块的方式放置在其它电路板上以即插即用的方式,大大简化系统的设计。

本项目用到的核心板是STEP-MXO2-LPC,它的优点是:

  1. 使用了USB Type C接口提供板上+5V供电、FPGA的配置,并新增了UART通信的功能,因此无需再通过其它端口与PC进行数据通信
  2. 支持U盘模式(连接到上位机的USB端口,上位机自动弹出StepFPGA的U盘盘符)的下载,任何操作系统的电脑 - Windows、Mac OS以及Linux(包括树莓派)都可以在不安装任何驱动程序的情况下,直接将生成的jed配置文件发送到StepFPGA盘中即可完成编程
  3. 可以使用升级版的Web IDE系统,用户不必再下载安装Diamond软件,即可在任何一款电脑上通过浏览器进行FPGA的编程和编译

4.项目分析

4.1.Web IDE图形化部分

4.1.1. 思路分析

根据上述分析,对第一部分功能的具体实现方式可画流程图如下:

5049149f179d27938a508b4b454200f2.png

4.1.2. 模块分析

1、分频器1模块

分频器1主要实现的功能是将12MHz的时钟转化为1Hz的时钟,以实现计时的效果。本模块中使用了Web IDE图形化编程中自定义模块的code模块,在其中输入如下代码:

reg [22:0] count;
reg clk_out;
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
count <= 1'b0;
clk_out <= 1'b0;
end
else if(count == 6_000_000) begin
count <= 1'b0;
clk_out <= ~ clk_out;
end
else
count <= count + 1'b1;
end

定义好code模块的输入输出端口,并为其分配输入输出引脚,如下图所示:

d32a6008c2bbe89a1f88d9b371daf14e.png

即可得到1Hz的输出信号clk_out。

2、计数器和逻辑控制模块

此模块主要用来控制小脚丫fpga核心板上数码管的输入信号,​同时控制RGB的颜色等等。通过状态机实现红绿黄状态的转化。

reg [3:0] out1,out2;
reg [1:0] en,led,led1;
reg state,yellow;

localparam
STATE_GREEN = 1'b0, // 绿灯状态(主状态)
STATE_YELLOW = 1'b1; // 黄灯状态(过渡状态)

reg state;
// 初始值参数
localparam INIT_OUT1 = 4'd6;
localparam INIT_OUT2 = 4'd1;
localparam YELLOW_TIME = 4'd3;

// 主状态机
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
// 异步复位
state <= STATE_GREEN;
out1 <= INIT_OUT1;
out2 <= INIT_OUT2;
en <= 2'b00; // 默认使能第一位
led <= 2'b01; // 绿灯初始状态
yellow <= 1'b1; // 黄灯关闭
end else begin
case (state)
//-----------------------------
// 绿灯状态(主倒计时)
//-----------------------------
STATE_GREEN: begin
yellow <= 1'b1; // 关闭黄灯

if (out1 == 4'd0) begin
if (out2 == 4'd0) begin
// 倒计时结束,进入黄灯状态
state <= STATE_YELLOW;
out1 <= YELLOW_TIME; // 设置黄灯3秒
led <= 2'b00; // 关闭绿灯
end else begin
// 高位借位处理
out2 <= out2 - 1'b1;
out1 <= 4'd9; // 低位重置为9
end
en <= 2'b10;
end else begin
// 正常倒计时
out1 <= out1 - 1'b1;
end
end

//-----------------------------
// 黄灯状态(过渡)
//-----------------------------
STATE_YELLOW: begin
yellow <= 1'b0; // 开启黄灯

if (out1 == 4'd0) begin
// 黄灯结束,返回绿灯状态
state <= STATE_GREEN;
out1 <= INIT_OUT1; // 重置倒计时
out2 <= INIT_OUT2;
en <= 2'b00; // 恢复使能位
led <= 2'b10; // 切换LED状态(示例)
end else begin
// 黄灯倒计时
out1 <= out1 - 1'b1;
end
end
endcase
end
end

并定义好输入输出端口,如下图所示:

634ea0627b5c1ea7276a1dd71b34c8dc.png

即可控制数码管显示想要的结果。

3、蜂鸣器驱动和逻辑控制模块

本模块主要实现的功能是当上述计数器计数小于等于4时,控制蜂鸣器发出提示音。

通过观察STEP Baseboard4.0底板上的蜂鸣器原理图如下:

bb91b0ac21d188b1a41cdbfdae299eb6.png

可知:STEP Baseboard4.0底板使用的为无源蜂鸣器,需要输入有一定频率的信号驱动。由于本项目中不需要声音频率的变化,故用到了分频器2模块将输入时钟分频到固定的频率来使蜂鸣器发出提示音。在本模块中,将用到sn74hc08和sn74hc85两个74系列元器件。sn74hc08是2输入端4与门,sn74hc85是四位数字比较器。使用sn74hc08的A1、B1口分别连接分频器2的信号输出口和分频器1的信号输出口,类比于通信原理中的调制解调,第一步实现的波形图如下图:

dc61f487c773705cedac0c218135b9b9.png

然后使用sn74hc85把低4位bcd码与4‘b0100比较,通过观察Web IDE图形化编程内部模块的代码如下

always@(*) begin
if(A>B)
out_compare <= 3'b100;
else if (A<B)
out_compare <= 3'b010;
else begin
case(in_compare)
3'b000 : out_compare <= 3'b110;
3'b001 : out_compare <= 3'b001;
3'b010 : out_compare <= 3'b010;
3'b011 : out_compare <= 3'b001; ///完全复制芯片数据手册
3'b100 : out_compare <= 3'b100;
3'b101 : out_compare <= 3'b001;
3'b110 : out_compare <= 3'b000;
3'b111 : out_compare <= 3'b001;
endcase
end
end

可知,当in_compare即输入口I0I1I2取3’b000时启用大于/小于功能,然后将输出口F1与高4位bcd码的最低位接入sn74hc08的A2、B2口,此时实现的是判断8位bcd码表示的值是否小于等于4,然后将Y1、Y2分别接入A3、B3,此时Y3即可连接蜂鸣器实现对应的功能。

4、静态数码管驱动模块

本模块中使用了Web IDE图形化编程中自定义模块的code模块,在其中输入如下代码:

reg [6:0] segment;
always@(seg_data)
case(seg_data)
4'd0: segment = 7'h3f;
4'd1: segment = 7'h06;
4'd2: segment = 7'h5b;
4'd3: segment = 7'h4f;
4'd4: segment = 7'h66;
4'd5: segment = 7'h6d;
4'd6: segment = 7'h7d;
4'd7: segment = 7'h07;
4'd8: segment = 7'h7f;
4'd9: segment = 7'h6f;
default: segment = 7'h00;
endcase

assign segment_led = {en,seg_dot,segment};

通过简单的case语句即可驱动静态数码管,定义好输入输出引脚如下图

67709e45337e364b6ef77fa0fc0056dd.png

然后分别应用两此该模块分别控制两个数码管即可实现功能。

4.1.3. 图形总览

图形化编程总览如下图所示

120e61035e0cda4809e79106a58cc0f7.png

4.1.4. FPGA资源占用报告

be9bde3b9cd52597b745b67fd74b7b8a.png

4.2.Diamond Verilog部分

4.2.1. 思路分析

对第二部分功能的具体实现方式可画流程图如下:

cde8364572e694f3108e8897e5f81c4e.png


4.2.2. 模块分析

1、rpr0521rs驱动模块

本模块实现的功能是通过I2C总线与rpr0521rs传感器通信,读取其ALS(光敏)、IR(红外)和Prox(接近)传感器数据,并输出数据有效脉冲。为驱动I2C通信,使用 10 位计数器从系统时钟clk生成一个400kHz的时钟信号clk_400khz,代码如下:

	reg					clk_400khz;
reg [9:0] cnt_400khz;
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_400khz <= 10'd0;
else if(cnt_400khz == CNT_NUM-1)
cnt_400khz <= 10'd0;
else
cnt_400khz <= cnt_400khz + 1'b1;
end
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
clk_400khz <= 1'b0;
else if(cnt_400khz == CNT_NUM-1)
clk_400khz <= ~clk_400khz;
end

使用4位寄存器实现状态机来控制传感器时序,主要状态包括:

  1. IDLE: 初始化或复位状态。
  2. MAIN: 主控状态,控制整个流程(写寄存器、读数据等)。
  3. MODE1: 单次写操作(配置传感器寄存器)。
  4. MODE2: 两次读操作(读取传感器数据)。
  5. START: I2C 起始信号生成。
  6. WRITE: I2C 写操作。
  7. READ: I2C 读操作。
  8. STOP: I2C 停止信号生成。
  9. DELAY: 延时状态(12ms延时)。

2、动态数码管驱动模块

模块采用三级状态机控制机制(IDLE初始化、MAIN数据装配、WRITE串行输出),以12MHz系统时钟分频生成的40kHz扫描时钟为驱动基准,通过动态扫描技术,依次刷新8位数码管。模块接收8组4位二进制数输入(dat_1至dat_8)分别对应数码管位,结合字库(预存0-F的7段码)将数字转换为段选信号,同时支持通过dat_en使能控制各数码管显示状态、dot_en独立控制小数点。首先预存16组7位段码:

reg[6:0] seg [15:0]; 
always @(negedge rst_n) begin
seg[0] = 7'h3f; // 0
seg[1] = 7'h06; // 1
seg[2] = 7'h5b; // 2
seg[3] = 7'h4f; // 3
seg[4] = 7'h66; // 4
seg[5] = 7'h6d; // 5
seg[6] = 7'h7d; // 6
seg[7] = 7'h07; // 7
seg[8] = 7'h7f; // 8
seg[9] = 7'h6f; // 9
seg[10] = 7'h77; // A
seg[11] = 7'h7c; // b
seg[12] = 7'h39; // C
seg[13] = 7'h5e; // d
seg[14] = 7'h79; // E
seg[15] = 7'h71; // F
end

然后将输入数据(dat_1~dat_8)经字库转换后,与独立小数点控制信号(dot_en)拼接为8位段选信号,结合位选使能信号(dat_en)生成16位传输数据包(高8位段选+小数点,低8位位选)。

通过时序状态机实现SPI类通信协议:在WRITE状态下,利用32个时钟周期完成16位数据串行输出(seg_sck下降沿更新seg_din,上升沿锁存数据)。数据传送完成后,通过seg_rck上升沿触发并行输出,确保显示同步更新。

reg		[15:0]		data;
reg [2:0] cnt_main;
reg [5:0] cnt_write;
reg [2:0] state = IDLE;
always@(posedge clk_40khz or negedge rst_n) begin
if(!rst_n) begin //复位状态下,各寄存器置初值
state <= IDLE;
cnt_main <= 3'd0; cnt_write <= 6'd0;
seg_din <= 1'b0; seg_sck <= LOW; seg_rck <= LOW;
end else begin
case(state)
IDLE:begin //IDLE作为第一个状态,相当于软复位
state <= MAIN;
cnt_main <= 3'd0; cnt_write <= 6'd0;
seg_din <= 1'b0; seg_sck <= LOW; seg_rck <= LOW;
end
MAIN:begin
cnt_main <= cnt_main + 1'b1;
state <= WRITE; //在配置完发给74HC595的数据同时跳转至WRITE状态,完成串行时序
case(cnt_main)
//对8位数码管逐位扫描
//data [15:8]为段选, [7:0]为位选
3'd0: data <= {{dot_en[7],seg[dat_1]},dat_en[7]?8'hfe:8'hff};
3'd1: data <= {{dot_en[6],seg[dat_2]},dat_en[6]?8'hfd:8'hff};
3'd2: data <= {{dot_en[5],seg[dat_3]},dat_en[5]?8'hfb:8'hff};
3'd3: data <= {{dot_en[4],seg[dat_4]},dat_en[4]?8'hf7:8'hff};
3'd4: data <= {{dot_en[3],seg[dat_5]},dat_en[3]?8'hef:8'hff};
3'd5: data <= {{dot_en[2],seg[dat_6]},dat_en[2]?8'hdf:8'hff};
3'd6: data <= {{dot_en[1],seg[dat_7]},dat_en[1]?8'hbf:8'hff};
3'd7: data <= {{dot_en[0],seg[dat_8]},dat_en[0]?8'h7f:8'hff};
default: data <= {8'h00,8'hff};
endcase
end
WRITE:begin
if(cnt_write >= 6'd33) cnt_write <= 1'b0;
else cnt_write <= cnt_write + 1'b1;
case(cnt_write)
//74HC595是串行转并行的芯片,3路输入可产生8路输出,而且可以级联使用
//74HC595的时序实现,参考74HC595的芯片手册
6'd0: begin seg_sck <= LOW; seg_din <= data[15]; end //SCK下降沿时SER更新数据
6'd1: begin seg_sck <= HIGH; end //SCK上升沿时SER数据稳定
6'd2: begin seg_sck <= LOW; seg_din <= data[14]; end
6'd3: begin seg_sck <= HIGH; end
6'd4: begin seg_sck <= LOW; seg_din <= data[13]; end
6'd5: begin seg_sck <= HIGH; end
6'd6: begin seg_sck <= LOW; seg_din <= data[12]; end
6'd7: begin seg_sck <= HIGH; end
6'd8: begin seg_sck <= LOW; seg_din <= data[11]; end
6'd9: begin seg_sck <= HIGH; end
6'd10: begin seg_sck <= LOW; seg_din <= data[10]; end
6'd11: begin seg_sck <= HIGH; end
6'd12: begin seg_sck <= LOW; seg_din <= data[9]; end
6'd13: begin seg_sck <= HIGH; end
6'd14: begin seg_sck <= LOW; seg_din <= data[8]; end
6'd15: begin seg_sck <= HIGH; end
6'd16: begin seg_sck <= LOW; seg_din <= data[7]; end
6'd17: begin seg_sck <= HIGH; end
6'd18: begin seg_sck <= LOW; seg_din <= data[6]; end
6'd19: begin seg_sck <= HIGH; end
6'd20: begin seg_sck <= LOW; seg_din <= data[5]; end
6'd21: begin seg_sck <= HIGH; end
6'd22: begin seg_sck <= LOW; seg_din <= data[4]; end
6'd23: begin seg_sck <= HIGH; end
6'd24: begin seg_sck <= LOW; seg_din <= data[3]; end
6'd25: begin seg_sck <= HIGH; end
6'd26: begin seg_sck <= LOW; seg_din <= data[2]; end
6'd27: begin seg_sck <= HIGH; end
6'd28: begin seg_sck <= LOW; seg_din <= data[1]; end
6'd29: begin seg_sck <= HIGH; end
6'd30: begin seg_sck <= LOW; seg_din <= data[0]; end
6'd31: begin seg_sck <= HIGH; end
6'd32: begin seg_rck <= HIGH; end //当16位数据传送完成后RCK拉高,输出生效
6'd33: begin seg_rck <= LOW; state <= MAIN; end
default: ;
endcase
end
default: state <= IDLE;
endcase
end
end

3、二进制转bcd码转码模块

通过加三移位法将输入的二进制数转化为bcd码,需要转化的二进制数有多少位循环多少次,代码如下:

reg		[67:0]		shift_reg; 
always@(bin_code or rst_n)begin
shift_reg = {36'h0,bin_code};
if(!rst_n)
bcd_code = 0;
else begin
repeat(32) begin //循环32次
if (shift_reg[35:32] >= 5)
shift_reg[35:32] = shift_reg[35:32] + 2'b11;
if (shift_reg[39:36] >= 5)
shift_reg[39:36] = shift_reg[39:36] + 2'b11;
if (shift_reg[43:40] >= 5)
shift_reg[43:40] = shift_reg[43:40] + 2'b11;
if (shift_reg[47:44] >= 5)
shift_reg[47:44] = shift_reg[47:44] + 2'b11;
if (shift_reg[51:48] >= 5)
shift_reg[51:48] = shift_reg[51:48] + 2'b11;
if (shift_reg[55:52] >= 5)
shift_reg[55:52] = shift_reg[55:52] + 2'b11;
if (shift_reg[59:56] >= 5)
shift_reg[59:56] = shift_reg[59:56] + 2'b11;
if (shift_reg[63:60] >= 5)
shift_reg[63:60] = shift_reg[63:60] + 2'b11;
if (shift_reg[67:64] >= 5)
shift_reg[67:64] = shift_reg[67:64] + 2'b11;
shift_reg = shift_reg << 1;
end
bcd_code = shift_reg[67:36];
end
end

4、逻辑控制及转码模块

本模块调用上述二进制转bcd码转码模块,将rpr0521rs传感器读到的接近检测数据环境光强度数据红外光强度数据从二进制转化为bcd码,并且将其与规定值比较,使路灯(小脚丫核心板上的单色LED)在夜晚人接近的时候亮起,在白天则不亮起。

对接收到的接近检测数据进行消抖操作如下:

reg	[15:0] prox_dat0,prox_dat1,prox_dat2;
always @(posedge dat_valid) begin
prox_dat0 <= prox_dat;
prox_dat1 <= prox_dat0;
if(((prox_dat1-prox_dat0) >= 16'h800)||((prox_dat1-prox_dat0) >= 16'h800))
prox_dat2 <= prox_dat2;
else
prox_dat2 <= prox_dat0;
end

prox_dat2即为经过消抖后的接近检测数据。​当光照强度低于指定值的同时,物体越接近,亮起的灯越多;否则没有灯亮起

always@(prox_dat2[11:9]) begin
if(lux <= 32'd10000) begin
case (prox_dat2[11:9])
3'b000: Y_out = 8'b11111111;
3'b001: Y_out = 8'b11111110;
3'b010: Y_out = 8'b11111100;
3'b011: Y_out = 8'b11111000;
3'b100: Y_out = 8'b11110000;
3'b101: Y_out = 8'b11100000;
3'b110: Y_out = 8'b11000000;
3'b111: Y_out = 8'b10000000;
endcase
end else
Y_out = 8'b11111111;
end

4.2.3. FPGA资源占用报告

e26343ec7e689de06a76877de195fc6a.png

5.实物展示


328a5cf665f6b1452d42539109c43e7a.jpg

0fa729d09604295f52527ccc2a630d2b.jpg

00bf5a23925a374590f359763555b563.jpg


024792e7dece188e9cfe1ea35b68884c.jpg

0b14e7149f53f56ddb6c6147a75c62d7.jpg

6.项目总结

感谢硬禾学堂提供的训练机会,本次活动使我受益良多。

附件下载
archive (1).zip
Web IDE
lab8_prox_detect_1.zip
Diamond
团队介绍
小田
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号