2025寒假练 - 基于小脚丫FPGA实现交通灯控制系统
该项目使用了小脚丫FPGA,实现了交通灯控制系统的设计,它的主要功能为:在数码管上显示计时信息、蜂鸣器报警、接近传感器检测人员走近、环境光感知,自动点亮路灯。
标签
FPGA
Wiki
更新2025-03-17
华南理工大学
25


项目总结报告

项目介绍(webide的项目链接:https://www.stepfpga.com/project/17248/gui?type=top)



完全使用 WebIDE 的图形化编程,使用常规的 74 系列的元器件,构建一个交通灯控制系统:

  1. 在数码管上显示计时信息 - 图形化

  2. 蜂鸣器报警 - 图形化

  3. 接近传感器检测人员走近 -Verilog

  4. 环境光感知,自动点亮路灯(小脚丫核心板上的单色 LED)- Verilog

软硬件介绍

  1. Lattice DIamond:



    Lattice Diamond 是一款由 Lattice Semiconductor 开发的 FPGA 设计软件,适用于低功耗、小尺寸的 FPGA 器件。它提供全面的设计工具,包括综合、布局布线、仿真和调试功能,支持 Lattice 的 FPGA 系列,如 iCE40、ECP5 和 MachXO。Diamond 界面友好,集成第三方工具,适用于消费电子、工业控制和通信等领域,帮助开发者高效实现复杂设计。





  2. Visual Studio Code:



    Visual Studio Code(简称 VSCode)是由微软开发的免费、开源的代码编辑器,支持 Windows、macOS 和 Linux 系统。它以轻量、高效著称,内置对 JavaScript、TypeScript 和 Node.js 的深度支持,并通过丰富的扩展插件支持多种编程语言和框架。VSCode 提供智能代码补全、调试、Git 集成、终端等功能,界面简洁且高度可定制,深受开发者喜爱,适用于前端、后端、数据科学等多种开发场景。




    image-20250309000512092.png


  3. 基于 Lattice MXO2 的小脚丫 FPGA 核心板:



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




    image-20250309000541980.png




    4、STEP BaseBoard V4.0 扩展底板



    STEP BaseBoard V4.0 是第 4 代小脚丫 FPGA 扩展底板,可以用于全系列小脚丫核心板的功能扩展,采用 100mm×161.8mm 的黄金比例尺寸,板子集成了存储器、温湿度传感器、接近式传感器、矩阵键盘、旋转编码器、HDMI 接口、RGBLCD 液晶屏、8 个 7 位数码管、蜂鸣器模块、UART 通信模块、ADC 模块、DAC 模块和 WIFI 通信模块,配合小脚丫 FPGA 板能够完成多种实验,是数字逻辑、微机原理、可编程逻辑语言以及 EDA 设计工具等课程完美的实验平台。




    image-20250309000554250.png


方案框图和项目设计思路介绍



实验一和实验二方案框图:

image-20250309000657744.png




实验一和实验二为交通灯控制系统,采用状态机实现红灯(S1)、绿灯(S2)和黄灯(S3)的状态切换。系统通过分频器模块将 12MHz 时钟信号转换为 1Hz 和 1kHz 信号,分别用于控制交通灯状态切换和蜂鸣器模块。数码管模块用于显示当前状态,蜂鸣器在绿灯状态时发出提示音。整体设计简洁高效,确保交通灯按预定时序切换,适用于模拟交通信号控制场景。



实验三和实验四方案框图:


image-20250309000710976.png




实验三和实验四设计一个智能环境光与行人检测系统,通过环境光和接近传感器实时监测周围环境。当检测到行人接近时,系统会亮起三色 LED 灯以提供视觉提示。若环境光低于 200lux,系统将自动亮起单色 LED 灯以增强照明。底板上的数码管实时显示当前环境亮度数值,便于用户了解光照情况。


软件流程图和关键代码介绍



实验一和实验二图像化编程:

image-20250309000735814.png




系统通过分频器模块将 12MHz 时钟信号转换为 1Hz 和 1kHz 信号,分别用于控制交通灯状态切换和蜂鸣器模块。数码管模块用于显示当前状态,蜂鸣器在绿灯状态时发出提示音。



状态机模块:


parameter S1 = 2'b00, S2 = 2'b01, S3 = 2'b10;

parameter time_s1 = 4'd9, time_s2 = 4'd6, time_s3 = 4'd3;

parameter led_s1 = 3'b011, led_s2 = 3'b101, led_s3 = 3'b001; // 修改为3位



reg [1:0] cur_state;

reg [1:0] next_state;

reg [3:0] timecont;

reg [2:0] led_out;

reg buzzer_out;



always @ (posedge clk or negedge rst_n)

begin

if(!rst_n) begin

cur_state <= S1;

led_out <= led_s1;

timecont <= time_s1;

buzzer_out <= 0; // 初始化蜂鸣器

end

else begin

cur_state <= next_state;

case(next_state)

S1:begin

led_out <= led_s1;

buzzer_out <= 0; // 红灯时蜂鸣器关闭

if(timecont == 1)

timecont <= time_s1;

else

timecont <= timecont - 1;

end



S2:begin

led_out <= led_s2;

buzzer_out <= 1;

if(timecont == 1)

timecont <= time_s2;

else

timecont <= timecont - 1;

end



S3:begin

led_out <= led_s3;

buzzer_out <= 0; // 黄灯时蜂鸣器关闭

if(timecont == 1)

timecont <= time_s3;

else

timecont <= timecont - 1;

end



default:begin

led_out <= led_s1;

buzzer_out <= 0; // 默认情况下蜂鸣器关闭

end

endcase

end

end



always @ (cur_state or rst_n or timecont)

begin

if(!rst_n) begin

next_state = S1;

end else begin

case(cur_state)

S1:begin

if(timecont==1)

next_state = S2;

else

next_state = S1;

end



S2:begin

if(timecont==1)

next_state = S3;

else

next_state = S2;

end



S3:begin

if(timecont==1)

next_state = S1;

else

next_state = S3;

end



default: next_state = S1;

endcase

end

end



这段代码实现了交通灯控制系统的基本功能,包括状态切换、时间控制和输出控制。



数码管模块:

wire [8:0] seg_out;

reg [8:0] seg [9:0];



initial begin

seg[0] = 9'h3f;

seg[1] = 9'h06;

seg[2] = 9'h5b;

seg[3] = 9'h4f;

seg[4] = 9'h66;

seg[5] = 9'h6d;

seg[6] = 9'h7d;

seg[7] = 9'h07;

seg[8] = 9'h7f;

seg[9] = 9'h6f;

end



assign seg_out = seg[timecont];



这段代码实现了一个数码管显示模块,用于控制数码管的显示内容。



蜂鸣器模块:

assign OUT = EN ? CLK : 1'b0;



这段代码实现了对蜂鸣器的简单控制,当使能 EN 引脚时,蜂鸣器以 1kHz 的频率响起。



实验三和实验四代码模块:



bin_to_bcd.v:

module bin_to_bcd(

input rst_n, //系统复位,低有效

input [31:0] bin_code, //需要进行BCD转码的二进制数据

output reg [31:0] bcd_code //转码后的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次

//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[67:36];

end

end



endmodule



这段代码实现了一个将 32 位二进制数转换为 BCD 码(二进制编码的十进制数)的模块。其主要功能是将输入的 32 位二进制数据(bin_code)转换为对应的 BCD 码,并通过 bcd_code 输出。该模块常用于将 ADC(模数转换器)采样的二进制数据转换为易于显示的十进制格式。



decoder.v:

module decoder(

input rst_n,

input dat_valid,

input [15:0] prox_dat,

input [15:0] ch0_dat,

input [15:0] ch1_dat,

output [31:0] lux_data,

output reg [2:0] Y_out

);



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



always @(prox_dat2[11:9]) begin

case (prox_dat2[11:9])

3'b000: Y_out = 3'b111;

3'b001: Y_out = 3'b111;

3'b010: Y_out = 3'b111;

3'b011: Y_out = 3'b111;

3'b100: Y_out = 3'b000;

3'b101: Y_out = 3'b000;

3'b110: Y_out = 3'b000;

3'b111: Y_out = 3'b000;

default:Y_out = 3'b111;

endcase

end



wire [31:0] data0,data1;

reg [31:0] lux;



assign data0 = ch0_dat;

assign data1 = ch1_dat;



always @(data0 or data1) begin

if(data1 < data0 * 595)

lux = data0 * 1682 - data1 * 1877;

else if(data1 < data0 * 1015)

lux = data0 * 644 - data1 * 132;

else if(data1 < data0 * 1352)

lux = data0 * 756 - data1 * 243;

else if((data1 < data0 * 3053))

lux = data0 * 766 - data1 * 25;

else

lux = 0;

end



//进行BCD转码处理

wire [31:0] T_data_bcd;



bin_to_bcd u1(

.rst_n (rst_n ), //系统复位,低有效

.bin_code (lux ), //需要进行BCD转码的二进制数据

.bcd_code (T_data_bcd ) //转码后的BCD码型数据输出

);



assign lux_data = T_data_bcd;



endmodule



这段代码实现了一个接近传感器和环境光传感器模块,主要用于处理接近传感器和环境光传感器的数据,并输出相应的光照强度值和状态信号。



prox_detect.v:

module prox_detect(

input clk,

input rst_n,



output i2c_scl, //I2C时钟总线

inout i2c_sda, //I2C数据总线

output seg_rck, // 74HC595的RCK管脚

output seg_sck, // 74HC595的SCK管脚

output seg_din, // 74HC595的SER管脚



output reg [7:0] led, //led灯

output [2:0] led_RGB //led三色灯

);



wire dat_valid;

wire [15:0] ch0_dat, ch1_dat, prox_dat;

wire [31:0] lux_data;



rpr0521rs_driver u1(

.clk (clk ), //系统时钟

.rst_n (rst_n ), //系统复位,低有效

.i2c_scl (i2c_scl ), //I2C总线SCL

.i2c_sda (i2c_sda ), //I2C总线SDA



.dat_valid (dat_valid ), //数据有效脉冲

.ch0_dat (ch0_dat ), //ALS数据

.ch1_dat (ch1_dat ), //IR数据

.prox_dat (prox_dat ) //Prox数据

);



decoder u2(

.rst_n (rst_n ),

.dat_valid (dat_valid ),

.ch0_dat (ch0_dat ),

.ch1_dat (ch1_dat ),

.prox_dat (prox_dat ),

.lux_data (lux_data ),

.Y_out (led_RGB )

);



segment_scan u4(

.clk (clk ), //系统时钟 12MHz

.rst_n (rst_n ), //系统复位 低有效

.dat_1 (lux_data[31:28]), //SEG1 显示的数据输入

.dat_2 (lux_data[27:24]), //SEG2 显示的数据输入

.dat_3 (lux_data[23:20]), //SEG3 显示的数据输入

.dat_4 (lux_data[19:16]), //SEG4 显示的数据输入

.dat_5 (lux_data[15:12]), //SEG5 显示的数据输入

.dat_6 (lux_data[11:08]), //SEG6 显示的数据输入

.dat_7 (lux_data[07:04]), //SEG7 显示的数据输入

.dat_8 (lux_data[03:00]), //SEG8 显示的数据输入

.dat_en (8'b1111_1111 ), //数码管数据位显示使能,[MSB~LSB]=[SEG1~SEG8]

.dot_en (8'b0000_0100 ), //数码管小数点位显示使能,[MSB~LSB]=[SEG1~SEG8]

.seg_rck (seg_rck ), //74HC595的RCK管脚

.seg_sck (seg_sck ), //74HC595的SCK管脚

.seg_din (seg_din ) //74HC595的SER管脚

);



//当光照强度低于200lux时,led灯全亮

always @(posedge clk or negedge rst_n) begin

if(!rst_n)

led <= 8'b1111_1111;

else if (lux_data[31:8] < 200)

led <= 8'b0000_0000;

else

led <= 8'b1111_1111;

end



endmodule



这段代码实现了一个接近检测和光照强度监测系统,主要功能是通过 I2C 接口与 RPR-0521RS 传感器通信,获取环境光(ALS)、红外(IR)和接近(Prox)数据,并根据这些数据控制 LED 灯和数码管显示。



rpr0521rs_driver.v:

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数据

);



parameter CNT_NUM = 15;



localparam IDLE = 4'd0;

localparam MAIN = 4'd1;

localparam MODE1 = 4'd2;

localparam MODE2 = 4'd3;

localparam START = 4'd4;

localparam WRITE = 4'd5;

localparam READ = 4'd6;

localparam STOP = 4'd7;

localparam DELAY = 4'd8;



localparam ACK = 1'b0;

localparam NACK = 1'b1;



//使用计数器分频产生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



reg scl,sda,ack,ack_flag;

reg [3:0] cnt, cnt_main, cnt_mode1, cnt_mode2, cnt_start, cnt_write, cnt_read, cnt_stop;

reg [7:0] data_wr, dev_addr, reg_addr, reg_data, data_r, dat_l, dat_h;

reg [23:0] cnt_delay, num_delay;

reg [3:0] state, state_back;



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

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 //单次写操作

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 //两次读操作

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

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

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的操作

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

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延时

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



assign i2c_scl = scl; //对SCL端口赋值

assign i2c_sda = sda; //对SDA端口赋值



endmodule



这段代码实现了一个 I2C 通信驱动模块,用于与 RPR-0521RS 传感器进行通信,读取环境光(ALS)、红外(IR)和接近(Prox)数据。



segment_scan.v:

module segment_scan(

input clk, //系统时钟 12MHz

input rst_n, //系统复位 低有效

input [3:0] dat_1, //SEG1 显示的数据输入

input [3:0] dat_2, //SEG2 显示的数据输入

input [3:0] dat_3, //SEG3 显示的数据输入

input [3:0] dat_4, //SEG4 显示的数据输入

input [3:0] dat_5, //SEG5 显示的数据输入

input [3:0] dat_6, //SEG6 显示的数据输入

input [3:0] dat_7, //SEG7 显示的数据输入

input [3:0] dat_8, //SEG8 显示的数据输入

input [7:0] dat_en, //数码管数据位显示使能,[MSB~LSB]=[SEG1~SEG8]

input [7:0] dot_en, //数码管小数点位显示使能,[MSB~LSB]=[SEG1~SEG8]

output reg seg_rck, //74HC595的RCK管脚

output reg seg_sck, //74HC595的SCK管脚

output reg seg_din //74HC595的SER管脚

);



localparam CNT_40KHz = 300; //分频系数



localparam IDLE = 3'd0;

localparam MAIN = 3'd1;

localparam WRITE = 3'd2;

localparam LOW = 1'b0;

localparam HIGH = 1'b1;



//创建数码管的字库,字库数据依段码顺序有关

//这里字库数据[MSB~LSB]={G,F,E,D,C,B,A}

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



//计数器对系统时钟信号进行计数

reg [9:0] cnt = 1'b0;



always @(posedge clk or negedge rst_n) begin

if(!rst_n)

cnt <= 1'b0;

else if(cnt >= (CNT_40KHz - 1))

cnt <= 1'b0;

else

cnt <= cnt + 1'b1;

end



//根据计数器计数的周期产生分频的脉冲信号

reg clk_40khz = 1'b0;



always @(posedge clk or negedge rst_n) begin

if(!rst_n)

clk_40khz <= 1'b0;

else if(cnt < (CNT_40KHz >> 1))

clk_40khz <= 1'b0;

else

clk_40khz <= 1'b1;

end



//使用状态机完成数码管的扫描和74HC595时序的实现

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



endmodule



这段代码实现了一个数码管扫描显示模块,用于控制 8 位数码管的显示内容。

功能展示图及说明


image-20250309000859578.png



image-20250309000912645.png



image-20250309000917356.png




红灯经过 9s 后变成绿灯,此时蜂鸣器响起,经过 6s 后变成黄灯,再经过 3s 后重新变回红灯。


image-20250309000931909.png




当光照强度低于 200lux 时,8 个单色 led 灯亮起。


image-20250309000946734.png




当检测到有人通过时,三色 led 亮起。


FPGA 资源占用


实验一和实验二:

微信截图_20250309002731.png

实验三和实验四:

image-20250309001005526.png



项目中遇到的难题和解决方法



本次项目遇到的难题就是使用 WebIDE 进行图形化编程:

  1. 当我增加完或者修改完模块的时候,返回顶层设计,顶层设计都会消失;解决方法就是首先先把所有的模块都写完测试完在进行顶层设计。

  2. 第二个遇到的问题就是我按 ctrl+s 保存,有时候还是会丟顶层设计;解决方法就是不要按 ctrl+s 保存,而是按左上角的保存设计视图。

  3. 第三个遇到的问题还是增加和修改模块的问题,增加和修改模块完后按回车会丢失全部的设计;解决方法就是按左上角的保存模块。

对本次活动的心得体会(包括意见或建议)



通过参与本次基于 WebIDE 的图形化编程项目,我深刻体会到了 FPGA 开发在数字电路设计中的强大功能和灵活性。项目中使用 Lattice Diamond 和 Visual Studio Code 等工具,结合小脚丫 FPGA 核心板和扩展底板,成功实现了交通灯控制系统和智能环境光检测系统。这不仅让我对 FPGA 的硬件描述语言(Verilog)有了更深的理解,也让我熟悉了图形化编程的便捷性和局限性。



在项目中,我遇到了一些挑战,尤其是在使用 WebIDE 进行图形化编程时,模块的增加和修改常常导致顶层设计丢失。通过不断尝试和调整,我学会了先完成所有模块的设计和测试,再进行顶层设计的整合,避免了频繁的保存和修改带来的问题。此外,我也意识到图形化编程虽然直观易用,但在复杂系统的设计中,仍需结合代码编写来实现更精细的控制。



总的来说,本次活动不仅提升了我的技术能力,也让我对 FPGA 的应用场景有了更广泛的认识。我建议未来的项目中可以增加更多实际应用案例,帮助学员更好地将理论知识应用于实践。同时,提供更多的调试工具和资源,将有助于提高开发效率和项目质量。


附件下载
prox_detect.rar
实验三和实验四
implement.jed
实验一和实验二
团队介绍
黄伟健,华南理工大学
团队成员
Wiki
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号