用Quartus原理图设计FPGA电子琴:从按键消抖到音符分频

发布时间:2026/10/20 5:15:14
用Quartus原理图设计FPGA电子琴:从按键消抖到音符分频 简介一套基于Quartus原理图设计方法的电子琴FPGA工程文件包面向数字逻辑与FPGA入门及进阶学习者完整呈现从原理图规划、模块例化到编译仿真、下载调试的完整设计流程。设计围绕音符频率生成、定时计数、数模转换、播放控制与乐曲存储等核心环节展开通过查找表存放频率值并利用计数器产生精确时长整体构架清晰便于逐级拆解。压缩包内共413个文件主要包含原理图文件、符号文件、仿真波形文件以及工程设置、编译报告、乐曲初始化数据和少量文本描述代码压缩后仅5.42兆字节方便快速下载和导入Quartus环境学习。资源已有1045人学习使用适合课程设计、竞赛准备或业余电子琴制作项目参考。通过研读可学习如何将音乐理论中的音名编码转换为硬件可处理的频率参数理解计数器分频与节拍控制机制并借助现成工程模板修改乐谱和音色避开常见编译时序陷阱显著提高设计效率与成功率。1. 原理图电子琴不是玩具它把时序电路变成了听得见的反馈用 Quartus 原理图画一台电子琴听起来像教科书里的教学实验但实际做出来的东西可以直接在 FPGA 开发板上弹奏甚至能跑出一段完整旋律。这个项目最特别的地方在于从按键消抖到音符分频所有关键逻辑都以原理图符号的形式呈现没有一行 HDL 代码它把一个时序电路的真实运行状态直接转化为声音反馈——跑对了耳朵能听见跑错了同样听得出来。适合正在学 FPGA 基础、想验证计数器与触发器行为的初学者也适合需要一份能快速改造成课程设计原型的开发者。下文从一个可运行的工程角度把模块划分、分频系数、连线方式和改造路径逐一讲透尽量让你看完就能照着搭。2. 工程拆解Quartus 原理图输入与电子琴发声链路2.1 原理图工程的文件构成与设计流程用原理图方式做项目最容易翻车的地方是把所有逻辑都堆在一张 BDF 图里最后复杂到连自己都看不清连线。我拿到一套电子琴工程时第一反应是先看文件组织。一个规范的 Quartus 原理图工程通常由三类文件组成.bdf 块设计文件作为顶层图纸.bsf 符号文件作为可复用的模块封装.v 或 .vhd 文件承载底层逻辑。一个 8 音符电子琴工程的典型目录结构长这样project/ ├── top.bdf -- 顶层原理图模块之间的连线都在这里完成 ├── keyboard_ctrl.bsf -- 按键扫描与消抖模块的符号文件 ├── tone_divider.bsf -- 音符分频模块的符号文件 ├── freq_table.mif -- 音符分频系数存储表供 ROM 读取 ├── pin_assignments.qsf -- 引脚分配文件避免每次重新分配引脚 └── sim/ └── ekey_tb.v -- 可选仿真激励验证分频输出波形不要小看这个结构。顶层 BDF 只做连线不写逻辑分频器、扫描器都封装成独立符号。这样做的直接收益是改一个模块的参数不需要动顶层图纸Quartus 在重新生成网表时会自动同步排查问题时也能按图索骥单独仿真某一个模块。实际操作顺序我一般这样排先新建工程指定顶层实体名然后新建 Block Diagram/Schematic File在 Symbol 库中搜索需要的计数器、触发器或自己生成的模块符号放置到图纸中接下来用连线工具把符号引脚连起来再用引脚属性指定输入输出最后编译一次打开 Pin Planner 分配引脚下载到板子。每完成一步就编译一次不要等到全部画完再一次性编译否则几十个错误堆在一起神仙也难定位。2.2 音符频率与分频系数先算清楚再连线电子琴发声的本质是产生不同频率的方波。以常见的 50MHz 开发板主时钟为例要得到 262Hz 的中央 C需要把时钟频率分频到 262Hz。不同音符对应的方波频率不同原理图设计里最常见的方式是做一个计数器计数到预定值后翻转一次输出电平这样输出方波的周期就是时钟周期的两倍。因此分频系数 K 与目标频率 f 的关系是K 50_000_000 / (2 × f)计数到 K-1 时触发翻转。以 C 大调一个八度为例分频系数可以这样算音符频率约值 (Hz)分频系数 K推荐计数位宽C4261.639555617D4293.668513817E4329.637584617F4349.237158817G4392.006377617A4440.005681817B4493.885061917C5523.254777816 亦可建议统一 17 位注意分频系数 K 是一个整数而 50MHz 除以两倍频率往往带小数。我建议所有音符统一向下取整不要有的四舍五入、有的直接截断否则高音区会明显偏低。另外计数器的计数范围从 0 开始所以实际逻辑里是计数到 K-1 时清零或翻转如果你误写成计数到 K 再清零实际分频周期就变成了 K1每个音符都偏低一点。这类问题在仿真波形里很难一眼看出但一上板人耳马上就发现。2.3 发声链路与模块边界整台电子琴的信号路径可以切成四段每一段对应原理图上的一个区域按键输入8 个按键引脚对应 C 大调八度音消抖与编码按键信号先做防抖处理再编码成 3 位音符索引分频查表根据音符索引从分频系数表中拿到对应 K 值驱动音调分频器音频输出分频器输出方波经引脚送到无源蜂鸣器。这里有一个非常容易被忽略的硬件细节蜂鸣器分有源和无源两类。有源蜂鸣器通电自带固定频率给它一个高电平就会响无源蜂鸣器需要外部提供振荡方波才能发声。这个电子琴项目的输出必须接无源蜂鸣器或小喇叭否则你辛辛苦苦生成一个 262Hz 的翻转电平它响的却是自己内部固定的那个音完全不听你的分频器。连线之前先查板子原理图确认扬声器类型能省掉后面一整晚的调试时间。这段链路也决定了调试策略如果按键模块输出的索引正确、分频模块输出的方波频率正确声音就必然正确。所以排查时可以分段验证——先看按键编码再看分频输出波形最后才怀疑硬件连线。3. 核心模块拆解一个音符从按键到扬声器的完整路径3.1 按键扫描与消抖模块的原理图实现按键模块要解决两件事消抖和编码。机械按键按下时会有约 5 到 20 毫秒的抖动如果不处理一次按下去可能触发两三次音符切换。常见做法是引入一个约 10 毫秒的计数窗口检测到电平变化后开始计时计时结束电平仍然稳定才认为按键有效。在原理图里我一般这样设计模块内部结构 1. 50MHz 时钟经 20 级计数器分频得到约 1kHz 采样时钟 2. 每次采样时钟上升沿将按键电平移入 4 位移位寄存器 3. 仅当 4 位寄存器全为 0 或全为 1 时判定为有效电平 4. 有效电平送入 3 位优先编码器生成音符索引原理图里体现为一个分频计数器生成采样脉冲两个串行移位寄存器做延时滤波再加一个优先编码器输出索引。Quartus 的符号库里能找到 7493 这类现成计数器也可以直接用 LPM_COUNTER 宏功能模块不需要自己搭门电路。消抖时间参数可以这样调采样时钟频率取 1kHz每个电平需要连续 4 次采样确认对应约 4ms 的确认窗口。如果你觉得 4ms 对比较硬的按键还是偏短把采样频率降到 500Hz确认时间就变成约 8ms。原理图里只需要改分频计数器的模值不影响其他区域。3.2 音符分频模块查表驱动计数器分频模块是发声的关键。它接收按键编码器输出的 3 位索引通过查表取得对应的分频系数然后用这个系数控制计数器的翻转时刻。这里有两种常见做法一种是用多路选择器在 8 个预置分频系数里选一个另一种是用只读存储器 ROM 存放一张 8 深度的分频系数表按地址读取。我更推荐第二种因为当你要扩展成 12 个半音甚至 16 个音符时只需要改 mif 文件内容原理图一根连线都不用动。一个 8 音符版本的 mif 文件长这样-- freq_table.mif 片段8 音符版本 DEPTH 8; WIDTH 17; ADDRESS_RADIX DEC; DATA_RADIX DEC; CONTENT BEGIN 0 : 95556; -- C4 261.63Hz 1 : 85138; -- D4 293.66Hz 2 : 75846; -- E4 329.63Hz 3 : 71588; -- F4 349.23Hz 4 : 63776; -- G4 392.00Hz 5 : 56818; -- A4 440.00Hz 6 : 50619; -- B4 493.88Hz 7 : 47778; -- C5 523.25Hz END;mif 文件写好之后在原理图里放一个 altsyncram 或 LPM_ROM 宏功能模块数据文件指向这个 mifROM 地址来自按键编码器输出数据输出直接接到计数器预置值输入端。整个过程不需要写任何 HDL 代码这是原理图设计最舒服的地方。ROM 输出的分频系数需要配合一个加载信号配合使用按键变化时产生一个 load 脉冲把新系数写入计数器预置端没有按键变化时计数器保持现有分频系数继续运行音符不会断。对应到原理图就是一个计数器模块模值输入端接 ROM 数据输出加载端接按键变化脉冲。为了帮你理解这个模块在原理图里的行为我用一句 Verilog 描述它的参考逻辑实际工程中你不需要写这行代码只需要在原理图里把 LPM_COUNTER 的模值输入连好// 分频计数器参考模型原理图中用宏功能模块实例化 module tone_div #(parameter WIDTH 17)( input clk, // 50MHz 系统时钟 input [WIDTH-1:0] div,// 来自 ROM 的分频系数 input load, // 按键变化时产生一个加载脉冲 output reg tone_out // 输出方波接无源蜂鸣器 ); reg [WIDTH-1:0] cnt; always (posedge clk) begin if (load) begin cnt 0; tone_out 1b1; end else if (cnt div - 1) begin cnt 0; tone_out ~tone_out; end else begin cnt cnt 1; end end endmodule逻辑说明load 为高时计数器清零并输出高电平之后每个时钟周期 cnt 加 1计数到 div-1 时输出翻转。输出方波周期等于 2 × div × 时钟周期因此 div 就是前面算的分频系数 K。WIDTH 参数由最大分频系数决定中央 C 的 95556 需要 17 位二进制表示所以统一取 17 位后续扩展到更低音区也能放下。3.3 顶层原理图连线方式顶层 BDF 是把各模块串起来的地方。一张 8 音符顶层图的关键连线可以整理成下面的清单实际画图时在图纸上对应拖放即可源信号连接目标说明8 个按键输入引脚keyboard_ctrl 模块输入按键默认低电平有效keyboard_ctrl 输出 note_index[2:0]ROM 地址端选择音符分频系数keyboard_ctrl 输出 key_pressedtone_div 加载端 load按下或释放时触发重新加载50MHz 时钟引脚keyboard_ctrl 与 tone_div 时钟端所有模块共用同一个时钟源ROM 数据输出 div_coef[16:0]tone_div 分频系数输入位宽必须匹配tone_div 输出 tone_out输出引脚 spk_out接无源蜂鸣器或小喇叭连线时注意几点所有模块的时钟必须来自同一个全局时钟引脚不要在原理图中间用门电路产生派生时钟否则 Quartus 在做时序分析时会给你报一堆时钟偏斜的问题ROM 地址和数据位宽要跟计数器预置值匹配不一致时编译会提示长度不匹配输出引脚建议加一个输出缓冲或直接连普通 IO避免下载后出现多个驱动源互相拉扯电平。编译顺序建议这样先单独编译 keyboard_ctrl 模块用仿真确认消抖和编码正确再单独编译 tone_div 模块给定一个固定分频系数看输出频率对不对最后拼装顶层统一编译。每个模块先独立验证再做系统集成能减少至少一半的排查时间。4. 常见问题与避坑仿真、下载与硬件调试实录4.1 现象仿真波形正常上板完全无声这是我自己踩过最狠的一个坑。分频器仿真波形每个翻转点都对下载到板子后却一点声音没有。排查到最后才发现板子上那个蜂鸣器是有源的它只需要直流电平就能响并不需要外部方波。工程输出引脚接错到了有源蜂鸣器的控制端方波的快速高低变化在里面被当成开关信号处理效果就是极其微弱甚至听不到声音。解决办法是查板子原理图确认扬声器类型。无源蜂鸣器才适合接收分频方波。如果板子上同时有两种蜂鸣器检查是否有跳线切换把输出引脚切到无源那一路。另外还有一个容易被忽略的原因输出方波频率如果落在 200Hz 以下或 20kHz 以上人耳敏感度很低听起来就像没声音。用示波器或逻辑分析仪测一下 spk_out 引脚的波形频率很快能确认。4.2 现象按键响应时快时慢甚至一键触发多个音符这是消抖窗口设置太短的典型表现。机械按键抖动一般持续 5 到 20 毫秒如果采样确认窗口只有 2 毫秒抖动期间的杂散电平会被当成有效信号编码器在一个按键事件里输出了多个不同的音符索引听感上就是一个键按下去声音乱跳。解决方法是在消抖模块里把采样时钟降到 1kHz 以下让确认窗口覆盖整个抖动区间。原理图上的改动通常是把分频计数器的模值调大把采样频率从 2kHz 降到 1kHz确认窗口从 2ms 变成 4ms。如果按键质量比较差可以继续降到 500Hz。不要用遮蔽法处理抖动就是那种检测到变化后封锁输入一段时间的方法这种方式容易漏掉连续按下的第二个按键。4.3 现象每个音符都偏高或偏低且越往高音偏差越大这类现象跟分频系数的取整方式有关。以 A4 的 440Hz 为例50MHz 除以两倍频率等于 56818.18取整到 56818 时输出频率误差极小人耳听不出来。但如果你有的音符向下取整有的四舍五入误差就会被放大。更常见的错误是翻转点写错计数器按 K 计数时如果误在计数到 K 时才清零由于计数器从 0 开始实际等于走了 K1 个周期每个音符都偏低约一个时钟周期的比例。解决办法是统一取整规则并且严格按“计数到 K-1 清零或翻转”来设计。高音区偏差更明显是因为频率越高 K 越小取整的相对误差就越大。对 8 音符单手琴来说误差在 1% 以内几乎听不出区别但如果扩展成 12 半音版本建议每个系数先算到小数点后两位再统一向下取整然后用仿真确认每个音的实际输出频率。4.4 现象下载成功但按键引脚电平异常相邻按键互相影响第一反应是查原理图连线但实际上很多是引脚分配冲突。开发板上的按键往往和 LED 或扩展排针共用同一个 IO默认情况下该引脚可能已经被分配成别的用途比如方向被设为输出或者在电路上接了强下拉。解决办法是打开 Pin Planner重新指定按键输入引脚并检查 .qsf 文件里有没有同一个引脚被分配给两个信号。另一类电平异常来自上拉设置。FPGA 输入引脚默认可能有弱上拉如果按键电路是按键一端接地、另一端接 IO那么默认上拉是合理设计按下时拉低。如果按键接的是电源就需要关掉内部上拉或把消抖模块里的有效电平判断反过来。我一般把按键统一设计成低电平有效消抖模块只判断低电平状态这样换不同的开发板时改动最小。4.5 现象增加消抖延迟后出现长按无响应这个坑很隐蔽。为了让消抖更可靠有人会把采样确认时间加长结果快速连续按两个键时第二个键没了反应。原因是确认窗口太长第一个键释放和第二个键按下都落进了同一个窗口逻辑认为还是没有变化。解决思路是采样与确认分离持续以固定周期采样按键电平但只在电平状态从稳定变成另一个稳定时才产生脉冲。换句话说消抖模块的职责是过滤毛刺而不是阻塞输入。每次采样都运行只是输出脉冲的条件变严格了。这个做法在原理图里实现也不复杂把移位寄存器的输出端加一个异或门检边检测到边沿再触发加载即可。5. 二次开发思路从单音电子琴扩展成双音与音色切换5.1 先给音调分频器加一个第二声部最简单有效的改造是把 tone_div 模块复制一份新模块使用另一组 ROM 表两个模块的输出通过一个或门合并到同一个蜂鸣器引脚。注意不能直接短路两个输出FPGA 引脚内部驱动并联会互相冲突应该在输出级加一个逻辑门或三态缓冲器。这样左手按低音区按键、右手按高音区按键时两个分频器同时工作就实现了双音效果。改造后按这样的顺序验证先单独听低音分频器输出确认音准再单独听高音分频器确认音准最后把两个输出合并听有没有相互抢占时序导致的毛刺声。如果合并后声音变闷或变炸多半是输出驱动能力不足在合并点后面加一个缓冲器就能解决。5.2 用占空比调节模拟音色如果你不满足于方波可以给 tone_div 加一个占空比参数。具体做法是在计数器后面加两个比较器计数器计到半周期时输出翻高再计到一个偏移值后翻低。例如把高电平时间固定为周期 50%就是标准方波改成 25%输出窄脉冲波谐波成分更丰富听起来更尖锐改成 75% 则圆润一些。在原理图上这个改动只需要加两个常数寄存器和一个比较器不需要重新算分频系数。占空比参数可以做成两个拨码开关输入运行时切换音色。加上前面的双音改造这台电子琴就从单音玩具变成了可以玩出一点样子的实验平台。我把这套工程拆完之后的习惯是每次改动只动一个模块下载前先在仿真器里看一条音符的输出频率确认误差可接受后再上板试听。这样手里拿到任何一块新板子半天之内都能把引脚、分频系数、蜂鸣器类型这些环境变量全部适配完。希望这篇拆解帮到你也少走一些我曾经走过的弯路。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询