FPGA电子琴实战:用Quartus原理图设计替代Verilog,从分频到发声
发布时间:2026/10/16 11:46:46 锦皓数字建站

简介这份资源面向FPGA初学者与数字电路课程实践者提供一套基于Quartus II原理图设计方法实现的电子琴程序帮助读者理解如何用图形化逻辑元件搭建音符频率生成、定时计数、DA转换接口、按键控制与乐曲存储等模块并完成从建项、绘制原理图到编译仿真、下载测试的完整流程。压缩包共413个文件约5.42MB以bdf原理图、bsf符号文件、cdb与hdb编译数据库、qmsg消息报告、tdf与vhd描述文件、vwf仿真波形及sof下载文件为主另含qpf、qsf工程配置与mif存储器初始化文件完整保留了工程目录与编译中间产物。目前已有1045人学习下载适合作为课程设计或自学练手项目读者可借此掌握FPGA音乐合成的基本思路、模块划分方式与Quartus工程组织方法并对照仿真波形与报告排查设计问题。1. 从零搭一台能弹的电子琴为什么我选 Quartus 原理图而不是 Verilog很多人第一次在 FPGA 上做电子琴下意识就去写 Verilog 状态机结果卡在分频系数算错、按键消抖时序对不上最后板子上跑出来的是噪声而不是音阶。我这次拆的这个资源走的是另一条路——用 Quartus 的原理图设计方法Block Diagram/Schematic File.bdf把电子琴搭出来。它的核心思路是把分频器、按键编码、音阶查找表、蜂鸣器驱动这几个模块当成图形符号在画布上连线而不是一行行敲代码。对刚接触 CPLD/FPGA、又被 Verilog 时序折磨过的人来说这种方式的反馈特别直接连线错了编译就报错逻辑对了下载就能响。它适合三类人数字电路课程设计要交作业的学生、想快速验证发声逻辑的硬件新手、以及需要给非代码背景同事演示 FPGA 流程的工程师。资源本身是一套完整的 Quartus 工程包含原理图顶层和若干底层模块能直接编译下载到常见开发板上发声。2. 原理图设计方法拆解分频、编码、查表三步走2.1 为什么电子琴的核心是分频器电子琴发声的本质是把一个高频时钟开发板上通常是 50MHz分频成不同频率的方波再送去驱动蜂鸣器或无源喇叭。音阶频率是固定的中音 Do 约 262HzRe 约 294HzMi 约 330Hz一路到高音 Do 约 523Hz。分频系数 系统时钟频率 / (2 × 目标频率)因为方波一个周期要翻转两次。以 50MHz 为例Do 的分频系数约 95420这个数已经超出 16 位计数器的直觉范围所以原理图里通常用一个参数化计数器模块把分频系数做成输入端口不同按键选不同的系数。常见做法是顶层放一个 50MHz 时钟输入接一个「音阶选择」多路器多路器的选择端来自按键编码模块输出端接分频计数器计数器输出接蜂鸣器引脚。这样按键一变分频系数就变音调跟着变。这里有个容易翻车的点分频系数必须算准差一点点人耳就能听出音不准尤其是做《小星星》这种旋律时整首曲子会越弹越偏。2.2 按键编码与消抖在原理图里怎么落地机械按键按下时会有 520ms 的抖动直接进分频器会导致一次按键触发多次音阶切换听起来像「颤音」。在原理图方法里消抖一般用一个低频采样时钟比如 1kHz去采按键状态连续采到 N 次相同值才认账。这个逻辑可以用一个计数器加 D 触发器搭出来也可以用 Quartus 自带的 megafunction如 LPM_COUNTER拼。按键编码部分如果开发板上有 4 个独立按键可以直接用 4 位二进制编码出 16 种状态取其中 78 种对应音阶。如果是矩阵键盘就得先做行列扫描再编码。原理图里做矩阵扫描比较繁琐我一般建议课程设计阶段先用独立按键把发声链路跑通再考虑换矩阵键盘。下面是一个用 Verilog 写的分频计数器模块虽然资源是原理图为主但底层模块经常用 Verilog 生成符号再导入 .bdf这样比纯图形搭计数器省事得多// 参数化分频计数器clk_in 为系统时钟div_num 为分频系数 // clk_out 输出目标频率方波 module divider( input clk_in, input rst_n, input [31:0] div_num, // 分频系数由按键编码模块给出 output reg clk_out ); reg [31:0] cnt; always (posedge clk_in or negedge rst_n) begin if (!rst_n) begin cnt 0; clk_out 0; end else if (cnt div_num) begin cnt 0; clk_out ~clk_out; // 翻转输出形成方波 end else begin cnt cnt 1; end end endmodule逻辑说明计数器从 0 累加到 div_num 后归零同时翻转输出电平。div_num 由外部按键编码模块动态给出这样同一个模块就能覆盖所有音阶不用为每个音写一个分频器。参数说明div_num 位宽取 32 位是留余量实际 50MHz 下最高音的分频系数也就几万rst_n 低电平复位保证上电时输出从确定状态开始。这个模块在 Quartus 里生成符号后直接拖进 .bdf 画布连上时钟和按键编码输出即可。2.3 音阶查找表用多路器替代 if-else原理图方法里没有 if-else音阶选择靠多路器MUX实现。常见做法是做一个 8 选 1 多路器选择端接按键编码的 3 位输出8 个数据端分别接 8 个分频系数常量。这些常量可以用 Quartus 的 LPM_CONSTANT 模块生成也可以直接在一个 Verilog 查找表模块里用 case 语句写死再生成符号。// 音阶查找表根据按键编码输出对应分频系数 // 系统时钟 50MHz系数 50_000_000 / (2 * 目标频率) module tone_lut( input [2:0] key_code, // 3 位按键编码对应 8 个音阶 output reg [31:0] div_num ); always (*) begin case (key_code) 3d0: div_num 32d95420; // 中音 Do, 约 262Hz 3d1: div_num 32d85034; // Re, 约 294Hz 3d2: div_num 32d75758; // Mi, 约 330Hz 3d3: div_num 32d71509; // Fa, 约 349Hz 3d4: div_num 32d63776; // Sol, 约 392Hz 3d5: div_num 32d56818; // La, 约 440Hz 3d6: div_num 32d50607; // Si, 约 494Hz 3d7: div_num 32d47774; // 高音 Do, 约 523Hz default: div_num 32d95420; endcase end endmodule逻辑说明case 语句把 3 位编码映射到 8 个分频系数综合后会变成多路器结构和原理图里拖 MUX 等价但写起来快得多。参数说明系数按 50MHz 系统时钟计算如果你的板子是 24MHz 或 12MHz这些数要按比例改否则音调整体偏移。实际调试时我习惯先用 SignalTap 抓一下 clk_out 的实际频率确认和理论值对得上再往下调音阶。3. 在 Quartus 里把原理图跑通从新建工程到下载发声3.1 新建工程与器件选择打开 QuartusFile → New Project Wizard指定工程路径和顶层实体名。器件选择这一步别随手选要对着你手上开发板的型号来常见的是 Cyclone IV E 系列比如 EP4CE6E22C8 或 EP4CE10F17C8。选错器件最直接的后果是引脚分配对不上编译能过但下载后没反应。工程建好后File → New → Block Diagram/Schematic File得到一张空白 .bdf 画布。3.2 把 Verilog 模块导入原理图前面写的 divider 和 tone_lut 不能直接拖进 .bdf要先让 Quartus 认识它们。做法是File → New → Verilog HDL File把代码贴进去保存然后对每个文件执行 Analysis Synthesis。综合成功后在 .bdf 画布空白处双击Symbol 对话框里会出现这两个模块的符号。把它们拖到画布上再拖入 input、output 引脚符号用连线工具连起来。顶层连线关系是时钟输入 → divider 的 clk_in按键输入 → tone_lut 的 key_codetone_lut 的 div_num → divider 的 div_numdivider 的 clk_out → 输出引脚 → 蜂鸣器。3.3 引脚分配与编译下载连线完成后Processing → Start Compilation 先跑一次确认没有语法和连线错误。然后 Assignments → Pin Planner把输入输出引脚绑到开发板实际管脚上。时钟引脚要查板子原理图按键和蜂鸣器同理。绑好后重新全编译打开 Programmer选 USB-Blaster加载 .sof 文件点 Start。下载成功后按按键蜂鸣器应该发出对应音阶。信号名方向绑定对象注意事项clk_50minput板载 50MHz 晶振引脚必须查板子原理图确认key[3:0]input独立按键注意上拉/下拉避免悬空buzzeroutput蜂鸣器驱动引脚无源蜂鸣器才能变调rst_ninput复位按键低电平复位别接反提示有源蜂鸣器只会响一个固定音做电子琴必须用无源蜂鸣器这一点新手经常买错件。4. 避坑与排查那些让板子不响的细节4.1 下载后完全没声音现象编译下载都成功按按键蜂鸣器一点反应没有。原因最常见的是蜂鸣器类型选错有源当无源用或者输出引脚绑到了错误管脚。解决先用万用表量蜂鸣器两端有没有电压变化再回 Pin Planner 核对引脚号必要时用 SignalTap 抓 clk_out 看有没有波形。4.2 音调不对整体偏高或偏低现象能响但音阶听起来不对像跑调。原因分频系数按错误的系统时钟算的比如板子是 24MHz 却按 50MHz 算系数。解决确认板载晶振频率把所有系数按「实际时钟 / (2 × 目标频率)」重算一遍。这个坑我踩过整首曲子偏了半个音查了半天才发现是时钟频率看错了。4.3 按键一按就连续变调现象按一下按键音阶跳好几个。原因按键抖动没消或者消抖采样时钟太快。解决加消抖模块用 1kHz 左右的采样时钟连续采到 816 次相同值再输出。原理图里可以用计数器加比较器搭也可以直接写个 Verilog 消抖模块生成符号。4.4 编译报「multiple drivers」错误现象编译过不去提示某条线有多个驱动源。原因原理图里两条输出线连到了同一个节点或者一个输出引脚被重复赋值。解决检查画布上的连线尤其是总线分叉处确保每个节点只有一个驱动源。原理图方法没有 Verilog 的 wire 优先级冲突会直接报错这其实是好事逼你把连线理清楚。4.5 换开发板后工程跑不起来现象原来板子能跑换一块就废。原因器件型号、引脚定义、时钟频率全变了。解决新建工程时重新选器件Pin Planner 重新绑分频系数按新时钟重算。别想着改几个引脚就完事时钟频率一变音阶系数全得动。5. 进阶玩法把单音电子琴改成能存旋律的版本单音电子琴跑通之后可以往上加两个东西一是自动演奏二是录音回放。自动演奏的思路是把一段旋律的音阶编码和节拍存进 ROM用一个地址计数器按节拍递增依次读出音阶编码送给 tone_lut。Quartus 里可以用 MegaWizard 生成 ROM初始化文件用 .mif 格式里面按行写音阶编码。节拍控制用一个分频到 1Hz 左右的时钟驱动地址计数器每个节拍换一个音。// 自动演奏地址计数器每来一个节拍脉冲地址加一 module melody_player( input clk, input beat_pulse, // 节拍脉冲约 2Hz input rst_n, output reg [4:0] addr // ROM 地址最多 32 个音符 ); always (posedge clk or negedge rst_n) begin if (!rst_n) addr 0; else if (beat_pulse) addr addr 1; // 循环读取 ROM end endmodule逻辑说明beat_pulse 由另一个分频器产生频率决定演奏速度addr 递增后送给 ROM 读数据端ROM 输出接 tone_lut 的 key_code。参数说明addr 位宽 5 位对应 32 个音符够存一小段旋律如果想循环播放可以在 addr 到最大值时归零。录音回放更复杂一些需要把按键编码写进 RAM再用同样的地址计数器读出来这里就不展开了先把自动演奏跑通再说。验证方法上我习惯分三步先用 SignalTap 抓 addr 和 key_code确认旋律数据读得对再抓 clk_out确认频率和音阶对得上最后才上喇叭听。这样即使声音不对也能快速定位是数据问题还是发声问题。从那以后我每次做发声类 FPGA 项目都强制先抓波形再听声音省得在「到底是数据错还是喇叭坏」上浪费时间。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。