一、概述
我们在进行蜂鸣器的学习的时候,总会在想既然蜂鸣器能够发出声音,那么它能够播放音乐吗,今天这篇我们文章我们就一起来学习怎样使用使用蜂鸣器来播放音乐,也就是怎样成为一个音乐播放器。
1、蜂鸣器的类型
在设计的时候其实蜂鸣器的类型也对我们最后所实现的效果有一点影响,蜂鸣器分为有源和无源两种,有源蜂鸣器的器件内部本身就带有振荡器,本身来讲完全不用我们进行外部振荡,二无源蜂鸣器是不带振荡器的,所以当涉及到相关频率使用时就需要我们使用外部振荡。这里因为我们使用PWM进行设计实现,所以如果有条件的话使用无源蜂鸣器所实现的效果会更好。
2、PWM介绍
PWM(Pulse Width Modulation),即脉冲宽度调制,是一种模拟信号电平数字编码方法。它通过将有效的电信号分散成离散形式,从而来降低电信号所传递的平均功率。PWM技术的核心在于通过调节脉冲的时间宽度(占空比)来等效地获得所需要合成的相应幅值和频率的波形。通过调整PWM信号的占空比和频率,可以实现声音的音量调节和音调变化。
- 占空比:占空比是脉冲处于较高电压的时间占整个脉冲周期的百分比。通过改变占空比,可以实现对信号、能量等的调节。例如,在LED电路中,高占空比意味着LED非常明亮,而低占空比则意味着LED较为暗淡。
- 频率:频率是单位时间内脉冲信号的次数。PWM信号的频率越高,控制效果越接近模拟信号,但也会受到电路响应时长的限制。
3、PWM调制的实现方式
硬件实现 :许多微控制器和数字信号处理器(DSP)已包括了PWM控制器芯片,因此可以更轻松地实施数字化控制。这些芯片通过内部定时器或计数器来生成PWM信号,并通过调整相关参数来改变占空比和频率。
软件实现:在没有内置PWM控制器的系统中,也可以通过软件编程来模拟PWM信号。这通常涉及到对定时器中断的精确控制,以及在高电平和低电平之间快速切换IO口的输出状态。
4、音符的相关设计
这是关于高中低音符的频率和周期表,我们将会参考这个对代码进行设计。
二、工程实现
1、本次播放的音乐
本次我们设计得比较简单,设计了一个《我有一头小毛驴》的简单音乐播放器,具体乐谱如下:
2、基本构建思路
在本次的设计中,采用了一个音符播放播放0.5秒的设计,针对上述乐谱,采用四个字符为一组的方式进行划分,便于代码的赋值。利用三个计数器完成对于音乐播放的相关设计,第一个播放器用于技术每个音符技术的相关频率,第二个计数器用于对于在0.5秒播放时间内对于每个音符重复的次数进行计数,而第三个计数器用于对音符的播放顺序进行一个计数,由此关于音符的设置就设计完成,最后就是对于蜂鸣器进行占空比调制,对每个音符的频率进行一个8分频就完成了最终功能。
3、设计文件的编写
这里新建一个beep.v文件,如下:
cpp
//蜂鸣器
module beep(
input clk,
input rst_n,
output reg beep_out
);
parameter CLK_CLY=50_000_000;//时钟频率
localparam H1 =CLK_CLY/523,
H1_L=CLK_CLY/262,
H1_H=CLK_CLY/1047,
H2 =CLK_CLY/587,
H3 =CLK_CLY/659,
H4 =CLK_CLY/698,
H5 =CLK_CLY/784,
H6 =CLK_CLY/880,
H7 =CLK_CLY/988;
parameter TIME_500MS=25_000_000;//0.5秒
reg [17:0] cnt0;// 每个音符频率计数器
wire add_cnt0;
wire end_cnt0;
reg [9:0] cnt1;// 0.5秒内音符周期重复个数
wire add_cnt1;
wire end_cnt1;
reg [5:0] cnt2;// 计数音符播放顺序
wire add_cnt2;
wire end_cnt2;
reg [15:0] cnt_max;
reg [17:0] display;//音符计数器的最大值
//cnt0计数
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt0<=0;
else if(add_cnt0)begin
if(end_cnt0)
cnt0<=0;
else
cnt0<=cnt0+1'b1;
end
end
assign add_cnt0=1'b1;
assign end_cnt0=add_cnt0 &&(cnt0==display-1);
//cnt1计数
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt1<=0;
else if(add_cnt1)begin
if(end_cnt1)
cnt1<=0;
else
cnt1<=cnt1+1'b1;
end
end
assign add_cnt1=end_cnt0;
assign end_cnt1=add_cnt1 &&(cnt1==(TIME_500MS/display-1));
//cnt2计数
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt2<=0;
else if(add_cnt2)begin
if(end_cnt2)
cnt2<=0;
else
cnt2<=cnt2+1'b1;
end
end
assign add_cnt2=end_cnt1;
assign end_cnt2=add_cnt2 &&(cnt2==50-1);
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
display <=H1;
else begin
case (cnt2)
0:display <=H1_L;
1:display <=H1;
2:display <=H1;
3:display <=H3;
4:display <=H5;
5:display <=H5;
6:display <=H5;
7:display <=H5;
8:display <=H6;
9:display <=H6;
10:display <=H6;
11:display <=H1_H;
12:display <=H5;
13:display <=H5;
14:display <=H5;
15:display <=H5;
16:display <=H4;
17:display <=H4;
18:display <=H4;
19:display <=H6;
20:display <=H3;
21:display <=H3;
22:display <=H3;
23:display <=H3;
24:display <=H2;
25:display <=H2;
26:display <=H2;
27:display <=H2;
28:display <=H5;
29:display <=H5;
30:display <=H5;
31:display <=H5;
32:display <=H1;
33:display <=H1_L;
34:display <=H1;
35:display <=H3;
36:display <=H5;
37:display <=H5;
38:display <=H5;
39:display <=H5;
40:display <=H6;
41:display <=H6;
42:display <=H6;
43:display <=H1_H;
44:display <=H5;
45:display <=H5;
46:display <=H5;
47:display <=H5;
40:display <=H4;
41:display <=H4;
42:display <=H4;
43:display <=H6;
44:display <=H3;
45:display <=H3;
46:display <=H3;
47:display <=H3;
40:display <=H3;
41:display <=H3;
42:display <=H2;
43:display <=H2;
44:display <=H2;
45:display <=H3;
46:display <=H1;
47:display <=H1;
48:display <=H1;
49:display <=H1;
default: display <=H1;
endcase
end
end
//pwm输出
//蜂鸣器pwm
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
beep_out <= 1'b1;
end
else if(cnt0 == (display>>3))begin//设置占空比
beep_out <= 1'b1;
end
else if(cnt0==0)begin
beep_out<= 1'b0;
end
end
endmodule
4、测试文件的编写
新建beep_tb.v文件,这里出来蜂鸣器输出没有任何激励,所以我们只需要进行时钟和复位进行赋值就行。如下:
cpp
//定义时间尺度
`timescale 1ns/1ns
module beep_tb ;
//输入信号定义
reg clk ;
reg rst_n ;
wire beep_out ;
//模块例化
beep beep_inst(
/*input */ .clk (clk ) ,
/*input */ .rst_n (rst_n ) ,
/*output */ .beep_out (beep_out )
);
//激励信号产生
parameter CLK_CYC = 20;
//时钟
initial clk=1;
always #(CLK_CYC/2)clk=~clk;
//复位
initial begin
rst_n= 1'b0;
#(CLK_CYC*2);
#3;//复位结束避开时钟上升沿
rst_n= 1'b1;
end
endmodule
5、仿真波形图
通过对于波形图的观察,我们可以看到在三个计数器的不断计数之下蜂鸣器的输出波形占空比在不断进行变化 ,并且在经过对于计数器进行检查后没有发现什么问题。(这里方针波形图太长,没有截全),感兴趣的可以去自行进行仿真观察。最后进行下板验证之后,蜂鸣器也进行了正常播放,我们的设计成功。