一、项目介绍
本项目小脚丫FPGA开发板,结合WebIDE图形化编程与Verilog代码,构建了一个交通灯控制系统。系统的主要功能包括了数码管显示倒计时——实时显示红绿灯剩余时间;蜂鸣器报警——红灯和绿灯切换时响铃和有人接近时响铃;检测人员接近传感器——通过蜂鸣器报警功能,当有人靠近时响应;环境光感知——按照光照强度自动控制LED路灯点亮。
二、硬件介绍
本次项目使用的板卡是STEP Baseboard4.0底板和STEP MXO2 LPC核心板,构造如图所示。
小脚丫FPGA的核心板是主控单元,负责逻辑处理与模块协调;核心板上的RGB LED和单色光负责红绿灯的切换和单色光的点亮;环境光和接近传感器,负责检测环境光照强度和实现接近检测功能;蜂鸣器支持多频率报警输出;八位数码管负责显示接近和环境光数值,同时核心板的数码管负责倒计时。以上是我们本次实验所用的模块,此外还有剩余没用到的模块,例如温湿度传感器、加速度计、电位计等模块,也将有各自的功能和实现方式。
三、项目实现过程
3.1方案框图和设计思路
对于整个实现的系统框图,将分为五个部分,如下图所示,
其中,计时模块通过yellow_func生成交通灯倒计时数据,控制RGB交通灯状态;传感器驱动rpr0521rs_driver模块通过I2C协议读取光强和接近数据;报警控制beeper模块在检测到行人时,驱动蜂鸣器按预设频率报警;环境光响应decoder模块将光强数据转换为BCD码,控制LED的开关阈值;显示驱动segment_scan模块驱动74HC595,实现多位数码管动态扫描。
3.2软件流程图
根据此流程图,我们清晰地知道,系统启动后首先完成初始化,然后通过I2C协议初始化环境光和接近传感器,并且持续获取光强和行人接近数据,若光强低于阈值,点亮LED;否则关闭LED,若检测到行人,触发动态音调报警(模式二);否则检查交通灯状态,若遇到红灯上下沿切换,触发固定频率报警(模式一);否则更新数码管显示,并循环至数据读取步骤。
3.3关键代码介绍
3.3.1 图形化编程
其中有三个自定义模块,首先是Data_generator模块,此模块的功能是生成倒计时秒数及红绿灯的状态控制,定义了红绿灯的倒计时时间,红灯20秒,绿灯15秒和黄灯3秒的颜色编码;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
counter <= 0;
sec0_R <= GREEN_SEC0;
sec1_R <= GREEN_SEC1;
state <= 0; // Start with green light
led_rgb_R <= GREEN_COLOR;
end else if (counter == CNT_1s_MAX - 1) begin
counter <= 0; // Reset counter every second
if (sec0_R == 0) begin
if (sec1_R == 0) begin
case (state)
0: begin // Green to Yellow
sec0_R <= YELLOW_SEC0;
sec1_R <= YELLOW_SEC1;
state <= 1;
led_rgb_R <= YELLOW_COLOR;
end
1: begin // Yellow to Red
sec0_R <= RED_SEC0;
sec1_R <= RED_SEC1;
state <= 2;
led_rgb_R <= RED_COLOR;
end
2: begin // Red to Green
sec0_R <= GREEN_SEC0;
sec1_R <= GREEN_SEC1;
state <= 0;
led_rgb_R <= GREEN_COLOR;
end
endcase
end else begin
sec1_R <= sec1_R - 1; // Decrement timer
sec0_R <= 9;
end
end else begin
sec0_R <= sec0_R - 1;
end
is_red_light_R <= (state == 2);
is_yellow_light_R <= (state == 1);
end else begin
counter <= counter + 1;
is_red_light_R <= (state == 2);
is_yellow_light_R <= (state == 1);
end
end
在主逻辑中,当复位信号 rst_n
为低时,计时器(sec0_R
, sec1_R
)和状态机(state
)初始化为绿灯状态。系统使用一个24位宽的计数器 counter
来实现每秒的倒计时。具体来说,当个位秒数 sec0_R
减至0时,如果十位秒数 sec1_R
大于0,则递减 sec1_R
并将 sec0_R
重置为9;如果 sec0_R
和 sec1_R
都归零(即倒计时完成),则根据当前状态(state
)切换到下一个状态。
其次是Segment模块,此模块负责将倒计时的秒数转换为数码管显示的编码。
reg [8:0] seg [9:0];
initial
begin
seg[0] = 9'h3f;
seg[1] = 9'h06; //7段显示数字 1
seg[2] = 9'h5b; //7段显示数字 2
seg[3] = 9'h4f; //7段显示数字 3
seg[4] = 9'h66; //7段显示数字 4
seg[5] = 9'h6d; //7段显示数字 5
seg[6] = 9'h7d; //7段显示数字 6
seg[7] = 9'h07; //7段显示数字 7
seg[8] = 9'h7f; //7段显示数字 8
seg[9] = 9'h6f; //7段显示数字 9
end
assign seg_led_1 = seg[seg_data_1];
assign seg_led_2 = seg[seg_data_2];
每个数字对应一个9 位的编码,其中第 8 位是小数点(DP),第 0 位到第 7 位是段选信号。
还有Buzzer模块,此模块负责在红灯上下沿切换时触发蜂鸣器提示,因为黄灯仅有3秒钟,而蜂鸣器报警2秒钟,为避免发生误会,故不在黄灯和绿灯之间添加蜂鸣器,在红灯起始和结束添加蜂鸣器,以供提醒红灯的到来和结束。这里主要关注蜂鸣器的输出控制和启用逻辑,
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt <= 1'b1;
else if(cnt >= CYCLE)
cnt <= 1'b1;
else
cnt <= cnt + 1'b1;
always@(posedge clk or negedge rst_n)
if(!rst_n)
beeper_R <= 1'b1;
else if(beeper_en)
if(cnt < CYCLE >>1)
beeper_R <= 1'b1;
else
beeper_R <= 1'b0;
else
beeper_R <= beeper_R;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
is_red_light_d1 <= is_red_light;
end else begin
is_red_light_d1 <= is_red_light;
end
end
根据beeper_en 的值控制蜂鸣器的输出,如果beeper_en 为1,则蜂鸣器在半个周期内输出高电平,另半个周期输出低电平。
// 蜂鸣器响2s
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_2s <= 32'd0;
else if(beeper_en)
cnt_2s <= cnt_2s + 1'b1;
else
cnt_2s <= 32'd0;
end
//上升沿或者下降沿(红绿灯切换的2s时间内)蜂鸣器会工作
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
beeper_en <= 1'b0;
else if(~is_red_light_d1 & is_red_light)
beeper_en <= 1'b1;
else if(is_red_light_d1 & ~is_red_light)
beeper_en <= 1'b1;
else if(cnt_2s == SOUND_MAX << 1)
beeper_en <= 1'b0;
else
beeper_en <= beeper_en;
end
在红灯起始和结束时触发蜂鸣器,蜂鸣器持续响2 秒。
3.3.2 I2C传感器驱动模块(rpr0521rs_driver)
本模块通过I2C协议与RPR0521RS传感器通信,以获取环境光强度(ALS)和接近(Proximity)数据。该过程由一系列状态机控制,包括IDLE、MAIN、MODE1、MODE2、START、WRITE、READ、STOP和DELAY状态,每个状态负责执行特定的步骤。系统时钟通过计数器分频产生400kHz的I2C时钟信号(clk_400khz),确保I2C通信时序稳定。
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 //软件自复位,主要用于程序跑飞后的处理----------------0
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'd5) cnt_mode1 <= 1'b0; //对START中的子状态执行控制cnt_start
else cnt_mode1 <= cnt_mode1 + 1'b1;
state_back <= MODE1;
case(cnt_mode1)
4'd0: begin state <= START; end //I2C通信时序中的START
4'd1: begin data_wr <= dev_addr<<1; state <= WRITE; end //设备地址
4'd2: begin data_wr <= reg_addr; state <= WRITE; end //寄存器地址
4'd3: begin data_wr <= reg_data; state <= WRITE; end //写入数据
4'd4: begin state <= STOP; end //I2C通信时序中的STOP
4'd5: begin state <= MAIN; end //返回MAIN
default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
endcase
end
MODE2:begin //两次读操作----------------------3
if(cnt_mode2 >= 4'd10) cnt_mode2 <= 1'b0; //对START中的子状态执行控制cnt_start
else cnt_mode2 <= cnt_mode2 + 1'b1;
state_back <= MODE2;
case(cnt_mode2)
4'd0: begin state <= START; end //I2C通信时序中的START
4'd1: begin data_wr <= dev_addr<<1; state <= WRITE; end //设备地址
4'd2: begin data_wr <= reg_addr; state <= WRITE; end //寄存器地址
4'd3: begin state <= START; end //I2C通信时序中的START
4'd4: begin data_wr <= (dev_addr<<1)|8'h01; state <= WRITE; end //设备地址
4'd5: begin ack <= ACK; state <= READ; end //读寄存器数据
4'd6: begin dat_l <= data_r; end
4'd7: begin ack <= NACK; state <= READ; end //读寄存器数据
4'd8: begin dat_h <= data_r; end
4'd9: begin state <= STOP; end //I2C通信时序中的STOP
4'd10: begin state <= MAIN; end //返回MAIN
default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
endcase
end
START:begin //I2C通信时序中的起始START------------------------4
if(cnt_start >= 3'd5) cnt_start <= 1'b0; //对START中的子状态执行控制cnt_start
else cnt_start <= cnt_start + 1'b1;
case(cnt_start)
3'd0: begin sda <= 1'b1; scl <= 1'b1; end //将SCL和SDA拉高,保持4.7us以上
3'd1: begin sda <= 1'b1; scl <= 1'b1; end //clk_400khz每个周期2.5us,需要两个周期
3'd2: begin sda <= 1'b0; end //SDA拉低到SCL拉低,保持4.0us以上
3'd3: begin sda <= 1'b0; end //clk_400khz每个周期2.5us,需要两个周期
3'd4: begin scl <= 1'b0; end //SCL拉低,保持4.7us以上
3'd5: begin scl <= 1'b0; state <= state_back; end //clk_400khz每个周期2.5us,需要两个周期,返回MAIN
default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
endcase
end
WRITE:begin //I2C通信时序中的写操作WRITE和相应判断操作ACK-----------------------------5
if(cnt <= 3'd6) begin //共需要发送8bit的数据,这里控制循环的次数
if(cnt_write >= 3'd3) begin cnt_write <= 1'b0; cnt <= cnt + 1'b1; end
else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end
end else begin
if(cnt_write >= 3'd7) begin cnt_write <= 1'b0; cnt <= 1'b0; end //两个变量都恢复初值
else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end
end
case(cnt_write)
//按照I2C的时序传输数据
3'd0: begin scl <= 1'b0; sda <= data_wr[7-cnt]; end //SCL拉低,并控制SDA输出对应的位
3'd1: begin scl <= 1'b1; end //SCL拉高,保持4.0us以上
3'd2: begin scl <= 1'b1; end //clk_400khz每个周期2.5us,需要两个周期
3'd3: begin scl <= 1'b0; end //SCL拉低,准备发送下1bit的数据
//获取从设备的响应信号并判断
3'd4: begin sda <= 1'bz; end //释放SDA线,准备接收从设备的响应信号
3'd5: begin scl <= 1'b1; end //SCL拉高,保持4.0us以上
3'd6: begin ack_flag <= i2c_sda; end //获取从设备的响应信号并判断
3'd7: begin scl <= 1'b0; if(ack_flag)state <= state; else state <= state_back; end //SCL拉低,如果不应答循环写
default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
endcase
end
READ:begin //I2C通信时序中的读操作READ和返回ACK的操作----------------------------6
if(cnt <= 3'd6) begin //共需要接收8bit的数据,这里控制循环的次数
if(cnt_read >= 3'd3) begin cnt_read <= 1'b0; cnt <= cnt + 1'b1; end
else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end
end else begin
if(cnt_read >= 3'd7) begin cnt_read <= 1'b0; cnt <= 1'b0; end //两个变量都恢复初值
else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end
end
case(cnt_read)
//按照I2C的时序接收数据
3'd0: begin scl <= 1'b0; sda <= 1'bz; end //SCL拉低,释放SDA线,准备接收从设备数据
3'd1: begin scl <= 1'b1; end //SCL拉高,保持4.0us以上
3'd2: begin data_r[7-cnt] <= i2c_sda; end //读取从设备返回的数据
3'd3: begin scl <= 1'b0; end //SCL拉低,准备接收下1bit的数据
//向从设备发送响应信号
3'd4: begin sda <= ack; end //发送响应信号,将前面接收的数据锁存
3'd5: begin scl <= 1'b1; end //SCL拉高,保持4.0us以上
3'd6: begin scl <= 1'b1; end //SCL拉高,保持4.0us以上
3'd7: begin scl <= 1'b0; state <= state_back; end //SCL拉低,返回MAIN状态
default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
endcase
end
STOP:begin //I2C通信时序中的结束STOP-----------------------------7
if(cnt_stop >= 3'd5) cnt_stop <= 1'b0; //对STOP中的子状态执行控制cnt_stop
else cnt_stop <= cnt_stop + 1'b1;
case(cnt_stop)
3'd0: begin sda <= 1'b0; end //SDA拉低,准备STOP
3'd1: begin sda <= 1'b0; end //SDA拉低,准备STOP
3'd2: begin scl <= 1'b1; end //SCL提前SDA拉高4.0us
3'd3: begin scl <= 1'b1; end //SCL提前SDA拉高4.0us
3'd4: begin sda <= 1'b1; end //SDA拉高
3'd5: begin sda <= 1'b1; state <= state_back; end //完成STOP操作,返回MAIN状态
default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
endcase
end
DELAY:begin //12ms延时---------------------------8
if(cnt_delay >= num_delay) begin
cnt_delay <= 1'b0;
state <= MAIN;
end else cnt_delay <= cnt_delay + 1'b1;
end
default:;
endcase
end
end
MAIN
状态负责配置写入和数据读取;MODE1
和MODE2
分别处理写入配置和读取数据的操作;START产生I2C起始条件;WRITE按照I2C协议发送数据,并检查从设备的响应信号;READ接收数据并发送确认信号给从设备;STOP生成I2C停止条件。
3.3.3 二进制转BCD码模块(bin_to_bcd)
此模块将32位二进制数据转换为BCD码,用于数码管显示十进制数值。
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次
//BCD码各位数据作满5加3操作,
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[63:32]; //[67:32]
end
end
每次左移后,若某BCD位值≥5,则加3以避免溢出。
3.3.4 蜂鸣器控制模块(beeper)
此模块根据交通灯状态和行人接近距离,生成不同频率的PWM信号驱动蜂鸣器。
//第6段:模式一:无人接近时
//上升沿或者下降沿(红绿灯切换的2s时间内)蜂鸣器会工作
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
beeper_en <= 1'b0;
else
if(prox_dat2 <= ThreHold)
if(~is_red_light_d1 & is_red_light)
beeper_en <= 1'b1;
else if(is_red_light_d1 & ~is_red_light)
beeper_en <= 1'b1;
else if(cnt_2s == CNT_1s_MAX << 1)
beeper_en <= 1'b0;
else
beeper_en <= beeper_en;
else
beeper_en <= 1'b0;
end
//第7段:模式二:有人接近时
//随着人接近的距离,音调逐渐变高
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
beeper_en2 <= 1'b0;
else
if(prox_dat2 >= ThreHold)
if(cnt_alarm == ALARM_PRT)
beeper_en2 <= ~beeper_en2;
else
beeper_en2 <= beeper_en2;
else
beeper_en2 <= 1'b0;
end
模式一:通过检测is_red_light的上升沿或者下降沿,触发2秒定时报警(cnt_2s计数器)。
模式二:根据prox_dat2(接近传感器数据)动态选择PWM周期参数(cycle),距离越近,频率越高。
3.3.5 数码管驱动模块(segment_scan)
此模块通过74HC595移位寄存器驱动8位数码管动态扫描显示。
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.3.6 交通灯倒计时模块(yellow_func)
此模块生成交通灯倒计时数据(秒级),控制RGB LED状态。
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
counter <= 0;
sec0_R <= GREEN_SEC0;
sec1_R <= GREEN_SEC1;
state <= 0; // Start with green light
led_rgb_R <= GREEN_COLOR;
end else if (counter == CNT_1s_MAX - 1) begin
counter <= 0; // Reset counter every second
if (sec0_R == 0) begin
if (sec1_R == 0) begin
case (state)
0: begin // Green to Yellow
sec0_R <= YELLOW_SEC0;
sec1_R <= YELLOW_SEC1;
state <= 1;
led_rgb_R <= YELLOW_COLOR;
end
1: begin // Yellow to Red
sec0_R <= RED_SEC0;
sec1_R <= RED_SEC1;
state <= 2;
led_rgb_R <= RED_COLOR;
end
2: begin // Red to Green
sec0_R <= GREEN_SEC0;
sec1_R <= GREEN_SEC1;
state <= 0;
led_rgb_R <= GREEN_COLOR;
end
endcase
end else begin
sec1_R <= sec1_R - 1; // Decrement timer
sec0_R <= 9;
end
end else begin
sec0_R <= sec0_R - 1;
end
is_red_light_R <= (state == 2);
is_yellow_light_R <= (state == 1);
end else begin
counter <= counter + 1;
is_red_light_R <= (state == 2);
is_yellow_light_R <= (state == 1);
end
end
当rst_n
为低电平时,所有变量被初始化,包括计数器counter
、个位和十位显示秒数sec0_R
和sec1_R
、状态state
以及RGB LED的颜色。如果计数器达到了CNT_1s_MAX - 1
,则重置计数器,并检查是否需要更新秒数显示和交通灯状态。当秒数显示达到0(即sec0_R == 0
且sec1_R == 0
),会切换到下一个状态(绿色->黄色->红色->绿色),并相应地更新RGB LED的颜色。若仅个位秒数到达0,则将个位重置为9,并减少十位秒数sec1_R
。
3.3.7 FPGA资源占用
图形化占用
diamond占用
四、功能展示图
4.1绿灯倒计时
如图,我们看到绿灯的点亮,同时此时是剩余2秒的倒计时。
4.2黄灯点亮
如图,我们看到黄灯的点亮,同时此时是剩余2秒的倒计时。
4.3红灯点亮
如图,我们看到红灯的点亮,同时此时是剩余17秒的倒计时。
4.4检测人员靠近
如图,从图中我们看到,数码管左四位是显示接近值,当检测到有人靠近时,接近值将会变大很大,同时蜂鸣器会发出警报。
4.5环境光感知
如图,我们考到数码管右四位显示的是环境光的感知值,当这个值小于100时将会点亮单色灯,而当值超过100,也就是环境较亮时,单色灯处于关闭的状态。
五、项目中遇到的问题及解决方法
5.1问题
5.1.1
如何将多个功能模块(红绿灯控制、数码管显示、蜂鸣器提示、环境光传感器数据处理)集成到一个顶层模块中,并确保各模块之间的信号传递和协同工作。
5.1.2
如何确保红绿灯的倒计时、蜂鸣器的提示音和数码管的显示在时间上同步和如何从环境光传感器获取有效数据,并将其转换为可显示的格式。
5.2解决方法
5.2.1
使用清晰的接口定义,确保每个模块的输入输出信号明确;并通过顶层模块top_seg 将各子模块实例化,并正确连接信号。
5.2.2
使用统一的时钟信号clk 和复位信号rst_n,确保所有模块在同一时钟域下工作;使用rpr0521rs_driver 模块驱动环境光传感器,获取环境光和接近数据。
六、心得体会
本项目通过STEP MXO2 LPC核心板与Baseboard 4.0底板的硬件平台,实现了多模块协同的智能交通灯控制系统。通过这个项目,我深刻体会到了模块化设计和时序控制在硬件设计中的重要性。通过将系统分解为多个功能模块,可以提高代码的可读性和可维护性。同时,精确的时序控制可以确保各模块的同步和系统的稳定性。