FPGA播放声音和音乐.docx

上传人:b****5 文档编号:7353248 上传时间:2023-01-23 格式:DOCX 页数:13 大小:46.89KB
下载 相关 举报
FPGA播放声音和音乐.docx_第1页
第1页 / 共13页
FPGA播放声音和音乐.docx_第2页
第2页 / 共13页
FPGA播放声音和音乐.docx_第3页
第3页 / 共13页
FPGA播放声音和音乐.docx_第4页
第4页 / 共13页
FPGA播放声音和音乐.docx_第5页
第5页 / 共13页
点击查看更多>>
下载资源
资源描述

FPGA播放声音和音乐.docx

《FPGA播放声音和音乐.docx》由会员分享,可在线阅读,更多相关《FPGA播放声音和音乐.docx(13页珍藏版)》请在冰豆网上搜索。

FPGA播放声音和音乐.docx

FPGA播放声音和音乐

这里我们将让我们的FPGA播放声音和音乐。

我们从产生一个单频音开始。

然后,逐步让它实现一些更加有趣的功能,例如播放警笛和曲子。

这个工程中用到的硬件器件包括:

一块Pluto板、一个扬声器(speaker)以及一个1千欧姆的电阻(resistor)。

 

关于此硬件系统的一个更加正式的表示方法如下图所示:

 

振荡器(oscillator)产生一个固定频率输入到FPGA,FPGA将此频率分频后驱动一个I/O口。

这个I/O口通过一个1千欧姆的电阻连接到一个扬声器。

通过改变这个I/O口的输出频率,就可以使扬声器发出各种声音。

HDL(硬件描述语言)设计

这里将分三部分来描述它:

∙ 第一部分-简单的哔哔声

∙ 第二部分-警笛声

∙ 第三部分-曲调

简单的哔哔声

FPGA可以很容易就实现二进制的计数。

让我们从一个16位的计数器开始。

首先从25MHz的时钟开始,对于这个时钟信号,我们可以简单的应用计数器来实现“分频”。

一个16位的计数器从0计到65535(一共65536个不同的值)。

计数器的最高位将以25000000/65536=381Hz的频率翻转。

对应的VerilogHDL语言如下所示:

代码

1.module music(clk, speaker);  

2.input clk;  

3.output speaker;  

4.  

5.// 16位的2进制计数器  

6.reg [15:

0] counter;  

7.always @(posedge clk) counter <= counter+1;  

8.  

9.// 使用计数器的最高有效位驱动扬声器  

10.assign speaker = counter[15];  

11.  

12.endmodule  

计数器的最低有效位(counter[0])以12.5MHz的频率翻转,类似的counter[1]以6.125MHz的频率翻转,以此类推。

我们使用最高有效位(counter[15])来驱动扬声器。

这样就可以给扬声器输出一个很好的381Hz的方波。

"A"调(440Hz)

好了,与其产生一个随机的频率,为何不试试得到一个440Hz的频率。

这个频率就是“A”调的频率。

这样一来,我们需要将25MHz的信号56818分频,下面是对应的VerilogHDL代码。

代码

1.module music(clk, speaker);  

2.input clk;  

3.output speaker;  

4.  

5.reg [15:

0] counter;  

6.always @(posedge clk) if(counter==56817) counter <= 0; else counter <= counter+1;  

7.  

8.assign speaker = counter[15];  

9.  

10.endmodule  

问题来了,输出信号的频率虽然是希望的440Hz,但是其占空比不再是50%。

因为低电平从0一直维持到32767(期间counter[15]等于0),而高电平则从32768维持到56817。

这样输出信号中,高电平的占空比仅为42%。

最简单的得到50%占空比的办法是添加一个状态,使输出信号先28409分频(56818的一半),然后再2分频。

以下是修改后的VerilogHDL代码。

代码

1.module music(clk, speaker);  

2.input clk;  

3.output speaker;  

4.  

5.reg [14:

0] counter;  

6.always @(posedge clk) if(counter==28408) counter <= 0; else counter <= counter+1;  

7.  

8.reg speaker;  

9.always @(posedge clk) if(counter==28408) speaker <= ~speaker;  

10.  

11.endmodule  

添加一个参数

下面的代码跟上面的代码效果完全一样,一个名为“clkdivider”的参数被添加到代码中,而计数器则变为向下技术(这个只是个人爱好问题).

代码

1.module music(clk, speaker);  

2.input clk;  

3.output speaker;  

4.parameter clkdivider = 25000000/440/2;  

5.  

6.reg [14:

0] counter;  

7.always @(posedge clk) if(counter==0) counter <= clkdivider-1; else counter <= counter-1;  

8.  

9.reg speaker;  

10.always @(posedge clk) if(counter==0) speaker <= ~speaker;  

11.  

12.endmodule

  

救护车笛声

让我们交替发出两个音调。

首先我们使用一个24位的计数器“tone”来产生一个低频的方波。

其最高有效位(tone[23])以大约1.5Hz的频率翻转。

我们使用这一位(tone[23])来控制主计数器产生在两个频率之间切换的输出波形,这样一来就可以交替发出两个音调。

下面是对应的VerilogHDL代码。

代码

1.module music(clk, speaker);  

2.input clk;  

3.output speaker;  

4.parameter clkdivider = 25000000/440/2;  

5.  

6.reg [23:

0] tone;  

7.always @(posedge clk) tone <= tone+1;  

8.  

9.reg [14:

0] counter;  

10.always @(posedge clk) if(counter==0) counter <= (tone[23] ?

 clkdivider-1 :

 clkdivider/2-1); else counter <= counter-1;  

11.  

12.reg speaker;  

13.always @(posedge clk) if(counter==0) speaker <= ~speaker;  

14.  

15.endmodule  

警车笛声

问题现在变得复杂起来。

我们需要产生一个音调的变化,使之听起来像是警车的笛声。

仍然从“tone”计数器开始。

我们仅使用23位,这样便可以得到两倍与前面的频率(最高有效位大约以3Hz的频率翻转)

下面是如何产生变化的音调的技巧。

使用一个寄存器“ramp”来表征当前的音调,则要求ramp的值在某一区间来回变化,例如...-2-1-0-1-2-3-...-127-126-125-...-2-1-0-1-2-...。

考虑“tone”计数器的15到21位(tone[21:

15]),这是一个在0到127之间循环递增的值,0-1-2-...-127-0-。

再考虑这几位的反转,即~tone[21:

15],这是一个在127-0之间循环递减的值。

如果能控制ramp在这两个值之间来回切换,即可得到一个形如...-0-1-2-...-127-126-125-...的计数器。

而这个变化规律正好符合警车笛声的音调变化规律。

为了让ramp在这两个值之间来回切换,我们使用tone[22]来控制。

可以这样考虑,tone[22:

15]从0计数,对于前128个值(0-127),tone[22]等于0,后128个值(128-255),tone[22]等于1。

于是我们就可以使用tone[22]来控制ramp的取值,当tone[22]等于0时,让ramp等于tone[21:

15],当tone[22]等于1时,让ramp等于~tone[21:

15]。

具体的硬件描述语言如下:

代码

1.wire [6:

0] ramp = (tone[22] ?

 tone[21:

15] :

 ~tone[21:

15]);  

2.  

3.// 含义  

4.// 当 tone[22]等于1 取 ramp=tone[21:

15] 否则 ramp=~tone[21:

15]  

这样一来ramp就会在7b'0000000与7b'1111111之间来回变化.为了得到一个对于产生声音有用的值,我们在其前面补上两位数据"01",并且在其尾部也补上6个0,即"000000"。

代码

1.wire [14:

0] clkdivider = {2'b01, ramp, 6'b000000};  

通过这样的处理,"clkdivider"就拥有了一个在15'b010000000000000与15'b011111111000000之间来回变化的值(或者以16进制表示在15'h2000与15'h3FC0,以十进制表示在8192到16320之间变化)。

当输入频率为25MHz时,将产生频率在765Hz到1525Hz之间变化的音调,从而产生类似于警车笛声的声音。

下面是整个模块的VerilogHDL语言描述。

代码

1.module music(clk, speaker);  

2.input clk;  

3.output speaker;  

4.  

5.reg [22:

0] tone;  

6.always @(posedge clk) tone <= tone+1;  

7.  

8.wire [6:

0] ramp = (tone[22] ?

 tone[21:

15] :

 ~tone[21:

15]);  

9.wire [14:

0] clkdivider = {2'b01, ramp, 6'b000000};  

10.  

11.reg [14:

0] counter;  

12.always @(posedge clk) if(counter==0) counter <= clkdivider; else counter <= counter-1;  

13.  

14.reg speaker;  

15.always @(posedge clk) if(counter==0) speaker <= ~speaker;  

16.  

17.endmodule  

高速追击

现在让我们看看如何让FPGA发出“高速追击”的声音。

这个时候警笛声时快时慢。

因此使用"tone[21:

15]"来得到一个快速的变调,而使用"tone[24:

18]"来得到一个慢速的变调。

代码

1.wire [6:

0] fastsweep = (tone[22] ?

 tone[21:

15] :

 ~tone[21:

15]);  

2.wire [6:

0] slowsweep = (tone[25] ?

 tone[24:

18] :

 ~tone[24:

18]);  

3.wire [14:

0] clkdivider = {2'b01, (tone[27] ?

 slowsweep :

 fastsweep), 6'b000000};  

完整的VerilogHDL代码是这样子的:

代码

1.module music(clk, speaker);  

2.input clk;  

3.output speaker;  

4.  

5.reg [27:

0] tone;  

6.always @(posedge clk) tone <= tone+1;  

7.  

8.wire [6:

0] fastsweep = (tone[22] ?

 tone[21:

15] :

 ~tone[21:

15]);  

9.wire [6:

0] slowsweep = (tone[25] ?

 tone[24:

18] :

 ~tone[24:

18]);  

10.wire [14:

0] clkdivider = {2'b01, (tone[27] ?

 slowsweep :

 fastsweep), 6'b000000};  

11.  

12.reg [14:

0] counter;  

13.always @(posedge clk) if(counter==0) counter <= clkdivider; else counter <= counter-1;  

14.  

15.reg speaker;  

16.always @(posedge clk) if(counter==0) speaker <= ~speaker;  

17.  

18.endmodule  

弹奏曲子

现在我们希望通过FPGA来弹奏曲子。

首先我们需要一个类似与键盘的东西来弹奏音符。

如果我们使用6位去编码一个音符,那么我们可以得到64个音符。

每个音阶有12个音符,所以64个音符可以包括比5个还要多的音阶,这对与弹奏一小曲子来说已经足够了。

第一步

为了实现一升调的方式依次64个音符,我们使用一个28位的计数器,使用它的最高6位来作为我们希望弹奏的音符。

代码

1.reg [27:

0] tone;  

2.always @(posedge clk) tone <= tone+1;  

3.  

4.wire [5:

0] fullnote = tone[27:

22];  

当输入时钟频率为25MHz时,每个音符持续167ms,总共需要10.6s才能播放完全部64个音符。

第二步.

我们将“fullnote”除以12,从而得到八度音阶(五个音阶(0-4),所以3位就足够了)和12个音符(0-11,所以4位就够了)。

代码

1.wire [2:

0] octave;  

2.wire [3:

0] note;  

3.divide_by12 divby12(.numer(fullnote[5:

0]), .quotient(octave), .remain(note));  

可以看到,这里我们使用了除法模块divby12来完成除法,具体的细节将在后面谈到。

第三步.

当从一个音阶跳到下一个音阶,频率需要乘以2时,这个在硬件上很容易实现,具体将在第四步中讨论。

但是当需要乘以“1.0594”时,这个在硬件上很难实现。

因此我们使用一个存储了预先计算好的值的查找表来实现。

我们将主时钟除以512得到A调,除以483得到A#调,除以456得到B调。

除以一个越小的值,得到的音调越高。

代码

1.always @(note)  

2.case(note)  

3. 0:

 clkdivider = 512-1; // A  

4. 1:

 clkdivider = 483-1; // A#/Bb  

5. 2:

 clkdivider = 456-1; // B  

6. 3:

 clkdivider = 431-1; // C  

7. 4:

 clkdivider = 406-1; // C#/Db  

8. 5:

 clkdivider = 384-1; // D  

9. 6:

 clkdivider = 362-1; // D#/Eb  

10. 7:

 clkdivider = 342-1; // E  

11. 8:

 clkdivider = 323-1; // F  

12. 9:

 clkdivider = 304-1; // F#/Gb  

13. 10:

 clkdivider = 287-1; // G  

14. 11:

 clkdivider = 271-1; // G#/Ab  

15. 12:

 clkdivider = 0; // 永远不会发生  

16. 13:

 clkdivider = 0; // 永远不会发生  

17. 14:

 clkdivider = 0; // 永远不会发生  

18. 15:

 clkdivider = 0; // 永远不会发生  

19.endcase  

20.  

21.always @(posedge clk) if(counter_note==0) counter_note <= clkdivider; else counter_note <= counter_note-1;  

每次"counter_note"等于0,都意味着将要转到下一个音阶,对应到程序中就是counter_octave除以2。

第四步.

好了。

现在我们来处理一下音阶。

对于最低的音阶,我们将"counter_note"除以256。

对于音阶1,除以128...以此类推...

代码

1.reg [7:

0] counter_octave;  

2.always @(posedge clk)  

3.if(counter_note==0)  

4.begin  

5.if(counter_octave==0)  

6. counter_octave <= (octave==0?

255:

octave==1?

127:

octave==2?

63:

octave==331:

octave==4?

15:

7);  

7.else  

8. counter_octave <= counter_octave-1;  

9.end  

10.  

11.reg speaker;  

12.always @(posedge clk) if(counter_note==0 && counter_octave==0) speaker <= ~speaker;  

完整的代码如下所示:

代码

1.module music(clk, speaker);  

2.input clk;  

3.output speaker;  

4.  

5.reg [27:

0] tone;  

6.always @(posedge clk) tone <= tone+1;  

7.  

8.wire [5:

0] fullnote = tone[27:

22];  

9.  

10.wire [2:

0] octave;  

11.wire [3:

0] note;  

12.divide_by12 divby12(.numer(fullnote[5:

0]), .quotient(octave), .remain(note));  

13.  

14.reg [8:

0] clkdivider;  

15.always @(note)  

16.case(note)  

17.0:

 clkdivider = 512-1; // A  

18.1:

 clkdivider = 483-1; // A#/Bb  

19.2:

 clkdivider = 456-1; // B  

20.3:

 clkdivider = 431-1; // C  

21.4:

 clkdivider = 406-1; // C#/Db  

22.5:

 clkdivider = 384-1; // D  

23.6:

 clkdivider = 362-1; // D#/Eb  

24.7:

 clkdivider = 342-1; // E  

25.8:

 clkdivider = 323-1; // F  

26.9:

 clkdivider = 304-1; // F#/Gb  

27.10:

 clkdivider = 287-1; // G  

28.11:

 clkdivider = 271-1; // G#/Ab  

29.12:

 clkdivider = 0; // 永远不会发生  

30.13:

 clkdivider = 0; // 永远不会发生  

31.14:

 clkdivider = 0; // 永远不会发生  

32.15:

 clkdivider = 0; // 永远不会发生  

33.endcase  

34.  

35.reg [8:

0] counter_note;  

36.always @(posedge clk) if(counter_note==0) counter_note <= clkdivider; else counter_note <= counter_note-1;  

37.  

38.reg [7:

0] counter_octave;  

39.always @(posedge clk)  

40.if(counter_note==0)  

41.begin  

42.if(counter_octave==0)  

43.counter_octave <= (octave==0255:

octave==1127:

octave==263:

octave==331:

octave==415:

7);  

44.else  

45.counter_octave <= counter_octave-1;  

46.end  

47.  

48.reg speaker;  

49.always @(posedge clk) if(counter_note==0 && counter_octave==0) speaker <= ~speaker;  

50.  

51.endmodule  

除以12:

“除以12”这么模块完成将一个6位的数(number)除以12这个功能。

结果我们将得到一个3位的商(0..5)和一个4位的余数(0..11)。

我们尝试使用厂商提供的除法模块,但是它提供的是一个针对通用除法优化的模块。

而这里,除数是固定不变的。

所以需要设计一个定制的除法模块。

为了完成处理12,我们采用以下技巧,先将数除以4,然后再除以3。

除以4只需要将数据右移2位即可,移出的2位作为余数。

这样我们只剩下6-2=4位数据,只要将他们除以3即可。

除以3的操作是用查找表的方法实现的。

(为什么这么做:

避免使用除法器,可以获得更高的速度,并节省器件资源)

代码

1.module divide_by12(numer, quotient, remain);  

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 农林牧渔 > 林学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1