任务目标
****分秒计数器核心功能:****实现从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两种状态,清晰地实现了计时器的启动和暂停功能。此外,硬件设计中的细节处理,如按键消抖和七段数码管的显示逻辑,让我认识到细节对系统稳定性的重要性。通过延时采样和段码转换,我解决了抖动和显示错误的问题,确保了计时器的准确性和可靠性。
这次实践让我更加体会到硬件设计是一个不断试错和优化的过程。从分频模块的调试到显示模块的优化,每一步都需要反复测试和调整。通过逐步集成和模块化测试,我不仅成功解决了多个技术难题,还积累很多经验。