Verilog编程实现一个分秒计数器

发布于:2025-04-03 ⋅ 阅读:(21) ⋅ 点赞:(0)

        本次实验我们在DE2-115开发板上用 Verilog编程实现了一个分秒计数器,并使用状态机的思想添加了按键暂停和按键消抖功能。

模块规划

project/
├── src/
    ├── clk_divider.v		// 分频模块
    ├── debounce_fsm.v		// 按键消抖模块
    ├── ctrl_fsm.v			// 控制状态机
    ├── counter_60.v		// 分秒计数器模块
    └── led.v				// 顶层模块(这里是建项目时出了点小差错,建议改为其它名称)

模块设计

        首先我们需要两个计数器,一个秒计数器,一个分钟计数器,而DE2-115板子通常有50MHz的时钟,所以需要分频得到1Hz的信号来驱动计数器,所以我们可以设计一个分频模块,将50MHz分频到1Hz,作为计数器的时钟源。

// 分频模块
module clk_divider(
    input clk_50m,
    input rst_n,
    output reg clk_1hz
);
reg [25:0] cnt;  // 50MHz→1Hz需50M周期=50,000,000=26位计数器

always @(posedge clk_50m or negedge rst_n) begin
    if (!rst_n) begin
        cnt <= 0;
        clk_1hz <= 0;
    end else if (cnt == 26'd24_999_999) begin
        cnt <= 0;
        clk_1hz <= ~clk_1hz;
    end else begin
        cnt <= cnt + 1;
    end
end
endmodule

        然后我们进行按键的暂停和消抖处理,这里我们要用到状态机思想,就需要为状态机设置一些状态,空闲状态(正常计数)、暂停状态(停止计数)、等待释放状态(按键按下后的稳定期)。当检测到按键按下并经过消抖后,进入暂停状态,停止计数器;当按键释放并消抖后,回到空闲状态,继续计数。

// 按键消抖模块
module debounce_fsm(
    input clk_1hz,
    input rst_n,
    input raw_btn,
    output reg btn_ok
);
// 状态定义:IDLE/DEBOUNCE_PRESSED/DEBOUNCE_RELEASED
localparam IDLE = 2'b00;
localparam DEBOUNCE_PRESSED = 2'b01;
localparam DEBOUNCE_RELEASED = 2'b10;

reg [19:0] cnt;  // 1Hz下20ms消抖对应20个时钟周期
reg [1:0] state, next_state;
reg btn_prev;

// 状态转移逻辑
always @(posedge clk_1hz or negedge rst_n) begin
    if (!rst_n) begin
        state <= IDLE;
        cnt <= 0;
        btn_prev <= 1'b1;
    end else begin
        state <= next_state;
        if (state == DEBOUNCE_PRESSED) cnt <= cnt + 1;
        else cnt <= 0;
        btn_prev <= raw_btn;
    end
end

// 状态机逻辑
always @(*) begin
    next_state = state;
    btn_ok = 1'b1;
    case(state)
        IDLE: begin
            if (raw_btn == 1'b0 && btn_prev == 1'b1) begin
                next_state = DEBOUNCE_PRESSED;
            end
        end
        DEBOUNCE_PRESSED: begin
            if (cnt == 20'd19) begin  // 20ms稳定后确认按下
                next_state = DEBOUNCE_RELEASED;
                btn_ok = 1'b0;       // 输出有效按下信号
            end
        end
        DEBOUNCE_RELEASED: begin
            if (raw_btn == 1'b1 && btn_prev == 1'b0) begin
                next_state = IDLE;
            end
        end
    endcase
end
endmodule
// 控制状态机
module ctrl_fsm(
    input clk_1hz,
    input rst_n,
    input btn_ok,
    output reg run_en
);
	localparam RUN = 1'b1;
	localparam PAUSE = 1'b0;

	reg [1:0] state, next_state;

	always @(posedge clk_1hz or negedge rst_n) begin
		if (!rst_n) 
			begin
				state <= RUN;
			end 
		else 
			begin
				state <= next_state;
			end
	end

	always @(*) begin
		next_state = state;
		case(state)
			RUN: 
				begin
					if (btn_ok) next_state = PAUSE;  // 按键按下进入暂停
				end
			PAUSE: 
				begin
					if (!btn_ok) next_state = RUN;   // 按键释放恢复运行
				end
		endcase
	end

	always @(*) begin
		run_en = (state==RUN);
	end
	
endmodule

        在模块划分上,我们需要将分频器、按键消抖模块、状态机控制模块和计数器模块分开。分频器模块处理时钟分频,按键消抖模块处理按键信号,状态机模块控制暂停和继续,计数器模块处理分秒的计数。这样的模块化设计更清晰,也便于调试。
        另外我们可以使用计数器来延迟检测,确保按键状态稳定。

// 分秒计数器模块
module counter_60(
    input clk_1hz,
    input run_en,
    input rst_n,
    output reg [5:0] sec,
    output reg [5:0] min
);
always @(posedge clk_1hz or negedge rst_n) begin
    if (!rst_n) begin
        sec <= 0;
        min <= 0;
    end else if (run_en) begin
        if (sec == 6'd59) begin
            sec <= 0;
            if (min == 6'd59) min <= 0;
            else min <= min + 1;
        end else begin
            sec <= sec + 1;
        end
    end
end
endmodule

        最后则是我们的顶层模块设计,来实现DE2-115板子上的分秒计数器。

// 顶层模块
module led(
    input clk_50m,
    input rst_n,
    input raw_btn,
	 output [6:0] hex0,		// 秒个位显示
    output [6:0] hex1, 		// 秒十位显示
    output [6:0] hex2,  	// 分个位显示
    output [6:0] hex3   	// 分十位显示

);
wire clk_1hz;
wire btn_ok;
wire run_en;
reg [5:0] sec,
reg [5:0] min

// 实例化模块
clk_divider u1 (.clk_50m(clk_50m), 
					.rst_n(rst_n), 
					.clk_1hz(clk_1hz));
debounce_fsm u2 (.clk_1hz(clk_1hz), 
					.rst_n(rst_n), 
					.raw_btn(raw_btn), 
					.btn_ok(btn_ok));
ctrl_fsm u3 (.clk_1hz(clk_1hz), 
					.rst_n(rst_n), 
					.btn_ok(btn_ok), 
					.run_en(run_en));
counter_60 u4 (.clk_1hz(clk_1hz), 
					.run_en(run_en), 
					.rst_n(rst_n), 
					.sec(sec), 
					.min(min));
					
// BCD转换和显示模块
wire [3:0] min_tens = min / 10;  // 分钟十位
wire [3:0] min_ones = min % 10;  // 分钟个位
wire [3:0] sec_tens = sec / 10;  // 秒十位
wire [3:0] sec_ones = sec % 10;  // 秒个位

// 七段显示译码器
seg7_decoder seg0(.num(sec_ones), .seg(hex0));
seg7_decoder seg1(.num(sec_tens), .seg(hex1));
seg7_decoder seg2(.num(min_ones), .seg(hex2));
seg7_decoder seg3(.num(min_tens), .seg(hex3));

endmodule

        最后记得把顶层模块设为顶层文件。

        对于七段数码管模块,我们的输入情况有9种,所以七段译码器需要将4位BCD码转换为对应的段码,当FPGA输出低电压的时候,对应的字码段点亮。

// 七段显示译码模块
module seg7_decoder(
    input [3:0] num,
    output reg [6:0] seg  // 格式:abcdefg(低有效)
);

always @(*) begin
    case(num)
        4'd0 : seg = 7'b1000000; // 0
        4'd1 : seg = 7'b1111001; // 1
        4'd2 : seg = 7'b0100100; // 2
        4'd3 : seg = 7'b0110000; // 3
        4'd4 : seg = 7'b0011001; // 4
        4'd5 : seg = 7'b0010010; // 5
        4'd6 : seg = 7'b0000010; // 6
        4'd7 : seg = 7'b1111000; // 7
        4'd8 : seg = 7'b0000000; // 8
        4'd9 : seg = 7'b0010000; // 9
        default: seg = 7'b1111111; // 灭
    endcase
end
endmodule

        接着进行管脚配置。

程序烧录

        然后就可以连上DE2-115开发板,进行程序烧录。

总结

        通过本次实验,我掌握了Verilog中分频器模计数器和数码管驱动的核心设计方法,理解了时序逻辑与组合逻辑的协同工作原理。实验进一步强化了硬件描述语言的工程实践能力,为复杂计时器设计奠定了基础。