fpga:分秒计时器

任务目标

****分秒计数器核心功能:****实现从00:00到59:59的循环计数,通过四个七段数码管显示分钟和秒。

****复位功能:****支持硬件复位,将计数器归零并显示00:00。

****启动/暂停控制:****通过按键控制计时的启动和暂停。

****消抖处理:****消除按键抖动对计时逻辑的影响,确保按键响应稳定。

实现思路

核心功能

计数逻辑:

使用两个BCD(二进制编码的十进制)计数器分别存储分钟和秒:

秒计数器:个位(0-9)和十位(0-5)。

分钟计数器:个位(0-9)和十位(0-5)。

每秒递增秒计数器:

当秒的个位达到9时,十位加一,个位归零。

当秒的十位达到5且个位达到9时,分钟计数器加一,秒计数器归零。

当分钟的个位达到9且十位达到5时,计数器归零。

显示逻辑:

将BCD计数器的值转换为七段数码管的段码。

使用查找表(case语句)将每个数字映射到对应的段码。

确保数码管的段码格式正确(共阳极或共阴极)。

边界条件:

秒从59递增到0(即60)时,分钟加一。

分钟从59递增到0(即60)时,计数器归零。

七段数码管

数码管的一种是半导体发光器件,数码管可分为七段数码管和八段数码管,区别在于八段数码管比七段数码管多一个用于显示小数点的发光二极管单元DP(decimal point),其基本单元是发光二极管

七段数码管分为共阳极及共阴极,共阳极的七段数码管的正极(或阳极)为八个发光二极管的共有正极,其他接点为独立发光二极管的负极(或阴极),使用者只需把正极接电,不同的负极接地就能控制七段数码管显示不同的数字。共阴极的七段数码管与共阳极的只是接驳方法相反而已。

如图是数码管显示编码表,包含了共阳和共阴两种不同的极性。

复位功能

复位信号:

使用一个硬件按键作为复位输入,按下时复位信号激活。

复位信号为低电平有效或高电平有效,需根据具体硬件设计确定。

复位逻辑:

当复位信号激活时,将所有计数器清零。

同时将状态机重置到初始状态(暂停状态)。

确保复位信号优先级高于其他操作,避免复位过程中计数器继续运行。

启动/暂停控制 功能

状态管理:

使用状态机管理计时逻辑,定义两个状态:

IDLE(暂停状态)。

RUNNING(运行状态)。

按键按下时,状态在IDLE和RUNNING之间切换。

按键检测:

使用边沿检测识别按键的按下事件,避免误触发。

在状态机中,根据当前状态和按键输入更新下一个状态。

状态输出:

在RUNNING状态下,每秒递增计数器。

在IDLE状态下,计数器保持不变。

消抖功能

抖动特性:

按键抖动会导致信号短时间内多次变化,需要通过延时采样消除抖动。

消抖逻辑:

使用一个计时器检测按键状态的稳定性:

当检测到按键状态变化时,启动计时器。

如果在设定时间(如20ms)内状态保持稳定,则更新按键状态。

计时器的位宽需足够大,确保能够覆盖设定的延时。

稳定输出:

只有当按键状态稳定后,才输出有效的控制信号。

确保消抖后的信号与原始按键信号的极性一致。

思考

为什么要消抖?

机械按键在按下或释放的瞬间,由于物理触点的弹性作用,会产生一系列快速的开闭跳变,这种现象称为按键抖动。抖动信号表现为一系列短暂的高低电平变化,而不是理想的单一电平变化。

抖动信号可能导致电路误判按键状态,例如,一次按键可能被误认为多次按键。同时抖动信号会导致输出不稳定,影响后续逻辑电路的正常工作;频繁的信号变化可能增加电路的功耗和资源占用等。

因此消抖处理在数字电路设计中至关重要。

如下是按键原理图

状态机思想

状态机思想是一种用于设计和分析数字系统行为的模型,通过定义一系列状态、输入信号和状态转移规则来描述系统的动态行为。状态机的核心在于将复杂的行为分解为若干个简单的状态,并根据输入信号触发状态之间的转移,每个状态对应特定的输出或行为。状态机通常分为Moore型(输出仅取决于当前状态)和Mealy型(输出取决于当前状态和输入信号),具有模块化设计、清晰逻辑和高效资源利用的优点。

在本次任务中我们可以设置IDLE(暂停状态,计数器停止运行)RUNNING(运行状态,计数器每秒递增)两个状态。

代码实现

在这里我们采用分模块化编写代码,具体使用为:在Visual Studio Code中分模块化编写代码,然后编译(配置verilog语言编写环境,可以参考上期报告),编写无误后,使用Quartus 烧录下载,进行实现。

这里分为分频模块、消抖模块、暂停模块、计数器模块、显示模块、顶层模块

分频模块

复制代码
module clock_divider #(

    parameter DIVISOR = 50000000  // 默认50MHz->1Hz

)(

    input clk_in,

    input reset_n,

    output reg clk_out

);

  

reg [31:0] counter;

  

always @(posedge clk_in or negedge reset_n) begin

    if (!reset_n) begin

        counter <= 0;

        clk_out <= 0;

    end

    else begin

        if (counter >= (DIVISOR/2 - 1)) begin

            counter <= 0;

            clk_out <= ~clk_out;

        end

        else begin

            counter <= counter + 1;

        end

    end

end

  

endmodule

消抖模块

复制代码
module debounce(

    input clk,

    input reset_n,

    input key_in,

    output reg key_pulse

);

  

reg [19:0] count;  // 20ms 消抖计数器 (50MHz时钟下,1ms=50000个周期)

reg key_in_sync1, key_in_sync2;

reg key_stable;

  

always @(posedge clk or negedge reset_n) begin

    if (!reset_n) begin

        key_in_sync1 <= 1'b1;

        key_in_sync2 <= 1'b1;

        key_stable <= 1'b1;

        count <= 20'd0;

        key_pulse <= 1'b0;

    end

    else begin

        // 同步输入信号

        key_in_sync1 <= key_in;

        key_in_sync2 <= key_in_sync1;

         

        // 检测按键状态变化

        if (key_in_sync2 != key_stable) begin

            key_stable <= key_in_sync2;

            count <= 20'd0;

        end

        else if (count < 20'd1000000) begin  // 20ms (50MHz * 0.02s)

            count <= count + 1'b1;

        end

        else begin

            key_pulse <= key_in_sync2 & ~key_stable;  // 产生上升沿脉冲

        end

    end

end

  

endmodule

暂停模块

复制代码
module pause_control(

    input clk,

    input reset_n,

    input pause_pulse,

    output reg pause_toggle

);

  

always @(posedge clk or negedge reset_n) begin

    if (!reset_n) begin

        pause_toggle <= 1'b0;

    end

    else if (pause_pulse) begin

        pause_toggle <= ~pause_toggle;

    end

end

endmodule

计数器模块

复制代码
module top(

    input clk_50m,         // 50MHz 时钟输入

    input reset_n,         // 复位按键 (低电平有效)

    input pause_key,       // 暂停/继续按键

    output [6:0] hex0,     // 秒个位数码管

    output [6:0] hex1,     // 秒十位数码管

    output [6:0] hex2,     // 分个位数码管

    output [6:0] hex3,     // 分十位数码管

    output [7:0] ledg      // LED 显示秒数 (可选)

);

  

// 内部信号定义

wire clk_1hz;             // 1Hz 时钟

wire pause_pulse;         // 消抖后的暂停脉冲信号

wire pause_toggle;        // 暂停状态信号

wire [3:0] sec_ones;      // 秒个位

wire [3:0] sec_tens;      // 秒十位

wire [3:0] min_ones;      // 分个位

wire [3:0] min_tens;      // 分十位

  

// 时钟分频模块

clock_divider #(

    .DIVISOR(50000000)    // 50MHz -> 1Hz

) clk_div (

    .clk_in(clk_50m),

    .reset_n(reset_n),

    .clk_out(clk_1hz)

);

// 按键消抖模块

debounce debounce_pause (

    .clk(clk_50m),

    .reset_n(reset_n),

    .key_in(pause_key),

    .key_pulse(pause_pulse)

);

  

// 暂停控制模块 (将脉冲信号转换为 toggle 信号)

pause_control pause_ctrl (

    .clk(clk_50m),

    .reset_n(reset_n),

    .pause_pulse(pause_pulse),

    .pause_toggle(pause_toggle)

);

  

// 分秒计数器模块

min_sec_counter counter (

    .clk(clk_1hz),

    .reset_n(reset_n),

    .pause(pause_toggle),

    .sec_ones(sec_ones),

    .sec_tens(sec_tens),

    .min_ones(min_ones),

    .min_tens(min_tens)

);

// 数码管显示模块

seven_seg_display seg0 (.digit(sec_ones), .seg(hex0));

seven_seg_display seg1 (.digit(sec_tens), .seg(hex1));

seven_seg_display seg2 (.digit(min_ones), .seg(hex2));

seven_seg_display seg3 (.digit(min_tens), .seg(hex3));

  

  

endmodule

显示模块

复制代码
module seven_seg_display(

    input [3:0] digit,

    output reg [6:0] seg

);

  

always @(*) begin

    case (digit)

        4'h0: seg = 7'b1000000;  // 0

        4'h1: seg = 7'b1111001;  // 1

        4'h2: seg = 7'b0100100;  // 2

        4'h3: seg = 7'b0110000;  // 3

        4'h4: seg = 7'b0011001;  // 4

        4'h5: seg = 7'b0010010;  // 5

        4'h6: seg = 7'b0000010;  // 6

        4'h7: seg = 7'b1111000;  // 7

        4'h8: seg = 7'b0000000;  // 8

        4'h9: seg = 7'b0010000;  // 9

        default: seg = 7'b1111111;  // 全灭

    endcase

end

endmodule

顶层模块

复制代码
module top(

    input clk_50m,         // 50MHz 时钟输入

    input reset_n,         // 复位按键 (低电平有效)

    input pause_key,       // 暂停/继续按键

    output [6:0] hex0,     // 秒个位数码管

    output [6:0] hex1,     // 秒十位数码管

    output [6:0] hex2,     // 分个位数码管

    output [6:0] hex3,     // 分十位数码管

    output [7:0] ledg      // LED 显示秒数 (可选)

);

  

// 内部信号定义

wire clk_1hz;             // 1Hz 时钟

wire pause_pulse;         // 消抖后的暂停脉冲信号

wire pause_toggle;        // 暂停状态信号

wire [3:0] sec_ones;      // 秒个位

wire [3:0] sec_tens;      // 秒十位

wire [3:0] min_ones;      // 分个位

wire [3:0] min_tens;      // 分十位

  

// 时钟分频模块

clock_divider #(

    .DIVISOR(50000000)    // 50MHz -> 1Hz

) clk_div (

    .clk_in(clk_50m),

    .reset_n(reset_n),

    .clk_out(clk_1hz)

);

// 按键消抖模块

debounce debounce_pause (

    .clk(clk_50m),

    .reset_n(reset_n),

    .key_in(pause_key),

    .key_pulse(pause_pulse)

);

  

// 暂停控制模块 (将脉冲信号转换为 toggle 信号)

pause_control pause_ctrl (

    .clk(clk_50m),

    .reset_n(reset_n),

    .pause_pulse(pause_pulse),

    .pause_toggle(pause_toggle)

);

  

// 分秒计数器模块

min_sec_counter counter (

    .clk(clk_1hz),

    .reset_n(reset_n),

    .pause(pause_toggle),

    .sec_ones(sec_ones),

    .sec_tens(sec_tens),

    .min_ones(min_ones),

    .min_tens(min_tens)

);

// 数码管显示模块

seven_seg_display seg0 (.digit(sec_ones), .seg(hex0));

seven_seg_display seg1 (.digit(sec_tens), .seg(hex1));

seven_seg_display seg2 (.digit(min_ones), .seg(hex2));

seven_seg_display seg3 (.digit(min_tens), .seg(hex3));

  

  

endmodule

顶层模块是整个分秒计数器系统的集成中心,它负责协调各个子模块的工作,实现从时钟输入到数码管显示的完整功能链路。

编译无误过后就可以通过Quartus 烧录了。

实物效果

分秒计数器

总结

通过这次分秒计时器的设计实践,我对数字电路的模块化设计和状态机思想有了更深刻的理解。模块化设计将复杂系统分解为分频、消抖、暂停控制、计数器和显示等独立模块,不仅降低了设计难度,还提高了代码的可维护性和可扩展性。状态机的应用则通过定义IDLE和RUNNING两种状态,清晰地实现了计时器的启动和暂停功能。此外,硬件设计中的细节处理,如按键消抖和七段数码管的显示逻辑,让我认识到细节对系统稳定性的重要性。通过延时采样和段码转换,我解决了抖动和显示错误的问题,确保了计时器的准确性和可靠性。

这次实践让我更加体会到硬件设计是一个不断试错和优化的过程。从分频模块的调试到显示模块的优化,每一步都需要反复测试和调整。通过逐步集成和模块化测试,我不仅成功解决了多个技术难题,还积累很多经验。

相关推荐
Terasic友晶科技14 分钟前
【教程】Agilex 5 SoC 的两种启动模式的开发演示(一)(FPGA Configuration First)
fpga开发·agilex 5 soc
ALINX技术博客18 分钟前
【黑金云课堂】FPGA技术教程Vitis开发:SD卡WAV文件读取音频播放实验
fpga开发·音视频
仲南音2 天前
Xilinx FPGA——时钟IP核MMCM、PLL
fpga开发
I'm a winner3 天前
基于Xilinx FPGA的LVDS高速串行通信系统(三)--数据训练及握手机制【文末源码】
fpga开发
upper20203 天前
coremark移植坑之-nostartfiles -nostdlib参数
学习·fpga开发·coremark移植·riscv设计
Eloudy3 天前
Vivado 纯命令行构建 FPGA 项目教程
fpga开发
Rambo.xia3 天前
AXI跨时钟域与数据一致性——CDC FIFO深度不够、写后读冒险、多Master仲裁、乱序返回,出了Bug根本查不出来
fpga开发·bug
upper20203 天前
FPGA部署卷积神经网络识别MINST图片
fpga开发·卷积神经网络·minst·minst识别·部署神经网络·神经网络识别图片
你是我的解忧王子4 天前
嵌入式 ADC采样 原始数据分析上层(平均值 波动率 正态分布 线性补偿 系统误差)
fpga开发·数据挖掘·数据分析
9527华安4 天前
FPGA纯verilog代码ISP图像处理培训教程,基于IMX214,提供工程源码+视频教程+FPGA开发板
图像处理·fpga开发·isp·imx214·mipi