2024寒假练-基于小脚丫FPGA核心板实现秒表
该项目使用了STEP-MXO2-LPC核心板,实现了秒表的设计,它的主要功能为:在0.0到9.9之间循环计数,并且具有启动、暂停、增加与清除功能。
标签
FPGA
数字逻辑
参加活动/培训
2024寒假练
zxdrft6
更新2024-04-03
北京理工大学
416

1 项目需求

  • 使用核心板上的七段数码管,创建一个两位数的秒表
  • 秒表从0.0计数到9.9,之后翻转,每0.1秒更新一次数字
  • 使用四个轻触按键分别实现以下功能:1、开始,点击后秒表以10Hz速率递增;2、暂停,点击后秒表停止递增,并且保持显示当前值;3、增量,点击后秒表计数在当前值上加0.1;4、复位,点击后秒表重置为0.0状态并停止在此
  • 上电之后显示0.0并停止,等待开始信号

2 设计思路

2.1 启停功能

使用变量hold_flag控制计数的启停,hold_flag为1时表示停止,hold_flag为0时表示计数。hold_flag的初始状态为1,点击开始按键后将hold_flag设置为0,点击暂停按键后将hold_flag设置为1,点击复位后恢复hold_flag为初始状态1

2.2 时钟功能

将FPGA中的12MHz时钟信号作为clk信号输入,分频器产生1000Hz信号,c_1ms随时钟计数,计数到99计数加0.1,并将c_1ms置0

2.3 秒表功能

变量seg_data_1对应个位,变量seg_data_2对应十分位。两者初始值为0。当hold_flag为0,且变量c触发秒表计数加0.1时,seg_data_2加1。当seg_data_2为9时,置0并向seg_data_1进1。当两者都为9则同时置0。复位时两者同时置0

2.4 按键功能

四个按键分别对应四个输入信号start、stop、plus和reset,经过消抖模块处理后在always块中对是否按下进行判断

2.5 显示功能

seg_dp与seg数组分别存储带有小数点的数字和不带有小数点的数字两种情况对应的段码,根据seg_data_1调用seg_dp对应段码写入个位输出信号seg_led_1,根据seg_data_2调用seg对应段码写入十分位输出信号seg_led_2

3 流程图

4 功能展示

4.1 开始计数

点击开始按钮开始计数

4.2 暂停计数

点击暂停按钮暂停计数

4.3 计数增加

点击增量按钮,计数加0.1

4.4 复位功能

点击复位,重置为初始状态,不论是否处于计数状态中

5 资源占用

6 主要代码展示和说明

module top_module (
input wire clk, // 输入时钟信号
input wire start, // 输入开始信号
input wire stop, // 输入暂停信号
input wire plus, // 输入增加信号
input wire reset, // 复位信号
output reg [8:0] seg_led_1, // 第一个数码管控制信号
output reg [8:0] seg_led_2, // 第二个数码管控制信号
output [7:0] led, // 8个LED灯控制信号
output [5:0] rgb_led // 2个RGB灯控制信号
);

这一块定义了顶层模块的输入输出

wire start_p;                                               // 开始信号消抖
wire stop_p; // 暂停信号消抖
wire plus_p; // 增加信号消抖
wire reset_p; // 复位信号消抖
wire clk1000; // 1000Hz时钟信号
reg hold_flag = 1'b1; // 暂停标志
reg [8:0] seg [9:0]; // 数码管段码无小数点
reg [8:0] seg_dp [9:0]; // 数码管段码有小数点
reg [3:0] seg_data_1; // 第一个数码管显示数字
reg [3:0] seg_data_2; // 第二个数码管显示数字
reg [7:0] led; // 8个LED灯控制变量
reg [5:0] rgb_led; // 2个RGB灯控制变量
reg [7:0] c_1ms;

定义了使用的变量

frequency_divider c1 (.clk(clk),.clkout(clk1000));
debounce b1 (.clk(clk1000),.key_in(start),.key_out(start_p));
debounce b2 (.clk(clk1000),.key_in(stop),.key_out(stop_p));
debounce_1 b3 (.clk(clk1000),.key_in(plus),.key_out(plus_p));
debounce b4 (.clk(clk1000),.key_in(reset),.key_out(reset_p));

实例化信号消抖模块和分频器模块

initial begin
seg[0] = 9'h3f; // 0
seg[1] = 9'h06; // 1
seg[2] = 9'h5b; // 2
seg[3] = 9'h4f; // 3
seg[4] = 9'h66; // 4
seg[5] = 9'h6d; // 5
seg[6] = 9'h7d; // 6
seg[7] = 9'h07; // 7
seg[8] = 9'h7f; // 8
seg[9] = 9'h6f; // 9
seg_dp[0] = 9'hbf; // 0和小数点
seg_dp[1] = 9'h86; // 1和小数点
seg_dp[2] = 9'hdb; // 2和小数点
seg_dp[3] = 9'hcf; // 3和小数点
seg_dp[4] = 9'he6; // 4和小数点
seg_dp[5] = 9'hed; // 5和小数点
seg_dp[6] = 9'hfd; // 6和小数点
seg_dp[7] = 9'h87; // 7和小数点
seg_dp[8] = 9'hff; // 8和小数点
seg_dp[9] = 9'hef; // 9和小数点
led = 8'b11111111; // LED全熄灭
rgb_led = 6'b111111; // RGB全熄灭
hold_flag <= 1'b1; // 暂停标志置1
seg_data_1 <= 4'd0; // 第一个数码管初始置0
seg_data_2 <= 4'd0; // 第二个数码管初始置0
seg_led_1 <= seg_dp[seg_data_1]; // 写入第一个数码管
seg_led_2 <= seg[seg_data_2]; // 写入第二个数码管
c_1ms = 8'd0; // 时钟计数到100个周期为1ms
end

初始化段码数组以及数码管和LED灯的初始状态

always @(posedge clk1000) begin
if (reset_p == 1'b0) begin // 复位
seg_data_1 <= 4'd0;
seg_data_2 <= 4'd0;
hold_flag <= 1'b1;
end else if (start_p == 1'b0) begin // 开始
hold_flag <= 1'b0;
end else if (stop_p == 1'b0) begin // 停止
hold_flag <= 1'b1;
end else if (plus_p == 1'b1) begin // 增加
if (seg_data_1 == 4'd9) begin
if (seg_data_2 == 4'd9) begin
seg_data_1 <= 4'd0;
seg_data_2 <= 4'd0;
end else begin
seg_data_2 <= seg_data_2 + 4'd1;
end
end else begin
if(seg_data_2 == 4'd9) begin
seg_data_1 <= seg_data_1 + 4'd1;
seg_data_2 <= 4'd0;
end else begin
seg_data_2 <= seg_data_2 + 4'd1;
end
end
end else if (c_1ms == 8'd99) begin // 自动增加
c_1ms <= 8'd0;
if (hold_flag == 1'b0) begin
if (seg_data_1 == 4'd9) begin
if (seg_data_2 == 4'd9) begin
seg_data_1 <= 4'd0;
seg_data_2 <= 4'd0;
end else begin
seg_data_2 <= seg_data_2 + 4'd1;
end
end else begin
if(seg_data_2 == 4'd9) begin
seg_data_1 <= seg_data_1 + 4'd1;
seg_data_2 <= 4'd0;
end else begin
seg_data_2 <= seg_data_2 + 4'd1;
end
end
end
end else begin
c_1ms <= c_1ms + 8'd1;
end
seg_led_1 <= seg_dp[seg_data_1];
seg_led_2 <= seg[seg_data_2];
led = 8'b11111111;
rgb_led = 6'b111111;
end

时钟上升沿触发,首先检测是否有按键信号,有则进行相应动作。之后进行时钟计数,时钟计数到1200000则将该值置0,如果hold_flag为0则同时让秒表加0.1,否则从0开始继续时钟计数。最后更新数码管显示值,并保持LED和RGB的状态

module frequency_divider(  
input wire clk, // 输入12MHz
output reg clkout // 输出1000Hz
);

// 定义分频比,为12MHz/1000Hz/2
localparam N = 6000;
// 定义计数器
reg [13:0] counter; // 14位计数器,因为12000是14位数

// 时钟分频逻辑
always @(posedge clk) begin
// 计数器增加
counter <= counter + 1;

// 如果计数器达到N,则输出一个时钟脉冲并重置计数器
if (counter == N - 1) begin
clkout <= ~clkout; // 翻转clkout的状态
counter <= 0; // 重置计数器
end
end

endmodule

分频器模块,使用文心一言生成,并做了修改,详见视频介绍

module debounce(  
input wire clk, // 1000Hz时钟信号
input wire key_in, // 按键输入信号,高电平表示抬起,低电平表示按下
output reg key_out // 消抖后的按键输出信号
);

reg [6:0] counter; // 计数器,用于计时
reg last_key_in; // 上一个按键输入信号的状态
reg key_pressed; // 标志位,表示按键是否已经被按下

// 初始化
initial begin
counter = 0;
last_key_in = 1; // 假设按键初始状态为抬起
key_out = 1;
key_pressed = 0;
end

// 在每个时钟上升沿触发
always @(posedge clk) begin
if (key_in != last_key_in) begin
// 如果按键状态改变,重置计数器
counter <= 0;
end else if (counter < 20) begin
// 如果按键状态未改变,且计数器未达到消抖时间,递增计数器
counter <= counter + 1;
end else if (key_in == 0 && ~key_pressed) begin
// 如果计数器达到消抖时间,且按键被按下但尚未标记为按下
key_pressed <= 1; // 标记按键为已按下
key_out <= 0; // 输出低电平
end else if (key_in == 1 && key_pressed) begin
// 如果按键已抬起且之前被按下过
key_pressed <= 0; // 重置按键按下标志位
key_out <= 1; // 输出高电平
end
if (counter >= 20 && key_pressed) begin
key_out <= 1;
end
// 更新上一个按键输入信号的状态
last_key_in <= key_in;
end
endmodule

按键消抖模块,按下就生效,使用文心一言生成,详见视频介绍,有过修改,与视频不太一样。视频里的会导致按住一直输出,这个不会

module debounce_1(  
input wire clk, // 1000Hz时钟信号
input wire key_in, // 按键输入信号(抬起时高电平,按下时低电平)
output reg key_out // 消抖后的按键输出信号
);

reg last_key_in; // 上一个按键输入信号的状态
reg key_pressed; // 按键是否被按下的状态

// 初始化
initial begin
last_key_in = 1; // 假设初始按键状态为抬起(高电平)
key_out = 0;
key_pressed = 0;
end

// 在每个时钟上升沿触发
always @(posedge clk) begin
if (key_in != last_key_in) begin
if (key_in == 1) begin // 按键抬起
key_pressed <= 0; // 更新按键状态为未按下
if (last_key_in == 0) begin // 如果之前按键是按下状态
key_out <= 1; // 按键抬起后,输出信号变为高电平
end
end else begin // 按键按下
key_pressed <= 1; // 更新按键状态为按下
key_out <= 0; // 按键按下时,保持输出信号为低电平
end
end
if (key_in == last_key_in) begin
if (key_pressed == 0) begin
key_out <= 0;
end
end
last_key_in <= key_in;// 更新上一个按键输入信号的状态
end
endmodule

按键消抖模块,按下并抬起后生效,使用文心一言生成,并作了修改,详见视频介绍

7 主要难题与解决

项目本身不复杂,难点在于首次接触并实际使用verilog为FPGA编程。方法就是边学边试,观察示例程序的代码如何写的,还用文心一言大模型生成一些简单的verilog程序,查看生成的代码是怎么写的,运行时会有什么问题,进而学习使用verilog的方法

8 未来展望

简单学习了verilog语言,大概会尝试一下其他项目,虽然难度升高了

附件下载
yansipeng-mxo2.jed
yansipeng-mxo2.zip
团队介绍
严斯芃-北京理工大学
团队成员
zxdrft6
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号