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

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

目录

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

一、项目需求及设计思路

1. 项目需求

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

2. 设计思路

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

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

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

二、项目实现

2.1 模块划分

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

2.2代码实现

  1. 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
  1. 倒计时模块
  • 功能代码
//现在就是对计数器进行倒计时的改动 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
  1. 数码管显示模块
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
  1. 蜂鸣器模块
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
  1. 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 码来分别表示时间的十时、时、十分、分、十秒、秒。
    • 进位/借位规则:秒位(最低两位)从0059循环,当秒从00变为59时,向分位借 1;分位同理,向时位借 1。代码中通过判断低位是否归零(如cnt_data_1 == 0 && cnt_time == TIME)来触发高位的递减,实现了级联的倒计时逻辑。
    • 初始化与加载:模块复位或接收到新时间数据时,会将 RX 模块传来的并行数据加载到各个 BCD 计数器中,作为倒计时的初始值。

3.3 数码管动态扫描显示

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

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

3.4 蜂鸣器与 PWM 发声原理

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

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

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

四、上板演示

  • 下载程序后先复位一下然后在上位机输入想设置的时间

  • RX模块将数据通过顶层连接到倒计时模块开始倒计时,可以看到数码管开始显示

  • 倒计时正常运行

  • 当倒计时记到00.00.00时,开始播放音乐,此时摁下复位即可关闭

http://www.jsqmd.com/news/1401959/

相关文章:

  • 数据结构与算法实战指南:从核心原理到高频考点解析
  • 模型只是 CPU,插件运行时才是 Agent 的操作系统:从 DeepSeek Harness 到 Zongsoft
  • 2026 年现阶段,枣庄诚信的球墨铸铁方井直销厂家哪个好,别再踩检查井的坑!这款省心的排水设施让小区道路少了麻烦。-安行铸件 - 行业推荐官【认证】
  • 2026电除尘干冰清洗行业格局解读,口碑好的机构优选攻略 - myqiye
  • OpenClaw v2026.3.8深度解析:备份、浏览器联动与跨平台优化
  • 2026废轮胎炼油设备加工厂口碑成本控制佳,价格透明不踩坑,实力测评推荐 - mypinpai
  • 怎么给 DeepSeek Harness 写个插件
  • Windows批处理脚本实现图片批量重命名实战
  • GBase 8a MPP数据库单机部署实战:从环境准备到服务验证
  • 2026成熟高流水主播择业指南 合规挂靠进阶增收实操测评 - nuanyin
  • 2026 年新发布:怀来靠谱的铜锅涮肉鲜肉刨片一体机批发厂家电话,开火锅店省上万成本的这玩意儿,到底有啥过人之处? - 企业推荐管【认证】
  • 2026 年新发布:湖北热门的无机纤维棉喷涂施工供应厂家有哪些,住厂房冬暖夏凉不用愁,这施工手法到底靠不靠谱? - 行业推荐官-2
  • Windows鼠标禁用唤醒无效?四层排查法彻底解决电脑睡眠被唤醒问题
  • 两江新区考公面试班机构筛选名录,高正公职辅警课程与OMO督学营客户口碑力荐 - myqiye
  • GBase 8a MPP数据库实战部署指南:从环境准备到集群调优
  • 2026抖音合规公会择业标准测评 全维度筛选靠谱直播合作机构 - nuanyin
  • 笔记本摄像头失灵?从注册表损坏到驱动故障的完整修复指南
  • Android16 SELinux 关闭方式汇总
  • 一体化泵站源头厂家深度测评,实力与价格双透明,避坑必看 - mypinpai
  • 计算机网络入门:从微信聊天看懂硬件、软件、协议与数据
  • 2026 年更新:西峡诚信的景观石推广企业推荐几家,家门口的“自然摆件”竟能拉满庭院气场?90%的人都没找对门道。 - 行业推荐官-2
  • 2026抖音八大赛道直播测评 全品类深耕适配各类主播增收攻略 - nuanyin
  • 黑龙江靠谱的特斯拉挡杆公司,认准这家就对了 - 装修教育财税推荐2026
  • 2026年8月新发布考公面试班资深中心:五家风格各异的专业机构深度解析 - myqiye
  • 华为鸿蒙背书背课文APP—小羊背诵
  • 2026优选宜宾旅游住宿口碑推荐:住进五粮液酒香里的全季酒店 - 装修教育财税推荐2026
  • 靖江洗衣机零售推荐:小好家电批发零售的专业之选 - 装修教育财税推荐2026
  • 在免费云服务器上搭建 Gitea 私有 Git 仓库实践
  • 宇树打新:物理AI 国产的「本体」先跑通了商业化
  • 零代码AI医学研究:医学生快速发表论文的三大方向与实操指南