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

2023-05-16

文章目录

  • 前言
  • 一、蜂鸣器
    • 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核储存每个简谱名所播放的时间:

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

总结

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 计算机保研面试题-数据结构

    快速排序算法 xff0c 归并排序算法的复杂度 xff08 简单介绍各种排序 xff09 算法的特点 xff08 1 xff09 插入排序 xff1a a 直接插入排序 xff1a 比如将X插入有序序列L当中 xff0c 首先找到X在序列L

随机推荐

  • 计算机保研面试题——操作系统

    目录 1 操作系统的特点 xff1f 功能 xff1f 2 中断和系统调用的区别 3 进程 线程的概念以及区别 xff1f 进程间的通信方式 xff1f 4 进程有哪几种状态 xff0c 状态之间的转换 进程调度策略 xff1f 5 读写者
  • 计算机保研面试题——计算机网络

    目录 计算机网络体系结构 OSI xff0c TCP IP xff0c 五层协议的体系结构 xff0c 以及各层协议 IP地址的分类 32位地址 各种协议 xff1f TCP三次握手和四次挥手的全过程 六 TCP和UDP的区别 xff1f
  • 论文学习:Austere Flash Caching with Deduplication and Compression

    论文题目 xff1a Austere Flash Caching with Deduplication and Compression 来源 xff1a USENIX ATC 2020 链接 xff1a Austere Flash Cach
  • ubuntu 查看占用文件空间大小

    1 查看分区情况 fdisk l 2 查看系统的磁盘空间占用情况 df h df TH 3 查看某个目录的使用空间大小 du sh 需要先进入该目录 或者后面加上路径 du sh 路径 4 查看该目录下 每个文件夹占用的空间大小 查看某目录
  • 操作系统地址生成

    逻辑地址生成 从符号逻辑地址 gt 内存中具体的逻辑地址 不需要操作系统的帮助 xff0c 而是通过编译器 load等等完成 对于程序代码 cfile开始 xff0c 最开始的逻辑地址是对应的函数位置 变量名称 xff0c 通过编译成为 s
  • 主流数据库以及适用场景思维导图

  • 超声波测距模块(HC-SR04模块)特点及使用介绍

    超声波测距模块 xff08 HC SR04模块 xff09 特点及使用介绍 前言一 超声波测距模块 xff08 HC SR04模块 xff09 外观二 原理图三 相关参数讲解1 参数2 测量范围3 计算公式4 优点5 产品特性 xff08
  • ESP8266AT指令测试无返回值的问题

    ESP8266AT指令测试无返回值 xff1a 如图 xff0c 在使用esp8266测试at指令的时候无返回值 xff0c 然而我在检查硬件的时候没有任何错误 xff0c usb转ttl模块也是没有问题的 解决方法 xff1a 这时候只要
  • 重要:智能指针的使用(C++ 11 以上)

    智能指针的声明与初始化 xff1a 第一种 xff1a std shared ptr 指针 初始化方式1 xff08 推荐 xff09 std shared ptr lt int gt sp3 sp3 61 std make shared
  • 自顶向下和自底向上的实现方法

    1将一个大问题分解为小的易处理的子问题 xff0c 每个子问题可以使用一个方法来实现 xff0c 这种方法使得问题更加易于编写重用调试 xff0c 修改和维护 2当一个大问题分解为许多子问题 xff0c 各个子问题可以分配给不同的编程人员
  • c# --- 接口

    接口从某种程度上来说也是一个类 xff0c 但是接口中只包含方法的声明而没有方法的实现z 创建接口的关键字 xff1a interface 接口 xff0c 人机交互界面 创建接口的语法 xff1a 接口的访问权限 interface 接口
  • MAML++:HOW TO TRAIN YOUR MAML论文精读

    论文地址 https arxiv org abs 1810 09502 Abstract MAML是目前通过元学习进行少样本学习的最佳方法之一 MAML简单 xff0c 优雅和非常强大 xff0c 然而 xff0c 它有各种各样的问题 xf
  • webpack中的loader

    什么是loader loader是webpack中一个非常核心的概念 webpack用来做什么呢 xff1f 在我们之前的实例中 xff0c 我们主要是用webpack来处理我们写的js代码 xff0c 并且webpack会自动处理js之间
  • strtok()函数

    strtok 将字符串拆分成tokens xff0c tokens是被分隔符中的任何字符分隔的连续字符序列 char strtok char str const char sep sep参数是个字符串 xff0c 定义了用作分隔符的字符集合
  • MySQL夺命连环15问,你能坚持到第几问?

    文章目录 前言一 关系型和非关系型的区别 xff0c 以及使用场景二 Mysql索引优缺点三 给字段加索引最好怎么加 xff1f 四 什么情况下会导致索引失效 xff1f 五 为什么使用模糊匹配会使索引失效六 回表查询和索引覆盖是什么七 联
  • educoder数字逻辑实训:比较器设计(Logisim)

    第1关 xff1a 1位比较器设计 任务描述 本关任务 xff1a 在Logisim中完成1位比较器电路的绘制并完成测试 第2关 xff1a 2位比较器设计 任务描述 本关任务 xff1a 在Logisim中完成2位比较器电路的绘制并完成测
  • 1、树莓派的VNC文件传输

    目录 第一步 xff1a 在树莓派端打开VNC服务器 第二步 xff1a 输入指令运行vncserver 第三步 xff1a 在电脑端安装VNC查看器 第一步 xff1a 在树莓派端打开VNC服务器 第二步 xff1a 输入指令运行vncs
  • Hadoop 3.x(HDFS)----【HDFS 的读写流程】

    Hadoop 3 x xff08 HDFS xff09 HDFS 的读写流程 1 HDFS写数据流程1 剖析文件写入2 网络拓扑 结点距离计算3 机架感知 xff08 副本存储节点选择 xff09 1 机架感知说明2 Hadoop3 1 3
  • 【C++】程序运行时间计算的方式

    1 利用C 43 43 标准库 std chrono xff0c 如下计算一个主体程序的运行时间 xff0c 算出来的时间差值单位为毫秒 lt float std milli gt 表示用毫秒表示 auto t start 61 std c
  • FPGA之蜂鸣器播放音乐《花海》

    文章目录 前言一 蜂鸣器1 蜂鸣器简介 xff1a 2 有源蜂鸣器 xff1a 3 无源蜂鸣器 xff1a 二 简谱常识1 音符时值 xff1a 2 简谱名 xff1a 3 简谱名频率 xff1a 三 程序设计1 调用ROM IP核储存每个