欢迎光临
我们一直在努力

FPGA番茄时钟:从串口指令到倒计时提醒的硬件实现

目录

  • 一、项目需求及设计思路
    • 1. 项目需求
    • 2. 设计思路
  • 二、项目实现
    • 2.1 模块划分
    • 2.2 代码实现
    • 2.3 仿真波形
  • 三、核心理论基础
    • 3.1 UART 异步串行通信
    • 3.2 BCD 码与计时逻辑
    • 3.3 数码管动态扫描显示
    • 3.4 蜂鸣器与 PWM 发声原理
  • 四、上板演示

一、项目需求及设计思路

1. 项目需求

  • 我想要实现一个类似于番茄时钟的专注时间倒计时系统,用户输入想要专注的时间,到时间之后系统能提醒用户专注时间结束,然后用户可以重新进行其他活动的安排。

2. 设计思路

  • 现有的知识基础有 UART 的 RX 模块,基于数码管实现的秒表和蜂鸣器音乐播放器,那么用户就可以在上位机输入时间数据,通过 RX 给到秒表系统(设置为 23.59.59,对应时分秒)进行倒计时,当秒表系统出现为 00.00.00 时发出信号给到,蜂鸣器播放事先写好的音乐。利用计数器实现 BCD 的,59 就是用的两组四位宽的二进制,也就是两位十六进制,不用进行数据的转换,那我就直接给到 6 位十六进制对应的就已经是具体的时间了,直接进行连续的三组数据输入,只是先后到来的顺序我进行约束给到秒表系统。在这里插入图片描述

    经过刚刚的思考我发现其实对这个小项目进行优化我们还可以得到其他功能:

  • 时间显示:由于 FPGA 的掉电失忆特性,只有上电才能工作,那我将秒表系统调为正常的时间计数逻辑,就可以在上位机给出实时的时间,不掉电的情况之下,就是一个正常的闹钟显示时间。
  • 闹铃模式:同倒计时模式,我设置一个具体的时间节点给出闹铃信号,就开始启动蜂鸣器。
  • 其他模式:大家可以自行进行扩展,本文只是对短期学习综合起来的一个小练习就暂时不做过多的功能扩展了。

二、项目实现

2.1 模块划分

rx 模块 -> 倒计时模块 -> 数码管显示模块 -> 蜂鸣器模块以及 top 模块。

2.2代码实现

  • rx模块
    • 功能代码

    module rx (
    input wire clk ,
    input wire rst_n ,
    input wire uart_rxd_r ,//接收的串行数据
    output reg [7:0] data ,//串并转换后的并行数据
    output wire rx_doen //握手信号,提示我这个字节已经接收完了
    );
    parameter sysclk = 50_000_000 ,//1s
    bps = 9600 ,//比特率
    delay = sysclk / bps;//单波特周期数
    reg [12:0] baud_cnt ;//单波特计数器
    reg [1:0] rx_reg ;//接收串行数据的二级寄存
    reg [3:0] bit ;//比特数0-9
    reg [7:0] data_reg ;//并行数据寄存信号
    reg rx_en ;//数据接收使能
    wire flag ;//空闲位下降沿检测信号、也是使能拉高的瞬间

    //接受信号的二级寄存
    always @(posedge clk or negedge rst_n)
    if(rst_n == 0)
    rx_reg <= 2'b11;
    else
    rx_reg <= {rx_reg[0],uart_rxd_r};

    //flag信号
    assign flag = (rx_reg == 2'b10 && rx_en == 0) ? 1 : 0;//二级寄存中遇到下降沿的时候出现10信号,且是在使能信号无效的时候被触发

    //先写两个计数器
    //单波特计数器
    always @(posedge clk or negedge rst_n)
    if(rst_n == 0)
    baud_cnt <= 0;
    else if(rx_en) begin
    if(baud_cnt == delay – 1)
    baud_cnt <= 0;
    else
    baud_cnt <= baud_cnt + 1;
    end
    else
    baud_cnt <= 0;

    //比特数计数器
    always @(posedge clk or negedge rst_n)
    if(rst_n == 0)
    bit <= 0;
    else if(rx_en) begin
    if(baud_cnt == delay – 1)
    bit <= bit + 1;
    else
    bit <= bit;
    end
    else
    bit <= 0;

    //使能信号
    always @(posedge clk or negedge rst_n)
    if(rst_n == 0)
    rx_en <= 0;
    else if(flag)
    rx_en <= 1;
    else if(baud_cnt == delay/2 – 1 && bit == 9)
    rx_en <= 0;

    //进行数据的串并转换
    always @(posedge clk or negedge rst_n)
    if(rst_n == 0)
    data_reg <= 0;
    else if(rx_en)begin
    if(bit > 0 && bit < 9 && baud_cnt == delay/2 – 1)//在1-8的时候找到中间采样点
    data_reg[bit – 1] <= rx_reg[1]; //同步变化,从最低位开始向高位赋值
    else
    data_reg <= data_reg;
    end
    else
    data_reg <= 0;

    //握手信号和并行数据
    assign rx_doen = (baud_cnt == delay/2 – 1 && bit == 9) ? 1 : 0;

    //信号有效才给data
    always @(posedge clk or negedge rst_n)
    if(rst_n == 0)
    data <= 0;
    else if(rx_doen)
    data <= data_reg;
    else
    data <= data;

    endmodule

    • 仿真代码

    `timescale 1ns/1ps
    module tb ();

    reg clk ;
    reg rst_n ;
    reg rxd_r ;

    wire [7:0] data ;
    wire rx_doen;

    initial begin
    clk <= 0 ;
    rst_n <= 0 ;
    rxd_r <= 1 ;
    #20
    rst_n <= 1 ;
    #2000
    rxd_r <= 0 ; //空闲位结束,起始位的开始,拉低了
    #1060
    rxd_r <= 0 ; //开始以波特为单位进行数据接收,第一个数据
    #1060
    rxd_r <= 1 ;
    #1060
    rxd_r <= 0 ;
    #1060
    rxd_r <= 0 ;
    #1060
    rxd_r <= 0 ;
    #1060
    rxd_r <= 0 ;
    #1060
    rxd_r <= 0 ;
    #1060
    rxd_r <= 0 ; //8个数据接收完成
    #1060

    rxd_r <= 1 ; //停止位一半、空闲位一半
    #1060

    rxd_r <= 0 ; //空闲位结束,起始位的开始,拉低了
    #1060
    rxd_r <= 0 ; //开始以波特为单位进行数据接收,第一个数据
    #1060
    rxd_r <= 1 ;
    #1060
    rxd_r <= 0 ;
    #1060
    rxd_r <= 0 ;
    #1060
    rxd_r <= 1 ;
    #1060
    rxd_r <= 0 ;
    #1060
    rxd_r <= 0 ;
    #1060
    rxd_r <= 0 ; //8个数据接收完成
    #1060

    rxd_r <= 1 ; //停止位一半、空闲位一半
    #1060

    rxd_r <= 0 ; //空闲位结束,起始位的开始,拉低了
    #1060
    rxd_r <= 0 ; //开始以波特为单位进行数据接收,第一个数据
    #1060
    rxd_r <= 0 ;
    #1060
    rxd_r <= 0 ;
    #1060
    rxd_r <= 1 ;
    #1060
    rxd_r <= 0 ;
    #1060
    rxd_r <= 0 ;
    #1060
    rxd_r <= 1 ;
    #1060
    rxd_r <= 0 ; //8个数据接收完成
    #1060

    rxd_r <= 1 ; //停止位一半、空闲位一半
    #10600

    $stop;
    end

    always #10 clk = ~clk;//50MHZ时钟

    rx #(
    .BAUD (52)
    )rx_inst(
    .clk (clk ),
    .rst_n (rst_n ),
    .uart_rxd_r (rxd_r ),
    .data (data ),
    .rx_doen (rx_doen)
    );

    endmodule

  • 倒计时模块
    • 功能代码

    //现在就是对计数器进行倒计时的改动
    module cnt (
    input wire clk ,
    input wire rst_n ,
    input wire [23:0] data_rx_in ,//rx送进来的时间
    input wire start ,
    output wire [23:0] cnt_data
    );
    parameter TIME = 49_999_999 ;
    reg [25:0] cnt_time ;//计数1s
    reg [3:0] cnt_data_1 ,
    cnt_data_2 ,
    cnt_data_3 ,
    cnt_data_4 ,
    cnt_data_5 ,
    cnt_data_6 ;
    reg [1:0] start_reg ;//赋值信号
    reg done ;//计数器控制信号

    //赋值信号start上升沿,start_reg==2'b01的时候
    always @(posedge clk or negedge rst_n)
    if(rst_n == 0)
    start_reg <= 2'b00;
    else
    start_reg <= {start_reg[0],start};

    //倒计时条件
    always @(posedge clk or negedge rst_n)
    if(rst_n == 0)
    done <= 1;
    else if(start) //开始倒计时
    if(cnt_data == 24'b0)//倒计时锁定
    done <= 1;
    else
    done <= 0; //持续倒计时

    //1s计数器
    always @(posedge clk or negedge rst_n)
    if(rst_n == 0)
    cnt_time <= 0;
    else if(done == 0)
    if(cnt_time == TIME )
    cnt_time <= 0;
    else
    cnt_time <= cnt_time + 1;
    //各位数据的计数逻辑
    always @(posedge clk or negedge rst_n)begin
    if(rst_n == 0 )
    cnt_data_1 <= data_rx_in[3:0];
    else if(cnt_time == TIME )
    if(cnt_data_1 == 0)
    cnt_data_1 <= 9;
    else
    cnt_data_1 <= cnt_data_1 – 1;//实现9s-0s的倒计时
    else if(start_reg==2'b01)
    cnt_data_1 <= data_rx_in[3:0];
    end

    always @(posedge clk or negedge rst_n)begin
    if(rst_n == 0 )
    cnt_data_2 <= data_rx_in[7:4];
    else if(cnt_data_1 == 0 && cnt_time == TIME )
    if(cnt_data_2 == 0)
    cnt_data_2 <= 5;
    else
    cnt_data_2 <= cnt_data_2 – 1;//实现的是5到0的倒计时,这里单位是10s
    else if(start_reg==2'b01)
    cnt_data_2 <= data_rx_in[7:4];
    end

    always @(posedge clk or negedge rst_n)begin
    if(rst_n == 0 )
    cnt_data_3 <= data_rx_in[11:8];
    else if(cnt_data_2 == 0 && cnt_data_1 == 0 && cnt_time == TIME )
    if(cnt_data_3 == 0)
    cnt_data_3 <= 9;
    else
    cnt_data_3 <= cnt_data_3 – 1;//实现9-0的分钟倒计时
    else if(start_reg==2'b01)
    cnt_data_3 <= data_rx_in[11:8];
    end

    always @(posedge clk or negedge rst_n)begin
    if(rst_n == 0 )
    cnt_data_4 <= data_rx_in[15:12];
    else if(cnt_data_3 == 0 && cnt_data_2 == 0 && cnt_data_1 == 0 && cnt_time == TIME )
    if(cnt_data_4 == 0)
    cnt_data_4 <= 5;
    else
    cnt_data_4 <= cnt_data_4 – 1;//实现5-0的十分钟级倒计时
    else if(start_reg==2'b01)
    cnt_data_4 <= data_rx_in[15:12];
    end

    always @(posedge clk or negedge rst_n)begin
    if(rst_n == 0 )
    cnt_data_5 <= data_rx_in[19:16];
    else if(cnt_data_4 == 0 && cnt_data_3 == 0 && cnt_data_2 == 0 && cnt_data_1 == 0 && cnt_time == TIME )
    if(cnt_data_5 == 0)
    cnt_data_5 <= 9;
    else
    cnt_data_5 <= cnt_data_5 – 1;//实现的是9-0的小时级倒计时
    else if(start_reg==2'b01)
    cnt_data_5 <= data_rx_in[19:16];
    end

    always @(posedge clk or negedge rst_n)begin
    if(rst_n == 0 )
    cnt_data_6 <= data_rx_in[23:20];
    else if(cnt_data_5 == 0 && cnt_data_4 == 0 && cnt_data_3 == 0 && cnt_data_2 == 0 && cnt_data_1 == 0 && cnt_time == TIME )
    if(cnt_data_6 == 0)
    cnt_data_6 <= 0;
    else
    cnt_data_6 <= cnt_data_6 – 1;//2-0的十小时计数
    else if(start_reg==2'b01)
    cnt_data_6 <= data_rx_in[23:20];
    end

    //数码管数据
    assign cnt_data = {cnt_data_6,cnt_data_5,cnt_data_4,cnt_data_3,cnt_data_2,cnt_data_1};
    endmodule

    • 仿真代码

    `timescale 1ns/1ps
    module tb ();

    reg clk ;
    reg rst_n ;
    reg start ;

    reg [23:0] data_rx_in ;//02.12.48
    wire [23:0] cnt_data ;

    initial begin
    clk <= 0 ;
    rst_n <= 0 ;
    start <= 0 ;
    data_rx_in <= {4'h2,4'h3,4'h5,4'h9,4'h5,4'h9};
    #20
    rst_n <= 1 ;
    #500
    data_rx_in <= {4'h0,4'h2,4'h1,4'h2,4'h4,4'h8};
    start <=1;
    #200
    #100000
    $stop;
    end

    always #10 clk = ~clk;//50MHZ时钟

    cnt #(
    .TIME (4)
    )cnt_inst(
    .clk (clk ),
    .rst_n (rst_n ),
    .data_rx_in (data_rx_in),
    .start (start ),
    .cnt_data (cnt_data )
    );

    endmodule

  • 数码管显示模块
  • module smg(
    input clk ,
    input rst_n ,
    input [23:0] num ,
    output reg [5:0] sel , //位选
    output reg [7:0] seg //段选
    );
    parameter TIMEM= 49999 ;
    reg [19:0] cnt_time ;
    wire [19:0] data_1 ,
    data_2 ,
    data_3 ,
    data_4 ,
    data_5 ,
    data_6 ;
    reg [3:0] data ;
    reg point ;
    //data赋值
    assign data_1 = num[23:20] ;
    assign data_2 = num[19:16] ;
    assign data_3 = num[15:12] ;
    assign data_4 = num[11:8] ;
    assign data_5 = num[7:4] ;
    assign data_6 = num[3:0] ;
    //刷新
    always @(posedge clk or negedge rst_n)
    if(rst_n == 0)
    cnt_time <= 0;
    else if(cnt_time == TIMEM)
    cnt_time <= 0;
    else
    cnt_time <= cnt_time + 1;
    //位选
    always @(posedge clk or negedge rst_n)
    if(rst_n == 0)
    sel <= 6'b011_111;
    else if(cnt_time == TIMEM)
    sel <= {sel[0],sel[5:1]};
    //段选
    always @(posedge clk or negedge rst_n)
    case (sel)
    6'b111_110:begin data <= data_1;point <= 1;end
    6'b111_101:begin data <= data_2;point <= 0;end
    6'b111_011:begin data <= data_3;point <= 1;end
    6'b110_111:begin data <= data_4;point <= 0;end
    6'b101_111:begin data <= data_5;point <= 1;end
    6'b011_111:begin data <= data_6;point <= 1;end
    default:begin data <= data_1;point <= 1;end
    endcase
    //段选
    always @(posedge clk or negedge rst_n)
    if(rst_n == 0)
    seg <= 8'b1_100_0000;
    else
    case (data)
    0 : seg <= {point,7'b100_0000};
    1 : seg <= {point,7'b111_1001};
    2 : seg <= {point,7'b010_0100};
    3 : seg <= {point,7'b011_0000};
    4 : seg <= {point,7'b001_1001};
    5 : seg <= {point,7'b001_0010};
    6 : seg <= {point,7'b000_0010};
    7 : seg <= {point,7'b111_1000};
    8 : seg <= {point,7'b000_0000};
    9 : seg <= {point,7'b001_0000};
    default:seg <= {point,7'b100_0000};
    endcase
    endmodule

  • 蜂鸣器模块
  • module beep (
    input wire clk ,
    input wire rst_n ,
    input wire [23:0] cnt_data,//当倒计时结束,该数据会被锁在24‘b0,复位之后锁在23.59.59
    output wire buzzer
    );
    parameter DO = 47801 ,
    RE = 42589 ,
    MI = 37936 ,
    FA = 35816 ,
    SO = 31887 ,
    LA = 28409 ,
    SI = 25303 ,

    DELAY = 24_999_999 ;
    reg [15:0] cnt_freq ;//频率计数
    reg [15:0] cnt_max ;//音频频率计数
    reg [24:0] cnt ;//每个音符的延时计数器
    reg [31:0] cnt_num ;//实现循环播放的音符计数

    //频率计数器
    always @(posedge clk or negedge rst_n)
    if(rst_n == 0)
    cnt_freq <= 0;
    else if(cnt_data == 24'b0)
    if (cnt_freq == cnt_max – 1 ||cnt == DELAY)//这里是针对仿真来说,cnt_freq <= cnt_max>>1要满足就得设置cnt_freq的清零条件
    cnt_freq <= 0;
    else
    cnt_freq <= cnt_freq + 1;
    //0.5s计数器
    always @(posedge clk or negedge rst_n)
    if(rst_n == 0)
    cnt <= 0;
    else if(cnt_data == 24'b0)
    if (cnt == DELAY )
    cnt <= 0;
    else
    cnt <= cnt + 1;
    //循环播放模块
    always @(posedge clk or negedge rst_n)
    if(rst_n == 0)
    cnt_num <= 1;
    else if (cnt == DELAY)
    cnt_num <= (cnt_num == 32'd48) ? 32'd1 : cnt_num + 32'd1;

    always @(posedge clk or negedge rst_n)
    if(rst_n == 0)
    cnt_max <= DO;
    else
    case (cnt_num)
    1 : cnt_max <= MI ;
    2 : cnt_max <= DO ;
    3 : cnt_max <= MI ;
    4 : cnt_max <= DO ;

    5 : cnt_max <= SO ;
    6 : cnt_max <= MI ;
    7 : cnt_max <= SO ;
    8 : cnt_max <= MI ;

    9 : cnt_max <= LA ;
    10 : cnt_max <= SO ;
    11 : cnt_max <= MI ;
    12 : cnt_max <= DO ;

    13 : cnt_max <= MI ;
    14 : cnt_max <= DO ;
    15 : cnt_max <= MI ;
    16 : cnt_max <= DO ;

    17 : cnt_max <= SO ;
    18 : cnt_max <= MI ;
    19 : cnt_max <= SO ;
    20 : cnt_max <= MI ;

    21 : cnt_max <= LA ;
    22 : cnt_max <= SO ;
    23 : cnt_max <= MI ;
    24 : cnt_max <= DO ;

    25 : cnt_max <= MI ;
    26 : cnt_max <= SO ;
    27 : cnt_max <= LA ;
    28 : cnt_max <= SO ;

    29 : cnt_max <= MI ;
    30 : cnt_max <= DO ;
    31 : cnt_max <= RE ;
    32 : cnt_max <= MI ;

    33 : cnt_max <= SO ;
    34 : cnt_max <= LA ;
    35 : cnt_max <= SO ;
    36 : cnt_max <= MI ;

    37 : cnt_max <= RE ;
    38 : cnt_max <= MI ;
    39 : cnt_max <= SO ;
    40 : cnt_max <= MI ;

    41 : cnt_max <= DO ;
    42 : cnt_max <= RE ;
    43 : cnt_max <= MI ;
    44 : cnt_max <= SO ;

    45 : cnt_max <= LA ;
    46 : cnt_max <= SO ;
    47 : cnt_max <= MI ;
    48 : cnt_max <= DO ;
    default: cnt_max <= DO;
    endcase

    assign buzzer = (cnt_freq <= cnt_max>>1) ? 0 : 1 ;
    endmodule

  • top模块(博主在进行上板验证的时候,发现数据字节数上传老是有问题,画了时序图之后才发现是握手信号相关时序的问题,这应该也是大多数fpga新手容易遇到的问题,对于时序相关的问题,还是需要多画时序图和看仿真)
  • module top(
    input wire clk ,
    input wire rst_n ,
    input wire uart_rxd_r ,
    output wire [5:0] sel ,
    output wire [7:0] seg ,
    output wire buzzer
    );

    wire [7:0] data ;
    wire rx_doen ;
    reg rx_doen_reg ;
    wire [23:0] cnt_data ;
    reg [23:0] data_rx_in ;//24位
    reg start ;//倒计时启动信号
    reg [1:0] cnt_done ;
    wire over ;
    //对握手信号进行寄存,这里只是为了满足时序要求,一级寄存就够了
    always @(posedge clk or negedge rst_n)
    if(rst_n == 0)
    rx_doen_reg <= 0;
    else
    rx_doen_reg <= rx_doen;

    //数据传输
    always @(posedge clk or negedge rst_n)
    if(rst_n == 0)begin
    data_rx_in <= {4'h2,4'h3,4'h5,4'h9,4'h5,4'h9} ;//复位给到最大计数值
    start <= 0 ;
    cnt_done <= 0 ;
    end
    else if(rx_doen_reg)
    if(cnt_done == 2)begin
    data_rx_in <= {data_rx_in[15:0],data};
    start <= 1 ;
    end
    else
    begin
    data_rx_in <= {data_rx_in[15:0],data};//将rx给到的数据放到低八位,最后给进来的就是秒的设置
    cnt_done <= cnt_done + 1;
    end

    beep beep_inst(
    .clk (clk ),
    .rst_n (rst_n ),
    .cnt_data (cnt_data ),
    .buzzer (buzzer )
    );

    cnt cnt_inst(
    .clk (clk ),
    .rst_n (rst_n ),
    .data_rx_in (data_rx_in ),
    .start (start ),
    .cnt_data (cnt_data )
    );

    smg smg_inst(
    .clk (clk ),
    .rst_n (rst_n ),
    .num (cnt_data ),
    .sel (sel ),
    .seg (seg )
    );

    rx rx_inst(
    .clk (clk ),
    .rst_n (rst_n ),
    .uart_rxd_r (uart_rxd_r ),
    .data (data ),
    .rx_doen (rx_doen )
    );

    endmodule

    2.3 仿真波形

    • rx 模块(数据正确输入,但是握手信号会提前 data)。在这里插入图片描述

    • 倒计时模块。在这里插入图片描述

    三、核心理论基础

    本项目的实现依赖于以下四项核心硬件与通信技术。理解这些原理是设计和调试类似嵌入式系统的基础。

    3.1 UART 异步串行通信

    UART(Universal Asynchronous Receiver/Transmitter) 是一种广泛使用的异步串行通信协议。其核心特点如下:

    • 异步通信:通信双方没有统一的时钟信号,依靠预先约定好的波特率(Baud Rate) 来同步数据位。本项目中使用的是常见的 9600 bps。
    • 帧格式:一帧数据通常由 1 个起始位(低电平)、5-9 个数据位、可选的奇偶校验位 和 1-2 个停止位(高电平) 组成。本项目采用 1 起始位、8 数据位、无校验、1 停止位(8N1) 的格式。
    • 采样策略:接收端(RX)在检测到起始位下降沿后,会在每个比特位的中间点进行采样,以提高抗干扰能力。代码中的 baud_cnt == delay/2 – 1 即实现了中点采样逻辑。
    • 应用:在本项目中,UART RX 模块负责从上位机(如 PC 串口助手)接收用户设定的时间数据(格式化为字节流),并将其转换为 FPGA 内部可处理的并行数据。

    3.2 BCD 码与计时逻辑

    BCD(Binary-Coded Decimal)码 是一种用二进制编码表示十进制数字的方法。在数字时钟、计时器中应用广泛。

    • 编码规则:每位十进制数(0-9)用 4 位二进制数表示。例如,十进制数 59 的 BCD 码为 0101 1001。
    • 计时逻辑:本项目中的倒计时模块采用了 6 组 4 位 BCD 码 来分别表示时间的十时、时、十分、分、十秒、秒。
      • 进位/借位规则:秒位(最低两位)从 00 到 59 循环,当秒从 00 变为 59 时,向分位借 1;分位同理,向时位借 1。代码中通过判断低位是否归零(如 cnt_data_1 == 0 && cnt_time == TIME)来触发高位的递减,实现了级联的倒计时逻辑。
      • 初始化与加载:模块复位或接收到新时间数据时,会将 RX 模块传来的并行数据加载到各个 BCD 计数器中,作为倒计时的初始值。

    3.3 数码管动态扫描显示

    数码管动态扫描 是一种利用人眼视觉暂留效应,分时点亮多个数码管以显示不同数字的技术,能极大节省硬件 I/O 资源。

    • 硬件结构:一个多位数码管由多个独立的“段”(构成数字的笔画,a-g 和 dp)和“位”(选择哪个数码管亮)组成。
    • 工作原理:
    • **位选(sel)**:在极短的时间周期内(如本项目中的 TIMEM 计数器控制),依次选中一位数码管(使其共阴极为低电平或共阳极为高电平)。
    • **段选(seg)**:在选中某一位的期间,根据该位需要显示的数字,输出对应的段码(a-g 的电平组合)。
    • 快速循环:以高于人眼识别频率的速度(通常 > 60Hz)循环执行上述步骤,由于视觉暂留,人眼看到的是所有位数码管同时稳定显示。
    • 应用:本项目的 smg 模块通过一个计数器 cnt_time 循环产生位选信号 sel,并根据当前选中的位,从 num(即倒计时数据)中取出对应的 BCD 码,查表转换为段码 seg 输出,从而实现了六位数码管对“时:分:秒”的动态显示。

    3.4 蜂鸣器与 PWM 发声原理

    蜂鸣器 分为有源(内部带振荡源,通电即响)和无源(需要外部驱动频率)两种。本项目使用的是无源蜂鸣器,通过产生不同频率的方波来驱动其发声。

    • PWM(Pulse Width Modulation):通过调节一个周期信号中高电平所占的比例(占空比)来等效模拟不同电压或功率。对于发声,我们主要利用其频率控制特性。
    • 发声原理:
    • 音高(频率):声音的音高由方波的频率决定。例如,中音 DO(C4)的频率约为 262Hz,对应的计数周期为 系统时钟频率 / 目标频率。代码中的 DO、RE、MI 等参数就是根据 50MHz 系统时钟计算出的分频计数值。
    • 音长(时长):每个音符的持续时间由另一个计数器控制(如代码中的 cnt 和 DELAY 参数)。
    • 实现:beep 模块包含两个核心计数器:
    • 音符频率计数器 (cnt_freq):以 cnt_max(当前音符对应的分频值)为周期进行循环计数,产生特定频率的方波输出给 buzzer。
    • 音符时长计数器 (cnt):控制每个音符的持续时间,时间到则切换 cnt_num,从而改变 cnt_max 的值,切换到下一个音符的频率,实现旋律播放。
    • 旋律存储:通过一个大的 case 语句(或可扩展为 ROM)将预先编好的乐谱(音符序列)映射为对应的频率计数值 (cnt_max)。

    总结:UART 负责数据输入,BCD 计时器负责核心逻辑,动态扫描负责视觉输出,PWM 驱动蜂鸣器负责听觉反馈。这四大技术有机结合,构成了一个完整的、交互式的倒计时提醒系统。

    四、上板演示

    • 下载程序后先复位一下然后在上位机输入想设置的时间在这里插入图片描述

    • 等RX模块将数据通过顶层连接到倒计时模块开始倒计时,可以看到数码管开始显示在这里插入图片描述

    • 倒计时正常运行在这里插入图片描述

    • 当倒计时记到00.00.00时,开始播放音乐,此时摁下复位即可关闭在这里插入图片描述

    赞(0)
    未经允许不得转载:171主机测评 » FPGA番茄时钟:从串口指令到倒计时提醒的硬件实现
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址