小梅哥Xilinx FPGA学习笔记20——无源蜂鸣器驱动设计与验证(音乐发生器设计)
2024-01-07 21:05:53
目录
一:章节导读
?????? 蜂鸣器是一种产生声音的器件。其广泛应用于报警器、电子玩具、汽车电子设备、定时器等电子产品中作为发声器件。本章将介绍 ACZ702 开发板上使用的蜂鸣器电路,并使用 FPGA 来实现驱动
无源蜂鸣器按照“哆来咪发梭拉西”7个音调发声。并在此基础上实现
“天空之城”的音乐发生器。
???? 蜂鸣器按照构造方式的不同,可分为压电式蜂鸣器和电磁式蜂鸣器两种类型,由于两种蜂鸣器发音原理不同,压电式结构简单耐用但音调单一音色差,适用于报警器等设备。而电磁式由于音色好,所以多用于语音、音乐等设备。?
?????? 蜂鸣器按照驱动电路的不同可以分为有源蜂鸣器与无源蜂鸣器。有源蜂鸣器内部带震荡源,所以只要通电就会鸣叫;而无源蜂鸣器内部不带震荡源,因此如果用直流信号无法令其鸣叫,这就需要用 2K-5K 的方波(声音频率)去驱动。ACZ702 开发板上使用了一枚 3.3V 直流电压驱动的电磁式无源蜂鸣器。其原理图如下图所示:
BEEP 端口接FPGA 输出管脚,使用时只需要在 BEEP 信号上输入 2~5KHz 的 PWM 波,就能
驱动蜂鸣器按照既定的频率产生振动信号。
二:无源蜂鸣器驱动原理
?????? 通过前面对无源蜂鸣器的特点介绍可知,要使无源蜂鸣器能够正常发声,需要在控制端 BEEP
给出相应频率的
PWM
波。因此,对于无源蜂鸣器的控制,
就转化为了设计一个
PWM
波发生电路。因此,接下来将介绍
PWM
波的发生部
分相关设计。
?????? PWM 波即脉冲宽度调制,以下为周期为
1KHz
,脉冲宽度(占空比)为
20%的示意图
?????? 由上图可知,当信号周期一定,信号高电平时间所占信号周期的百分比不一样,即为不同占空比的
PWM 波。而除了调整
PWM
信号的占空比,
PWM 信号的周期也是可以调整的,
对于不同的器件,对驱动信号的频率要求也不一样,还需要能够对
PWM
波的频率进行调整。
?????? 通过以上分析,可以看出,要设计一个
PWM
发生电路,需要能够实现对信号的频率和占空比的调节。
在
单片机或者 DSP
中,产生
PWM
波的方法就是
使用片上定时器进行循环计数,通过设定定时器的一个定时周期时长来确定对
应输出
PWM
信号的频率,同时还有一个比较器,该比较器比较定时器的实时
计数值与用户设定的比较值的大小,根据比较结果来控制输出信号的电平高低。
通过设定不同的比较值,即可实现不同占空比的
PWM 信号输出。使用定时器产生
PWM
原理图如下:
三:PWM 发生器模块设计
对于
FPGA
来说,要产生
PWM
波,也可以借鉴单片机或
DSP
使用定时器产生
PWM
波的思路。依据
PWM 模块发生器原理可知需设计两个主要电路:定
时器
/
计数器电路以及输出比较电路。
3.1 PWM 发生器模块框图
3.2 PWM 发生器模块接口功能描述
最终输出
PWM
波的频率计算公式为:
??????????????????? ? ? ? ? ?? ? ?? ??
其中,这里的
counter_arr
是自减计数器的预重装值,计数器是从
counter_arr开始递减到
1
。因此,当输出频率确定时,可计算得到预重装值,计算公式为:
? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? ? ? ??
例如,当希望设置输出信号频率为
5KHz
时
因此,只需要设置
counter_arr
值为
10000
即可使得最终输出信号频率为5KHz
。
?????? 当输出
PWM
频率确定后,其输出占空比计算则为输出比较值与预重装值
之商。计算公式为:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ??
因此,当输出占空比确定时,可计算得到输出比较值,计算公式为:
? ? ? ? ? ? ? ? ?? ?
?????? 在运行过程中,修改预重装值可以设置输出 PWM
信号的频率,并将同时影响输出占空比,而在预重装值确定的情况下,修改输出比较值,则可以设置
输出占空比。
3.3 PWM波生成设计文件代码
module pwm_gen(
input clk,
input reset_n,
input pwm_gen_en,//使能信号
input [31:0]counter_arr,//预重装值,用来设定频率
input [31:0]counter_crr,//比较值,用来调节占空比
output reg pwm_out//输出pwm波
);
reg [31:0]pwm_gen_cnt;
always@(posedge clk or negedge reset_n)
if(!reset_n)
pwm_gen_cnt <= counter_arr;
else if(pwm_gen_en)begin
if(pwm_gen_cnt <= 1)
pwm_gen_cnt <= counter_arr; //计数减到 1,加载预重装寄存器值
else
pwm_gen_cnt <= pwm_gen_cnt - 1; //计数器自减 1
end
else //未使能时,计数器值等于预重装寄存器值
pwm_gen_cnt <= counter_arr;
always@(posedge clk or negedge reset_n)
if(!reset_n)
pwm_out <= 0;//复位时,PWM 输出低电平
else if(pwm_gen_cnt <= counter_crr) //计数值小于比较值,PWM 输出高电平
pwm_out <= 1;
else
pwm_out <= 0; //计数值大于比较值,PWM 输出低电平
endmodule
3.4 测试仿真文件
`timescale 1ns / 1ps
module pwm_gen_tb();
reg clk;
reg reset_n;
reg pwm_gen_en;
reg [31:0]counter_arr;
reg [31:0]counter_crr;
wire pwm_out;
pwm_gen pwm_gen_inst(
.clk (clk) ,
.reset_n (reset_n) ,
.pwm_gen_en (pwm_gen_en) ,//使能信号
.counter_arr (counter_arr) ,//预重装值,用来设定频率
.counter_crr (counter_crr) ,//比较值,用来调节占空比
.pwm_out (pwm_out) //输出pwm波
);
initial clk =1;
always #10 clk = ~clk;
initial begin
reset_n = 0;
pwm_gen_en = 0;
counter_arr =0;
counter_crr = 0;
#201;
reset_n = 1;
#201;
counter_arr = 1000; //设置输出信号频率为 50KHz
counter_crr = 400; //设置输出 PWM 波占空比为 40%
#100;
pwm_gen_en = 1; //启动计数以产生 PWM 输出
#100050;
counter_crr = 700; //设置输出 PWM 波占空比为 70%
#100050;
pwm_gen_en = 0; //停止计数以关闭 PWM 输出
counter_arr = 500; //设置输出信号频率为 100KHz
counter_crr = 250; //设置输出 PWM 波占空比为 50%
#100;
pwm_gen_en = 1; //启动计数以产生 PWM 输出
#50050;
counter_crr = 100; //设置输出 PWM 波占空比为 20%
#50050;
pwm_gen_en = 0; //停止计数以关闭 PWM 输出
#200;
$stop;
end
endmodule
3.5 测试仿真结果
放大局部波形可知满足测试仿真文件的要求。
3.6 板级调试与验证之顶层文件设计
module pwm_gen_test(
input clk,
input reset_n,
output beep
);
reg [27:0]delay_500ms; //500ms延时计数器
reg [2:0] bit_cnt;//位计数器
reg [31:0]counter_arr; //预重装值寄存器
wire [31:0]counter_ccr; //输出比较值
parameter MCNT_500ms = 2500_0000;
//7个音调的预重装值,也叫频率周期
localparam D1 = 170068, //D 调音 1
D2 = 151515, //D 调音 2
D3 = 142857, //D 调音 3
D4 = 127227, //D 调音 4
D5 = 113379, //D 调音 5
D6 = 101010, //D 调音 6
D7 = 89928 ; //D 调音 7
//输出比较值为预重装值一半
assign counter_ccr = counter_arr >> 1;//右移一位,数值变为一半
pwm_gen pwm_gen_inst(
.clk (clk) ,
.reset_n (reset_n) ,
.pwm_gen_en (1) ,//使能信号
.counter_arr (counter_arr) ,//预重装值,用来设定频率
.counter_crr (counter_ccr) ,//比较值,用来调节占空比
.pwm_out (beep) //输出pwm波
);
//500ms持续计数逻辑
always@(posedge clk or negedge reset_n)
if(!reset_n)
delay_500ms <= 0;
else if(delay_500ms == MCNT_500ms - 1)
delay_500ms <= 0;
else
delay_500ms <= delay_500ms + 1;
//每 500ms 切换一次音调
always@(posedge clk or negedge reset_n)
if(!reset_n)
bit_cnt <= 0;
else if(delay_500ms == MCNT_500ms - 1)
bit_cnt <= bit_cnt + 1;
else
bit_cnt <= bit_cnt;
//根据音调编号给预重装值给相应的值
always@(posedge clk or negedge reset_n)
if(!reset_n)
counter_arr <= 1;
else begin
case(bit_cnt)
0:counter_arr <= 1 ;
1:counter_arr <= D1;
2:counter_arr <= D2;
3:counter_arr <= D3;
4:counter_arr <= D4;
5:counter_arr <= D5;
6:counter_arr <= D6;
7:counter_arr <= D7;
endcase
end
endmodule
3.7 管脚约束文件列表
set_property IOSTANDARD LVCMOS33 [get_ports beep]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports reset_n]
set_property PACKAGE_PIN G15 [get_ports beep]
set_property PACKAGE_PIN U18 [get_ports clk]
set_property PACKAGE_PIN F20 [get_ports reset_n]
???? 以上代码即可实现蜂鸣器即开始循环播放“哆来咪发梭拉西”7 个音。
四:基于 PWM 波的音乐发生器设计
前面已经实现并上板验证了使用
PWM
产生模块实现对
“
哆来咪发梭拉
西
”音调的产生。
现在将在此基础上结合模块化设计思想,利用蜂鸣器实现播放
一段音乐的功能。
这里选取乐谱相对简单点的一首乐谱“天空之城”,在ACZ702
开发板上实现音乐播放。
4.1 “天空之城”乐谱
?? ? ? ? ? ? ? ? ? ? ???
从简谱看,该音乐是
D
调的,这里的各音符对应的频率对应的是上表中
D调的部分。另外,该音乐为四分之四拍,每个对应为
1
拍。(音符节奏分为一拍、
半拍、
1/4
拍、
1/8
拍,)几个特殊音符说明如下(我们规定一拍音符的时间为
1
;
半拍为
0.5
;
1/4
拍为
0.25
;
1/8
拍为
0.125……
)。
(
1
) 普通音符。如音符
7
,对应频率
556
,占
1
拍。
(
2
) 带下划线音符,表示
0.5
拍;两个下划线是四分之一拍(
0.25
)。
(
3
) 有的音符后带一个点,表示多加
0.5
拍,即
1+0.5
。
(
4
) 有的音符后带一个“—”,表示多加
1
拍,即
1+1
。
(
5
)
有的两个连续的音符上面带弧线,表示连音,可以稍微改下连音后面那个音的频率,比如减少或增加一些数值(需自己调试),这样表现会更
流畅,其实不做处理,影响也不大。
这里我们采用
ROM
来存储乐谱,然后通过依次读取
ROM
的数据,然后产生对应的音调。存储式采用
5bit
数据对音调进行编码,高
2bit
数据数值
3
,
2
,
1
作为高中低音编码,低
3bit
数据数值
1~7
作为
“
哆来咪发梭拉西
”
的编码。例如
存储的数据
5’b01_011
表示的是低音“
3
”。通过这种形式将乐谱进行编码存储在
ROM
中。
在节拍的设计上,如果以
250ms
时间为一拍的时间,半拍的时间是
125ms
,通过上面的乐谱来看,没有低于半拍的,那设计时就以
125ms
为基本单位时间,
则
1
拍就是
2
个单位时间,半拍是一个单位时间。
get_pitch
模块一方面产生这
125ms
的单位时间,另一方面在一个个单位时间的控制下产生获取
rom
里音符
的编码地址。
4.2 get_pitch 模块的代码
module get_pitch(
input clk,
input reset_n,
output reg [8:0]pitch_num //获取音符 rom 的地址编号
);
reg [23:0]cnt_125ms; //125ms 延时计数器,每个节拍的时间
parameter MCNT_125ms_MAX = 6250000;
//125ms持续计数逻辑
always@(posedge clk or negedge reset_n)
if(!reset_n)
cnt_125ms <= 0;
else if(cnt_125ms == MCNT_125ms_MAX - 1)
cnt_125ms <= 0;
else
cnt_125ms <= cnt_125ms + 1;
//每125ms切换一次音调的位控制逻辑 (每125ms获取一次音符 rom 的地址编号(递增))
always@(posedge clk or negedge reset_n)
if(!reset_n)
pitch_num <= 0;
else if(cnt_125ms == MCNT_125ms_MAX - 1)
pitch_num <= pitch_num + 1;
else
pitch_num <= pitch_num ;
endmodule
4.3 rom配置
存放数据是以“天空之城”的乐谱实际不超过
256
个音符,也就是说初始化文件的数据不超过
512
个数据(数据以半拍为单位进行存储,一个节拍用两
个数据存储),这里
ROM
的深度设置为
512
,没有初始化的
ROM
数据选择使用
0
来进行填充。这样正好让两次音乐播放之间还能留有一定的时间间隔。
4.4 coe文件
文件在下面的百度网盘里
链接:https://pan.baidu.com/s/1w3spnKZWKi7u2du7ML6Prw
提取码:qzof
提取码:qzof
4.5 顶层文件设计
module music_gen(
input clk,
input reset_n,
output beep
);
reg [31:0]counter_arr ;//预重装值寄存器
wire [31:0]counter_crr; //输出比较值
wire [8:0]pitch_num; //音乐的音调编号,0-->最大值 循环递增
wire [4:0]pitch; //音乐的音调
localparam DL1 = 340136, //D 调低音 1
DL2 = 303030, //D 调低音 2
DL3 = 285714, //D 调低音 3
DL4 = 255102, //D 调低音 4
DL5 = 226244, //D 调低音 5
DL6 = 201613, //D 调低音 6
DL7 = 179856, //D 调低音 7
D1 = 170068, //D 调音 1
D2 = 151515, //D 调音 2
D3 = 142857, //D 调音 3
D4 = 127227, //D 调音 4
D5 = 113379, //D 调音 5
D6 = 101010, //D 调音 6
D7 = 89928 , //D 调音 7
DH1 = 84889, //D 调高音 1
DH2 = 75643, //D 调高音 2
DH3 = 71429, //D 调高音 3
DH4 = 63613, //D 调高音 4
DH5 = 56689, //D 调高音 5
DH6 = 50505, //D 调高音 6
DH7 = 44964; //D 调高音 7
//根据 rom 存储输出不同的音调输出不同的预置数
always@(posedge clk or negedge reset_n)
if(!reset_n)
counter_arr <= 1;
else begin
case(pitch)
5'b01_001:counter_arr = DL1; //D 调低音 1
5'b01_010:counter_arr = DL2; //D 调低音 2
5'b01_011:counter_arr = DL3; //D 调低音 3
5'b01_100:counter_arr = DL4; //D 调低音 4
5'b01_101:counter_arr = DL5; //D 调低音 5
5'b01_110:counter_arr = DL6; //D 调低音 6
5'b01_111:counter_arr = DL7; //D 调低音 7
5'b10_001:counter_arr = D1; //D 调音 1
5'b10_010:counter_arr = D2; //D 调音 2
5'b10_011:counter_arr = D3; //D 调音 3
5'b10_100:counter_arr = D4; //D 调音 4
5'b10_101:counter_arr = D5; //D 调音 5
5'b10_110:counter_arr = D6; //D 调音 6
5'b10_111:counter_arr = D7; //D 调音 7
5'b11_001:counter_arr = DH1; //D 调高音 1
5'b11_010:counter_arr = DH2; //D 调高音 2
5'b11_011:counter_arr = DH3; //D 调高音 3
5'b11_100:counter_arr = DH4; //D 调高音 4
5'b11_101:counter_arr = DH5; //D 调高音 5
5'b11_110:counter_arr = DH6; //D 调高音 6
5'b11_111:counter_arr = DH7; //D 调高音 7
default: counter_arr = 32'd1;//休止符
endcase
end
//rom例化
blk_mem_gen_0 your_instance_name (
.clka(clk), // input wire clka
.addra(pitch_num), // input wire [8 : 0] addra
.douta(pitch) // output wire [4 : 0] douta
);
get_pitch get_pitch(
.clk(clk),
.reset_n(reset_n),
.pitch_num(pitch_num)
);
pwm_gen pwm_gen(
.clk (clk),
.reset_n (reset_n),
.pwm_gen_en(1),//使能信号
.counter_arr(counter_arr),//预重装值,用来设定频率
.counter_crr(counter_crr),//比较值,用来调节占空比
.pwm_out(beep)//输出pwm波
);
//设置输出比较值为预重装值一半
assign counter_crr = counter_arr >> 1;
endmodule
4.6 仿真验证代码
`timescale 1ns / 1ps
module music_gen_tb();
reg clk;//系统时钟输入,50M
reg reset_n; //复位信号输入,低有效
wire beep;//pwm 输出信号
music_gen music_gen(
.clk(clk),
.reset_n(reset_n),
.beep(beep)
);
defparam music_gen.get_pitch.MCNT_125ms_MAX = 24'd6250;//缩短仿真时间
initial clk = 1;
always #10 clk = ~clk;
initial
begin
reset_n = 0;
#201;
reset_n = 1;
wait(music_gen.pitch_num == 512);//一轮播放结束
#200;
$stop;
end
endmodule
4.7 仿真结果
从波形上可以看出,循环音乐循环播放之间有一定的时间间隔,这个与设计预期是一致的。可以放大波形后看,其波形也是与预期设计是一致的。
4.8 板上验证?
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports reset_n]
set_property IOSTANDARD LVCMOS33 [get_ports beep]
set_property PACKAGE_PIN G15 [get_ports beep]
set_property PACKAGE_PIN F20 [get_ports reset_n]
set_property PACKAGE_PIN U18 [get_ports clk]
至此“天空之城”音乐发生器设计完毕。
文章来源:https://blog.csdn.net/m0_51430584/article/details/135383365
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!