查看: 1751|回复: 0

教你如何用一块FPGA开发板制作音乐盒?!

[复制链接]

该用户从未签到

发表于 2019-6-19 17:02:06 | 显示全部楼层 |阅读模式
分享到:
相信大家已经在网上见识过很多“民间大神”们的天才(奇葩)手工DIY了,一些简简单单的材料在他们的捣鼓下,瞬间华丽转身,变废为宝。今天,我们为大家整理了一位手工大神的DIY心得——如何利用FPGA开发板DIY一个音乐盒,也就是如何使我们的FPGA播放声音和音乐!

下面开始吧....

首先需生成单个音调,然后慢慢地做更有趣的事情,如制作警报声和播放曲调。
需要准备的硬件
该项目使用的是一个Pluto的FPGA开发板,还有一个扬声器和一个1kΩ的电阻。

2.jpg

更加直观的展示是下面这样的:
1.jpg
本次DIY的音乐盒分为4个部分:
1.简单的哔哔声
2.警报声
3.音调
4.曲子

1.简单的哔哔声
FPGA可以轻松实现二进制计数器。例如,在回滚之前,16位计数器将从0到65535(65536个不同的值)计数。Pluto板具有一个25MHz的时钟振荡器,因此我们可以轻松构建一个25MHz时钟16位自动计数器。其最高位切换频率为25000000/65536 = 381Hz。

其Verilog HDL的代码如下:
module music(clk, speaker);
input clk;
output speaker;
// 首先创建一个16位二级制计数器
reg [15:0] counter;
always @(posedge clk) counter <= counter+1;
// 使用计数器的最高有效位来驱动扬声器
assign speaker = counter[15];
endmodule
详细讲解的话就是,“clk”在25MHz下运行,“counter [0]”看起来像是一个12.5MHz信号(它以25MHz的频率进行更新变化,变化的值为0 1 0 1 ......因此看起来像12.5MHz信号),“counter [1]“是6.125MHz信号,依此类推。

由于我们使用计数器的最高有效位(第15位)来驱动输出,因此“扬声器”输出会产生一个完美的381Hz方波信号。
2.警报声
我们可以在两个音调之间循环。我们首先用一个24位计数器的“音调”来产生一个较慢的方波。其MSB(最高有效位)“tone[23]”是一1.5Hz的频率进行变换的。

然后我们就可以用这个最高有效位切换到另一个计数器,从而在两个频率之间切换。

代码如下:
module music(clk, speaker);
input clk;
output speaker;
parameter clkdivider = 25000000/440/2;
reg [23:0] tone;
always @(posedge clk) tone <= tone+1;
reg [14:0] counter;
always @(posedge clk) if(counter==0) counter <= (tone[23] ? clkdivider-1 : clkdivider/2-1); else counter <= counter-1;
reg speaker;
always @(posedge clk) if(counter==0) speaker <= ~speaker;
endmodule
3.演奏音符
现在我们想要演奏一首曲子,所以需要来获取不同的音符,就像电子琴一样。

如果我们用6位来实现音符,那么我可以获得64个音符。每个八度一共有12个音符,所以64个音符可以实现5个八度,完全足够弹奏一首曲目了。
步骤1
要想实现一组音调不断升高的声音,我们用一个28位计数器来距离,提取其中6个MSB,从而给到6位的音调。

代码如下:
reg [27:0] tone;
always @(posedge clk) tone <= tone+1;
wire [5:0] fullnote = tone[27:22];
在25MHz的时钟下,每个音符持续167ms,64个音符一共需要10.6s才能演奏完成。
步骤2
我们将“fullnote”分成12份。这样就可以给到我们一个八度(一共有5个8度,所以我们只需要3位,从0到4)和音符(从0到11,需要4位)。

代码如下:
wire [2:0] octave;
wire [3:0] note;
divide_by12 divby12(.numer(fullnote[5:0]), .quotient(octave), .remain(note));
此处用了一个叫divide_by12的子模块来实现分频。细节稍后讲解。
步骤3
从一个八度到另一个八度,频率乘以了“2”。这个在硬件上很容易实现,我们在步骤4上进行实现。但是要从一个音符到另一个音符,频率就要乘以1.0594。这样的话就没那么容易在硬件上实现了,所以我们需要看下提前计算好的音符值。
我们将主时钟除以512得到音符A,除以483得到音符A#,除以456得到音符B…记住,除以一个更低的值得到的是更高的音符。

一个八度下的音符数值如下:
always @(note)
case(note)
  0: clkdivider = 512-1; // A
  1: clkdivider = 483-1; // A#/Bb
  2: clkdivider = 456-1; // B
  3: clkdivider = 431-1; // C
  4: clkdivider = 406-1; // C#/Db
  5: clkdivider = 384-1; // D
  6: clkdivider = 362-1; // D#/Eb
  7: clkdivider = 342-1; // E
  8: clkdivider = 323-1; // F
  9: clkdivider = 304-1; // F#/Gb
  10: clkdivider = 287-1; // G
  11: clkdivider = 271-1; // G#/Ab
  12: clkdivider = 0; // should never happen
  13: clkdivider = 0; // should never happen
  14: clkdivider = 0; // should never happen
  15: clkdivider = 0; // should never happen
endcase
always @(posedge clk) if(counter_note==0) counter_note <= clkdivider; else counter_note <= counter_note-1;
每当counter_note等于0时,就进入到下一个八度。
步骤4
现在我们要处理好不同的八度,对于最低的八度,我们将“counter_note”除以256,对于第二个八度,除以128,以此类推。

reg [7:0] counter_octave;
always @(posedge clk)
if(counter_note==0)
begin
if(counter_octave==0)
counter_octave<=(octave==0?255:octave==1?127:octave==2?63:octave==3?31:octave==4?15:7);
else
  counter_octave <= counter_octave-1;
end
reg speaker;
always @(posedge clk)if(counter_note==0&&counter_octave==0)speaker <= ~speaker;
完整代码如下:
module music(clk, speaker);
input clk;
output speaker;
reg [27:0] tone;
always @(posedge clk) tone <= tone+1;
wire [5:0] fullnote = tone[27:22];
wire [2:0] octave;
wire [3:0] note;
divide_by12 divby12(.numer(fullnote[5:0]), .quotient(octave), .remain(note));
reg [8:0] clkdivider;
always @(note)
case(note)
  0: clkdivider = 512-1; // A
  1: clkdivider = 483-1; // A#/Bb
  2: clkdivider = 456-1; // B
  3: clkdivider = 431-1; // C
  4: clkdivider = 406-1; // C#/Db
  5: clkdivider = 384-1; // D
  6: clkdivider = 362-1; // D#/Eb
  7: clkdivider = 342-1; // E
  8: clkdivider = 323-1; // F
  9: clkdivider = 304-1; // F#/Gb
  10: clkdivider = 287-1; // G
  11: clkdivider = 271-1; // G#/Ab
  12: clkdivider = 0; // should never happen
  13: clkdivider = 0; // should never happen
  14: clkdivider = 0; // should never happen
  15: clkdivider = 0; // should never happen
endcase
reg [8:0] counter_note;
always @(posedge clk) if(counter_note==0) counter_note <= clkdivider; else counter_note <= counter_note-1;
reg [7:0] counter_octave;
always @(posedge clk)
if(counter_note==0)
begin
if(counter_octave==0)
  counter_octave <= (octave==0?255:octave==1?127:octave==2?63:octave==3?31:octave==4?15:7);
else
  counter_octave <= counter_octave-1;
end
reg speaker;
always @(posedge clk) if(counter_note==0 && counter_octave==0) speaker <= ~speaker;
endmodule
4.曲子
告诉音乐盒:你已经是一个成熟的音乐盒了,创作曲子是你自己应该要做好的事情。


回复

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

站长推荐上一条 /4 下一条



手机版|小黑屋|与非网

GMT+8, 2024-11-24 02:41 , Processed in 0.118430 second(s), 16 queries , MemCache On.

ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

苏公网安备 32059002001037号

Powered by Discuz! X3.4

Copyright © 2001-2024, Tencent Cloud.