当前位置:   article > 正文

FPGA之蜂鸣器播放音乐《花海》_蜂鸣器可以放出来好听的歌

蜂鸣器可以放出来好听的歌


前言

蜂鸣器是我们常用的电子元器件,本文使用无源蜂鸣器进行音乐《花海》的播放


一、蜂鸣器

1.蜂鸣器简介:

蜂鸣器按其是否带有信号源分为有源蜂鸣器和无源蜂鸣器,有源蜂鸣器内部装有集成电路,不需要音频驱动电路,只需要接通直流电源就可以直接发出声响,而无源蜂鸣器只有外加音频驱动才能发出响声。
在这里插入图片描述

2.有源蜂鸣器:

内部自带震荡源,只需要加上适当的直流电源即可发生,程序控制比较简单,通常使用于报警、提示等,但因为声音是固定的,所以无法实现音乐播放。

3.无源蜂鸣器

相比于有源蜂鸣器,无源蜂鸣器成本更低,声音频率可控,需要输入PWM方波才能驱动其发声,通过改变PWM波的频率,可以实现不同音调的改变;通过改变PWM波的占空比,可以实现声音大小的改变,所以我们只需要产生不同频率和占空比的PWM方波去驱动无源蜂鸣器就能让无源蜂鸣器发出不同的音调了。
在这里插入图片描述

在这里插入图片描述

二、简谱常识

作为一个资深的只会唱歌,不会看谱子的音乐小白痴,对简谱的理解我是废了九牛二虎之力,问了好几个人,才明白具体原理:

1.音符时值:

4/4拍是指按四分音符为一拍,每小节有四拍,我们按照一拍时间为一秒计算,半拍就是1/2秒,四分之一拍就是1/4秒,如果音符后面加一个圆点,时值延长本身时长的二分之一。
在这里插入图片描述

2.简谱名:

中音就是我们常见的Do,Re,Mi,Fa,Sol,La,Si,在下面加一点表示低音,加两点表示超低音,在上面加一点表示高音,加两点表示超高音,本实验中只编码了低、中、高、超高音(因为我找到了这些音的现成频率计数值,所以就没编码超低音,想要更完整的可以自己去搜索一下)

3.简谱名频率:

每个音调的高低,是因为声音频率的不同,以下是各简谱名对应的频率,在播放某一个简谱名时,只需要转换出相应的频率给蜂鸣器就可以了。
在这里插入图片描述

      三、程序设计

      1.调用ROM IP核储存每个简谱名所播放的时间:

      我们把一秒分为8份,这样一拍就是8✖一个数,这个数是八分之一秒的计数值,这样我们就得到了每一个简谱名对应的拍数对应的计数值:
      time_cycle<=time_music(CLK_FRE1000000/8)**
      我们在ROM里面储存的数据即为每个简谱名对应的拍数。
      ROM选用单口,位宽8位,深度256即可(可根据储存的简谱长度来做适当调整)。这里给出《花海》的一些简谱时间
      在这里插入图片描述
      可以看到,我们储存了112(0~111)个简谱名,对应的我们在调用ROM的时候需要注意address即地址的长度,一定要大于111,否则就可能出现歌没唱完就结束了的情况。

      2.编写频率计数值选用代码及用ROM IP核储存对应选择值:

      频率计数值选用代码:

      module hz(
      	input wire [7:0]hz_sel,
      	
      	output reg [19:0]cycle
      );
      	parameter CLK_FRE = 50 ;
      
      	always @(*)
      	begin
      		case(hz_sel)
      			8'h01 : cycle <= CLK_FRE*1000000/261 ; //low 1 261Hz
      			8'h02 : cycle <= CLK_FRE*1000000/293 ; //low 2 293Hz
      			8'h03 : cycle <= CLK_FRE*1000000/329 ; //low 3 329Hz
      			8'h04 : cycle <= CLK_FRE*1000000/349 ; //low 4 349Hz
      			8'h05 : cycle <= CLK_FRE*1000000/392 ; //low 5 392Hz
      			8'h06 : cycle <= CLK_FRE*1000000/440 ; //low 6 440Hz
      			8'h07 : cycle <= CLK_FRE*1000000/499 ; //low 7 499Hz
      			8'h11 : cycle <= CLK_FRE*1000000/523 ; //middle 1 523Hz
      			8'h12 : cycle <= CLK_FRE*1000000/587 ; //middle 2 587Hz
      			8'h13 : cycle <= CLK_FRE*1000000/659 ; //middle 3 659Hz
      			8'h14 : cycle <= CLK_FRE*1000000/698 ; //middle 4 698Hz
      			8'h15 : cycle <= CLK_FRE*1000000/784 ; //middle 5 784Hz
      			8'h16 : cycle <= CLK_FRE*1000000/880 ; //middle 6 880Hz
      			8'h17 : cycle <= CLK_FRE*1000000/998 ; //middle 7 998Hz
      			8'h21 : cycle <= CLK_FRE*1000000/1046 ; //high 1 1046Hz
      			8'h22 : cycle <= CLK_FRE*1000000/1174 ; //high 2 1174Hz
      			8'h23 : cycle <= CLK_FRE*1000000/1318 ; //high 3 1318Hz
      			8'h24 : cycle <= CLK_FRE*1000000/1396 ; //high 4 1396Hz
      			8'h25 : cycle <= CLK_FRE*1000000/1568 ; //high 5 1568Hz
      			8'h26 : cycle <= CLK_FRE*1000000/1760 ; //high 6 1760Hz
      			8'h27 : cycle <= CLK_FRE*1000000/1976 ; //high 7 1976Hz
      			8'h31 : cycle <= CLK_FRE*1000000/2093 ; //super high 1 2093Hz
      			8'h32 : cycle <= CLK_FRE*1000000/2349 ; //super high 2 2349Hz
      			8'h33 : cycle <= CLK_FRE*1000000/2637 ; //super high 3 2637Hz
      			8'h34 : cycle <= CLK_FRE*1000000/2794 ; //super high 4 2794Hz
      			8'h35 : cycle <= CLK_FRE*1000000/3136 ; //super high 5 3136Hz
      			8'h36 : cycle <= CLK_FRE*1000000/3520 ; //super high 6 3520Hz
      			8'h37 : cycle <= CLK_FRE*1000000/3951 ; //super high 7 3951Hz
      			default:cycle<=20'd0;
      		endcase
      	end
      
      endmodule 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43

      低音对应的选择值为1-7,中音为11-17,高音为21-27,超高音为31-37。我们在使用ROM进行储存时只需要储存简谱名对应的选择值即可。
      ROM选用单口,位宽8位,深度256即可(可根据储存的简谱长度来做适当调整)。这里给出《花海》的一些简谱频率计数值
      在这里插入图片描述

      3.PWM产生:

      如何控制PWM方波的产生呢?
      首先是PWM频率:
      我们需要定义一个频率计数器:cnt_cycle,计数时长为频率计数值,即为我们上面频率选择模块中的cycle,在播放一个频谱名时,计数器记满频率计数值或者本次频谱名播放时间结束,计数器清零。
      然后是PWM占空比:
      我们需要定义一个PWM占空比数值:duty_data,当占空比为50%时,duty_data=cycle/2,通过调节占空比大小,可以调节声音大小,因为我的蜂鸣器声音有点大,所以我把占空比调到了25%。在频率计数器计数值小于duty_data时,输出到蜂鸣器为0,大于时为1。

      //频率计数器
      always @(posedge clk or negedge rst_n)
      if(!rst_n)
      	cnt_cycle<=1'b0;
      else if(cnt_cycle==cycle || cnt==time_cycle)
      	cnt_cycle<=1'b0;
      else
      	cnt_cycle<=cnt_cycle+1'b1;
      	
      //占空比
      assign duty_data=cycle/4;
      
      //蜂鸣器输出PWM
      always @(posedge clk or negedge rst_n)
      if(!rst_n)
      	beep=1'b0;
      else if(cnt_cycle>=duty_data)
      	beep=1'b1;
      else
      	beep=1'b0;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20

      4.ROM存储器地址改变:

      每计数完成一个频谱名的时间,地址加一。

      //频率rom计数器加一
      always @(posedge clk or negedge rst_n)
      if(!rst_n)
      	address<=1'b0;
      else if(address==8'd111 && cnt==time_cycle)
      	address<=1'b0;
      else if(cnt==time_cycle)
      	address<=address+1'b1;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

      四、整体代码:

      RTL代码:

      module beep_music(
      
      	input wire clk,
      	input wire rst_n,
      	
      	output reg beep
      
      );
      	parameter CLK_FRE   = 50 ;
      
      	reg [31:0]cnt;
      
      	wire [19:0]duty_data;
      	
      	reg [7:0]address;
      	wire [7:0]time_music;
      	reg [31:0]time_cycle;
      
      	wire [7:0]hz_sel;
      	
      	wire [19:0]cycle;
      	reg [19:0]cnt_cycle;
      
      	time_music time_music0(
      		.address(address),
      		.clock(clk),
      		.q(time_music)
      	);
      	
      	hz_sel hz_sel0(
      		.address(address),
      		.clock(clk),
      		.q(hz_sel)
      	);
      	
      	hz hz0(
      		.hz_sel(hz_sel),
      		.cycle(cycle)
      	);
      
      
      	
      	//单个音符时间计数值
      	always @(posedge clk or negedge rst_n)
      	if(!rst_n)
      		time_cycle<=32'd0;
      	else
      		time_cycle<=time_music*(CLK_FRE*1000000/8) ;
      	
      	//计时器
      	always @(posedge clk or negedge rst_n)
      	if(!rst_n)
      		cnt<=1'b0;
      	else if(cnt==time_cycle)
      		cnt<=1'b0;
      	else
      		cnt<=cnt+1'b1;
      	
      	//频率rom计数器加一
      	always @(posedge clk or negedge rst_n)
      	if(!rst_n)
      		address<=1'b0;
      	else if(address==8'd111 && cnt==time_cycle)
      		address<=1'b0;
      	else if(cnt==time_cycle)
      		address<=address+1'b1;
      		
      	//频率计数器
      	always @(posedge clk or negedge rst_n)
      	if(!rst_n)
      		cnt_cycle<=1'b0;
      	else if(cnt_cycle==cycle || cnt==time_cycle)
      		cnt_cycle<=1'b0;
      	else
      		cnt_cycle<=cnt_cycle+1'b1;
      		
      	//占空比
      	assign duty_data=cycle/4;
      	
      	//蜂鸣器输出PWM
      	always @(posedge clk or negedge rst_n)
      	if(!rst_n)
      		beep=1'b0;
      	else if(cnt_cycle>=duty_data)
      		beep=1'b1;
      	else
      		beep=1'b0;
      
      endmodule 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89

      仿真测试模块:

      `timescale 1ns/1ns
      `define clk_period 20
      
      module beep_music_tb;
      
      	reg clk;
      	reg rst_n;
      	
      	wire beep;
      
      	beep_music beep_music(
      
      		.clk(clk),
      		.rst_n(rst_n),
      		
      		.beep(beep)
      
      	);
      	initial clk=1'b1;
      	always #(`clk_period/2) clk=~clk;
      	initial begin
      		rst_n=1'b0;
      		#(`clk_period*20+1);
      		rst_n=1'b1;
      		#(`clk_period*100000000);
      		$stop;
      	end
      endmodule 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28

      这个程序的仿真需要改变一些计数值来进行,否则运行时间过长,无法观察到有效波形,因此就不在此贴出仿真波形图了,想进行仿真的可以自行修改计数时长来观察仿真。

      五、遇到的问题:

      这个代码不算长也不难,但是我却调试了很久才正确播放,有几个细节问题导致了错误,希望对你有用:
      1.设置寄存器的长度:cycle这个数值的长度不对是我一开始无法正确播放音乐的原因,因为长度不够,所以频率截止了,导致每个音都是同样的音调。
      2.ROM调用时的地址长度,如果选小了就会一句音乐循环播放了。

      总结

      写完代码就可以上板听音乐了,但蜂鸣器的声音着实是不太好听,可以学着用扬声器(喇叭)来进行播放,音质会好很多。

      声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/756594
      推荐阅读
      相关标签
        

      闽ICP备14008679号