简介:本资源是一份基于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,要得到 Do(262Hz)这种低频,必须分频。原理图里一般会放一个“分频器”符号,它的内部可能是一个计数器模块,或者直接用 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_23 | 50MHz 时钟 |
| KEY1 | 输入 | PIN_45 | 按键1 |
| KEY2 | 输入 | PIN_46 | 按键2 |
| BEEP | 输出 | 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 需要约 95420,16 位不够。rst_n是低电平复位,常见做法是接一个按键或上电复位电路。
4.2 音符译码模块的 case 实现
译码模块把按键值转成div值。假设有 7 个按键对应 Do Re Mi Fa Sol La Si:
module note_decoder( input [2:0] key, // 按键编码,0~6 output reg [31:0] div ); always @(*) begin case (key) 3'd0: div = 32'd95420; // Do 262Hz 3'd1: div = 32'd85034; // Re 294Hz 3'd2: div = 32'd75758; // Mi 330Hz 3'd3: div = 32'd71509; // Fa 349Hz 3'd4: div = 32'd63776; // Sol 392Hz 3'd5: div = 32'd56818; // La 440Hz 3'd6: div = 32'd50607; // Si 494Hz default: div = 32'd95420; endcase end endmodule逻辑说明:key是 3 位,支持 8 个状态,这里用了 7 个。div的值是根据 50MHz 时钟算出来的。如果你板子的时钟不是 50MHz,需要重新算。
参数说明:div的计算公式是clk_freq / (2 * note_freq) - 1。比如 50MHz 和 262Hz:50000000 / (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) 5'd0: div = 32'd95420; // Do 5'd1: div = 32'd90000; // Do# 5'd2: div = 32'd85034; // Re // ... 继续加 default: div = 32'd95420; endcase end endmodule6.2 加入自动演奏功能
如果你想让它自动播放一段旋律,可以在顶层加一个“旋律存储器”模块,里面存一串音符和时长。用一个计数器按节拍读出音符,送到译码模块。这样按键和自动演奏可以共存,用一个开关切换。
常见做法是:用一个 LPM_ROM 存音符编码,地址由节拍计数器给出。节拍计数器每到一个时间点就加 1,ROM 输出对应的音符编码,再送到分频器。这样你就能听到自动播放的曲子了。
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。希望帮到你。
本文还有配套的精品资源,点击获取