一、项目介绍
交通灯控制系统
使用WebIDE的图形化编程和Diamond,使用常规的74系列的元器件,构建一个交通灯控制系统。
任务一:在数码管上显示计时信息-WebIDE(Verilog也实现了)
任务二:蜂鸣器报警-WebIDE(Verilog也实现了)
任务三:接近传感器检测人员走近- Verilog
任务四:环境光感知,自动点亮路灯(小脚丫核心板上的单色LED)- Verilog
二、硬件介绍
STEP Baseboard4.0底板和STEP MXO2 LPC核心板
三、功能效果
红绿灯分别进行20秒倒计时,在红灯向绿灯或绿灯向红灯的转变过程中,黄灯亮起1秒,同时蜂鸣器报警进行警示。在检测到人员靠近和环境过暗时,自动亮起单色LED。
四、具体实现
由于我们先在本地Diamond上用Verilog实现了任务一、二、三、四,再用WebIDE图形化编程实现了任务一、二。因此接下来会分别介绍任务一、二的Diamond实现,任务一、二的WebIDE实现。
1、任务一和任务二的WebIDE实现
链接:stepfpga (xjxxjxxjx用户的traffic2项目)
(1)设计截图
(2)设计思路
以clk和rst_n为基本控制信号,clk是时钟输入,rst_n是复位信号,用于初始化或重置系统。时钟分频模块将输入的高频时钟信号转换为1Hz的时钟信号,用于控制倒计时的显示更新。create_data2模块根据1Hz的时钟信号生成倒计时所需的秒数数据,包括sec0[3:0]和sec1[3:0],分别表示当前倒计时的十位和个位数字。这些数据被传递到后续的显示模块。
rgb_led模块接收来自倒计时逻辑的控制信号led_rgb[2:0],用于显示当前的交通灯颜色状态。根据不同的信号,RGB LED会显示红、黄或绿灯,以指示当前的交通信号。
segment模块将倒计时的秒数数据sec0[3:0]和sec1[3:0]转换为七段数码管能够显示的格式,生成seg_led_1[6:0]和seg_led_2[6:0]信号,分别控制两个七段数码管显示倒计时的十位和个位数字。
beeper_ctrl模块在黄灯显示时激活蜂鸣器。它接收beepper_en信号,当该信号有效时,控制蜂鸣器发出提示音。该模块根据beepper_en信号的时长来控制蜂鸣器的发声时间,确保在黄灯阶段持续发声。
sn74hc85模块用于比较倒计时的秒数数据,以确定是否达到预设的时间点(如红灯或绿灯的20秒倒计时结束,或者黄灯的1秒显示结束),从而触发状态切换信号,使系统进入下一个交通灯状态。
bit-0模块用于对输入信号进行位处理或转换,以便与其他模块的信号格式匹配。它接收输入信号I0、I1、I2,并根据内部逻辑进行处理,输出处理后的信号,这些信号可能用于控制其他模块的使能或选择功能。
整体设计思路是通过状态机的方式,在不同的时间间隔切换交通灯的状态,同时控制蜂鸣器的开启和关闭,以实现预期的交通信号灯控制功能。系统以时钟和复位信号为基本控制信号,通过各个模块之间的信号传递和协同工作,确保交通信号灯能够按照设定的时间和顺序进行切换,并在黄灯阶段提供蜂鸣器提示。
(3)资源占用
2、任务一和任务二的diamond实现
信号灯倒计时设计思路
create_data.v
模块通过 12MHz 时钟输入生成一个从 20 秒开始的倒计时信号,将倒计时的秒数分为个位(sec0
)和十位(sec1
)输出。模块内部使用一个 24 位计数器 counter
来计数时钟周期,每 12,000,000 个周期(即 1 秒)更新一次秒数寄存器 sec0_R
和 sec1_R
。倒计时逻辑通过递减秒数寄存器实现,当个位秒数减到 0 时,十位秒数递减,直到倒计时结束并重新初始化为 20 秒。模块在复位信号有效时初始化所有寄存器为 0,并通过 assign
语句将内部寄存器的值赋给输出端口,代码如下。
module create_data #(
parameter CNT_1s_MAX = 12_000_000 // 12MHz时钟,1秒的周期
)(
input clk, // 12MHz时钟输入
input rst_n, // 低有效复位信号
output [3:0] sec0, // 秒的个位
output [3:0] sec1 // 秒的十位
);
reg [23:0] counter; // 用于12百万周期的24位计数器
// 倒计时逻辑
reg [3:0] sec0_R;
reg [3:0] sec1_R;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
counter <= 0;
sec0_R <= 0;
sec1_R <= 0;
end else if (counter == CNT_1s_MAX - 1) begin
counter <= 0; // 每秒重置计数器
if (sec0_R == 0) begin
if (sec1_R == 0) begin
sec1_R <= 20 / 10; // 初始值设为20秒的十位
sec0_R <= 20 % 10; // 初始值设为20秒的个位
end else begin
sec1_R <= sec1_R - 1;
sec0_R <= 9;
end
end else begin
sec0_R <= sec0_R - 1; // 倒计时
end
end else begin
counter <= counter + 1; // 递增计数器
end
end
assign sec0 = sec0_R;
assign sec1 = sec1_R;
endmodule
RGB灯颜色和蜂鸣器控制设计思路(交通信号灯切换逻辑)
rgb_led.v
模块通过状态机控制交通灯和蜂鸣器,根据倒计时信号在红灯、黄灯和绿灯之间切换。
红灯和绿灯各持续20秒,黄灯持续1秒。
state是当前信号灯的状态,分为红绿黄。
红绿灯在倒计时结束后,通过对秒数的判断,将state切换到黄灯进入下一个状态。
main_state记录是黄灯前的主状态(红灯或绿灯),在黄灯结束后根据main_state切换到对应灯,并改变自身状态,实现红绿灯的交替,蜂鸣器仅在黄灯时启用。
localparam YELLOW_DURATION = 12_000_000常量用于黄灯计时,时长为1秒钟。
代码如下
module rgb_led_module #(
parameter CNT_1s_MAX = 12_000_000 // 12MHz时钟,1秒的周期
)(
input clk, // 12MHz时钟输入
input rst_n, // 低有效复位信号
output [2:0] led_rgb,
output beeper_en
);
wire [3:0] sec0;
wire [3:0] sec1;
reg [2:0] led_rgb_R;
reg beeper_en_R;
assign led_rgb = led_rgb_R;
assign beeper_en = beeper_en_R;
// 交通灯逻辑
reg [1:0] state;
localparam RED = 2'b00;
localparam YELLOW = 2'b01;
localparam GREEN = 2'b10;
// 记录当前主状态(红灯或绿灯)
reg main_state;
// 黄灯持续时间计数器
reg [23:0] yellow_counter;
localparam YELLOW_DURATION = 12_000_000; // 1秒(12,000,000个时钟周期)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
led_rgb_R <= 3'b011; // 红灯初始状态
beeper_en_R <= 0;
state <= RED;
main_state <= 0; // 初始主状态为红灯
yellow_counter <= 0;
end else begin
case (state)
RED: begin
led_rgb_R <= 3'b011;
beeper_en_R <= 0;
main_state <= 0; // 记录当前主状态为红灯
if (sec0 == 0 && sec1 == 0) begin
state <= YELLOW;
yellow_counter <= YELLOW_DURATION;
end
end
YELLOW: begin
led_rgb_R <= 3'b001;
beeper_en_R <= 1;
if (yellow_counter > 0) begin
yellow_counter <= yellow_counter - 1;
end else begin
if (main_state == 0) begin // 如果之前是红灯
state <= GREEN;
main_state <= 1; // 更新主状态为绿灯
end else begin // 如果之前是绿灯
state <= RED;
main_state <= 0; // 更新主状态为红灯
end
end
end
GREEN: begin
led_rgb_R <= 3'b101;
beeper_en_R <= 0;
main_state <= 1; // 记录当前主状态为绿灯
if (sec0 == 0 && sec1 == 0) begin
state <= YELLOW;
yellow_counter <= YELLOW_DURATION;
end
end
default: begin
led_rgb_R <= 3'b011; // 默认红灯
beeper_en_R <= 0;
state <= RED;
main_state <= 0;
end
endcase
end
end
// 计时器实例化
create_data u1_create_data (
.clk(clk),
.rst_n(rst_n),
.sec0(sec0),
.sec1(sec1)
);
endmodule
3、任务三和任务四的diamond实现
rpr0521rs_driver.v用于驱动RPR0521RS传感器(环境光和接近检测传感器),通过I2C接口与外界通信。
module rpr0521rs_driver(
input clk, //系统时钟
input rst_n, //系统复位,低有效
output i2c_scl, //I2C总线SCL
inout i2c_sda, //I2C总线SDA
output reg dat_valid, //数据有效脉冲
output reg [15:0] ch0_dat, //ALS数据,环境光传感器
output reg [15:0] ch1_dat, //IR数据,红外
output reg [15:0] prox_dat //Prox数据,接近(Proximity)传感器的数据
);
状态机设计:
IDLE:初始化状态,用于复位和初始化。
MAIN:主状态,用于控制传感器的配置和数据读取。
MODE1:单次写操作状态,用于向传感器写入配置数据。
MODE2:两次读操作状态,用于从传感器读取数据。
START: I2C通信的起始信号状态。
WRITE: I2C通信的写操作状态。
READ: I2C通信的读操作状态。
STOP: I2C通信的停止信号状态。
DELAY:延时状态,用于等待传感器完成操作。
always@(posedge clk_400khz or negedge rst_n) begin
if(!rst_n) begin //如果按键复位,将相关数据初始化
scl <= 1'd1; sda <= 1'd1; ack <= ACK; ack_flag <= 1'b0; cnt <= 1'b0;
cnt_main <= 1'b0; cnt_mode1 <= 1'b0; cnt_mode2 <= 1'b0;
cnt_start <= 1'b0; cnt_write <= 1'b0; cnt_read <= 1'b0; cnt_stop <= 1'b0;
cnt_delay <= 1'b0; num_delay <= 24'd4800;
state <= IDLE; state_back <= IDLE;
end else begin
case(state)
IDLE:begin
scl <= 1'd1; sda <= 1'd1; ack <= ACK; ack_flag <= 1'b0; cnt <= 1'b0;
cnt_main <= 1'b0; cnt_mode1 <= 1'b0; cnt_mode2 <= 1'b0;
cnt_start <= 1'b0; cnt_write <= 1'b0; cnt_read <= 1'b0; cnt_stop <= 1'b0;
cnt_delay <= 1'b0; num_delay <= 24'd4800;
state <= MAIN; state_back <= IDLE;
end
MAIN:begin//-------------------------1
if(cnt_main >= 4'd11) cnt_main <= 4'd4; //写完控制指令后循环读数据
else cnt_main <= cnt_main + 1'b1;
case(cnt_main)
4'd0: begin dev_addr <= 7'h38; reg_addr <= 8'h40; reg_data <= 8'h0a; state <= MODE1; end //写入配置
4'd1: begin dev_addr <= 7'h38; reg_addr <= 8'h41; reg_data <= 8'hc6; state <= MODE1; end //写入配置
4'd2: begin dev_addr <= 7'h38; reg_addr <= 8'h42; reg_data <= 8'h02; state <= MODE1; end //写入配置
4'd3: begin dev_addr <= 7'h38; reg_addr <= 8'h43; reg_data <= 8'h01; state <= MODE1; end //写入配置
4'd4: begin state <= DELAY; dat_valid <= 1'b0; end //12ms延时
4'd5: begin dev_addr <= 7'h38; reg_addr <= 8'h44; state <= MODE2; end //读取配置
4'd6: begin prox_dat<= {dat_h,dat_l}; end //读取数据
4'd7: begin dev_addr <= 7'h38; reg_addr <= 8'h46; state <= MODE2; end //读取配置
4'd8: begin ch0_dat <= {dat_h,dat_l}; end //读取数据
4'd9: begin dev_addr <= 7'h38; reg_addr <= 8'h48; state <= MODE2; end //读取配置
4'd10: begin ch1_dat <= {dat_h,dat_l}; end //读取数据
4'd11: begin dat_valid <= 1'b1; end //读取数据
default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
endcase
end
MODE1:begin //单次写操作------------------------2
if(cnt_mode1 >= 4'
任务一、二、三、四用Verilog实现的资源占用:
五、整体设计思路
1、整体框架图
这是任务一、二、三、四均用本地Diamond实现情况下的完整架构图(WebIDE的简单架构在此基础上去掉rpr0521rs_driver.v即可,实际实现思路与用Verilog实现任务一、二基本一致)
2、具体思路
以时钟和复位信号为基本控制,通过create_data模块生成倒计时所需的秒数数据,包括秒的个位和十位数字。rgb_led_module模块根据倒计时逻辑控制RGB LED显示当前交通灯颜色,红灯和绿灯各持续20秒,黄灯持续1秒并伴有蜂鸣器提示,通过beeper_ctrl模块实现蜂鸣器的控制。segment模块将倒计时秒数转换为七段数码管可显示的格式,实时显示倒计时信息。rpr0521rs_driver模块获取环境光和接近传感器数据,seg_ctrl模块根据传感器数据控制led灯的亮灭,当有人接近或环境过暗时点亮led灯。decoder模块对传感器数据进行处理和转换,segment_scan模块控制数码管的扫描和显示。整个系统通过状态机的方式在不同时间间隔切换交通灯状态,确保交通信号灯按设定的时间和顺序进行切换,并在黄灯阶段提供蜂鸣器提示,同时根据环境条件自动控制led灯的亮灭,以实现完整的交通信号灯控制功能。
六、功能展示
详见展示视频结尾,包含信号灯倒计时、红绿灯转换、黄灯蜂鸣器报警、接近检测亮灯等功能。
七、心得体会
很高兴能借助这个机会,复习两年前在数字逻辑课程中学习到的知识,相比于之前课程中的作业任务,这次寒假练的项目更加系统和完整,在完成任务的过程中,我们感受到了模块化设计的重要性,通过整体性设计,将整个系统划分为多个功能模块,大大提高了代码的可读性和可维护性。复习并编写了状态机逻辑,完成了复杂逻辑和时序控制的任务。其实FPGA开发并非我们的专业领域,但通过这次机会我们也学习到了新的知识和编程技术,积累了实践经验。