环境配置
- WebIDE:作为平台推荐的开发网页,WebIDE页面简洁,基础功能齐全,简单易上手,非常适合我这样的初学者。并且无需安装,直接编程,减少了在安装软件并熟悉建立项目操作时所耗费的时间。
硬件介绍
核心器件:Lattice LCMXO2-4000HC-4MG132
- 132脚BGA封装,引脚间距0.5mm,芯片尺寸8mm x 8mm;
- 上电瞬时启动,启动时间<1ms;
- 4320个LUT资源, 96Kbit 用户闪存,92Kbit RAM;
- 2+2路PLL+DLL;
- 嵌入式功能块(硬核):一路SPI、一路定时器、2路I2C
- 支持DDR/DDR2/LPDDR存储器;
- 104个可热插拔I/O;
- 内核电压2.5-3.3V;
板载资源:
- 两位7段数码管;
- 两个RGB三色LED;
- 8路用户LED;
- 4路拨码开关;
- 4路按键;
- 36个用户可扩展I/O(其中包括一路SPI硬核接口和一路I2C硬核接口)
- 支持的开发工具思德普开发的Web IDE以及Lattice官方提供的Diamond
- 支持MICO32/8软核处理器以及RISC-V软核
- 板上集成FPGA编程器,采用U盘的模式
- 一路USB Type C接口,可用于给核心板供电、给FPGA下载JED文件以及同上位机通过UART通信
- 板卡尺寸52mm x 18mm
项目需求
通过小脚丫FPGA核心板上的2个数码管和轻触按键制作一个秒表,通过按键来控制秒表的功能,并在数码管上显示数值。
使用七段显示器作为输出设备,在小脚丫FPGA核心板上创建一个2位数秒表。
秒表应从 0.0 秒计数到 9.9秒,然后翻转,计数值每0.1秒精确更新一次。
秒表使用四个按钮输入:开始、停止、增量和清除(重置)。
- 开始输入使秒表开始以10Hz时钟速率递增(即每0.1秒计数一次);
- 停止输入使计数器停止递增,但使数码管显示当前计数器值;
- 每次按下按钮时,增量输入都会导致显示值增加一次,无论按住增量按钮多长时间;
- 复位/清除输入强制计数器值为零。
需求分析
- 秒表计时功能:
计时器精确度:确保计时器在每0.1秒更新一次,以实现秒表精确计时。
计时范围:秒表应该能够从0.0秒计数到9.9秒,然后重新计数。
计数器设计:设计一个计数器递增器,确保在每0.1秒更新一次,并且在达到9.9秒后重新计数。 - 按钮控制功能:
开始按钮:按下开始KEY1按钮后,秒表应该开始计时,计时器以10Hz的速率递增。
停止按钮:按下停止KEY2按钮后,秒表应该停止计时,但显示当前的计数器值。
增量按钮:按下增量KEY3按钮后,秒表的计数器值应该增加一次,无论按住按钮多长时间。
清除按钮:按下清除KEY4按钮后,秒表的计数器值应该重置为零。 - 数码管显示功能:
显示精度:确保数码管上的显示精确到0.1秒。
数码管设计:设计一个能够接收计数器值并将其转换为数码管上对应的数字显示的模块。
显示更新频率:确保数码管能够在每0.1秒更新一次,以保持与计时器的同步。 - 状态控制功能:
状态管理:秒表应该能够管理不同的状态,如运行状态、停止状态、清零状态等。
状态转换:确保在按下不同的按钮时,秒表能够正确地转换到相应的状态。 - FPGA核心板和外设连接:
确保数码管正确连接到FPGA核心板的驱动电路。
确保时钟频率足够高,以满足秒表计时的精确度要求。
实现方式
- 秒表计时功能实现方式:先设计一个计数器模块,利用 WebIDE中自带模块中的整数分频器将时钟分频,以驱动计时器递增。
- 按钮控制功能:通过边沿触发器检测按钮状态,并且根据按钮的当前状态和之前状态进行状态转换,以控制计数器的功能。
- 数码管显示功能:先设计一个数码管驱动模块,用于控制数码管的显示,且能接收数字输入,并将其转换为对应的七段显示器的控制信号(个位须有小数点)。
功能框图
代码
gpt部分代码:
对计数器部分生成了一个简单的架构。
计时器部分代码:
利用chat gpt3.5生成项目的架构,再对其中不足部分进行修改补充。
module timer(
input clk, // Clock input
input start, // Start button input
input stop, // Stop button input
input increment, // Increment button input
input rst, // Reset button input
output [8:0] seg_display_1, // Seven-segment display output for ones place
output [8:0] seg_display_2 // Seven-segment display output for tenths place
);
reg [8:0] smg_1 [9:0]; // Array to hold segment patterns for ones place
reg [8:0] smg_2 [9:0]; // Array to hold segment patterns for tenths place
reg [3:0] ones = 4'd0; // Register to hold ones place value (0-9)
reg [3:0] tenths = 4'd0; // Register to hold tenths place value (0-9)
reg flag = 1'd0; // Flag to control timer start/stop
将整数分频器实例化,以得到10hz的clk:
wire clk_10hz;
div div_u(
.clk(clk),
.pulse(clk_10hz)
);
将按键消抖模块实例化
wire start_btn; // Debounced start button signal
debounce debounce_u0(
.clk(clk),
.btn_in(start),
.btn_pressed(start_btn)
);
wire stop_btn; // Debounced stop button signal
debounce debounce_u1(
.clk(clk),
.btn_in(stop),
.btn_pressed(stop_btn)
);
wire increment_btn; // Debounced increment button signal
debounce debounce_u2(
.clk(clk),
.btn_in(increment),
.btn_pressed(increment_btn)
);
wire rst_btn; // Debounced reset button signal
debounce debounce_u3(
.clk(clk),
.btn_in(rst),
.btn_pressed(rst_btn)
);
初始化两个数组 smg_1 和 smg_2,分别用于表示个位数码管和十位数码管的显示逻辑。每个数码管的数字(0 到 9)都用一个 9 位的十六进制数来表示,这些数值是根据七段显示器的布置,以及每个段的控制信号来确定的。
// Segment patterns initialization
initial
begin
// Individual segment patterns for ones place
smg_1[0] = 9'hbf;
smg_1[1] = 9'h86;
smg_1[2] = 9'hdb;
smg_1[3] = 9'hcf;
smg_1[4] = 9'he6;
smg_1[5] = 9'hed;
smg_1[6] = 9'hfd;
smg_1[7] = 9'h87;
smg_1[8] = 9'hff;
smg_1[9] = 9'hef;
// Individual segment patterns for tenths place
smg_2[0] = 9'h3f;
smg_2[1] = 9'h06;
smg_2[2] = 9'h5b;
smg_2[3] = 9'h4f;
smg_2[4] = 9'h66;
smg_2[5] = 9'h6d;
smg_2[6] = 9'h7d;
smg_2[7] = 9'h07;
smg_2[8] = 9'h7f;
smg_2[9] = 9'h6f;
end
assign seg_display_1[8:0] = {2'b00,smg_1[ones]};
assign seg_display_2[8:0] = {2'b00,smg_2[tenths]};
这段代码的主要作用是控制秒表的运行逻辑,包括开始、停止、重置、计数和增量功能。
初始化时,将个位数和十位数的计数值设为零。
在always块中,根据时钟信号clk_10hz、复位信号rst、开始信号start、停止信号stop的变化来控制秒表的行为。
接收到复位消抖信号 rst,将计数值重置为零。
接收到开始消抖信号start,将保持计数的标志位置flag为1,表示秒表开始计时。(利用hold将开始和停止功能分别在两个按键上实现)
接收到停止消抖信号stop,将保持计数的标志位置flag为0,表示秒表停止计时,并且保持当前计数值。
如果保持计数的标志位为1,表示秒表在计时状态,按照 0.1 秒的时钟频率递增计数值;如果计数值达到 9.9,则将其归零并进行进位。
如果接收到增量消抖信号increment 且秒表处于停止状态,可以通过按下增量按钮来递增计数值。
标志位increment_flag的功能是用于防止持续按住增量按钮时计数值持续增加的情况。
always @(posedge clk)
begin
// Reset logic
if(rst_btn)
begin
ones <= 4'd0;
tenths <= 4'd0;
end
// Start/stop control
if(start_btn)
flag <= 1'd1;
if(stop_btn)
flag <= 1'd0;
// Timer increment logic
if((flag && clk_10hz) || increment_btn)
begin
// Increment tenths and handle carry
if((tenths == 9) && (ones == 9)) // Carry from 9.9 to 0.0
begin
tenths <= 4'd0;
ones <= 4'd0;
end
else if((tenths == 9) && (ones != 9)) // Carry from x.9 to x+1.0
begin
tenths <= 4'd0;
ones <= ones + 1;
end
else
tenths <= tenths + 1;
end
end
分频器部分代码:
module div(
input wire clk, // Clock input
output reg pulse // 100ms pulse output
);
initial pulse = 1'b0;
// Parameter definition
parameter CLOCK_FREQUENCY = 1_200_000; // Clock frequency in Hz
// Register definition
reg [20:0] counter = 0; // Counter for pulse period
always @(posedge clk) begin
if (counter < CLOCK_FREQUENCY- 1) begin
counter <= counter + 1; // Increment the counter
end else begin
counter <= 0; // Reset the counter
end
end
// State machine
always @(posedge clk) begin
if (counter == CLOCK_FREQUENCY- 1) begin
pulse <= 1; // Set pulse output high
end else begin
pulse <= 0; // Set pulse output low
end
end
endmodule
遇到的主要问题
在定义两个数码管smg_1和smg_2时,没有将其定义为数组,导致烧录后,并不能控制全部七个显示段。通过WebIDE中提供的计时控制示例项目,发现这个问题。
同时在验证秒表的四个功能时,会出现一个开关能够同时控制开始和暂停的功能,而项目需求中需要通过两个不同的按键控制两个功能,于是我通过引入hold,将两个功能分别在两个按键上实现。
持续按住增量按钮时计数值持续增加,而需求中要求无论按住增量按钮多长时间,显示值只增加一次,于是我引入标志位flag,在执行一次操作后,flag的值改变,以实现“一按一增”的功能。
长按开始按键,计数器同样会暂停。