ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA多功能数字钟设计:分频链、按键消抖与数码管动态扫描

FPGA多功能数字钟设计:分频链、按键消抖与数码管动态扫描 简介多功能数字钟实验报告面向电子工程、光电技术等专业修读EDA设计与数字逻辑课程的学生聚焦南京理工大学EDA(2)实验完整呈现基于QuartusII7.0设计的多功能数字钟方案。报告围绕24小时计时、保持、清零、快速校时校分、整点报时与动态显示等功能需求分模块讲解脉冲发生、计时、清零、校时校分、保持、报时、译码显示等电路的实现与波形图仿真验证并附总电路合并思路、SMART SOPC实验箱下载结果以及时分秒六位数码管动态显示、附加功能设想与实验问题解决记录。资源包共1个doc文件约688KB目录含封面、摘要、实验原理、模块电路设计、总电路图、电路下载、实验感想与参考文献结构完整便于按章节查阅。已有302人学习适合课程实验参考、报告撰写与模块化设计复习也可用于期末复习与课程设计查漏补缺。1. 多功能数字钟这个题目难点从来不在“钟”南京理工大学 EDA(2) 的实验报告里多功能数字钟出现的频率极高几乎成了默认选题。它表面上是“做一个会走时的东西”实际考的却是分频链怎么切、按键怎么消抖、数码管怎么扫得不闪、闹钟比较怎么不误触发这四件事。任何一个环节想当然上板就会出问题。代码跑通只能算及格线。综合出来的资源占用、时序约束写没写、管脚分配对齐没有、PCB 上段选走线是否互相串扰都会在验收现场暴露。所以下面按一条可复现的路径推进先定时钟基准和计数结构再写模块、做仿真、上板排错最后落到嘉立创 EDA 里把最小系统板画出来。适合正在交 EDA 课程设计、或者准备蓝桥杯 EDA 类赛题的人。参数都给默认值改两个常量就能适配自己手上的板子。2. 多功能数字钟的分频链与计时基准从 50 MHz 到 1 Hz2.1 为什么不用低频晶振直接驱动计数器新手最直觉的想法是找一颗 1 Hz 的晶振接上去就能走秒省掉分频器。这条路在实际上板时基本走不通。低频晶振多为音叉结构负载电容要求苛刻、起振慢、抗冲击差而且常见封装是圆柱形插件占板面积反而更大。更重要的是整个数字钟还需要扫描节拍、消抖节拍、蜂鸣器音调这几路时钟一颗低频晶振只能解决其中一路。常见做法是板上放一颗 50 MHz 有源晶振所有节拍都由它分频派生。这样全系统只有一个时钟域静态时序分析只需要约束一个时钟跨时钟域的问题根本不会出现。代价是分频器要占一些逻辑资源但一个 26 位计数器在 FPGA 里几乎不花什么代价。2.2 分频器参数表与占空比取舍分频有两种产物用途完全不同。一种是单周期使能脉冲只在计满的那一个时钟周期拉高用来驱动计数器的进位另一种是50% 占空比的方波用来驱动需要持续电平的负载比如蜂鸣器。混用这两种是排错时的重灾区先看表用途目标频率分频比 DIV计数器位宽计数上限输出形态秒基准1 Hz50_000_0002649_999_999单周期脉冲数码管位选约 760 Hz65_5361665_53550% 方波消抖采样1 kHz50_0001649_999单周期脉冲整点报时音2 kHz25_0001524_99950% 方波位宽按$clog2(DIV)算写死容易改错。分频模块本身很短// 通用分频器输出单周期使能脉冲全系统保持单时钟域 module tick_gen #( parameter integer DIV 50_000_000 // 默认 50 MHz - 1 Hz )( input wire clk, input wire rst_n, output reg tick ); localparam integer WID $clog2(DIV); reg [WID-1:0] cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt {WID{1b0}}; tick 1b0; end else if (cnt DIV - 1) begin cnt {WID{1b0}}; tick 1b1; // 只维持一个 clk 周期 end else begin cnt cnt 1b1; tick 1b0; end end endmoduleDIV做成 parameter 而不是 localparam是为了仿真时能在 testbench 里用#(.DIV(100))覆盖把 1 秒压缩到 2 微秒否则仿真跑满一天不现实。tick只拉高一个周期这点很关键如果写成cnt[DIV_BIT-1]那种取最高位的写法输出是 50% 方波直接拿去当下一级计数器的使能就会在每个高电平期间连续加很多次表现为“秒走得飞快”。2.3 时/分/秒进位的两种写法与取舍进位有两种写法。一种是把前一级的使能脉冲直接接到后一级的en上做级联代码量最小另一种是把时、分、秒塞进一个 always 块里逐层判断。前者模块化好、复用性强后者改起来方便但一改就牵一发动全身。我一般用级联秒计数器用tick_1hz作为使能它的carry输出就是分计数器的使能分计数器的carry再驱动时计数器。这样每一级都是独立的、可单独仿真的模块秒的模 60、分的模 60、时的模 24 只是参数不同。注意级联时不要用前一级的carry去当时钟always (posedge carry)那是典型的门控时钟会产生毛刺并使时序分析失效。carry只能作为使能。3. 用 Verilog 把多功能数字钟的核心模块写出来3.1 顶层端口划分与板级资源映射先定接口再写逻辑能省掉大量返工。下面这套端口在多数实验板上都能直接映射端口方向位宽说明板级资源clkin1系统时钟 50 MHz板载有源晶振rst_nin1异步复位低有效独立按键key_nin3模式切换 / 加 / 减3 个独立按键segout8段码 {dp,g,f,e,d,c,b,a}数码管段选anout4位选低有效数码管公共端buzzerout1整点报时无源蜂鸣器多功能体现在三个按键上短按切换“正常走时 / 校时”校时状态下用加、减键调整小时和分钟长按加键切换闹钟开关。这套交互不需要额外的拨码开关答辩时也好演示。3.2 时/分/秒计数与 BCD 进位核心是一个可复用的 BCD 计数器。秒和分用MAX_HI5, MAX_LO9模 60小时用MAX_HI2, MAX_LO3模 24// BCD 模计数器十进制两位输出carry 在回卷时给一个 clk 宽的脉冲 module bcd_counter #( parameter [3:0] MAX_HI 4d5, // 十位最大值 parameter [3:0] MAX_LO 4d9 // 个位最大值 )( input wire clk, input wire rst_n, input wire en, // 进位使能来自上一级 input wire load_en, // 校时时手动置数 input wire [7:0] load_val, // {hi, lo} output reg [3:0] lo, output reg [3:0] hi, output wire carry ); wire last (hi MAX_HI) (lo MAX_LO); assign carry en last; // 本级回卷的同时向后级发出使能 always (posedge clk or negedge rst_n) begin if (!rst_n) begin lo 4d0; hi 4d0; end else if (load_en) begin {hi, lo} load_val; end else if (en) begin if (last) begin lo 4d0; hi 4d0; end else if (lo 4d9) begin lo 4d0; hi hi 1b1; // 个位满 9 才动十位 end else begin lo lo 1b1; end end end endmodule注意这里没有用q 1那种二进制加法再转换的做法。BCD 计数器如果在4b1001上直接加一会得到4b1010这不是合法 BCD 码显示时会变成乱码。判断lo 4d9再清零并让十位进位是唯一稳妥的写法。小时计数器的load_en接校时逻辑load_val由按键加减产生。校时的时候要把秒清零这样按下确认键的瞬间秒从 00 开始符合大多数验收习惯。3.3 数码管动态扫描与段码译码4 位数码管如果用静态显示需要 4 × 8 32 根 IO板子根本不够用。动态扫描把 4 位共用一组段选靠位选快速轮换人眼看到的就是同时亮。刷新率低于 50 Hz 会明显闪烁常见做法是设在 500 Hz 到 1 kHz 之间// 4 位数码管动态扫描位选轮换 共阴段码译码 module seg_scan ( input wire clk, input wire rst_n, input wire [3:0] d3, d2, d1, d0, // 从左到右四位数字 input wire [1:0] dp_pos, // 哪一位点亮小数点 output reg [7:0] seg, // {dp,g,f,e,d,c,b,a} output reg [3:0] an // 位选低有效 ); reg [15:0] div; always (posedge clk or negedge rst_n) if (!rst_n) div 16d0; else div div 1b1; wire [1:0] pos div[15:14]; // 50 MHz / 2^16 ≈ 763 Hz 整轮刷新 always (*) begin case (pos) 2d0: begin an 4b1110; seg seven_seg(d0); end 2d1: begin an 4b1101; seg seven_seg(d1); end 2d2: begin an 4b1011; seg seven_seg(d2); end default: begin an 4b0111; seg seven_seg(d3); end endcase if (dp_pos pos) seg[7] 1b1; // 最高位是小数点 end function [7:0] seven_seg(input [3:0] d); case (d) 4d0: seven_seg 8b0011_1111; 4d1: seven_seg 8b0000_0110; 4d2: seven_seg 8b0101_1011; 4d3: seven_seg 8b0100_1111; 4d4: seven_seg 8b0110_0110; 4d5: seven_seg 8b0110_1101; 4d6: seven_seg 8b0111_1101; 4d7: seven_seg 8b0000_0111; 4d8: seven_seg 8b0111_1111; 4d9: seven_seg 8b0110_1111; default: seven_seg 8b0000_0000; endcase endfunction endmodulediv[15:14]取的是计数器的两个高位等效于每 2^14 个时钟切换一次位选整轮扫完是 2^16 个时钟约 1.3 ms。段码表按的是共阴、高电平点亮的接法如果板子上的数码管是共阳把seg和an整体取反即可不要逐位改。3.4 按键消抖为什么必须输出单周期脉冲机械按键按下一次会在几毫秒内产生几十次通断直接把key_n接到计数器的en上按一下会加好几秒。消抖模块要做两件事一是两级寄存器同步打掉亚稳态二是计数到约 20 ms 稳定后才认账。关键点在于输出形态。如果消抖模块输出的是稳定电平那么这个电平持续期间计数器每一拍都在加仍然会连击。所以消抖模块的输出必须是一个时钟周期宽的脉冲只在按下沿出现一次。提示rst_n走异步复位、同步释放比直接接按键更稳。如果实验板没有专用复位芯片可以用消抖模块的输出脉冲去触发一个复位状态机避免上电瞬间的抖动把状态机打飞。4. 数字钟的仿真验证与上板排错4.1 用 testbench 把一天压进几微秒分频比做成 parameter 之后仿真成本立刻降下来。把DIV从 5000 万改成 1001 秒只需要 200 个时钟周期20 微秒就能看到第一次秒进位timescale 1ns/1ps module tb_clock; reg clk 0, rst_n 0; always #10 clk ~clk; // 周期 20 ns等效 50 MHz wire [3:0] sec_l, sec_h, min_l, min_h; // 关键用参数覆盖把 1 Hz 压缩 50 万倍否则仿真跑不动 tick_gen #(.DIV(100)) u_tick_1hz ( .clk(clk), .rst_n(rst_n), .tick(tick_1hz) ); bcd_counter #(.MAX_HI(4d5), .MAX_LO(4d9)) u_sec ( .clk(clk), .rst_n(rst_n), .en(tick_1hz), .load_en(1b0), .load_val(8h00), .lo(sec_l), .hi(sec_h), .carry(sec_carry) ); initial begin #100 rst_n 1; // 200 个时钟周期 2 微秒 1 个秒等 60 个秒看分进位 #2400; if ({min_h, min_l} 8h01) $display(PASS: 分进位正确); else $display(FAIL: 分 %0d%0d, min_h, min_l); $finish; end endmodule跑这个 testbench 时重点看三个波形tick_1hz是不是只有一个周期宽、sec_carry是不是恰好出现在59 - 00那一拍、min是不是在sec_carry为高的下一拍才变化。第三个尤其容易错如果写成组合逻辑比较sec 59min会提前一拍进位累计一天就偏出好几秒。4.2 时序约束与管脚分配单时钟域设计的约束很轻但必须写。给 Quartus 用的 SDC 大致是# 50 MHz 板载晶振周期 20 ns create_clock -name sys_clk -period 20.000 [get_ports clk] # 按键和复位是异步输入不参与内部时序路径分析 set_false_path -from [get_ports {key_n[*] rst_n}] -to * # 数码管段选/位选是 760 Hz 级别的慢速信号不需要额外输出约束管脚分配写在 QSF 里引脚号必须以自己板子的原理图为准抄别人的必错# Quartus 管脚分配节选 set_location_assignment PIN_E1 -to clk set_location_assignment PIN_M1 -to rst_n set_location_assignment PIN_M2 -to key_n[0] set_location_assignment PIN_J15 -to seg[0] set_location_assignment PIN_H15 -to an[0] set_instance_assignment -name IO_STANDARD 3.3-V LVTTL -to seg[0]4.3 上板后不亮、跳秒、按键连击的排查路径现象常见原因检查点上电全灭共阴共阳搞反或位选极性反了对照板子原理图确认 an 是高有效还是低有效走时快一倍或慢一倍分频比按 25 MHz 算的板上却是 50 MHz示波器量 clk 引脚实测晶振频率某一位特别暗扫描频率太低或位选驱动电流不足把div取位右移一档提高刷新率按一次加好几秒消抖输出的是电平而不是单周期脉冲抓pulse波形确认宽度为一个 clk闹钟到点不响比较对象取成了下一拍的组合值比较必须建立在寄存器输出{hour, min}上整点报时提前触发用了 59而不是进位脉冲比较min_carry与sec_carry同时为高排查顺序建议从时钟开始先用示波器或 LED 分频闪灯确认clk频率对不对再看复位是否释放最后才怀疑逻辑。绝大多数“逻辑问题”其实是时钟和复位问题。5. 从 RTL 到板级在嘉立创 EDA 里画数字钟的最小系统板5.1 原理图里 EDA 元件的引脚与焊盘对应怎么核把设计落到自己的板子上时最容易踩的坑是“EDA 元件的引脚与焊盘未对应”。嘉立创 EDA 里从立创商城直接引用的元件符号引脚名和封装焊盘号是绑定的但手工画的符号如果不填焊盘编号转 PCB 时会出现空网络。核对方法很直接在原理图里点开元件属性看每一个引脚后面的“焊盘编号”字段是否为空为空就补上且必须与封装里实际焊盘的编号字符完全一致。管脚分配的另一个来源是原理图的网络名。数码管的段选走线如果命名成seg[0]到seg[7]导出网表后在 PCB 里就能按网络高亮联动。嘉立创 EDA 专业版里在原理图选择网络联动 PCB靠的就是这份网络名一致性命名混用大写小写或者用SEG0与seg[0]混排联动就会断。5.2 布局与星型接地的落地做法数字钟的板子虽然简单但数码管是脉冲电流负载段选同时导通时瞬时电流可以到几百毫安。布局上把晶振和 FPGA 的电源引脚靠近放置去耦电容贴在引脚 2 mm 以内数码管的限流电阻放在段选走线的中段而不是紧贴数码管这样电阻发热不会烤到数码管。接地用星型接地的思路所有去耦电容的接地端先用短线汇到一个点再由这个点单点连接到主地平面避免脉冲电流在地平面上乱窜去干扰晶振回路。四层板可以把第二层整层做地如果打样成本敏感做双层板至少保证晶振下方是完整的地铜皮不要被信号线割开。六层板在这类设计里属于过度设计。5.3 位号图导出与打样前自检焊接之前先导出位号图对照位号图逐个核对元件方向和极性。导出时勾选“显示位号”和“显示封装外框”把丝印层也一起打开这样能看出位号有没有被元件本体压住。自检清单是位号图与 BOM 数量一致、每个元件的极性方向正确、去耦电容没有和信号线抢位置、测试点留出来了。提示打样前把 Gerber 用嘉立创 EDA 自带的 Gerber 查看器再打开一遍重点看阻焊开窗和丝印有没有重叠这一步能省掉一次废板。本文还有配套的精品资源点击获取
返回列表