资讯详情

FPGA之蜂鸣器播放音乐《花海》

文章目录

  • 前言
  • 一、蜂鸣器
    • 1.蜂鸣器简介:
    • 2.有源蜂鸣器:
    • 3.无源蜂鸣器:
  • 二、简谱常识
    • 1.音符时值:
    • 2.简谱名:
    • 3.简谱名频率:
  • 三、程序设计
    • 1.调用ROM IP每个简谱名的播放时间:
    • 2.编写频率计数值选择代码ROM IP核存储对应的选择值:
    • 3.PWM产生:
    • 4.ROM更改存储器地址:
  • 四、整体代码:
  • 五、遇到的问题:
  • 总结


前言

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


一、蜂鸣器

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每个简谱名的播放时间:

我们把一秒分成八部分,这样一拍就是个数,这是八分之一秒的计数值,这样我们就可以得到每个简谱名对应的计数值: 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 span class="token operator">: 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-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;

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;

四、整体代码:

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 

仿真测试模块:

`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.设置寄存器的长度:cycle这个数值的长度不对是我一开始无法正确播放音乐的原因,因为长度不够,所以频率截止了,导致每个音都是同样的音调。 2.ROM调用时的地址长度,如果选小了就会一句音乐循环播放了。

总结

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

标签: 1720fa集成电路

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台