简介:Music-Sampler是一个基于DE2-115开发板的数字音乐采样系统,通过电子鼓组或键盘输入音频信号,利用Verilog语言实现硬件逻辑控制,完成声音的采集、存储与播放。系统将模拟信号经模数转换为数字样本,存储于板载内存,并通过音频编解码器还原为模拟输出。项目涵盖信号捕获、内存管理、时钟同步、编解码通信等关键模块,结合FPGA的实时处理能力,实现低延迟音乐采样功能。该设计不仅适用于电子音乐创作,也为数字音频硬件开发提供了实践范例。
Music-Sampler系统架构与音频处理实现
在当今嵌入式音频设备日益普及的背景下,从智能音箱到便携式录音笔,实时采样与回放功能已成为许多系统的标配。然而,要真正实现高保真、低延迟的音频体验,仅靠软件算法远远不够——底层硬件协同设计才是关键所在。
你有没有想过,为什么有些“土味”DIY音响播放音乐时总带着一股“数码味”,而专业设备却能还原出细腻的情感?这背后,FPGA(现场可编程门阵列)正悄悄发挥着它的魔力。它不像CPU那样按顺序执行指令,而是像一个“电路魔术师”,把整个信号通路用硬件逻辑直接搭出来,让每一个样本都在纳秒级完成处理。
今天我们要聊的 Music-Sampler 系统,正是这样一个基于FPGA的实时音频采集、存储与回放平台。它不仅是一个技术玩具,更是一次对数字音频本质的深度探索:从声波如何被“冻结”成一串数字,再到这些数据如何在内存中穿梭流转,最终又变回我们耳畔的旋律……这一切都将在Verilog代码和芯片引脚之间展开。
准备好了吗?让我们一起拆开这块“声音黑盒”,看看它是怎么工作的!🎧✨
音频系统的核心骨架:模块化思维是王道!
任何复杂的工程系统,归根结底都是由几个基本“积木块”拼接而成。对于Music-Sampler来说,这个骨架非常清晰:
- 输入端(ADC) :麦克风或线路输入 → 模拟转数字
- 处理层(FPGA逻辑) :采样、滤波、混音、存储控制
- 存储单元(RAM/SDRAM) :暂存海量音频数据
- 输出端(DAC) :数字再转模拟 → 耳机/扬声器发声
整个流程就像一条高效的工厂流水线,每一步都有专门的“工人”负责。而我们的主角——FPGA,则是这条产线的总调度员,确保每个环节无缝衔接,不丢一帧、不错一位。
采样率的秘密:为什么非得48kHz?
先来聊聊最基础但也最容易被忽视的问题: 采样频率 。
根据奈奎斯特采样定理(Nyquist-Shannon Theorem),要想无失真地重建原始信号,采样频率必须至少是信号最高频率的两倍。人耳听觉范围大约是20Hz~20kHz,所以理论上只要超过40kHz就行。
那为啥我们常用44.1kHz(CD标准)或48kHz(专业音频)呢?🤔
- 44.1kHz :源自早期数字音频录像带(DAT)的设计妥协,刚好能覆盖全频段;
- 48kHz :更适合视频同步,且留有更多余量应对抗混叠滤波器的滚降区;
- 更高的还有96kHz甚至192kHz,用于高端母带制作,尽管人类是否真能分辨仍有争议 😅。
在我们的系统中,选择 48kHz 作为基准采样率,既满足高清需求,又不会给FPGA带来过重负担。
// 示例:生成48kHz采样时钟(简化)
wire clk_48k;
assign clk_48k = (counter == CLK_DIVIDER) ? ~clk_48k : clk_48k;
💡 小贴士:实际项目中不会这样用
assign翻转,而是使用计数器+触发器构成稳定分频器,避免毛刺风险。
所有操作都将对齐于统一主时钟域,保证多模块间的数据一致性。这种 同步设计原则 ,是构建可靠系统的基石。
Verilog登场:当代码变成电路!
如果说C语言是用来“描述行为”的,那么Verilog就是用来“描绘电路”的。它不是写一段程序去“运行”,而是定义一组逻辑门如何连接、寄存器如何工作、状态机如何跳转。
这就好比你在纸上画一张电路图,只不过这张图纸可以直接烧进芯片里,变成实实在在的物理路径。是不是很酷?😎
组合逻辑 vs 时序逻辑:动静之间的艺术
在数字世界里,一切逻辑都可以归为两类:
| 特性 | 组合逻辑 | 时序逻辑 |
|---|---|---|
| 是否具有记忆 | 否 | 是(依赖触发器) |
| 典型应用场景 | 加法器、比较器、译码器 | 状态机、计数器、缓存 |
| 对时钟的依赖 | 无 | 必须有时钟 |
| 资源消耗 | 较低 | 较高(因使用FF) |
| 可预测性 | 差(易受延迟影响) | 高(同步于时钟边沿) |
| 是否适合流水线 | 不适合单独使用 | 适合作为流水级 |
举个例子,实现一个简单的立体声混音器:
module audio_mixer (
input [15:0] left_in,
input [15:0] right_in,
output [16:0] mixed_out
);
assign mixed_out = left_in + right_in;
endmodule
这段代码看起来像是“计算两个数之和”,但实际上综合工具会把它变成一组加法器电路。输入一变,输出立刻跟着变——这就是典型的 组合逻辑 。
但问题来了:如果输入信号本身就有噪声或抖动怎么办?输出可能瞬间产生奇怪的脉冲(glitch),导致后级误判。因此,在真实系统中,我们往往把它包装成 时序逻辑 版本:
module synced_audio_mixer (
input clk,
input rst_n,
input [15:0] left_in,
input [15:0] right_in,
output reg [16:0] mixed_out
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
mixed_out <= 17'd0;
else
mixed_out <= left_in + right_in;
end
endmodule
现在,运算结果只在时钟上升沿才更新,相当于加了一道“闸门”。哪怕中间过程再混乱,只要闸门没开,外面就看不到。这样一来,稳定性大大提升!
模块化设计:别再写“巨石代码”了!
还记得第一次看到几千行挤在一起的Verilog代码时那种窒息感吗?🤯 别担心,救星来了—— 模块化设计思想 。
我们可以把复杂系统拆分成一个个独立、可复用的小模块,就像搭乐高一样。顶层设计只需关心接口连接,不必深究内部细节。
以Music-Sampler为例,顶层模块大致长这样:
module music_sampler_top(
input clk_50MHz,
input rst_n,
input [1:0] btn,
inout [15:0] audio_io,
output bclk,
output lrclk,
output [7:0] led
);
wire [15:0] l_data, r_data;
wire [15:0] proc_l, proc_r;
wire sample_valid;
// 实例化I²S接收模块
i2s_receiver i2s_rx (
.clk(clk_50MHz),
.rst_n(rst_n),
.sdin(audio_io[0]),
.bclk(bclk),
.lrclk(lrclk),
.l_data(l_data),
.r_data(r_data),
.sample_valid(sample_valid)
);
// 实例化音频处理器(如滤波或混响)
audio_processor processor (
.clk(clk_50MHz),
.rst_n(rst_n),
.left_in(l_data),
.right_in(r_data),
.left_out(proc_l),
.right_out(proc_r)
);
// 实例化I²S发送模块
i2s_transmitter i2s_tx (
.clk(clk_50MHz),
.rst_n(rst_n),
.l_data(proc_l),
.r_data(proc_r),
.sdout(audio_io[1]),
.bclk(bclk),
.lrclk(lrclk)
);
// LED状态反馈
assign led[7:2] = 6'b0;
assign led[1:0] = btn;
endmodule
每个子模块职责分明:
- i2s_receiver :专注抓取I²S数据流;
- audio_processor :做你想做的任何效果(EQ、延迟、变速等);
- i2s_transmitter :把处理后的数据送出去;
- 所有模块共享同一套时钟,避免异步问题。
这种结构带来的好处简直太多啦:
- ✅ 可测试性增强 :每个模块可独立仿真验证;
- ✅ 团队协作便利 :不同成员并行开发;
- ✅ 复用性强 :下次做个新项目还能拿来用;
- ✅ 易于调试 :出了问题定位快准狠!
📊 表格对比传统单体设计与模块化设计差异:
| 维度 | 单体设计 | 模块化设计 |
|---|---|---|
| 开发周期 | 长 | 短(并行开发) |
| 错误排查难度 | 高 | 中等(边界清晰) |
| 扩展能力 | 差 | 强(插件式替换) |
| 资源共享 | 困难 | 易于通过接口实现 |
| 文档完整性要求 | 低 | 高(需明确接口规范) |
所以啊,别再一个人闷头写大块头代码了,学会拆解、学会抽象,才是高手之路!
跨时钟域难题:小心亚稳态这只“怪兽”!
FPGA中最让人头疼的问题之一,莫过于 跨时钟域 (CDC)。当你试图把一个信号从50MHz时钟域传到另一个48kHz域时,稍不留神就会掉进“亚稳态”陷阱。
什么叫亚稳态?简单说,就是触发器没能及时决定该输出0还是1,卡在中间态晃悠半天。虽然概率极小,但一旦发生,轻则数据错位,重则系统崩溃。😱
如何驯服这只“怪兽”?
🔹 单比特信号 → 双触发器同步法
最常见的做法是用两个D触发器串联:
module sync_ffs (
input src_clk,
input dst_clk,
input async_signal,
output reg synced_signal
);
reg meta1;
always @(posedge dst_clk) begin
meta1 <= async_signal;
synced_signal <= meta1;
end
endmodule
第一级捕获原始信号,可能存在亚稳态;第二级在其后一个周期读取,大幅降低失败概率。MTBF(平均故障间隔时间)可以轻松达到数百年以上,安心使用~
🔹 多比特总线 → 异步FIFO才是正道
如果是地址、数据这类多位宽信号,直接同步会导致各bit采样时刻不一致,出现“撕裂”现象。正确的姿势是使用 异步FIFO :
module async_fifo_aud (
input wr_clk,
input rd_clk,
input rst_n,
input [15:0] wdata,
input we,
output full,
input re,
output [15:0] rdata,
output empty
);
// 内部使用格雷码指针实现跨时钟读写
endmodule
它内部采用格雷码编码的读写指针,配合双口RAM,完美解决跨时同步问题。Quartus II自带IP核生成器,几分钟就能搞定一个工业级FIFO,强烈推荐!
🛠️ CDC最佳实践总结表:
| 信号类型 | 推荐同步方式 | 示例 |
|---|---|---|
| 单比特控制信号 | 双触发器同步 | sync_ffs |
| 多比特数据 | 异步FIFO / 握手协议 | async_fifo_aud |
| 高频时钟切换 | PLL/DLL锁相环预分频 | 使用Quartus IP核生成 |
| 复位信号 | 同步释放(deassertion) | rst_sync 模块确保干净去抖 |
最后,我们用一张Mermaid时序图展示安全通信流程:
sequenceDiagram
participant SRC as 源时钟域(12.288MHz)
participant DST as 目标时钟域(50MHz)
SRC->>DST: 发送请求(req)
Note over DST: 两级同步捕获
DST->>SRC: 应答(ack)
loop 数据传输
SRC->>DST: 数据+valid
DST-->>SRC: ready
end
这套握手机制确保即使两边节奏不同,也能实现可靠传输。在Music-Sampler系统中,这种设计贯穿始终,是保障流畅录制与回放的关键所在。
数字音频的灵魂:PCM与定点运算
声音本质上是连续变化的压力波,但计算机只能处理离散数值。于是就有了 脉冲编码调制 (PCM),将每一瞬间的振幅记录下来,形成一串数字序列。
PCM编码原理:听得见的数学之美
根据奈奎斯特定理:
$$ f_s \geq 2 \cdot f_{max} $$
我们已经知道,48kHz足够覆盖人耳极限。接下来就是量化——把电压值映射成整数。
常见格式是 16位有符号整数 (signed [15:0]),动态范围可达96dB,足够应付绝大多数场景。例如:
| 时间点 | 模拟电压(V) | 量化后16位值(十六进制) |
|---|---|---|
| t=0 | +1.5 | 0x7FFF |
| t=1 | -1.4 | 0x8A3E |
| t=2 | 0.0 | 0x0000 |
这些值通过I²S总线进入FPGA,在Verilog中声明为:
input signed [15:0] audio_in;
注意加上 signed 关键字,否则减法会出错哦!
定点数大战浮点数:资源换精度的游戏
FPGA天生不适合跑浮点运算(除非调用DSP硬核),所以我们普遍采用 定点数 表示法。
最流行的是 Q15格式 :1位符号 + 15位小数,表示范围[-1, 1),相当于把[-32768, 32767]映射到[-1.0, ~0.99997]。
乘法示例:
module q15_multiplier (
input clk,
input rst_n,
input [15:0] a,
input [15:0] b,
output reg [15:0] prod
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
prod <= 16'd0;
else
prod <= ($signed(a) * $signed(b)) >>> 15;
end
endmodule
解释一下:
- $signed() 强制解释为有符号数;
- 16×16→32位结果;
- 算术右移15位恢复Q15精度;
- >>> 保持符号扩展。
可以用这个模块做音量调节:
wire [15:0] gain = 16'sh4000; // 0.5 增益(Q15)
q15_multiplier vol_ctrl(.a(audio_in), .b(gain), ...);
📌 常见定点格式一览:
| 格式 | 总位宽 | 整数位 | 小数位 | 范围 | 精度 |
|---|---|---|---|---|---|
| Q15 | 16 | 1 | 15 | [-1, 0.999969] | ~3e-5 |
| Q31 | 32 | 1 | 31 | [-1, ~1) | ~4.6e-10 |
| Q1.14 | 16 | 1 | 14 | [-2, 2) | ~6e-5 |
一般音乐采样用Q15足矣,滤波器系数可用Q31提升信噪比。
FPGA并行魔法:让四个声道同时跳舞!
音频天然具有时间连续性和空间并行性。利用FPGA的并行优势,我们可以同时处理多个样本或多通道数据。
比如做一个四通道FIR滤波器:
parameter TAPS = 64;
reg [15:0] coeff[TAPS-1:0];
reg [15:0] delay_line[3][TAPS-1:0];
always @(posedge clk) begin
for (int ch = 0; ch < 4; ch++) begin
delay_line[ch][0] <= audio_in[ch];
for (int i = 1; i < TAPS; i++)
delay_line[ch][i] <= delay_line[ch][i-1];
end
end
配合MAC(乘累加)单元,吞吐率直线飙升!再加个流水线,每周期都能输出一个新结果。
graph LR
subgraph Parallel Processing Pipeline
A[Ch0 Sample] --> M1[Multiply-Accumulate]
B[Ch1 Sample] --> M2[Multiply-Accumulate]
C[Ch2 Sample] --> M3[Multiply-Accumulate]
D[Ch3 Sample] --> M4[Multiply-Accumulate]
M1 --> O[Output Bus]
M2 --> O
M3 --> O
M4 --> O
end
这样的架构适用于多轨录音、环绕声渲染等高级应用。想象一下,四个独立的声道各自进行卷积运算,互不干扰——这才是真正的“实时”处理!
DE2-115平台揭秘:你的音频实验室
有了理论,还得有实战舞台。我们选用 DE2-115 开发板,搭载 Cyclone II EP4CE115F29C7 FPGA,配上WM8731编解码器,组成一套完整的音频实验平台。
Cyclone II芯片性能一览
| 参数 | 规格 |
|---|---|
| 逻辑单元(LEs) | 114,480 |
| 查找表(LUTs) | 约57,240 |
| 嵌入式存储器比特数 | 4,158,720 bits(约4.16 Mb) |
| 18×18乘法器数量 | 288个 |
| PLL锁相环数量 | 4个 |
| I/O引脚数 | 592个(最大可用) |
| 封装形式 | FBGA-780 |
| 最大工作频率 | ~200 MHz(典型应用) |
资源绰绰有余!你可以放心大胆地堆pipeline、搞多通道、加特效,完全不用担心“爆仓”。
更重要的是,它支持PLL时钟管理,能精准生成12.288MHz(256×48kHz)这类特殊频率,完美匹配I²S协议需求。
// 示例:使用PLL生成多个时钟信号
module pll_audio_clock (
input inclk0,
output reg c0,
output reg c1,
output reg c2
);
// 由Quartus MegaWizard生成ALTPLL实例
endmodule
WM8731音频编解码器详解
这块小芯片可是系统的灵魂人物 👑:
- 支持I²S、Left-Justified等多种格式;
- 内置耳机驱动,最大输出75mW @ 16Ω;
- 可编程增益、静音、去加重;
- 通过I²C配置寄存器。
连接拓扑如下:
graph TD
A[FPGA (EP4CE115)] -->|I²S Data Out (SDOUT)| B[WM8731 DAC]
A -->|I²S Data In (SDIN)| B[WM8731 ADC]
A -->|BCLK| B
A -->|LRCLK| B
A -->|I²C_SCL/I²C_SDA| C[WM8731 Control Registers]
B -->|Analog Output| D[Headphone Jack]
E[Mic Input] --> B
F[Line In] --> B
初始化流程也很讲究,必须通过I²C写一系列寄存器才能激活ADC/DAC功能。例如:
task init_wm8731;
begin
i2c_write(7'b1001010, 8'h1E); // 重置
#10us;
i2c_write(7'b1001010, 8'h17); // 左声道音量0dB
i2c_write(7'b1001010, 8'h0A); // 启用DAC,I²S模式
i2c_write(7'b1001010, 8'h00); // 设定48kHz采样率
end
endtask
⚠️ 注意延时和应答检测,否则容易失败!
存储资源布局
DE2-115提供了多层次存储体系:
| 存储类型 | 容量 | 速度 | 典型用途 |
|---|---|---|---|
| SRAM (ISSI IS61LV25616) | 512 KB | <10 ns 访问延迟 | 实时音频缓存、查找表 |
| SDRAM (Micron MT48LC16M16A2) | 32 MB | ~50 ns 初始延迟,突发模式高效 | 大容量采样存储 |
| Flash (Spansion FLA128P) | 64 MB | 较慢,只读为主 | 固件存储、配置信息 |
SRAM速度快但贵,适合存放当前播放片段;SDRAM便宜量大,适合长时间录音;Flash保存固件,上电自动加载。
合理分配它们,能让系统性能飞起来!🚀
Quartus II实战指南:从工程创建到下载调试
工欲善其事,必先利其器。Intel Quartus II是你最重要的伙伴。
工程创建与引脚分配
新建工程 → 选器件EP4CE115F29C7 → 添加源文件。
关键是要做好 引脚约束 :
| Signal Name | Device Pin | I/O Standard | Note |
|---|---|---|---|
| CLOCK_50 | PIN_Y2 | 3.3-V LVTTL | 主时钟输入 |
| KEY[0] | PIN_R24 | 3.3-V LVTTL | 用户按键复位 |
| SW[0] | PIN_AB28 | 3.3-V LVTTL | 模式选择开关 |
| LEDR[0] | PIN_AA24 | 3.3-V LVTTL | 系统运行指示 |
| AUD_ADCLRCK | PIN_D1 | 3.3-V LVTTL | WM8731 LRCLK |
| AUD_ADCDAT | PIN_B1 | 3.3-V LVTTL | ADC数据输入 |
| AUD_DACLRCK | PIN_G1 | 3.3-V LVTTL | DAC帧同步 |
| AUD_DACDAT | PIN_F1 | 3.3-V LVTTL | DAC数据输出 |
建议用Tcl脚本批量设置,效率更高:
set_location_assignment PIN_Y2 -to CLOCK_50
set_location_assignment PIN_R24 -to "KEY[0]"
...
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to *
SignalTap II在线抓波神器
想看内部信号?别靠猜了,用 SignalTap II 直接抓!
步骤超简单:
1. 添加 .stp 文件;
2. 选采样时钟和观测信号;
3. 设置触发条件(如按键按下);
4. 编译 → 下载 → 抓波!
graph LR
A[Design RTL Code] --> B[Add SignalTap II File]
B --> C[Select Sampling Clock & Signals]
C --> D[Define Trigger Condition]
D --> E[Compile with STP Integration]
E --> F[Download .sof to FPGA]
F --> G[Launch SignalTap GUI]
G --> H[Capture Real-time Waveforms]
再也不用外接示波器也能看清I²C波形、I²S帧同步是否对齐,简直是调试神器!
ADC采集全流程:从I²S到双声道分离
终于到了动手环节!第一步:搞定音频输入。
I²S协议解析
I²S三大要素:
- BCLK :位时钟,1.536MHz(48kHz × 32bit)
- LRCLK :帧时钟,48kHz,高低分别代表左右声道
- SDATA :串行数据,MSB先行
接收模块框架如下:
module i2s_receiver (
input clk_50m,
input rst_n,
input bclk,
input lrclk,
input sdata,
output reg[15:0] left_data,
output reg[15:0] right_data,
output data_valid
);
reg [4:0] bit_cnt;
reg prev_lrclk;
wire lr_rising = (prev_lrclk == 1'b0) && (lrclk == 1'b1);
always @(posedge bclk or negedge rst_n) begin
...
end
reg [15:0] shift_reg;
always @(posedge bclk) begin
if (lr_rising) shift_reg <= 16'b0;
else shift_reg <= {shift_reg[14:0], sdata};
end
always @(posedge bclk) begin
if (lr_rising) begin
if (prev_lrclk == 1'b0) left_data <= shift_reg;
else right_data <= shift_reg;
end
end
assign data_valid = (bit_cnt == 5'd16);
endmodule
核心思路:用 lrclk 跳变清零计数器, bclk 驱动移位寄存器逐位接收,最后根据前一状态判断声道归属。
WM8731初始化流程
别忘了先让它“醒过来”!
// 初始化序列(部分)
i2c_write(7'b1001010, 8'h1E); // Reset
i2c_write(7'b1001010, 8'h0A); // Enable DAC, I²S mode
i2c_write(7'b1001010, 8'h00); // 48kHz sampling rate
用有限状态机控制I²C写操作,确保每一步都成功应答。
stateDiagram-v2
[*] --> IDLE
IDLE --> START : start_i2c_write
START --> SEND_SLAVE_ADDR : generate_start
SEND_SLAVE_ADDR --> ACK_CHECK1 : send_byte(0x34 << 1)
ACK_CHECK1 --> SEND_REG_ADDR : ack_received
...
内存管理策略:RAM怎么选?深度怎么算?
采集来的数据往哪儿放?这是个大问题!
分布式RAM vs 块状RAM
| 特性 | 分布式RAM | 块状RAM |
|---|---|---|
| 实现方式 | 使用LUT构造小容量存储 | 专用RAM模块 |
| 单元大小 | ~256 bits per LE | 4Kbits per Block |
| 最大容量(EP4CE115) | ~288 Kbits | ~1,036 Kbits |
| 访问速度 | 快(组合路径) | 较慢(需走专用通路) |
| 是否支持双端口 | 是(通过LUT复制) | 是(原生支持) |
| 适用场景 | 小规模缓存、查找表、流水线暂存 | 大容量音频缓冲、帧存储 |
结论:小于512 bits → 分布式;大于1K bits → 块状RAM。
双端口RAM设计
录音和播放可能同时发生,必须隔离读写:
(* ramstyle = "dual-port" *) reg [15:0] audio_buffer [0:2047];
always @(posedge clk) begin
if (write_en) audio_buffer[wr_ptr] <= new_sample;
if (read_en) current_sample <= audio_buffer[rd_ptr];
end
综合工具会自动映射为BRAM。记得加满/空标志防止溢出!
存储深度计算
假设要录2秒立体声48kHz 16bit音频:
- 每秒数据量 = 48,000 × 2 × 2 = 192 KB
- 2秒总量 = 384 KB ≈ 196,608 words
- 地址宽度 ≥ log₂(196608) ≈ 18 bits
Cyclone II BRAM共约1,036 Kbits → 可存约64K样本 → 支持约1.35秒录制。
若需更久?考虑压缩、降采样或外扩SDRAM!
DAC输出驱动:让声音响起来!
采集完了,当然要播出来听听啦~
WM8731作为DAC的主从模式
必须设为 从模式 (Slave Mode),由FPGA提供BCLK/LRCLK。
通过寄存器6设置:
- MS = 0 :Slave Mode
- FORMAT = 01 :I²S
- WL = 00 :16-bit
BCLK与LRCLK生成
module clk_gen_audio (
input clk_sys,
input rst_n,
output reg bclk,
output reg lrclk
);
localparam DIVIDER = 50_000_000 / (2 * 1_536_000); // ~16
reg [15:0] cnt_bclk;
always @(posedge clk_sys or negedge rst_n) begin
if (!rst_n) cnt_bclk <= 0;
else if (++cnt_bclk >= DIVIDER) begin
cnt_bclk <= 0;
bclk <= ~bclk;
end
end
reg [4:0] lr_counter;
always @(posedge clk_sys) begin
if (bclk_posedge) begin
if (++lr_counter == 32) begin
lr_counter <= 0;
lrclk <= ~lrclk;
end
end
end
endmodule
数据对齐格式匹配
使用I²S格式时,数据应在LRCLK跳变后的第二个BCLK开始发送。
移位逻辑如下:
always @(posedge bclk) begin
if (lr_rising) shift_reg <= {current_sample, 16'd0};
else shift_reg <= {shift_reg[30:0], 1'b0};
end
assign i2s_dout = shift_reg[31];
系统整合与交互功能:打造完整产品体验
最后一步,把所有模块拧成一股绳!
顶层集成示例
module music_sampler_top(...);
pll_audio_clock u_pll(...);
adc_i2s_interface u_adc(...);
dac_i2s_interface u_dac(...);
audio_ram_controller u_ram_ctrl(...);
key_debounce u_key_db(...);
...
endmodule
按键去抖与触发识别
机械按键必须去抖:
always @(posedge clk) begin
btn_sync <= {btn_sync[1:0], key_raw};
if (btn_sync == 3'b001) toggle <= ~toggle;
end
还可识别单击、双击、长按等操作,赋予丰富语义。
LED状态反馈
用LED显示录音/播放状态、缓存占用比例:
wire [19:0] usage_ratio = (current_addr * 20'd100) >> 16;
assign led_output[7:3] = usage_ratio >= 5'd80 ? 5'b11111 :
usage_ratio >= 5'd60 ? 5'b11110 : ...;
变采样率播放(变调雏形)
改变读取速率即可实现音调变化:
read_counter <= read_counter + playback_rate;
if (read_counter >= MAX_RATE) begin
read_counter -= MAX_RATE;
addr_rd <= addr_rd + 1;
end
playback_rate=1.5 → 升调,0.7 → 降调,趣味十足!
结语:这只是起点,未来无限可能 🚀
看到耳机里传出自己写的代码生成的声音那一刻,你会明白:这不是冰冷的逻辑门组合,而是一种创造的艺术。
Music-Sampler不仅仅是个实验项目,它教会我们如何思考硬件、如何驾驭时序、如何平衡资源与性能。
下一步呢?
- 加SPI接口读SD卡,做大容量录音机;
- 接MIDI键盘,变身电子鼓组;
- 移植到Cyclone V SoC,跑Linux+Qt界面;
- 甚至加入AI降噪模型,实现实时语音增强……
可能性,永远比想象中多一点点 😉🎶
Keep hacking, keep sounding!
简介:Music-Sampler是一个基于DE2-115开发板的数字音乐采样系统,通过电子鼓组或键盘输入音频信号,利用Verilog语言实现硬件逻辑控制,完成声音的采集、存储与播放。系统将模拟信号经模数转换为数字样本,存储于板载内存,并通过音频编解码器还原为模拟输出。项目涵盖信号捕获、内存管理、时钟同步、编解码通信等关键模块,结合FPGA的实时处理能力,实现低延迟音乐采样功能。该设计不仅适用于电子音乐创作,也为数字音频硬件开发提供了实践范例。
883

被折叠的 条评论
为什么被折叠?



