草庐IT

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

南邮学渣 2023-04-22 原文

文章目录


前言

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


一、蜂鸣器

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-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调用时的地址长度,如果选小了就会一句音乐循环播放了。

总结

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

有关FPGA之蜂鸣器播放音乐《花海》的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. ruby - 如何以编程方式将 mp3 转换为 itunes 可播放的 aac/m4a 文件? - 2

    我一直在寻找一种以编程方式或通过命令行将mp3转换为aac的方法,但没有成功。理想情况下,我有一段代码可以从我的Rails应用程序中调用,将mp3转换为aac。我安装了ffmpeg和libfaac,并能够使用以下命令创建aac文件:ffmpeg-itest.mp3-acodeclibfaac-ab163840dest.aac当我将输出文件的名称更改为dest.m4a时,它无法在iTunes中播放。谢谢! 最佳答案 FFmpeg提供AAC编码功能(如果您已编译它们)。如果您使用的是Windows,则可以从here获取完整的二进制文件。

  3. ruby - 如何播放 mp3 文件? - 2

    我如何用ruby​​编写一个脚本,当从命令行执行时播放mp3文件(背景音乐)?我试过了run="mplayer#{"/Users/bhushan/resume/m.mp3"}-aosdl-vox11-framedrop-cache16384-cache-min20/100"system(run)但它也不起作用,以上是播放器特定的。如果用户没有安装mplayer怎么办。有没有更好的办法? 最佳答案 我一般都是这样pid=fork{exec'mpg123','-q',file} 关于ruby

  4. FPGA 之 时钟,时钟域, 以及复位系统的设计 - 2

    FPGA时钟和时钟域时钟树所谓时钟树为FPGA内部资源,分:全局时钟树,区域时钟树,IO时钟树原则上优先使用全局时钟树,在GT接口上使用IO时钟树,一般工具也会对GT时钟加以限制;时钟树使用方式正确的物理连接FPGA会由物理管脚专门用于全局时钟设置,通过查询数据手册可以在PCB设计阶段进行确认,当外部时钟接入此管脚时,工具会自动占有全局时钟树资源,当接入普通信号时不会分配时钟树资源;恰当的代码描述原语的使用,即BUFG的使用,可以将PLL的输出等内部时钟进行全局时钟资源的分配;IO时钟资源需要参考相应接口手册,以ultrascale的GTH为例,其JESD204的时钟方案针对不同的子类会由不同

  5. MicroBlaze在纯FPGA下 Xilinx SDK固化程序到外部SPI FLASH - 2

    外部SPIFLASH:MicronN25Q128A13ESE40G(128Mbit(16MByte))FPGA:XC7A100T CPU:Microblaze第一种情况:Microblaze在简单的应用,比如运行LED,IIC,SPI,UART之类的低俗接口驱动,或做一些简单的辅助型工作时,一般生成的applicationelf文件都不大,在10几KB或者几十,百几KB,此时使用FPGA内部的BRAM资源已经足够。XC7A100T本身就有600几KB的BRAM资源。这种情况下直接将硬件流文件和elf文件合并为download.bit文件,在直接烧录到外部SPIFLAH即可。1.Xilinx--

  6. ruby 声音播放 - 2

    这是2009年,早在2001年左右,ruby中的声音播放就没有好的绑定(bind)。有变化吗?我正在寻找可以控制原始声音或mp3、ogg和flac播放的东西。我的谷歌搜索已经枯竭。编辑:Linux、OSX,如果可能的话还有Windows。 最佳答案 您没有提到平台。Thispage描述了win32-sound库,它似乎至少支持WAV播放。对于一个更加平台中立的方式,Ruby/SDL为广受欢迎的SDL提供绑定(bind)图书馆。 关于ruby声音播放,我们在StackOverflow上找到

  7. FPGA配置之SelectMAP总线 - 2

    1FPGA启动流程图1 7SerialsFPGA配置流程1.1DevicePower-Up1.2ClearConfigurationMemory在上电后的任何时间内,可以对Slave-FPGA配置存储器(BlockRAM)进行复位处理。复位方式是将PROGRAM_B信号拉低(下降沿有效)。1.3SampleModePins当复位完成后,INIT_B恢复高电平,Slave-FPGA对M[2:0]模式引脚进行采样,然后开始在CCLK上升沿接收配置数据。1.4Synchronization在接收配置数据前,Slave-FPGA首先进行总线位宽检测。主机发送的配置文件中,“BusWidthAutoDe

  8. 《安富莱嵌入式周报》第301期:ThreadX老大离开微软推出PX5 RTOS第5代系统,支持回流焊的自焊接PCB板设计,单色屏实现多级灰度播放视频效果 - 2

    往期周报汇总地址:嵌入式周报-uCOS&uCGUI&emWin&embOS&TouchGFX&ThreadX-硬汉嵌入式论坛-PoweredbyDiscuz! 祝大家开工大吉视频版:https://www.bilibili.com/video/BV1GT411o7zr1、ThreadX老大离开微软,开发的第5代RTOS系统PX5RTOS正式上线最早是看到IAR的一条消息,全面支持PX5RTOS,然后就进一步上他们的官方下载白皮书了解相关消息当看到这两个名字时,很熟悉,这不就是ThreadX的老大BillLamie。 经过信息检索,应该是实锤了,领英上已经更新了他的工作经历: 然后再结合Azur

  9. javascript - 在 MediaSource HTML5 中播放 MediaRecorder block ——视频卡住 - 2

    我有这个简单的代码来获取视频流block并在MediaSource中播放它们。我看到视频,但有时它会停止。它可能会工作几秒钟或几分钟。但最后它在某个时刻停止了。chrome://media-internals/显示没有错误。这里有什么问题吗?navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia;varmediaSource=newMediaSource();varconstraints

  10. javascript - 使用 js/jquery 检测 flash 播放器的现代或非弃用方法? - 2

    首先,很抱歉在这里重新提出这个问题。我已经尝试了两天如何使用javascript/jquery完成这项工作,我想我已经阅读了所有堆栈溢出和其他关于此的博客文章,所以请不要将其标记为重复,因为我无法使用2012年到2017年的过时脚本。我有一个页面重定向到第三方电子学习平台,其中一些内容需要flash才能工作。许多用户不关心他们的机器上安装了哪些软件(多么新,呵呵)所以我需要检测它并显示典型消息“请单击此处安装/更新FlashPlayer”,但我找不到“现代"执行此操作的脚本/方式,在任何地方,尽可能简化。我尝试过的所有脚本都已弃用或在所有浏览器中返回false,即使我安装并激活了最新版

随机推荐