一、前言
本实验使用的无源蜂鸣器只有外加音频驱动信号才能发出声响。只要控制输入的 PWM 方波,输入不同的 PWM 方波发出的声音就不一样了。而不同频率和占空比的方波发出的声音是不同的,其中频率对音调有影响,占空比对音量大小有影响。只需产生不同频率和占空比的 PWM 方波去驱动无源蜂鸣器就能让无源蜂鸣器发出不同的音调了。本文通过驱动无源蜂鸣器进行七个基本音调“哆来咪发梭拉西”的循环鸣叫,每个音阶持续鸣叫 0.5s 后鸣叫下一个音阶。
二、程序设计
我们已经知道不同的频率产生的音调不同,所以这里我们需产生七个不同的频率以发出七个音调,而占空比主要是对音调的音量有影响,这里占空比我们保持为 50%即可。七个音调所对应的频率(Hz),如下表所示
音调 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
频率 | 262 | 294 | 330 | 349 | 392 | 440 | 494 |
知道了不同音调对应的频率,我们如何通过程序去实现呢?以音调“Do”为例,该音调的频率为 262,所以这里我们需输出频率为 262Hz,占空比为 50%的 PWM 方波。先计算该频率单个方波的时间:1 / 262 ≈ 0.003816794s=3816794ns;程序设计使用的系统时钟(50MHz)的时间为:1 / 50000000 = 0.00000002s = 20ns;所以我们需用:3816794 / 20 ≈ 190840 个系统时钟去产生一个 PWM 波,该 PWM 波形的频率即为 262Hz。要想实现占空比为 50%的波形,即高电平时间所占信号周期的百分比为 50%。信号周期为 190840 的话,50%占空比的高电平持续时间即为信号周期的一半,即为 95420。以上就是“Do”音调的输出 PWM 波产生方式,其余音调的产生方式是一样的,下表是计算出7个音调对应的数值。
频率/HZ |
音调分频计数值
|
占空比计数值
|
262 |
190840
|
95420
|
294 |
170068
|
85034
|
330
|
151515
|
75757
|
349
|
143266
|
71633
|
392
|
127551
|
63775
|
440
|
113636
|
56818
|
494
|
101214
|
50607
|
话不多说直接上代码,程序参考野火FPGA有兴趣的同学可以购买开发板学习。
`timescale 1ns/1ns
module beep
#(
parameter TIME_500MS = 25'd24999999, //0.5s计数值
parameter DO = 18'd190839 , //"哆"音调分频计数值(频率262)
parameter RE = 18'd170067 , //"来"音调分频计数值(频率294)
parameter MI = 18'd151514 , //"咪"音调分频计数值(频率330)
parameter FA = 18'd143265 , //"发"音调分频计数值(频率349)
parameter SO = 18'd127550 , //"梭"音调分频计数值(频率392)
parameter LA = 18'd113635 , //"拉"音调分频计数值(频率440)
parameter XI = 18'd101214 //"西"音调分频计数值(频率494)
)
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //系统复位,低有效
output reg beep //输出蜂鸣器控制信号
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//reg define
reg [24:0] cnt ; //0.5s计数器
reg [17:0] freq_cnt ; //音调计数器
reg [2:0] cnt_500ms ; //0.5s个数计数
reg [17:0] freq_data ; //音调分频计数值
//wire define
wire [16:0] duty_data ; //占空比计数值
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//设置50%占空比:音阶分频计数值的一半即为占空比的高电平数
assign duty_data = freq_data >> 1'b1;
//cnt:0.5s循环计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt <= 25'd0;
else if(cnt == TIME_500MS )
cnt <= 25'd0;
else
cnt <= cnt + 1'b1;
//cnt_500ms:对500ms个数进行计数,每个音阶鸣叫时间0.5s,7个音节一循环
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_500ms <= 3'd0;
else if(cnt == TIME_500MS && cnt_500ms == 6)
cnt_500ms <= 3'd0;
else if(cnt == TIME_500MS)
cnt_500ms <= cnt_500ms + 1'b1;
//不同时间鸣叫不同的音阶
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
freq_data <= DO;
else case(cnt_500ms)
0: freq_data <= DO;
1: freq_data <= RE;
2: freq_data <= MI;
3: freq_data <= FA;
4: freq_data <= SO;
5: freq_data <= LA;
6: freq_data <= XI;
default: freq_data <= DO;
endcase
//freq_cnt:当计数到音阶计数值或跳转到下一音阶时,开始重新计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
freq_cnt <= 18'd0;
else if(freq_cnt == freq_data || cnt == TIME_500MS)
freq_cnt <= 18'd0;
else
freq_cnt <= freq_cnt + 1'b1;
//beep:输出蜂鸣器波形
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
beep <= 1'b0;
else if(freq_cnt >= duty_data)
beep <= 1'b1;
else
beep <= 1'b0;
endmodule
仿真代码
`timescale 1ns/1ns
module tb_beep();
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//reg define
reg sys_clk ; //时钟
reg sys_rst_n ; //复位
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//对时钟,复位信号赋初值
initial
begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#100
sys_rst_n <= 1'b1;
end
//产生时钟信号
always #10 sys_clk = ~sys_clk;
//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
beep
#(
.TIME_500MS(25'd2499), //0.5s计数值
.DO (18'd190 ), //"哆"音调分频计数值(频率262)
.RE (18'd170 ), //"来"音调分频计数值(频率294)
.MI (18'd151 ), //"咪"音调分频计数值(频率330)
.FA (18'd143 ), //"发"音调分频计数值(频率349)
.SO (18'd127 ), //"梭"音调分频计数值(频率392)
.LA (18'd113 ), //"拉"音调分频计数值(频率440)
.XI (18'd101 ) //"西"音调分频计数值(频率494)
)
beep_inst
(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n ), //系统复位,低有效
.beep (beep ) //输出蜂鸣器控制信号
);
endmodule
仿真波形
由于我们仿真时把各参数缩小了约 1000 倍,所以其单位波形的时间也缩小了约 1000 倍,而前面说到 “Do”音调的单位方波持续时间为 3816794ns,这里将时间缩小 1000 倍为 3816ns,与上述抓取的单位波形持续时间几乎一致,说明我们的设计正确。
三、总结
程序相对简单,主要是学习通过计数的形式设计程序从而控制无源蜂鸣器。