当前位置: 首页 > news >正文

FPGA学习(三)——数码管实现四位分秒计数器

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) begin
    if (reset) begin
        counter <= 0;
        clk_out <= 0;
    end else begin
        counter <= 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) begin
    if (reset_btn) begin
        clk_div <= 0;
    end
    else begin
        clk_div <= clk_div + 1;
    end
end
assign clk_1hz = clk_div[25] & ~pause_state;  // 暂停时停止时钟

// 暂停状态控制
always @(posedge clk_50m or posedge reset_btn) begin
    if (reset_btn) begin
        pause_state <= 1'b0;
    end
    else if (pause_btn) begin
        pause_state <= ~pause_state;  // 每次按下切换暂停状态
    end
end

// 秒计数器(0-59)
reg [3:0] sec_one, sec_ten;
always @(posedge clk_1hz or posedge reset_btn) begin
    if (reset_btn) begin
        sec_one <= 0;
        sec_ten <= 0;
    end else begin
        if (sec_one == 9) begin
            sec_one <= 0;
            if (sec_ten == 5) sec_ten <= 0;
            else sec_ten <= sec_ten + 1;
        end else begin
            sec_one <= sec_one + 1;
        end
    end
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) begin
    if (reset_btn) begin
        min_one <= 0;
        min_ten <= 0;
    end else begin
        if (min_one == 9) begin
            min_one <= 0;
            if (min_ten == 5) min_ten <= 0;
            else min_ten <= min_ten + 1;
        end else begin
            min_one <= min_one + 1;
        end
    end
end

// 数码管显示驱动
function [7:0] digit_to_seg;
    input [3:0] digit;
    input dot;
    begin
        case(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};
        endcase
    end
endfunction

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

上述代码只是简单的实现,而在实际中,按键输入控制复位和启动/暂停,硬件会有机械抖动,可能导致误触发,因此我们要为上述代码添加上按键的消抖模块。

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) begin
        button_prev <= button_sync;
        counter <= 0;
    end else if (counter[19]) begin  // 计数器满(约21ms)
        button_out <= button_prev;
    end else begin
        counter <= counter + 1;
    end
end

endmodule

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) begin
    if (reset) begin
        current_state <= IDLE;
    end else begin
        current_state <= next_state;
    end
end

// 状态转移逻辑
always @(*) begin
    case (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 @(*) begin
    running_o = (current_state == RUNNING);
end

endmodule

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) begin
    if (reset) begin
        counter <= 0;
        clk_out <= 0;
    end else begin
        counter <= counter + 1;
        clk_out <= counter[DIV_WIDTH-1];  // 取最高位作为分频输出
    end
end

endmodule

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) begin
        button_prev <= button_sync;
        counter <= 0;
    end else if (counter[19]) begin  // 计数器满(约21ms)
        button_out <= button_prev;
    end else begin
        counter <= counter + 1;
    end
end

endmodule

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) begin
        if (reset) {sec_ten, sec_one} <= 8'h0;
        else if (enable) begin
            if ({sec_ten, sec_one} == 8'h59) 
                {sec_ten, sec_one} <= 8'h0;
            else if (sec_one == 9) begin
                sec_one <= 0;
                sec_ten <= sec_ten + 1;
            end
            else sec_one <= sec_one + 1;
        end
    end
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) begin
    if (reset) begin
        min_ten <= 0;
        min_one <= 0;
    end else if (enable) begin
        if (min_one == 9) begin
            min_one <= 0;
            if (min_ten == 5) begin
                min_ten <= 0;
            end else begin
                min_ten <= min_ten + 1;
            end
        end else begin
            min_one <= min_one + 1;
        end
    end
end

endmodule

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;  // 小数点控制
    begin
        case(digit)
            0: digit_to_seg = {dot, 7'b1000000}; // 0
            1: digit_to_seg = {dot, 7'b1111001}; // 1
            2: digit_to_seg = {dot, 7'b0100100}; // 2
            3: digit_to_seg = {dot, 7'b0110000}; // 3
            4: digit_to_seg = {dot, 7'b0011001}; // 4
            5: digit_to_seg = {dot, 7'b0010010}; // 5
            6: digit_to_seg = {dot, 7'b0000010}; // 6
            7: digit_to_seg = {dot, 7'b1111000}; // 7
            8: digit_to_seg = {dot, 7'b0000000}; // 8
            9: digit_to_seg = {dot, 7'b0010000}; // 9
            default: digit_to_seg = {dot, 7'b1111111}; // 灭
        endcase
    end
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) begin
    if (reset) begin
        current_state <= IDLE;
    end else begin
        current_state <= next_state;
    end
end

// 状态转移逻辑
always @(*) begin
    case (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 @(*) begin
    running_o = (current_state == RUNNING);
end

endmodule

5、实现效果

分秒计数器

四、总结

在这次基于DE2-115开发板的四位分秒计数器设计实验中,我深刻体会到了数字系统设计的完整流程与工程思维的重要性。从最初简单的分频计数到最终的模块化重构,整个过程让我对FPGA开发有了更立体的认识。通过引入消抖模块并优化参数,最终实现了可靠的按键检测。而三段式状态机的设计,不仅让逻辑更清晰,调试效率也大幅提升。最让我受益的是最后的模块化重构过程,将系统拆分为功能独立的模块,提高了代码复用性。

相关文章:

  • 【Easylive】saveCategory方法中的if判断(对应增加和修改)
  • 如何获取ecovadis分数?获取ecovadis分数流程,更快通过ecovadis分数方法
  • 27--当路由器学会“防狼术“:华为设备管理面安全深度解剖(完整战备版)
  • 2025年消费观念转变与行为趋势全景洞察:”抽象、符号、游戏、共益、AI”重构新世代消费价值的新范式|徐礼昭
  • 【Spiffo】环境配置:最简cmake工程构建(含使用例)
  • 2025年win10使用dockerdesktop安装k8s
  • 浙大研究团队揭示电场调控5-HT1AR的分子机制
  • 动态规划问题之最长公共子序列
  • 【Easylive】convertVideo2Ts 和 union 方法解析
  • UML统一建模语言
  • Mysql之事务(下)
  • 七. JAVA类和对象(二)
  • FreeCAD傻瓜教程-装配体Assembly的详细使用过程
  • java基础知识面试题总结
  • Android学习总结之算法篇三(排序)
  • git kex_exchange_identification 相关问题
  • C/C++ JSON 库综合对比及应用案例(六)
  • Redis 与 AI:从缓存到智能搜索的融合之路
  • UI设计系统:如何构建一套高效的设计规范?
  • S32K144入门笔记(二十三):FlexCAN解读(1)
  • 4月22城新房价格上涨:上海一二手房价环比均上涨,核心城市土地热带动市场热度提升
  • 南宁海关辟谣网传“查获600公斤稀土材料”:实为焊锡膏
  • 玛丽亚·凯莉虹口连唱两夜,舞台绽放唤醒三代人青春记忆
  • 幼儿园教师拍打孩子额头,新疆库尔勒教育局:涉事教师已被辞退
  • 一涉嫌开设赌场的网上在逃人员在山东威海落网
  • 师爷、文士、畸人:会稽范啸风及其著述