知识点
蜂鸣器按其结构可分为电磁式蜂鸣器和压电式蜂鸣器两种类型;蜂鸣器按其是否带有信号源又分为有源蜂鸣器和无源蜂鸣器。
有源蜂鸣器的内部装有集成电路,不需要音频驱动电路,只需要接通直流电源就能直接发出声响;而无源蜂鸣器只有外加音频驱动信号才能发出声响。
无源蜂鸣器与有源蜂鸣器不同,因其内部不带震荡源,所以其无法像有源蜂鸣器那样直接用直流信号驱动,这里需要使用PWM方波才能驱动其发声。输入不同频率和占空比的PWM方波发出的声音是不同的,其中频率对音调有影响,占空比对音量大小有影响。
实验内容
驱动FPGA开发板上的无源蜂鸣器,使蜂鸣器依次输出Do、Re、Mi、Fa、So、La、Si七个音调,每个音调持续时间为0.5s,占空比为50%。
波形分析
输入信号有时钟,复位信号,输出不同频率信号beep。
中间变量有计数器cnt,初值为0,计数最大值为50_000_000/2-1=2.5*10^7-1=24_999_999。计数到最大值就归零,然后开始下一个周期的计数。
另外声明一个中间变量cnt_500ms,初值为0,实现对0.5s的计数,当cnt完成一个0.5s计数时,即cnt等于24_999_999,cnt_500ms就自加1,cnt_500ms最大值为6,完成7个周期的计数后,就归零。
由于不同音调对应的频率不同,所以这里要用到分频计数器,中间变量分频计数器freq_cnt,初值为0,音调1(Do)对应频率为262Hz,而系统时钟对应的是50MHz,50_000_000 / 262 = 190840,所以262Hz对应的计数器的最大值为190840-1=190839,所以freq_cnt最大值为190839,其余音调所对应的频率最大值如下表所示:
为了控制清零条件,需要利用中间变量freq_data,当freq_cnt计数到190839过程,让freq_data 保持190839,从而实现了0.5s的1(Do)音调,当freq_cnt计数到170067过程,让freq_data保持170067,从而实现了0.5s的2(Re)音调,当freq_cnt计数到151514过程,让freq_data保持151514,从而实现了0.5s的3(Mi)音调,当freq_cnt计数到143265过程,让freq_data保持1143265,从而实现了0.5s的4(Fa)音调,当freq_cnt计数到127550过程,让freq_data保持127550,从而实现了0.5s的5(So)音调,当freq_cnt计数到113635过程,让freq_data保持113635,从而实现了0.5s的6(La)音调,当freq_cnt计数到101213过程,让freq_data保持101213,从而实现了0.5s的7(Si)音调。
再申明一个占空比计数值,使用该变量来控制占空比duty_data,其数值等于freq_data的一半。当freq_data计数值大于duty_data计数值时,输出信号beep拉高,保持高电平,如下图所示:
代码
module beep
#(
parameter CNT_MAX = 25'd24_999_999,
parameter DO = 18'd190839,
parameter RE = 18'd170076,
parameter MI = 18'd151514,
parameter FA = 18'd143265,
parameter SO = 18'd127550,
parameter LA = 18'd113635,
parameter XI = 18'd101213
)
(
input wire sys_clk,
input wire sys_rst_n,
output reg beep
);
reg [24:0] cnt;
reg [2:0] cnt_500ms;
reg [17:0] freq_data;
reg [17:0] freq_cnt;
wire [16:0] duty_data;
always @(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
cnt <= 25'd0;
else if(cnt == CNT_MAX)
cnt <= 25'd0;
else
cnt <= cnt + 1'b1;
end
always @(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
cnt_500ms <= 3'd0;
else if((cnt == CNT_MAX)&&(cnt_500ms == 3'd6))
cnt_500ms <= 3'd0;
else if (cnt == CNT_MAX)
cnt_500ms <= cnt_500ms + 1'b1;
else
cnt_500ms <= cnt_500ms;
end
always @(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
freq_cnt <= 18'd0;
else if((freq_cnt == freq_data)||(cnt == CNT_MAX))
freq_cnt <= 18'd0;
else
freq_cnt <= freq_cnt + 1'b1;
end
always @(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
freq_data <= DO;
else case(cnt_500ms)
3'd0:freq_data <= DO;
3'd1:freq_data <= RE;
3'd2:freq_data <= MI;
3'd3:freq_data <= FA;
3'd4:freq_data <= SO;
3'd5:freq_data <= LA;
3'd6:freq_data <= XI;
default:freq_data <= DO;
endcase
end
assign duty_data = freq_data >> 1;
always @(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
beep <= 1'b0;
else if(freq_data >= duty_data)
beep <= 1'b1;
else
beep <= 1'b0;
end
endmodule
`timescale 1ns/1ns
module tb_beep();
reg sys_clk;
reg sys_rst_n;
wire beep;
initial
begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
always #10 sys_clk <= ~sys_clk;
beep
#(
.CNT_MAX (25'd24_99_999),
.DO (18'd19083),
.RE (18'd17007),
.MI (18'd15151),
.FA (18'd14326),
.SO (18'd12755),
.LA (18'd11363),
.XI (18'd10121)
)
beep_inst
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.beep (beep)
);
endmodule