首页 / 资讯中心 / 文章详情

Quartus原理图设计电子琴:FPGA数字逻辑模块化实战

Quartus原理图设计电子琴:FPGA数字逻辑模块化实战 ★ FEATURED ARTICLE
简介本资源是一份基于Quartus原理图设计方法实现的FPGA电子琴程序面向具备数字电路基础、希望借助图形化方式入门FPGA开发的电子与计算机相关专业学生及爱好者。项目围绕音符频率生成、定时计数、DA转换接口、按键控制与乐曲存储等模块展开帮助读者理解数字逻辑在音乐合成中的实际应用。压缩包共413个文件约5.42MB以bdf原理图、bsf符号文件、cdb与hdb编译数据库、qmsg消息报告、tdf文本设计文件及vwf仿真波形等为主完整保留了工程从原理图绘制到编译仿真的过程痕迹。目前已有1045人学习下载。读者可从中获取可参考的电子琴工程结构、模块划分思路与仿真验证方法适合作为课程设计或FPGA入门练手项目对照学习原理图设计与硬件描述语言结合的实现方式。1. 从一份“画”出来的电子琴工程说起如果你第一次拿到“quartus原理图设计方法设计的电子琴程序”这个资源可能会愣一下电子琴程序怎么是“原理图设计”出来的这恰恰是这份资源最值得拆的地方。它不是一个用 Verilog 从头写到尾的纯代码工程而是用 Quartus 的 Block Diagram/Schematic File.bdf把分频器、按键消抖、音符译码、蜂鸣器驱动这些模块用图形化方式连起来最终在 FPGA 上跑出一个能按键发声的电子琴。适合谁适合刚学完数字电路、想找一个能上板验证的完整案例的在校生也适合想回头补一补“图形化顶层 底层模块”这种混合设计思路的工程师。它解决的不是“电子琴有多复杂”而是“怎么把一堆零散的数字逻辑模块用原理图的方式拼成一个能响的系统”。2. 工程结构拆解从 .bdf 顶层到各功能模块2.1 顶层原理图里到底放了什么这份工程的核心是一个顶层 .bdf 文件。用 Quartus 打开后你会看到几个矩形块Symbol和它们之间的连线。常见的做法是顶层只做“连线”和“例化”不写任何逻辑。具体来说顶层里通常包含这几类符号输入引脚按键KEY、时钟CLK、复位RST输出引脚蜂鸣器BEEP、可能的 LED 指示分频模块把板载高频时钟降到音频范围按键处理模块消抖 按键编码音符译码模块把按键值转成对应频率的分频系数发声模块输出方波驱动蜂鸣器这种“顶层只连线”的做法好处是结构一目了然改起来不用翻几百行代码。坏处是一旦模块多了连线会像蜘蛛网所以常见做法是给每个模块加文字标注甚至用总线Bus来减少连线数量。2.2 分频器与音符频率的对应关系电子琴发声的本质是让蜂鸣器在某个频率上振动。FPGA 板载时钟通常是 50MHz要得到 Do262Hz这种低频必须分频。原理图里一般会放一个“分频器”符号它的内部可能是一个计数器模块或者直接用 74161 这类计数器芯片的符号来搭。假设系统时钟 50MHz要得到 262Hz分频系数是50_000_000 / (2 * 262) ≈ 95420为什么是 2 倍因为方波一个周期需要翻转两次。所以分频计数器数到 95420 时翻转一次输出就能得到 262Hz 的方波。在原理图里这个“分频系数”通常不是固定的而是由按键译码模块动态给出。也就是说按下不同的键译码模块输出不同的计数值分频器根据这个值来改变输出频率。注意如果分频系数算错声音会明显偏高或偏低甚至听不见。常见做法是先在纸上把每个音符的分频系数算好做成一张表再在译码模块里用 LPM_ROM 或 case 语句实现。2.3 按键消抖与优先级处理机械按键按下时会有 10~20ms 的抖动如果不处理按一次键可能会触发多次发声。原理图里通常会用一个小模块来做消抖用一个低频时钟比如 1kHz去采样按键连续采样到稳定值才认为按键有效。消抖模块的输出一般是一个“按键有效”脉冲或者直接输出稳定的按键编码。如果同时按下多个键还需要优先级处理。常见做法是在译码模块里用优先级编码器或者直接规定“高音优先”或“低音优先”。这部分在原理图里可能是一个 74LS148 之类的编码器符号也可能是一个自定义的 LPM 模块。2.4 蜂鸣器驱动与输出级蜂鸣器分有源和无源。有源蜂鸣器给电就响频率固定无源蜂鸣器需要外部给方波。这份工程大概率驱动的是无源蜂鸣器所以输出级就是一个方波信号。原理图里这个信号可能直接连到输出引脚也可能经过一个三极管或 MOS 管驱动电路如果板子上没有集成驱动。在 Quartus 里输出引脚需要分配正确的引脚号并且设置好 I/O 标准比如 3.3V LVTTL。如果蜂鸣器声音很轻或者不响先查引脚分配再查驱动能力。3. 从零跑通Quartus 工程建立与上板步骤3.1 新建工程与添加原理图文件打开 Quartus版本不限9.0 到 20.x 都能打开 .bdf走一遍标准流程# 这里不是命令行是 Quartus 图形界面的操作顺序 File - New Project Wizard # 指定工程目录、工程名、顶层实体名 # 顶层实体名必须和 .bdf 文件名一致 # 比如 top.bdf顶层实体就是 top建好工程后把资源里的 .bdf 文件复制到工程目录然后在 Quartus 里File - Open打开。如果工程里还有 .v 或 .vhd 的底层模块也要一并添加。常见做法是顶层用 .bdf底层用 Verilog 写这样既保留了原理图的直观又避免了画复杂逻辑的痛苦。3.2 引脚分配与编译原理图画完后必须做引脚分配。打开Assignments - Pin Planner把输入输出信号分配到实际 FPGA 板子的引脚上。比如信号名方向引脚号说明CLK输入PIN_2350MHz 时钟KEY1输入PIN_45按键1KEY2输入PIN_46按键2BEEP输出PIN_12蜂鸣器引脚号必须查你手上板子的原理图不能照抄。分配完后点Processing - Start Compilation。如果编译报错常见原因是顶层实体名和文件名不一致、模块端口没连全、引脚冲突。3.3 下载与调试编译通过后用 USB-Blaster 下载 .sof 文件到 FPGA。下载成功后按按键应该能听到声音。如果没声音按这个顺序排查蜂鸣器是不是无源的有源蜂鸣器给方波可能不响。引脚分配对不对用万用表量一下输出引脚有没有电压变化。分频系数是不是太大或太小太大声音频率低到听不见太小可能超出蜂鸣器响应范围。按键消抖模块的采样时钟有没有接对如果消抖时钟太快按键会不灵敏。提示调试时可以先不管按键直接让分频器输出一个固定频率比如 1kHz确认蜂鸣器能响再回头查按键和译码部分。4. 核心模块代码与原理图混合设计4.1 分频器模块的 Verilog 实现虽然顶层是原理图但分频器这种逻辑用 Verilog 写更省事。下面是一个常见的分频器模块module divider( input clk, // 系统时钟50MHz input rst_n, // 低电平复位 input [31:0] div, // 分频系数由译码模块给出 output reg beep // 方波输出 ); reg [31:0] cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt 0; beep 0; end else begin if (cnt div) begin cnt 0; beep ~beep; // 翻转输出 end else begin cnt cnt 1; end end end endmodule逻辑说明每个时钟上升沿计数器加 1。当计数器达到div时清零并翻转beep。这样beep的频率就是clk / (2 * (div 1))。参数div由外部输入所以改变div就能改变音调。参数说明div是 32 位因为 50MHz 分频到 262Hz 需要约 9542016 位不够。rst_n是低电平复位常见做法是接一个按键或上电复位电路。4.2 音符译码模块的 case 实现译码模块把按键值转成div值。假设有 7 个按键对应 Do Re Mi Fa Sol La Simodule note_decoder( input [2:0] key, // 按键编码0~6 output reg [31:0] div ); always (*) begin case (key) 3d0: div 32d95420; // Do 262Hz 3d1: div 32d85034; // Re 294Hz 3d2: div 32d75758; // Mi 330Hz 3d3: div 32d71509; // Fa 349Hz 3d4: div 32d63776; // Sol 392Hz 3d5: div 32d56818; // La 440Hz 3d6: div 32d50607; // Si 494Hz default: div 32d95420; endcase end endmodule逻辑说明key是 3 位支持 8 个状态这里用了 7 个。div的值是根据 50MHz 时钟算出来的。如果你板子的时钟不是 50MHz需要重新算。参数说明div的计算公式是clk_freq / (2 * note_freq) - 1。比如 50MHz 和 262Hz50000000 / (2 * 262) - 1 ≈ 95419取 95420 误差不大。4.3 在原理图中例化这些模块在 .bdf 里右键Insert - Symbol找到你写好的 Verilog 模块生成的符号。如果模块还没生成符号先编译一次Quartus 会自动为每个模块创建符号。然后把符号放好用连线工具把端口连起来。常见做法是顶层 .bdf 里只放符号和连线所有逻辑都在 .v 文件里。这样既保留了原理图的“可视化”又不用在图形界面里画计数器。如果你非要用 74161 这类芯片符号来搭分频器也可以但连线会多很多改起来也麻烦。注意原理图里的连线必须一一对应不能有悬空端口。如果编译报“未连接端口”检查是不是漏连了某个输入。5. 避坑与常见问题排查5.1 编译报错“顶层实体找不到”现象Quartus 编译时提示Top-level entity xxx not found。 原因顶层 .bdf 的文件名和工程设置的顶层实体名不一致。 解决在Assignments - Settings - General里把 Top-level entity 改成和 .bdf 文件名一致。比如文件叫piano.bdf顶层实体就填piano。5.2 下载后蜂鸣器不响现象编译下载都成功但按键没声音。 原因可能是蜂鸣器类型不对、引脚分配错、或者分频系数太大。 解决先写一个最简单的测试工程让一个 LED 闪烁确认下载流程没问题。然后把蜂鸣器输出引脚直接接一个 1kHz 方波确认蜂鸣器能响。最后再查按键和译码。5.3 按键反应迟钝或连击现象按一次键声音断断续续或者要按很久才响。 原因消抖模块的采样时钟太快或太慢。太快消抖不彻底太慢按键不灵敏。 解决消抖采样时钟一般取 1kHz 左右也就是每 1ms 采样一次。如果系统时钟是 50MHz分频到 1kHz 的分频系数是 50000。5.4 声音频率明显不对现象按 Do 键出来的声音像 Re 或更低。 原因分频系数算错或者系统时钟不是 50MHz。 解决用示波器或频率计测一下输出引脚的实际频率。如果偏差固定倍数检查时钟频率设置。如果偏差不固定检查分频计数器位宽是否够用。5.5 原理图连线太乱改不动现象模块多了以后.bdf 里连线交叉严重想加一个模块无从下手。 原因顶层没有用总线也没有分层设计。 解决把功能相关的模块打包成一个子模块用子 .bdf 或 Verilog 模块封装。顶层只保留子模块之间的连线。常见做法是顶层只放“输入输出”和“子模块”子模块内部再细分。6. 二次开发从电子琴到自定义音效发生器这份工程最值钱的地方不是它现在能弹七个音而是它的结构可以让你快速改成别的东西。比如你想做一个“按键触发不同音效”的装置或者“根据传感器输入改变音调”的互动装置改法都很直接。6.1 扩展音阶与和弦现在的译码模块只支持 7 个音。如果你想支持更多音比如加半音可以把key位宽从 3 位扩到 4 位或 5 位然后在 case 里加更多分频系数。常见做法是建一个 ROM 表把音符频率对应的分频系数存进去按键值作为地址。这样改音阶不用改代码只改 ROM 初始化文件。// 用 ROM 替代 case 的示例 module note_rom( input [4:0] addr, output reg [31:0] div ); always (*) begin case (addr) 5d0: div 32d95420; // Do 5d1: div 32d90000; // Do# 5d2: div 32d85034; // Re // ... 继续加 default: div 32d95420; endcase end endmodule6.2 加入自动演奏功能如果你想让它自动播放一段旋律可以在顶层加一个“旋律存储器”模块里面存一串音符和时长。用一个计数器按节拍读出音符送到译码模块。这样按键和自动演奏可以共存用一个开关切换。常见做法是用一个 LPM_ROM 存音符编码地址由节拍计数器给出。节拍计数器每到一个时间点就加 1ROM 输出对应的音符编码再送到分频器。这样你就能听到自动播放的曲子了。6.3 用 PWM 控制音量现在的输出是固定幅度的方波音量不可调。如果你想加音量控制可以把方波改成 PWM 输出用占空比控制音量。具体做法是在输出级加一个比较器把方波和一个可变的阈值比较阈值由另一个按键或电位器通过 ADC控制。// PWM 音量控制示例 module pwm_volume( input clk, input [7:0] volume, // 0~255 input beep_in, // 原始方波 output reg beep_out ); reg [7:0] cnt; always (posedge clk) begin cnt cnt 1; if (cnt volume) beep_out beep_in; else beep_out 0; end endmodule逻辑说明cnt不断循环当cnt小于volume时输出原始方波否则输出 0。这样占空比就由volume控制音量也就变了。6.4 验证方法与调试习惯改完代码后不要直接上板。先在 Quartus 里跑一遍功能仿真用 ModelSim 或 Quartus 自带的仿真器看波形对不对。特别是分频器的输出频率仿真里可以直接测量周期。确认无误后再分配引脚、编译、下载。我自己的习惯是每次改完分频系数先在仿真里跑 1ms看输出翻转了几次算出来的频率和理论值差多少。如果差超过 1%就回去查时钟频率和计数器逻辑。从那以后我每次改分频参数都强制走一遍仿真省得在板子上反复拔插 USB-Blaster。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站