项目总结报告
项目介绍(webide的项目链接:https://www.stepfpga.com/project/17248/gui?type=top)
完全使用 WebIDE 的图形化编程,使用常规的 74 系列的元器件,构建一个交通灯控制系统:
- 在数码管上显示计时信息 - 图形化
- 蜂鸣器报警 - 图形化
- 接近传感器检测人员走近 -Verilog
- 环境光感知,自动点亮路灯(小脚丫核心板上的单色 LED)- Verilog
软硬件介绍
- Lattice DIamond:
Lattice Diamond 是一款由 Lattice Semiconductor 开发的 FPGA 设计软件,适用于低功耗、小尺寸的 FPGA 器件。它提供全面的设计工具,包括综合、布局布线、仿真和调试功能,支持 Lattice 的 FPGA 系列,如 iCE40、ECP5 和 MachXO。Diamond 界面友好,集成第三方工具,适用于消费电子、工业控制和通信等领域,帮助开发者高效实现复杂设计。 - Visual Studio Code:
Visual Studio Code(简称 VSCode)是由微软开发的免费、开源的代码编辑器,支持 Windows、macOS 和 Linux 系统。它以轻量、高效著称,内置对 JavaScript、TypeScript 和 Node.js 的深度支持,并通过丰富的扩展插件支持多种编程语言和框架。VSCode 提供智能代码补全、调试、Git 集成、终端等功能,界面简洁且高度可定制,深受开发者喜爱,适用于前端、后端、数据科学等多种开发场景。 - 基于 Lattice MXO2 的小脚丫 FPGA 核心板:
STEP 小脚丫 FPGA 学习平台是苏州思得普信息科技公司专门针对 FPGA 初学者(尤其是学习数字电路的在校同学)打造的一系列性价比最高、学习门槛最低的学习模块系列。板上选用的芯片兼具了 FPGA 和 CPLD 的优点,瞬时上电启动,无需外部重新配置 FPGA,是学习数字逻辑绝佳的选择。系列中所有板子的大小兼容标准的 DIP40 封装,尺寸只有 52mm×18mm,非常便于携带,而且能够直接插在面包板上或以模块的方式放置在其它电路板上以即插即用的方式,大大简化系统的设计。
4、STEP BaseBoard V4.0 扩展底板
STEP BaseBoard V4.0 是第 4 代小脚丫 FPGA 扩展底板,可以用于全系列小脚丫核心板的功能扩展,采用 100mm×161.8mm 的黄金比例尺寸,板子集成了存储器、温湿度传感器、接近式传感器、矩阵键盘、旋转编码器、HDMI 接口、RGBLCD 液晶屏、8 个 7 位数码管、蜂鸣器模块、UART 通信模块、ADC 模块、DAC 模块和 WIFI 通信模块,配合小脚丫 FPGA 板能够完成多种实验,是数字逻辑、微机原理、可编程逻辑语言以及 EDA 设计工具等课程完美的实验平台。
方案框图和项目设计思路介绍
实验一和实验二方案框图:
实验一和实验二为交通灯控制系统,采用状态机实现红灯(S1)、绿灯(S2)和黄灯(S3)的状态切换。系统通过分频器模块将 12MHz 时钟信号转换为 1Hz 和 1kHz 信号,分别用于控制交通灯状态切换和蜂鸣器模块。数码管模块用于显示当前状态,蜂鸣器在绿灯状态时发出提示音。整体设计简洁高效,确保交通灯按预定时序切换,适用于模拟交通信号控制场景。
实验三和实验四方案框图:
实验三和实验四设计一个智能环境光与行人检测系统,通过环境光和接近传感器实时监测周围环境。当检测到行人接近时,系统会亮起三色 LED 灯以提供视觉提示。若环境光低于 200lux,系统将自动亮起单色 LED 灯以增强照明。底板上的数码管实时显示当前环境亮度数值,便于用户了解光照情况。
软件流程图和关键代码介绍
实验一和实验二图像化编程:
系统通过分频器模块将 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 位数码管的显示内容。
功能展示图及说明
红灯经过 9s 后变成绿灯,此时蜂鸣器响起,经过 6s 后变成黄灯,再经过 3s 后重新变回红灯。
当光照强度低于 200lux 时,8 个单色 led 灯亮起。
当检测到有人通过时,三色 led 亮起。
FPGA 资源占用
实验一和实验二:
实验三和实验四:
项目中遇到的难题和解决方法
本次项目遇到的难题就是使用 WebIDE 进行图形化编程:
- 当我增加完或者修改完模块的时候,返回顶层设计,顶层设计都会消失;解决方法就是首先先把所有的模块都写完测试完在进行顶层设计。
- 第二个遇到的问题就是我按 ctrl+s 保存,有时候还是会丟顶层设计;解决方法就是不要按 ctrl+s 保存,而是按左上角的保存设计视图。
- 第三个遇到的问题还是增加和修改模块的问题,增加和修改模块完后按回车会丢失全部的设计;解决方法就是按左上角的保存模块。
对本次活动的心得体会(包括意见或建议)
通过参与本次基于 WebIDE 的图形化编程项目,我深刻体会到了 FPGA 开发在数字电路设计中的强大功能和灵活性。项目中使用 Lattice Diamond 和 Visual Studio Code 等工具,结合小脚丫 FPGA 核心板和扩展底板,成功实现了交通灯控制系统和智能环境光检测系统。这不仅让我对 FPGA 的硬件描述语言(Verilog)有了更深的理解,也让我熟悉了图形化编程的便捷性和局限性。
在项目中,我遇到了一些挑战,尤其是在使用 WebIDE 进行图形化编程时,模块的增加和修改常常导致顶层设计丢失。通过不断尝试和调整,我学会了先完成所有模块的设计和测试,再进行顶层设计的整合,避免了频繁的保存和修改带来的问题。此外,我也意识到图形化编程虽然直观易用,但在复杂系统的设计中,仍需结合代码编写来实现更精细的控制。
总的来说,本次活动不仅提升了我的技术能力,也让我对 FPGA 的应用场景有了更广泛的认识。我建议未来的项目中可以增加更多实际应用案例,帮助学员更好地将理论知识应用于实践。同时,提供更多的调试工具和资源,将有助于提高开发效率和项目质量。