音乐网站素材百度销售平台
FPGA学习(三)——数码管实现四位分秒计数器
目录
- FPGA学习(三)——数码管实现四位分秒计数器
- 一、实验要求
- 二、实验思考
- 三、四位分秒计数器实现
- 1、代码实现
- 2、添加按键消抖模块
- 3、添加状态机思想
- 4、模块化重构代码
- 5、实现效果
- 四、总结
一、实验要求
1、用FPGA控制四个数码管,分别显示分钟、秒钟,计数范围都是0~59。
2、按键控制复位和暂停。当复位时所有计数器归零,当启动/暂停按下,暂停计数,再次接下时继续。
3、采用状态机思想实现。
二、实验思考
1、数码管显示。为了在数码管上显示四位分秒计数器,在DE2-115开发板上使用四位共阳极的七段数码管,如hex3-hex0,分别作为分钟的十位、个位,秒的十位、个位。
2、计数逻辑。秒的计数器是0到59,当秒到59后,分钟加一,秒归零。分钟同样到59后归零。用一个4位的BCD计数器,每个单位用4位表示。例如,秒的个位和十位各用4位二进制码,组合成0-59的范围。
reg [3:0] sec_one; // 0-9
reg [3:0] sec_ten; // 0-5
reg [3:0] min_one;
reg [3:0] min_ten;/*详细的计数逻辑*/
3、分频逻辑。将50MHz的时钟分频成1Hz的信号,这样每秒钟计数器加一。分频的话,50MHz等于50,000,000 Hz,要得到1Hz,需要计数50,000,000次,需要26位计数器
reg [DIV_WIDTH-1:0] counter;always @(posedge clk or posedge reset) beginif (reset) begincounter <= 0;clk_out <= 0;end else begincounter <= counter + 1;clk_out <= counter[DIV_WIDTH-1]; // 取最高位作为分频输出end
end
4、复位/暂停。可以采用按键作为控制信号,当按键按下时,复位/启动/暂停计数。
if (reset) begin{sec_ones, sec_tens, min_ones, min_tens} <= 16'd0; // 全部清零end else if (!start_pause) begin // KEY1按下时暂停(低电平有效)
5、状态机。首先要定义系统可能达到的所有状态。可能的状态包有复位状态、运行状态、暂停状态以及空闲状态。此外考虑状态转移的条件和时机,将按键信号作为状态转移的触发条件,同时需要确保状态转移只在按键事件发生时进行一次切换,而不是持续切换。这可能需要边缘检测,即检测按键信号的上升沿或下降沿。同时,每个状态下的行为也需要明确,如:在RUNNING状态下,计数器每秒递增;在IDLE状态下,计数器保持不变,而复位信号无论当前处于什么状态,都应强制回到初始状态。
三、四位分秒计数器实现
1、代码实现
首先,在不考虑状态机的情况下,先来简单的实现一下这个分秒计数器。代码如下 :
module clock(input clk_50m, // 50MHz系统时钟input reset_btn, // 复位按键(直接连接)input pause_btn, // 暂停按键(直接连接)output [7:0] HEX3, // 分钟十位output [7:0] HEX2, // 分钟个位output [7:0] HEX1, // 秒十位output [7:0] HEX0 // 秒个位
);// 内部信号定义
wire clk_1hz;
wire [3:0] min_ten, min_one;
wire [3:0] sec_ten, sec_one;
wire carry;
reg pause_state; // 暂停状态寄存器// 1Hz时钟生成(简化版)
reg [25:0] clk_div;
always @(posedge clk_50m or posedge reset_btn) beginif (reset_btn) beginclk_div <= 0;endelse beginclk_div <= clk_div + 1;end
end
assign clk_1hz = clk_div[25] & ~pause_state; // 暂停时停止时钟// 暂停状态控制
always @(posedge clk_50m or posedge reset_btn) beginif (reset_btn) beginpause_state <= 1'b0;endelse if (pause_btn) beginpause_state <= ~pause_state; // 每次按下切换暂停状态end
end// 秒计数器(0-59)
reg [3:0] sec_one, sec_ten;
always @(posedge clk_1hz or posedge reset_btn) beginif (reset_btn) beginsec_one <= 0;sec_ten <= 0;end else beginif (sec_one == 9) beginsec_one <= 0;if (sec_ten == 5) sec_ten <= 0;else sec_ten <= sec_ten + 1;end else beginsec_one <= sec_one + 1;endend
end
assign carry = (sec_one == 9) && (sec_ten == 5);// 分钟计数器(0-59)
reg [3:0] min_one, min_ten;
always @(posedge carry or posedge reset_btn) beginif (reset_btn) beginmin_one <= 0;min_ten <= 0;end else beginif (min_one == 9) beginmin_one <= 0;if (min_ten == 5) min_ten <= 0;else min_ten <= min_ten + 1;end else beginmin_one <= min_one + 1;endend
end// 数码管显示驱动
function [7:0] digit_to_seg;input [3:0] digit;input dot;begincase(digit)0: digit_to_seg = {dot, 7'b1000000};1: digit_to_seg = {dot, 7'b1111001};2: digit_to_seg = {dot, 7'b0100100};3: digit_to_seg = {dot, 7'b0110000};4: digit_to_seg = {dot, 7'b0011001};5: digit_to_seg = {dot, 7'b0010010};6: digit_to_seg = {dot, 7'b0000010};7: digit_to_seg = {dot, 7'b1111000};8: digit_to_seg = {dot, 7'b0000000};9: digit_to_seg = {dot, 7'b0010000};default: digit_to_seg = {dot, 7'b1111111};endcaseend
endfunctionassign HEX3 = digit_to_seg(min_ten, 1'b1); // 分十位带小数点
assign HEX2 = digit_to_seg(min_one, 1'b0);
assign HEX1 = digit_to_seg(sec_ten, 1'b1); // 秒十位带小数点
assign HEX0 = digit_to_seg(sec_one, 1'b0);endmodule
上述代码只是简单的实现,而在实际中,按键输入控制复位和启动/暂停,硬件会有机械抖动,可能导致误触发,因此我们要为上述代码添加上按键的消抖模块。
2、添加按键消抖模块
在编写消抖模块时,通常的做法是使用计数器来检测按键状态的稳定。当检测到按键状态变化时,启动一个计数器,如果在20ms内状态没有变化,则认为按键稳定,输出新的状态。
消抖模块代码如下:
module button_debounce(input clk, // 50MHz时钟input button_in, // 原始按键输入output reg button_out // 消抖后的按键信号
);reg [19:0] counter; // 20位计数器(50MHz/2^20 ≈ 21ms)
reg button_sync;
reg button_prev;always @(posedge clk) begin// 同步输入信号,防止亚稳态button_sync <= button_in;// 消抖处理逻辑if (button_sync != button_prev) beginbutton_prev <= button_sync;counter <= 0;end else if (counter[19]) begin // 计数器满(约21ms)button_out <= button_prev;end else begincounter <= counter + 1;end
endendmodule
3、添加状态机思想
module fsm_controller(input clk,input reset,input pause,output reg running_o // 明确输出端口命名
);// Verilog-2001兼容的状态定义
parameter [1:0] IDLE = 2'b00,RUNNING = 2'b01,PAUSED = 2'b10;reg [1:0] current_state, next_state;// 状态寄存器更新
always @(posedge clk or posedge reset) beginif (reset) begincurrent_state <= IDLE;end else begincurrent_state <= next_state;end
end// 状态转移逻辑
always @(*) begincase (current_state)IDLE: next_state = RUNNING;RUNNING: next_state = pause ? PAUSED : RUNNING;PAUSED: next_state = pause ? RUNNING : PAUSED;default: next_state = IDLE;endcase
end// 输出逻辑
always @(*) beginrunning_o = (current_state == RUNNING);
endendmodule
4、模块化重构代码
将上述按键消抖模块及状态机思想加入,并模块化代码。完整代码如下:
clock.v
/** 顶层模块:clock_mmss_static_top* 修改说明:明确声明running信号并统一连接*/
module clock(input clk_50m, // 50MHz系统时钟input reset_btn, // 按键1 - 复位(KEY0)input pause_btn, // 按键2 - 暂停/恢复(KEY1)output [7:0] HEX3, // 分钟十位(最左数码管)output [7:0] HEX2, // 分钟个位(带小数点)output [7:0] HEX1, // 秒十位output [7:0] HEX0 // 秒个位(最右数码管)
);// 内部信号定义
wire clk_1hz;
wire reset_pulse, pause_pulse;
wire running; // 统一使用wire类型连接模块
wire [3:0] min_ten, min_one;
wire [3:0] sec_ten, sec_one;// 实例化时钟分频模块
clock_divider #(.DIV_WIDTH(25) // 50MHz -> 1Hz (2^25 = 33,554,432)
) u_clock_div (.clk(clk_50m),.reset(reset_pulse),.clk_out(clk_1hz)
);// 实例化按键消抖模块
button_debounce u_reset_debounce (.clk(clk_50m),.button_in(reset_btn),.button_out(reset_pulse)
);button_debounce u_pause_debounce (.clk(clk_50m),.button_in(pause_btn),.button_out(pause_pulse)
);// 实例化状态机控制器(关键修改)
fsm_controller u_fsm (.clk(clk_50m),.reset(reset_pulse),.pause(pause_pulse),.running_o(running) // 统一端口命名
);// 实例化秒计数器
second_counter u_sec_counter (.clk(clk_1hz),.reset(reset_pulse),.enable(running), // 直接使用running信号.sec_ten(sec_ten),.sec_one(sec_one),.carry(carry_w)
);// 实例化分钟计数器
minute_counter u_min_counter (.clk(carry_w),.reset(reset_pulse),.enable(running), // 直接使用running信号.min_ten(min_ten),.min_one(min_one)
);// 实例化静态显示驱动
static_display u_display (.min_ten(min_ten),.min_one(min_one),.sec_ten(sec_ten),.sec_one(sec_one),.HEX3(HEX3),.HEX2(HEX2),.HEX1(HEX1),.HEX0(HEX0)
);endmodule
clock_divider.v
/** 模块名称:clock_divider* 功能:将输入时钟分频为较低频率*/
module clock_divider #(parameter DIV_WIDTH = 25 // 分频计数器位宽
)(input clk,input reset,output reg clk_out
);reg [DIV_WIDTH-1:0] counter;always @(posedge clk or posedge reset) beginif (reset) begincounter <= 0;clk_out <= 0;end else begincounter <= counter + 1;clk_out <= counter[DIV_WIDTH-1]; // 取最高位作为分频输出end
endendmodule
button_debounce.v
/** 模块名称:button_debounce* 功能:对机械按键进行消抖处理(约20ms消抖时间)*/
module button_debounce(input clk, // 50MHz时钟input button_in, // 原始按键输入output reg button_out // 消抖后的按键信号
);reg [19:0] counter; // 20位计数器(50MHz/2^20 ≈ 21ms)
reg button_sync;
reg button_prev;always @(posedge clk) begin// 同步输入信号,防止亚稳态button_sync <= button_in;// 消抖处理逻辑if (button_sync != button_prev) beginbutton_prev <= button_sync;counter <= 0;end else if (counter[19]) begin // 计数器满(约21ms)button_out <= button_prev;end else begincounter <= counter + 1;end
endendmodule
second_counter.v
module second_counter(input clk,input reset,input enable,output reg [3:0] sec_ten,output reg [3:0] sec_one,output carry // 改为连续赋值方式
);// 进位条件:59秒时产生1个时钟周期高电平assign carry = (sec_one == 9) && (sec_ten == 5) && enable;always @(posedge clk or posedge reset) beginif (reset) {sec_ten, sec_one} <= 8'h0;else if (enable) beginif ({sec_ten, sec_one} == 8'h59) {sec_ten, sec_one} <= 8'h0;else if (sec_one == 9) beginsec_one <= 0;sec_ten <= sec_ten + 1;endelse sec_one <= sec_one + 1;endend
endmodule
minute_counter.v
/** 模块名称:minute_counter* 功能:实现0-59分钟计数*/
module minute_counter(input clk, // 由秒进位驱动input reset,input enable,output reg [3:0] min_ten, // 分十位(0-5)output reg [3:0] min_one // 分个位(0-9)
);always @(posedge clk or posedge reset) beginif (reset) beginmin_ten <= 0;min_one <= 0;end else if (enable) beginif (min_one == 9) beginmin_one <= 0;if (min_ten == 5) beginmin_ten <= 0;end else beginmin_ten <= min_ten + 1;endend else beginmin_one <= min_one + 1;endend
endendmodule
static_display.v
/** 模块名称:static_display* 功能:驱动4位数码管静态显示MM:SS* DE2-115数码管为共阳极,低电平点亮*/
module static_display(input [3:0] min_ten, // 分钟十位input [3:0] min_one, // 分钟个位input [3:0] sec_ten, // 秒钟十位input [3:0] sec_one, // 秒钟个位output [7:0] HEX3, // 分钟十位(最左)output [7:0] HEX2, // 分钟个位(带小数点)output [7:0] HEX1, // 秒钟十位output [7:0] HEX0 // 秒钟个位(最右)
);// 数字到7段码的转换(共阳极,低电平点亮)
function [7:0] digit_to_seg;input [3:0] digit;input dot; // 小数点控制begincase(digit)0: digit_to_seg = {dot, 7'b1000000}; // 01: digit_to_seg = {dot, 7'b1111001}; // 12: digit_to_seg = {dot, 7'b0100100}; // 23: digit_to_seg = {dot, 7'b0110000}; // 34: digit_to_seg = {dot, 7'b0011001}; // 45: digit_to_seg = {dot, 7'b0010010}; // 56: digit_to_seg = {dot, 7'b0000010}; // 67: digit_to_seg = {dot, 7'b1111000}; // 78: digit_to_seg = {dot, 7'b0000000}; // 89: digit_to_seg = {dot, 7'b0010000}; // 9default: digit_to_seg = {dot, 7'b1111111}; // 灭endcaseend
endfunction// 数码管连接(使用HEX2的小数点作为冒号)
assign HEX3 = digit_to_seg(min_ten, 1'b1); // 分钟十位,点亮小数点
assign HEX2 = digit_to_seg(min_one, 1'b0); // 分钟个位
assign HEX1 = digit_to_seg(sec_ten, 1'b1); // 秒钟十位,点亮小数点
assign HEX0 = digit_to_seg(sec_one, 1'b0); // 秒钟个位endmodule
fsm_controller.v
/** 模块名称:fsm_controller*/
module fsm_controller(input clk,input reset,input pause,output reg running_o // 明确输出端口命名
);// Verilog-2001兼容的状态定义
parameter [1:0] IDLE = 2'b00,RUNNING = 2'b01,PAUSED = 2'b10;reg [1:0] current_state, next_state;// 状态寄存器更新
always @(posedge clk or posedge reset) beginif (reset) begincurrent_state <= IDLE;end else begincurrent_state <= next_state;end
end// 状态转移逻辑
always @(*) begincase (current_state)IDLE: next_state = RUNNING;RUNNING: next_state = pause ? PAUSED : RUNNING;PAUSED: next_state = pause ? RUNNING : PAUSED;default: next_state = IDLE;endcase
end// 输出逻辑
always @(*) beginrunning_o = (current_state == RUNNING);
endendmodule
5、实现效果
分秒计数器
四、总结
在这次基于DE2-115开发板的四位分秒计数器设计实验中,我深刻体会到了数字系统设计的完整流程与工程思维的重要性。从最初简单的分频计数到最终的模块化重构,整个过程让我对FPGA开发有了更立体的认识。通过引入消抖模块并优化参数,最终实现了可靠的按键检测。而三段式状态机的设计,不仅让逻辑更清晰,调试效率也大幅提升。最让我受益的是最后的模块化重构过程,将系统拆分为功能独立的模块,提高了代码复用性。