2024寒假在家练-基于小脚丫FPGA核心板实现秒表功能
该项目使用了Lattice MXO2的小脚丫FPGA核心板以及stepfpga在线编程网站webIDE,实现了具有启动、停止、递增和清除功能的秒表的设计,它的主要功能为:0.0-9.9秒计时器,并具有启动、停止、递增和清除功能。。
标签
FPGA
数字逻辑
开发板
wyatt
更新2024-04-17
北京理工大学
145

设计目标及需求

本项目的目标是利用小脚丫FPGA核心板来制作一个能够显示2位数秒表计时(0.0-9.9秒)的装置。

秒表的计数范围应该是从0.0秒到9.9秒,然后重新开始计数。秒表的更新频率设置为每0.1秒更新一次,以保证计时的精确性。七段数码管作为显示器作为输出设备,将被用来显示秒表的数值。

需求分析包括以下几个方面:

  1. 显示功能:确保秒表能够以0.1秒的间隔准确地更新并显示数值。
  2. 按钮输入功能:包括开始按钮(启动秒表)、停止按钮(停止秒表但保持显示)、增量按钮(增加计数值)、清除按钮(将计数器重置为零)。
  3. 硬件实现:利用小脚丫FPGA核心板,通过核心板上的七段显示器和按钮来实现秒表的控制和显示。

设计思路

思路图如下:

所以实现方式上涉及三个底层部分的设计,这三个部分各自独立又互有串联,形成一个完整的计数器功能:

  1. 分频部分:用于控制输入时钟频率和输出时钟频率,以及控制复位和使能信号。
  2. 数码管部分:根据输入的数值控制两个七段数码管显示对应的数字。
  3. 按键消抖部分:通过状态机方式实现按键消抖,保证手动按下一次按键只产生一个按键脉冲。

代码及介绍

以下为代码介绍。

首先为计时器模块,在这里完成了分频部分和数码管显示部分,以及规定了按键功能。代码如下:

module timer
(
clk , //Clock
start , //Start button
reset , //Reset button
stop , //Stop button
increment , //Increment button
seg_led_1 , //7-segment display 1
seg_led_2 //7-segment display 2
);

input clk;
input start;
input reset;
input stop;
input increment;

output reg [8:0] seg_led_1,seg_led_2;

reg flag = 1'b0;
wire start_pulse; //Debounced signal for start button
wire reset_pulse; //Debounced signal for Reset button
wire stop_pulse; //Debounced signal for stop button
wire increment_pulse; //Debounced signal for Increment button

reg [3:0] cnt_ge = 4'd0; //Milliseconds
reg [3:0] cnt_shi = 4'd0; //seconds

reg [27:0] cnt = 28'd0;
reg [27:0] mcnt = 28'd0;

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

wire resetn;
assign resetn = ~reset_pulse;

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
flag = 1'b0;
cnt_ge <= 4'd0;
cnt_shi <= 4'd0;
seg_led_1 <= 9'd0;
seg_led_2 <= 9'd0;
seg_led_1 <= {seg[cnt_shi][8],1'b1,seg[cnt_shi][6:0]};
seg_led_2 <= seg[cnt_ge][8:0];
end

//debounce buttons and get signals
debounce b1 (
.clk(clk),
.reset(1'b1),
.key(start),
.key_pulse(start_pulse));

debounce b2 (
.clk(clk),
.reset(1'b1), .key(reset),
.key_pulse(reset_pulse));

debounce b3 (
.clk(clk),
.reset(1'b1),
.key(stop),
.key_pulse(stop_pulse));

debounce b4 (
.clk(clk),
.reset(1'b1),
.key(increment),
.key_pulse(increment_pulse));

always @(posedge clk)begin
if(reset_pulse == 1'b1)begin// Reset clock pulse detection
flag <= 1'b0;
cnt_ge <= 4'd0;
cnt_shi <= 4'd0;
end
else if(start_pulse == 1'b1)begin// Start button pulse detection
flag <= 1'b1;
end
else if(stop_pulse == 1'b1)begin // Stop button pulse detection
flag <= 1'b0;
end
else if(increment_pulse == 1'b1)begin // Increment button pulse detection
if(cnt_ge == 4'd9)begin
cnt_ge <= 4'd0;
if(cnt_shi == 4'd9)begin
cnt_shi <= 4'd0;
end
else begin
cnt_shi <= cnt_shi + 4'd1;
end
end
else begin
cnt_ge <= cnt_ge + 4'd1;
end
seg_led_1 <= {seg[cnt_shi][8],1'b1,seg[cnt_shi][6:0]};
seg_led_2 <= seg[cnt_ge][8:0];
end

// get 10 Hz clock and count increments every 0.1 second
if(mcnt == 28'd1200000)begin
mcnt <= 28'd0;
if(flag == 1'b1)begin
if(cnt_ge == 4'd9)begin
cnt_ge <= 4'd0;
if(cnt_shi == 4'd9)begin
cnt_shi <= 4'd0;
end
else begin
cnt_shi <= cnt_shi + 4'd1;
end
end
else begin
cnt_ge <= cnt_ge + 4'd1;
end
seg_led_1 <= {seg[cnt_shi][8],1'b1,seg[cnt_shi][6:0]};
seg_led_2 <= seg[cnt_ge][8:0];
end
end
else begin
mcnt <= mcnt + 28'd1;
end
seg_led_1 <= {seg[cnt_shi][8],1'b1,seg[cnt_shi][6:0]};
seg_led_2 <= seg[cnt_ge][8:0];
end

endmodule


这段 Verilog 代码实现了一个简单的计时器模块。

  1. 模块定义
    • 模块名为 timer。
    • 输入包括时钟信号 clk,以及用于控制计时器行为的按钮信号 start、reset、stop 和 increment。
    • 输出包括两个 7 段显示器的控制信号 seg_led_1 和 seg_led_2。
  2. 信号和寄存器
    • flag 用于表示计时器是否在运行状态。
    • cnt_ge 和 cnt_shi 分别用于存储秒的个位和十位数。
    • cnt 和 mcnt 分别用于计时和产生 10Hz 的时钟信号。
    • seg 数组用于存储每个数字的 7 段显示器编码。
  3. 按钮消抖
    • 使用 debounce 模块对每个按钮进行消抖,并得到消抖后的信号 start_pulse、reset_pulse、stop_pulse 和 increment_pulse。
  4. 状态控制
    • 在时钟上升沿检测到 reset_pulse 时,将 flag 和计数器清零。
    • 在检测到 start_pulse 时,设置 flag 为高,表示计时器开始运行。
    • 在检测到 stop_pulse 时,设置 flag 为低,表示计时器停止运行。
    • 在检测到 increment_pulse 时,根据当前计时器的状态对秒数进行递增,并更新显示器的输出。
  5. 计时器逻辑
    • 在每个时钟周期中,根据计时器是否在运行状态以及计数器的值,更新计时器的秒数。
    • 通过 mcnt 计数器来生成 10Hz 的时钟信号,以便每隔 0.1 秒更新一次计时器的显示。

由此代码,可以通过按钮控制计时器的启动、停止和递增功能,并在 7 段显示器上显示计时器的秒数。


然后为按键消抖模块,在这里实现按键消抖,代码如下:

// Debounce module for buttons
module debounce (clk,reset,key,key_pulse);

parameter N = 1; //Number of buttons to debounce

input clk;
input reset;
input [N-1:0] key; //Input buttons
output [N-1:0] key_pulse; //Output pulses generated by button actions

reg [N-1:0] key_rst_pre; //Register to store previous triggered button values
reg [N-1:0] key_rst; //Register to store current triggered button values

wire [N-1:0] key_edge; //High pulse generated when button changes from high to low state

//Use non-blocking assignment to store button state in two register variables at two clock edges
always @(posedge clk or negedge reset)
begin
if (!reset) begin
key_rst <= {N{1'b1}}; //Initialize key_rst with all ones, {} represents N ones
key_rst_pre <= {N{1'b1}};
end
else begin
key_rst <= key; //Assign value of key to key_rst on the first positive clock edge, and assign value of key_rst to key_rst_pre
key_rst_pre <= key_rst; //Non-blocking assignment. After two clock edges, key_rst stores the current value of key, and key_rst_pre stores the value of key at the previous clock edge
end
end

assign key_edge = key_rst_pre & (~key_rst);//Pulse edge detection. When key detects a falling edge, key_edge produces a high level for one clock cycle

reg [17:0] cnt; //Counter used for generating a delay, system clock is 12MHz, need to delay for about 20ms, at least an 18-bit counter is required

//Generate 20ms delay, counter resets and starts counting when key_edge is valid
always @(posedge clk or negedge reset)
begin
if(!reset)
cnt <= 18'h0;
else if(key_edge)
cnt <= 18'h0;
else
cnt <= cnt + 1'h1;
end

reg [N-1:0] key_sec_pre; //Register variable for delayed level detection
reg [N-1:0] key_sec;


//Detect key after delay, generate a high pulse for one clock cycle if the button state changes to low. If the button state is high, it means the button is not valid
always @(posedge clk or negedge reset)
begin
if (!reset)
key_sec <= {N{1'b1}};
else if (cnt==18'h3ffff)
key_sec <= key;
end
always @(posedge clk or negedge reset)
begin
if (!reset)
key_sec_pre <= {N{1'b1}};
else
key_sec_pre <= key_sec;
end
assign key_pulse = key_sec_pre & (~key_sec);

endmodule

这段代码是一个用于消抖按钮的模块。

  1. 模块定义
    • 模块名为 debounce。
    • 输入包括时钟信号 clk、复位信号 reset、以及一个 N 位宽的按钮输入 key。
    • 输出包括一个 N 位宽的脉冲输出 key_pulse。
  2. 参数和信号
    • 参数 N 用于指定需要消抖的按钮数量。
    • key_rst_pre 和 key_rst 是用于存储前一次和当前按钮触发状态的寄存器。
    • key_edge 是用于检测按钮从高电平到低电平状态变化的脉冲信号。
    • cnt 是一个用于产生延迟的计数器,以确保消除按钮按下时的抖动。
    • key_sec_pre 和 key_sec 是用于延迟检测按钮状态变化的寄存器。
  3. 时钟边沿检测
    • 使用 always 块对时钟信号进行边沿检测,以便在时钟上升沿时进行操作。
    • 在时钟上升沿或复位信号的负边沿时,根据 reset 信号来初始化或更新 key_rst 和 key_rst_pre 的值。
    • 使用非阻塞赋值,保证在时钟的两个边沿更新两个寄存器的值。
  4. 按钮状态检测
    • 使用 always 块对时钟信号进行边沿检测,以便在时钟上升沿时进行操作。
    • 根据复位信号初始化或更新 key_sec 的值,并通过计数器产生的延迟来确定按钮状态的变化。
    • 使用 key_sec_pre 和 key_sec 寄存器来延迟检测按钮状态的变化。
    • 最后,使用 assign 语句生成一个 key_pulse 信号,用于表示按钮按下的脉冲。

这段代码实现了一个简单但有效的按钮消抖功能,通过时钟信号和延迟计数器来确保稳定地检测到按钮按下的动作,并输出一个消除抖动的脉冲信号。

资源占用

运行结果

遇到的问题及解决

1.分时器分时设置问题

选择参考例程并询问chatGPT。

后期计划

1. 用小脚丫FPGA开发板去尝试实现各种数字电路通讯接口逻辑,如SPI、IIC、UART等。

2. 用FPGA实现编写数字信号处理代码,如滤波器和FFT等。

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