简介:基于Verilog HDL设计的多功能数字钟完整工程包,面向FPGA初学者、电子设计课程学生及硬件工程师,覆盖自动计时、手动校时、倒计时、闹钟及整点报时五大核心功能,并在Cyclone II平台上完成综合与LCD显示驱动,适合直接作为课程设计、毕业设计或自主实验的完整参考。资源共69个文件,压缩包仅2.12MB,包含Verilog源码、Word版设计报告、Quartus工程与配置文件(qpf/qsf)、仿真波形(vwf)、时序报告(rpt)、烧录文件(sof/pof),以及cdb、hdb、qmsg等EDA中间文件,目录结构清晰,便于对照FPGA设计全流程并快速定位所需内容。已有8535人浏览学习。压缩包内既有分模块的源码和详细注释,也有针对PLL时钟生成、计数器构建、LCD显示接口等关键环节的说明;Word报告系统梳理了系统概述、模块划分、测试验证等要点,可帮助读者完整复现并灵活扩展一个实用、可扩展的数字钟系统。
1. 一块板子搞定Verilog数字钟:自动计时、手动校时、倒计时、整点报时和LCD显示
手里只有一块基础开发板,想把Verilog数字钟从分频、计时、校时一直做到LCD显示,这份工程源码是我见过比较完整的参考样本。它把50MHz时钟逐级分频成1Hz秒脉冲,驱动BCD计数器完成自动计时;再通过按键和状态机切入手动校时,附加倒计时、整点报时,最后把时分秒和倒计时剩余时间显示在LCD1602上。适合还没完整做过一个“能跑起来”的时序系统的人:学过Verilog基础语法,知道always块和计数器,但没有把模块串起来上板的经验。也适合课程设计前想快速确认结构是否合理的人。你不需要再闭门造车,照着模块图把代码敲下来,逐步替换参数,就能在一两周内拿下整个系统。
2. 自动计时核心:分频器与BCD计数器怎么配合才不丢秒
2.1 分频器:把50MHz降到1Hz的边界参数与单脉冲输出
数字钟的起点不是计时器,而是一个可靠的分频器。开发板通常提供一个50MHz有源晶振,这个频率太快要先降频。常见做法是设置一个25_000_000的上限计数器,计数满后翻转输出电平,每翻转一次就是半个周期,两次翻转才能凑出1Hz。也就是说,要得到1Hz的方波,计数器不是计到25_000_000就输出一个脉冲,而是计满后翻转电平,让分频输出变成占空比50%的时钟。
module clk_div_1hz( input wire clk_50m, // 板载50MHz晶振 input wire rst_n, // 低电平复位 output reg clk_1hz // 分频得到的1Hz方波 ); // 50MHz / 1Hz = 50_000_000,翻转一次需要25_000_000个周期 parameter CNT_MAX = 25_000_000 - 1; reg [24:0] cnt; always @(posedge clk_50m or negedge rst_n) begin if (!rst_n) begin cnt <= 25'd0; clk_1hz <= 1'b0; end else if (cnt == CNT_MAX) begin cnt <= 25'd0; clk_1hz <= ~clk_1hz; // 计数到顶就翻转,形成50%占空比方波 end else begin cnt <= cnt + 1'b1; end end endmodule逻辑说明:复位后计数器和输出都清零。每个时钟上升沿先判断复位,再判断计数是否到达限定值;等于CNT_MAX就清零并翻转输出电平,否则连续加一。这样输出端得到一个2秒一个完整周期的方波,也就是1Hz。如果晶振不是50MHz,把CNT_MAX改成“晶振频率 / 2 - 1”即可。
参数说明:cnt是25位宽,因为25_000_000的二进制表示需要25位。这里很容易写错,24位寄存器最大值只有16_777_215,存不下25_000_000个周期,仿真时看不出来,上板后秒针会以大约3倍速狂走,这是最典型的翻车点。
第二种常见写法是输出单周期脉冲而不是方波,适用于后面的计数器使能:计数达到CNT_MAX时,把输出en拉高一个时钟周期后清零。代码上只需要把翻转那句换成en <= (cnt == CNT_MAX) ? 1'b1 : 1'b0;。我一般会同时保留两个信号:一个clk_1hz给蜂鸣器等需要连续方波的模块,一个tick_1s给计时器做使能。这样避免了多级时钟驱动同一逻辑带来的时钟偏移。
2.2 计数器链:秒、分、时的进位设计与进位标志的时序陷阱
有了1Hz的tick,下一步就是BCD码计数器。选BCD而不是二进制的原因是:显示模块和人机交互需要十进制,如果内部用二进制加法器再转换,逻辑上没错,但转换模块本身容易引入组合逻辑竞争,调试成本更高。既然是数字钟,直接用“每位一个十进制数”的计数结构,每个计数器输出就是一位BCD码。
module bcd_counter( input wire clk, // 统一使用50MHz系统时钟,不用分频后的时钟 input wire rst_n, input wire enable, // 每秒来一次的高电平使能(单脉冲) input wire load_en, // 校时写入 input wire [3:0] load_data, output reg [3:0] q, // BCD码输出,0~9 output reg carry // 进位标志,供下一级使用 ); always @(posedge clk or negedge rst_n) begin if (!rst_n) q <= 4'd0; else if (load_en) q <= load_data; else if (enable) begin if (q == 4'd9) q <= 4'd0; else q <= q + 1'b1; end end always @(*) begin if (enable && q == 4'd9) carry = 1'b1; else carry = 1'b0; end endmodule关键就在这个carry信号的产生。很多人在这一步把上一级的q == 9直接接到下一级的enable或者clk,这种做法在仿真里完全正常,一上板就出现偶尔跳秒、分钟和秒不同步的现象。原因是门控时钟和组合逻辑使能会造成建立时间违例,尤其是两级以上的级联,毛刺会被下级捕获。
我一般会做一个tick级联:上一个计数器的carry不是直接驱动下一个计数器的clk,而是连到下一个计数器的enable,所有计数器共用同一个50MHz时钟。第9秒时carry拉高一个时钟周期,下一秒的enable有效,分钟个位加一,秒个位回到0。这样就形成了“逐级使能”而非“逐级时钟”的结构。如果顶层直接拿这个组合逻辑carry去驱动下一级,可能引入毛刺。最常见的处理是让下一级寄存器接收后再当使能,或者顶层打一拍,两者选一即可,我习惯后者,因为顶层可见性更强,出问题好查。
顶层级联的例化模式大致如下:
bcd_counter u_sec_l (.clk(clk_50m), .enable(tick_1s), .q(sec_l), .carry(sec_l_carry), ...); bcd_counter u_sec_h (.clk(clk_50m), .enable(sec_l_carry), .q(sec_h), .carry(sec_h_carry), ...); bcd_counter u_min_l (.clk(clk_50m), .enable(sec_h_carry), .q(min_l), .carry(min_l_carry), ...);说明:使能不是持续一个秒周期而是单个时钟周期宽度,所以每一级进位判断必须用enable和q==9的与逻辑。时序上,上一级的q在时钟沿变成9后,下一拍enable才拉高,再下一拍十位才加一,看似延迟了两个周期,但对1Hz的人眼观察没有任何影响,时序收敛反而简单。
时计数器跟秒、分不一样,它的循环边界是23而不是逐位判断。秒和分是“个位9到0、十位5到0”的结构;小时则是个位和十位联合判断,十位为2且个位为3时,下一个tick整体回零。如果直接复制bcd_counter到小时位,时间走到12:59后再加1会变成13:00,但23:59再走就变20:00,半夜十二点直接翻车。我习惯给bcd_counter加一个max_value参数,当q == max时归零且拉高进位,小时模块直接传参23,秒和分模块传参59,统一处理。
3. 手动校时与倒计时:状态机切换、按键消抖和借位处理
3.1 校时状态机:四态循环与按键单脉冲的配合
校时的本质是把“时钟正在运行”这个默认状态打断,让用户能单独修改秒、分、时。常见做法是定义四个状态:正常计时、校秒、校分、校时。每按一次设置键,状态向下一个状态跳转,从校时按回正常计时,形成一个环。
localparam IDLE = 2'b00; localparam SET_SEC = 2'b01; localparam SET_MIN = 2'b10; localparam SET_HOUR = 2'b11; reg [1:0] state; reg [1:0] next_state; always @(posedge clk_50m or negedge rst_n) begin if (!rst_n) state <= IDLE; else state <= next_state; end always @(*) begin case (state) IDLE: next_state = key_set_pulse ? SET_SEC : IDLE; SET_SEC: next_state = key_set_pulse ? SET_MIN : SET_SEC; SET_MIN: next_state = key_set_pulse ? SET_HOUR : SET_MIN; SET_HOUR: next_state = key_set_pulse ? IDLE : SET_HOUR; default: next_state = IDLE; endcase end逻辑说明:状态切换依靠key_set_pulse这个单脉冲信号,而不是按键电平。如果用按键电平直接驱动组合逻辑,按住不松手状态机会一路从校时跳到正常计时,这是最常见的校时功能错乱原因。
参数说明:key_set_pulse是按键消抖后产出的一个时钟周期宽度的高电平脉冲,产生方式在3.2节细说。状态机的四态顺序也可以改成“秒-分-时-正常”作为默认,本质一样;但要注意状态跳变必须在消抖脉冲边沿同步,不能边沿和电平混在一起。
校时状态下,每次按加键时分或秒加一,加键同样是消抖后的脉冲。调秒的时候,最好顺手把秒计数器清零,这样对准整秒比较方便——现实中很少有人能用加键把秒数精确对上,通常的做法是一边看着LCD一边在秒接近0时按下设置键,然后直接跳过分调整。这个细节在不少参考代码里被省略。
3.2 按键消抖:20ms窗口为什么不能少,以及边沿处理的细节
按键按下时,机械触点会在几毫秒到十几毫秒内反复通断,直接用上升沿触发会导致一次按下被当成多次操作。消抖的常规参数是20ms:检测到按键电平变化后,启动一个20ms的计时器,计时结束后再采样一次电平,若仍保持新状态就认为按键稳定。
reg [19:0] debounce_cnt; reg key_stable_prev; reg key_set_pulse; always @(posedge clk_50m or negedge rst_n) begin if (!rst_n) begin debounce_cnt <= 20'd0; key_set_pulse <= 1'b0; end else if (key_set_raw != key_stable_prev) begin debounce_cnt <= 20'd0; // 电平变化,重新开始计数 end else if (debounce_cnt == 20'd999_999) begin key_stable_prev <= key_set_raw; key_set_pulse <= 1'b1; // 稳定后产生单脉冲 end else begin debounce_cnt <= debounce_cnt + 1'b1; key_set_pulse <= 1'b0; end end逻辑说明:CNT = 999_999,对应50MHz时钟下约20ms。debounce_cnt位宽需要20位,因为1_000_000的二进制表示需要20位。只要输入电平和上一个稳定值不同,计数器立即清零重新计时;电平保持不变的时间超过20ms后,才更新稳定值并产生一个单周期脉冲。这样即使抖动在10ms内结束,也判定为无效翻转。
踩坑提醒:有些代码在消抖后直接把稳定电平送给状态机,状态机会在按键按住期间反复进入同一状态,导致一次按键响应了多秒。务必把“稳定电平”和“稳定后的边沿脉冲”区分开,状态机和加数逻辑只认脉冲。
3.3 倒计时模块:预置初值、暂停与BCD借位链的实现
倒计时模块要求用户可以设定一个初值,比如05:00,然后每秒减一,到00:00后停止并给出提示。比加计数麻烦的是借位:个位从0减到9,同时十位减1;十位从0减到9,同时百位减1。如果直接用一个二进制计数器整体减一,再在显示端转换成BCD,倒计时不超过65535秒时没问题,一旦超过就需要更大位宽。但在BCD结构内部逐位借位能减少显示转换逻辑。
module bcd_downcounter( input wire clk, input wire rst_n, input wire tick, // 1Hz使能 input wire load_en, // 加载预置初值 input wire [3:0] ones_bcd, // 个位预置值 input wire [3:0] tens_bcd, // 十位预置值 output reg [3:0] ones_q, output reg [3:0] tens_q, output reg timeout // 到零停止标志 ); always @(posedge clk or negedge rst_n) begin if (!rst_n) begin ones_q <= 4'd0; tens_q <= 4'd0; timeout <= 1'b0; end else if (load_en) begin ones_q <= ones_bcd; tens_q <= tens_bcd; timeout <= 1'b0; end else if (tick) begin if (ones_q == 4'd0) begin ones_q <= 4'd9; if (tens_q == 4'd0) begin timeout <= 1'b1; // 停止,不再减 tens_q <= 4'd0; end else begin tens_q <= tens_q - 1'b1; end end else begin ones_q <= ones_q - 1'b1; end end end endmodule逻辑说明:每个tick到来先看个位。个位不是0就正常减一;个位是0则回9并向十位借位,若十位此时也是0,说明整个倒计时已经到零,timeout拉高并把十位保持0。这里用“个位回9”而不是“回10”,是因为十进制下借位后个位应从9开始。
参数说明:这个模块只处理了两位,分秒倒计时需要把十位借位继续级联。真正的实现里,倒计时一般只做“分:秒”或者“时:分:秒”,我习惯把整体拆成两个或三个bcd_downcounter,低位模块的timeout作为高位模块的tick输入。注意timeout和tick都是单时钟周期脉冲,级联时不需要再消抖。暂停功能可以直接在顶层把tick屏蔽:pause=1时把送入模块的tick强制置0,这样比在计数器内部加pause分支更干净。timeout同时可以接到蜂鸣器,实现倒计时结束后的提示音。
4. LCD显示与整点报时:字符拼接、驱动时序和脉冲宽度
4.1 LCD1602初始化:上电延迟、功能设置和忙标志的处理
LCD1602是字符型液晶,数据手册要求上电后至少等15ms,再依次发送0x38、0x0C、0x01、0x06这些命令。实际工程里,我见过很多代码只写了命令序列,没有第一条延时,结果下载后LCD只有背光没有字符,按复位键又偶尔能亮。原理是LCD控制器内部的复位逻辑需要时间,太快写命令会被丢弃。15ms这个下限不能省,稳妥一点我会直接延时40ms。
parameter INIT_DLY = 40_000_000; // 40ms @ 50MHz parameter CMD_INTERVAL = 5_000_000; // 命令间隔 5ms @ 50MHz初始化做成状态机:上电后先进入延时状态,计数器计满40ms再发第一条命令,每条命令之间再插入5ms间隔。如果LCD的忙标志BF可用,数据手册推荐每次写之前读BF,但很多精简代码直接用固定延时,因为读BF还要切换总线方向,逻辑更复杂。我的看法:固定延时在100ms的量级上足够可靠,实现也简单。
时序上还有一点要注意:E使能引脚是高脉冲有效,写数据时先把RS、RW和数据总线放好,E拉高,等至少几百纳秒,再拉低E。综合工具通常会把这种几百纳秒的组合延迟当作正常路径,不需要专门约束。真正需要卡时序的是读忙碌标志,这里用不到。
指令表如下,自己写驱动时对着检查:
| 命令 | 功能 | 说明 |
|---|---|---|
| 0x38 | 8位接口、双行、5x7点阵 | 功能设置 |
| 0x0C | 显示开、光标关、不闪烁 | 显示控制 |
| 0x01 | 清屏,光标回原点 | 需要较长时间,约1.6ms |
| 0x06 | 写入后地址自动加一 | 输入模式 |
4.2 显示内容拼接:BCD转ASCII与固定字符的组合
LCD1602接收的是ASCII码,而计时模块输出的是4位BCD。两者相差0x30,也就是字符'0'的ASCII码是0x30。转换公式是 ascii = bcd + 0x30,写成Verilog就是拼接:
wire [7:0] ascii_hour_l = {4'b0011, hour_l}; // 小时的个位 wire [7:0] ascii_hour_h = {4'b0011, hour_h}; // 小时的十位 wire [7:0] ascii_colon = 8'h3A; // 冒号 ":"逻辑说明:BCD的高四位是0,加0x30后高四位正好是0011,所以用拼接就能完成。再把冒号和空格这些固定字符用parameter定义,组成一串8位字符。LCD的DDRAM地址第一行从0x00开始,第二行从0x40开始,写入字符后地址自动加一,所以只要按顺序把字符从0x00写进去,就能显示“HH:MM:SS”。
显示切换也是一个容易被忽略的点:校时模式下,大多数人希望被调整的字段闪烁,这个功能不是LCD命令直接支持的,需要程序每隔0.5秒向某个字符位置写空格或写回数字。
if (flash_enable && (flash_cnt == 0)) lcd_data = 8'h20; // 空格,让位熄灭 else lcd_data = {4'b0011, cur_bcd};这样字段就会以0.5秒周期闪烁。注意别把写LCD的代码和计时逻辑放在同一个always块里,LCD写时序是几十微秒级别的操作,计时是1秒级别的操作,混在一起会让计数器产生毛刺。我一般把LCD写操作单独拉出来,由一个写状态机统一调度。
4.3 整点报时:识别整点时刻与蜂鸣器单次脉冲宽度控制
整点报时逻辑本身很简单:当时钟走到HH:00:00时,产生一个报时触发。但触发之后蜂鸣器响多久、响几次,才是这个模块的难点。如果直接把秒脉冲接到蜂鸣器使能上,整点时会连续响1秒,听起来像错误报警;如果把报时的触发信号直接连到蜂鸣器,脉冲宽度只有1个时钟周期,蜂鸣器几乎听不见。这两种效果我都见过。
合适的做法是做一个单稳态电路:检测到整点时刻,输出一个高电平,持续一定时间后自动拉低。对普通有源蜂鸣器,400ms到500ms的单声响比较舒服。用计数器实现:
reg beep_en; reg [24:0] beep_cnt; always @(posedge clk_50m or negedge rst_n) begin if (!rst_n) beep_en <= 1'b0; else if (alarm_trigger) begin beep_en <= 1'b1; beep_cnt <= 25'd0; end else if (beep_en) begin if (beep_cnt == 25'd24_999_999) begin // 约500ms beep_en <= 1'b0; beep_cnt <= 25'd0; end else begin beep_cnt <= beep_cnt + 1'b1; end end end逻辑说明:alarm_trigger是一次性的整点脉冲,它把beep_en置位并启动500ms计数;计数到达24_999_999(50MHz下正好500ms)后,beep_en自动拉低。如果想让蜂鸣器响多声,可以用一个小状态机在蜂鸣器ON和OFF之间交替切换。参数说明:24_999_999对应50MHz,换成其他晶振频率要同步修改。整点检测的条件是分钟和秒全为0,alarm_trigger必须使用单周期脉冲,否则在整点附近产生抖动时会重复触发,响个不停。
无源蜂鸣器的情况要单独说明:无源蜂鸣器需要2~4kHz的方波驱动,只给直流电平不会有声音。如果板载的是无源蜂鸣器,上述beep_en要作为方波振荡器的使能,振荡器在beep_en有效期间输出方波。查开发板原理图确认蜂鸣器类型,再决定驱动方案。
5. 数字钟项目常见问题排查:五个上板必踩的坑及解决办法
5.1 仿真能走、上板不走:分频器参数与复位极性对不上
现象:仿真里波形正常,1Hz方波输出准确,下载到开发板后秒针不走或者疯狂跳动。
原因:多数是分频器CNT_MAX与板载晶振不匹配,或者复位信号极性反了。比如开发板是高电平复位,代码写的是低电平复位,rst_n就没有生效。仿真激励里把复位信号定义成和代码一致,所以仿真照常走,上板后真实复位电路和代码对不上。还有一个隐蔽原因:计数器位宽给小了,进位溢出导致分频值不对。
解决:第一步用parameter定义晶振频率,例化时统一传入;第二步确认板载复位是高有效还是低有效,先做一个LED跑马灯验证复位通不通;第三步把分频计数器的位宽按“晶振频率/2 - 1”的二进制位数计算,少用十进制常量直接赋值。检查顺序是:先看分频输出,再看复位电平,最后检查位宽。这类问题十有八九是复位和位宽,说穿了就不是玄学。
5.2 按键一次跳好几秒:消抖不足与状态机连跳
现象:按一次加键,时间加了两三秒甚至更多;按校时键,状态直接从正常跳到校时又跳到校分。
原因:按键抖动在10ms内多次触发,或者消抖电路虽然正常但状态机使用的是稳定电平而不是单脉冲。使用稳定电平的典型错误是:高电平持续期间,状态机每个时钟周期都判定按键按下,所以一次按压走了多次。若消抖本身不足,例如延时只有5ms,机械抖动还没结束就采到稳定值,也会出现同样现象。
解决:把消抖窗口拉长到20ms,并确保输出的是单脉冲。单脉冲逻辑要写在独立always块里,用“当前稳定电平 && 上一个时钟周期的稳定电平 == 0”产生,即上升沿检测。状态机只允许在脉冲沿跳变,不会重复触发。同时要注意,在一个状态里按住加键不应该连续加数,如果需要“连加”功能,应设计成“按住1秒后每100ms自动加一”,主动节流。
5.3 LCD只亮背光不出字符:初始化时序和忙标志处理不当
现象:上电后LCD背光亮,屏幕全黑,按复位键偶尔能显示,但显示内容不稳定;或者显示了一串乱码。
原因:LCD上电后内部复位的延时没给够,第一条0x38命令被丢弃;后续命令也按同样节奏执行,整个初始化序列失败。另一个常见原因是E使能信号没有产生足够宽的高脉冲,命令虽然送上了数据线,但E的建立保持时间不满足。
解决:初始化第一步插入40ms延时,留出余量,每两条命令之间再加5ms延时;E信号要保证高电平持续至少1us,低电平至少2us。用固定延时驱动LCD没有问题,但延时计数器的位宽一定要算够,我见过有人写1ms实际只有10us,原因就是计数器加不到目标值。另外,清屏命令0x01要求1.6ms以上的执行时间,如果下一笔操作立即刷新,也会出现字符错位。
5.4 报时响不停或听不见:脉冲宽度设计与蜂鸣器驱动方式
现象:整点报时要么响一长声不停,要么只听到“啪”的极短一声,要么声音很小。
原因:报时使能的宽度不对。用秒脉冲直接驱动蜂鸣器,整点那一秒全程响,属于宽度过大;用触发信号的原始脉冲驱动,宽度只有一个时钟周期,蜂鸣器反应不过来;如果用的是无源蜂鸣器而不是有源蜂鸣器,还需要一个方波驱动信号,不能用简单高低电平驱动。
解决:查开发板原理图,确认是有源还是无源蜂鸣器。有源蜂鸣器用单稳态500ms电平驱动;无源蜂鸣器需要产生一个2~4kHz方波,并在报时期间持续输出方波。无源蜂鸣器只给直流电平是不会有声音的。宽度参数根据晶振频率计算后单独写成parameter,方便调整。判断标准是:响一声但耳朵能感觉到“一声”,停下来后不再响。
5.5 倒计时到零后乱码:缺停止标志,BCD借位断了
现象:倒计时走到00:00后继续走,LCD上出现9、9等乱七八糟的字符;或者从00:01跳到00:00后卡住但报时一直触发。
原因:计数器在00:00时还在接收tick,BCD借位链断了——低位从0借位时,高位已经变成0,没有能继续借的数字,逻辑产生悬空值。凡是双减计数器的设计,边界条件必须单列。
解决:在计数器内部加timeout标志,个位、十位同时为0时,屏蔽tick且输出保持0。顶层例化时用“tick && !timeout”作为模块使能,外围再加一个stop状态。借位链的处理要按位检查:个位0向十位借位时,若十位是0,则整体停止并明确输出归零标志,而不是让借位信号一路向上传。把timeout同时接入蜂鸣器,可以实现倒计时结束后的提示声音,一举两得。
6. 我的验证习惯:先点亮LED再看片上逻辑分析仪抓波形
6.1 三个先做的验证步骤
新板子第一次下载,我不会直接看LCD。先把分频器输出接到板载LED,如果LED以1秒周期翻转,说明时钟和复位都没问题。再按一下复位键,观察LED是否会重新停在高电平或低电平。第二步,把秒计数器的个位最低位接LED,正常应该0.5秒闪一次,这能确认进位链是否工作。第三步才接LCD,如果显示乱,至少能判断是显示驱动的问题而不是计时的问题。这是我从翻车经历里总结出来的调试顺序——直接在完整系统里调,坏了都不知道该查哪。
6.2 片上逻辑分析核的使用技巧
遇到仿真正常、上板不正常的偶发问题,最有效的工具是开发工具链自带的片上逻辑分析核。把需要观察的信号加进调试核,触发条件设置成复位信号的下降沿,就能抓到复位瞬间和后续几十个时钟周期的真实波形。数字钟的信号变化很慢,片内存储深度不需要太大,但采样时钟用50MHz采样1Hz信号,边缘会有偏差,我一般额外引出一个探针信号,把分频器计数器的最高位引出,这样能清晰看出计数有没有循环错位。
把探针加进综合工程重新综合后,再用调试工具观察,整个过程十分钟以内。从那以后,每次做完一个数字电路模块,我都强制自己先跑“LED冒烟测试”,再进片上逻辑分析核看关键内部信号;养成这个习惯以后,调试时间至少省了一半。希望帮到你。
本文还有配套的精品资源,点击获取