FPGA闹钟实现

FPGA闹钟实现

这里并没有用开发板上的DS1302模块,而是直接分频产生的时钟

写这篇文章的目的也是为了记录一下学习过程


开发环境

  • FPGA开发板:蓝桥杯FPGA的A版

  • 开发软件:VS Code , Quartus 18.0


实现目标

数码管

  • 界面 1:时钟界面
    • 初始会在数码管上显示 23-59-55(时-分-秒)。
  • 界面 2:闹钟设置界面
    • 初始为 00-00-00(时-分-秒)。

按键

  • S1:界面切换

    • 按下后会在界面1(时钟界面)和界面2(闹钟设置界面)之间进行一次切换。
    • 初始界面为时钟界面,按下后切换到闹钟设置界面,再按下切换回时钟界面。
  • S2:闹钟设置位切换

    • 按下后会切换选择闹钟的时、分、秒。
    • 选中的位会以500ms的间隔进行闪烁,表示当前设置的位。
  • S3:数字加

    • 按下后会让选中的闹钟时、分、秒数字加1。
    • 秒数最大为59,分数最大为59,时数最大为23,超过最大值时回到0。
  • S4:数字减

    • 按下后会让选中的闹钟时、分、秒数字减1。
    • 秒数最小为0,分数最小为0,时数最小为0,低于最小值时回到最大值(59秒、59分、23小时)。
  • 注意: S2,S3,S4仅在数码管处于界面二时生效

其他功能

  • 时钟显示

    • 实时的时钟数据会更新在数码管上,并且每秒更新时间。
  • 闹钟响铃

    • 当当前时间与设置的闹钟时间一致时,LED会闪烁并发出提示。
    • LED闪烁时间为8秒,然后停止闪烁。

代码部分

我这里文件的创建顺序为

  • prj (Quartus产生的文件)
  • src(我们编写的代码文件)
    • driver (底层驱动文件)
    • user (应用层驱动文件)

user文件

top.v
module top ( 
        input        clk,
        input        rst_n,
        input  [3:0] key_in,
        output [7:0] led,
        output [7:0] dula,
        output [7:0] wela
    );

    reg [3:0] seg_num [7:0];    // 改为 reg 类型,因为要在 always 中赋值

    wire [3:0] seg_mode;        // `seg_mode` 信号
    wire [7:0] seg_dp;
    wire [3:0] led_mode;        // `led_mode` 信号
    wire [1:0] set_time_indix;   // 设置时间指针信号
    wire [5:0] real_time [2:0];      // 实际时间
    wire [5:0] set_time [2:0];       // 闹钟时间
    wire [5:0] set_time_temp [2:0];  // 闹钟设置时间
    wire seg_star_flag;

    // 在 always 块中赋值 seg_num 数组
    always @(*) begin
        case (seg_mode)
            4'd0: begin
                seg_num[7] = real_time[2] / 10;
                seg_num[6] = real_time[2] % 10;
                seg_num[5] = 4'd11;
                seg_num[4] = real_time[1] / 10;
                seg_num[3] = real_time[1] % 10;
                seg_num[2] = 4'd11;
                seg_num[1] = real_time[0] / 10;
                seg_num[0] = real_time[0] % 10;
            end
            4'd1: begin
                seg_num[7] = (set_time_indix == 2'd2 && seg_star_flag == 1) ? 10 : set_time_temp[2] / 10;
                seg_num[6] = (set_time_indix == 2'd2 && seg_star_flag == 1) ? 10 : set_time_temp[2] % 10;
                seg_num[5] = 4'd11;
                seg_num[4] = (set_time_indix == 2'd1 && seg_star_flag == 1) ? 10 : set_time_temp[1] / 10;
                seg_num[3] = (set_time_indix == 2'd1 && seg_star_flag == 1) ? 10 : set_time_temp[1] % 10;
                seg_num[2] = 4'd11;
                seg_num[1] = (set_time_indix == 2'd0 && seg_star_flag == 1) ? 10 : set_time_temp[0] / 10;
                seg_num[0] = (set_time_indix == 2'd0 && seg_star_flag == 1) ? 10 : set_time_temp[0] % 10;
            end
        endcase
    end

    // 实例化按键处理模块
    key_proc key_proc_inst (
        .clk   (clk),
        .rst_n (rst_n),
        .key_in(key_in),        
        .seg_mode (seg_mode),
        .set_time_indix(set_time_indix),

        .set_time_h(set_time[2]),
        .set_time_d(set_time[1]),
        .set_time_s(set_time[0]),

        .set_time_temp_h(set_time_temp[2]),
        .set_time_temp_d(set_time_temp[1]),
        .set_time_temp_s(set_time_temp[0])
    );

    // 实例化 LED 处理模块
    led_proc led_proc_inst (
        .clk  (clk),
        .rst_n(rst_n),
        .led_mode (led_mode),
        .led  (led)
    );

    // 实例化数码管处理模块
    seg_proc u_seg_proc (
        .clk(clk),
        .rst_n(rst_n),
        .seg_number_in({
            seg_num[7], seg_num[6], seg_num[5], seg_num[4],
            seg_num[3], seg_num[2], seg_num[1], seg_num[0]
        }),
        .seg_dp(seg_dp),
        .dula(dula),
        .wela(wela)
    );

    // 实例化信息处理模块
    infor_deal u_infor_deal (
        .clk(clk),
        .rst_n(rst_n),
        .seg_mode(seg_mode),
        .seg_star_flag(seg_star_flag),
        .led_mode(led_mode),

        .real_time_h(real_time[2]),
        .real_time_d(real_time[1]),
        .real_time_s(real_time[0]),

        .set_time_h(set_time[2]),
        .set_time_d(set_time[1]),
        .set_time_s(set_time[0]),
    );
endmodule

seg_proc.v
module seg_proc ( 
        input clk,   // 50MHz 时钟信号
        input rst_n, // 低电平复位信号
        input [31:0] seg_number_in,  // 输入的数字内容
        input  [7:0] seg_dp,    // 每个位的小数点状态(1: 显示小数点,0: 不显示)
        output [7:0] dula,  // 段选信号
        output [7:0] wela   // 位选信号
    );

    wire       clk_1k;  // 1kHz 时钟信号
    reg  [2:0] bits;  // 当前显示的数码管位索引(0 到 7)
    reg  [3:0] current_num;  // 当前显示位的数字内容
    reg        current_dp;  // 当前显示位的小数点状态

    // 分频器实例化,将 50MHz 时钟分频至 1kHz
    frequency_divider u_frequency_divider (
                          .clk    (clk),
                          .rst_n  (rst_n),
                          .clkbase(50_000_000),
                          .clkdiv (1_000),
                          .clkout (clk_1k)
                      );

    // 根据当前的位选择信号 bits,选择对应的数码管输入数据和小数点状态
    always @(*) begin
        current_num = seg_number_in[(7-bits)*4+:4];  // 选择相应的数字
        current_dp  = seg_dp[7-bits];  // 获取小数点状态
    end

    // 数码管驱动模块实例化
    seg_driver u_seg_driver (
                   .digit       (current_num),
                   .rst_n       (rst_n),
                   .current_dp  (current_dp),
                   .position    (bits),
                   .digit_sel   (wela),
                   .segment_data(dula)
               );

    // 动态扫描逻辑,每 1ms 切换一个显示位
    always @(posedge clk_1k or negedge rst_n) begin
        if (!rst_n)
            bits <= 3'd0;  // 复位时从第 0 位开始显示
        else
            bits <= (bits == 3'd7) ? 3'd0 : bits + 3'd1;  // 切换到下一个显示位
    end
endmodule

key_proc.v
module key_proc (
        input clk,              // 系统时钟信号
        input rst_n,            // 低电平复位信号
        input [3:0] key_in,     // 4 个按键输入型号
        output reg [3:0] seg_mode,   // 模式输出,用于切换LED显示模式
        output reg [1:0] set_time_indix,

        output reg [5:0] set_time_h,
        output reg [5:0] set_time_d,
        output reg [5:0] set_time_s,

        output reg [5:0] set_time_temp_h,
        output reg [5:0] set_time_temp_d,
        output reg [5:0] set_time_temp_s
    );

    wire [3:0] down;    // 每个按键的按下检测信号
    wire [3:0] up;      // 每个按键的松开检测信号
    wire clk_100;       // 100Hz 时钟信号

    frequency_divider u_frequency_divider (
        .clk(clk),
        .rst_n(rst_n),
        .clkbase(50_000_000),
        .clkdiv(100),
        .clkout(clk_100)
    );

    // 实例化按键消抖模块 key_ctrl,用于按键消抖处理
    key_ctrl key_ctrl_inst (
        .clk_100(clk_100),      // 系统时钟输入
        .rst_n(rst_n),          // 低电平复位信号
        .key_in(key_in),        // 按键输入信号
        .down(down),            // 下降沿检测输出
        .up(up)                 // 上升沿检测输出
    );

    // 根据按键按下状态控制模式切换
    always @(posedge clk_100 or negedge rst_n) begin
        if (!rst_n) begin
            seg_mode <= 4'd0;  // 复位时模式设为初始模式 0
            set_time_h <= 6'd0;
            set_time_d <= 6'd0;
            set_time_s <= 6'd0;
            set_time_temp_h <= 6'd0;
            set_time_temp_d <= 6'd0;
            set_time_temp_s <= 6'd0;            
        end
        else begin
            // 界面切换
            if (down[0]) begin
                if (seg_mode == 4'd0)       // 真实时间界面
                    seg_mode <= 4'd1;       // 设置为闹钟时间界面
                else begin                  // 闹钟时间界面
                    seg_mode <= 4'd0;       // 设置为真实时间界面
                    //保存数据
                    set_time_h <= set_time_temp_h;
                    set_time_d <= set_time_temp_d;
                    set_time_s <= set_time_temp_s;
                    // 设置为秒
                    set_time_indix <= 2'd0;         
                end
            end
            // 设置闹钟指针
            else if (down[1] && seg_mode == 4'd1) begin
                if (set_time_indix == 2'd0)         // 如果是秒
                    set_time_indix <= 2'd1;         // 设置为分
                else if (set_time_indix == 2'd1)    // 如果是分
                    set_time_indix <= 2'd2;         // 设置为时
                else                                // 如果是时    
                    set_time_indix <= 2'd0;         // 设置为秒
            end
            // 加
            else if (down[2] && seg_mode == 4'd1) begin
                case (set_time_indix)
                    2'd0: begin
                        if (set_time_temp_s == 6'd59)
                            set_time_temp_s <= 6'd0;                    // 秒归零
                        else
                            set_time_temp_s <= set_time_temp_s + 6'd1;  // 秒加1 
                    end
                    2'd1: begin
                         if (set_time_temp_d == 6'd59)
                            set_time_temp_d <= 6'd0;                    // 分归零
                        else
                            set_time_temp_d <= set_time_temp_d + 6'd1;  // 分加1
                    end
                    2'd2: begin 
                         if (set_time_temp_h == 6'd23)
                            set_time_temp_h <= 6'd0;                    // 时归零
                        else
                            set_time_temp_h <= set_time_temp_h + 6'd1;  // 时加1
                    end
                endcase
            end
            // 减
            else if (down[3] && seg_mode == 4'd1) begin
                case (set_time_indix)
                    2'd0: begin
                        if (set_time_temp_s == 6'd0)
                            set_time_temp_s <= 6'd59;                   // 秒归59
                        else
                            set_time_temp_s <= set_time_temp_s - 6'd1;  // 秒减1 
                    end
                    2'd1: begin
                         if (set_time_temp_d == 6'd0)
                            set_time_temp_d <= 6'd59;                   // 分归59
                        else
                            set_time_temp_d <= set_time_temp_d - 6'd1;  // 分减1
                    end
                    2'd2: begin 
                         if (set_time_temp_h == 6'd0)
                            set_time_temp_h <= 6'd23;                   // 时归23
                        else
                            set_time_temp_h <= set_time_temp_h - 6'd1;  // 时减1
                    end
                endcase
            end
        end
    end
endmodule

led_proc.v
module led_proc (
        input clk,          // 50MHz 时钟输入
        input rst_n,        // 低电平复位信号
        input [3:0] led_mode,   // 模式选择信号
        output [7:0] led    // LED 输入
    );
    reg [7:0] led_pattern;  // LED 控制模式寄存器
    wire clk_100;             // 1Hz 分频时钟信号

    // 实例化 frequency_divider 模块,将 50MHz 时钟分频到 1Hz
    frequency_divider freq_diw_init (
        .clk(clk),
        .rst_n(rst_n),
        .clkbase(50_000_000),
        .clkdiv(100),
        .clkout(clk_100)
    );

    // 根据 mode 模式和 1Hz 慢时钟信号 clk_1 控制 LED 模式
    always @(*) begin  
        if (!rst_n) begin
            led_pattern <= 8'b0000_0000;  // 全灭
        end
        else begin
            case (led_mode)
                4'd0:
                    led_pattern <= 8'b0000_0000;  // 全灭
                4'd1:
                    led_pattern <= 8'b1111_1111;  // 全亮
                default:
                    led_pattern <= 8'b0000_0000;  // 全灭
            endcase
        end
    end

    // 实例化 LED 显示模块,将 led_pattern 映射到实际 LED 输出
    led_display led_display_init (
        .led_pattern(led_pattern),
        .led(led)
    );
endmodule

infor_proc.v
module infor_deal(
    input clk,
    input rst_n,
    output reg [5:0] real_time_h,
    output reg [5:0] real_time_d,
    output reg [5:0] real_time_s,

    input [5:0] set_time_h,
    input [5:0] set_time_d,
    input [5:0] set_time_s,

    output reg [3:0] led_mode,
    output reg seg_star_flag,
    input [3:0] seg_mode
    );

    wire led_star_clk_500ms_2Hz;
    wire real_time_clk_1s_1Hz;
    reg led_star_flag;
    reg [2:0] led_star_cnt;
    // 500ms时钟
    frequency_divider infor_frequency_divider (
        .clk(clk),
        .rst_n(rst_n),
        .clkbase(50_000_000),
        .clkdiv(2),
        .clkout(led_star_clk_500ms_2Hz)
    );
    // 1s时钟
    frequency_divider real_time_frequency_divider (
        .clk(clk),
        .rst_n(rst_n),
        .clkbase(50_000_000),
        .clkdiv(1),
        .clkout(real_time_clk_1s_1Hz)
    );

    // 闪烁控制
    always @(posedge led_star_clk_500ms_2Hz or negedge rst_n) begin
        if (!rst_n) begin
            led_mode <= 4'd0;
            seg_star_flag <= 0;
        end
        else begin
            // LED闪烁控制
            if (led_star_flag == 1) begin
                if (led_mode == 4'd0) led_mode <= 4'd1; // 进入全亮
                else led_mode <= 4'd0;                  // 进入全灭
            end
            else begin
                led_mode <= 4'd0;
            end
            // 数码管闪烁控制
            if (seg_star_flag == 1) seg_star_flag <= 0;
            else seg_star_flag <= 1;
        end
    end

    // 时间控制
    always @(posedge real_time_clk_1s_1Hz or negedge rst_n) begin
        if (!rst_n) begin
            real_time_h <= 6'd23;
            real_time_d <= 6'd59;
            real_time_s <= 6'd55;
            led_star_flag <= 0;
            led_star_cnt <= 3'd0;
        end
        else begin
            real_time_s <= real_time_s + 6'd1;
            //秒
            if (real_time_s == 6'd59) begin
                real_time_s <= 6'd0;
                real_time_d <= real_time_d + 6'd1;
            end
            if (real_time_d == 6'd59 && real_time_s == 6'd59) begin
                real_time_d <= 6'd0;
                real_time_h <= real_time_h + 6'd1;
            end
            if (real_time_h == 6'd23 && real_time_d == 6'd59 && real_time_s == 6'd59)
                real_time_h <= 6'd0;
            // 闹钟
            if (real_time_s == set_time_s && real_time_d == set_time_d && real_time_h == set_time_h)
                led_star_flag <= 1; // 闹钟响
            if (led_star_flag == 1) begin
                led_star_cnt <= led_star_cnt + 3'd1;    // 计数5s
                if (led_star_cnt == 3'd7) begin
                    led_star_cnt <= 3'd0;
                    led_star_flag <= 0; // 闹钟停止
                end
            end
        end
    end
endmodule

driver文件

frequency_divider.v
module frequency_divider (
        input  wire        clk,     // 输入时钟信号 50MHz
        input  wire        rst_n,   // 低电平复位信号
        input  wire [31:0] clkbase, // 基准时钟频率(单位:Hz),例如50MHz
        input  wire [31:0] clkdiv,  // 期望输出频率(单位:Hz),例如1000代表1kHz
        output reg         clkout   // 输出时钟信号
    );  

    reg [31:0] counter; // 计数器
    reg [31:0] limit;   // 计数限制值

    //计算每个分频周期的计数限制
    always @(*) begin
        if (clkdiv > 0) begin
            limit = clkbase / (2 * clkdiv); // 计算分频周期
        end
        else begin
            limit = 0;                      // 防止除以零
        end
    end

    // 计数逻辑
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            counter <= 0;   // 复位计数器
            clkout  <= 0;   // 复位输出时钟
        end
        else begin
            if (limit > 0) begin
                if (counter >= limit - 1) begin
                    clkout  <= ~clkout; // 翻转输出时钟
                    counter <= 0;       // 计数器归零
                end
                else begin
                    counter <= counter + 1; // 计数器加一
                end
            end
        end
    end
endmodule

seg_driver.v
module seg_driver (
        input [3:0] digit,              // 输入的十进制数字
        input rst_n,                    // 复位信号(低电平有效) 
        input current_dp,               // 小数点使能信号
        input [2:0] position,           // 当前显示的位
        output reg [7:0] digit_sel,     // 位选信号
        output reg [7:0] segment_data   // 段选信号
    );

    // 段码定义(每个数字对应的七段显示编码)
    localparam [7:0] SEG_0 = 8'b1100_0000;  //0
    localparam [7:0] SEG_1 = 8'b1111_1001;  //1
    localparam [7:0] SEG_2 = 8'b1010_0100;  //2
    localparam [7:0] SEG_3 = 8'b1011_0000;  //3
    localparam [7:0] SEG_4 = 8'b1001_1001;  //4
    localparam [7:0] SEG_5 = 8'b1001_0010;  //5
    localparam [7:0] SEG_6 = 8'b1000_0010;  //6
    localparam [7:0] SEG_7 = 8'b1111_1000;  //7
    localparam [7:0] SEG_8 = 8'b1000_0000;  //8
    localparam [7:0] SEG_9 = 8'b1001_0000;  //9
    localparam [7:0] SEG_10 = 8'b1111_1111; //灭
    localparam [7:0] SEG_11 = 8'b1011_1111; //-

    // 根据输入的数字和小数点状态生成段码
    always @(*) begin
        if (!rst_n) begin
            segment_data <= 8'b1111_1111;   // 复位时熄灭所有段码
            digit_sel <= 8'b1111_1111;      // 复位时关闭所有位选
        end
        else begin
            // 根据输入的数字选择对应的段码
            case (digit)
                4'd0: segment_data <= SEG_0;
                4'd1: segment_data <= SEG_1;
                4'd2: segment_data <= SEG_2;
                4'd3: segment_data <= SEG_3;
                4'd4: segment_data <= SEG_4;
                4'd5: segment_data <= SEG_5;
                4'd6: segment_data <= SEG_6;
                4'd7: segment_data <= SEG_7;
                4'd8: segment_data <= SEG_8;
                4'd9: segment_data <= SEG_9;
                4'd10: segment_data <= SEG_10;
                4'd11: segment_data <= SEG_11;
            endcase

            // 如果 decimal_en 为 1,则激活小数点(最高为小数点位)
            if (current_dp) segment_data[7] = 1'b0;

            // 根据当前位置 `position` 控制位选信号
            digit_sel = ~(8'b0000_00001 << position);
        end
    end
endmodule

key_ctrl.v
module key_ctrl (
        input            clk_100,   // 输入时钟信号(100Hz)
        input            rst_n,     // 低电平复位信号
        input      [3:0] key_in,    // 4个按键输入信号
        output reg [3:0] down,      // 按键按下检测信号(下降沿触发)
        output reg [3:0] up         // 按键释放检测信号(上升沿触发)
    );

    reg [3:0] new;  // 当前按键状态
    reg [3:0] old;  // 上一个时钟周期的按键状态

    //按键状态检测逻辑
    always @(posedge clk_100 or negedge rst_n) begin
        if (!rst_n) begin
            // 复位时将所有状态设为未按下
            new <= 4'b1111;
            old <= 4'b1111;
            down <= 4'b0000;
            up <= 4'b0000;
        end
        else begin
            old <= new;                 // 状态更新
            new <= key_in;              // 获取键码
            down <= new & (new ^ old);  // 检测下降沿
            up <= ~new & (new ^ old);   // 检测上升沿
        end
    end
endmodule

led_display.v
module led_display (
        input      [7:0] led_pattern,
        output reg [7:0] led
    );
    always @(*) begin
        led = ~led_pattern;
    end
endmodule
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值