15_无源蜂鸣器的驱动实验
1. 无源蜂鸣器驱动原理
2. 实验目标
实验目标:本次实验我们驱动无源蜂鸣器进行七个基本音调“哆来咪发梭拉西”的循环鸣叫。从实验目标我们知道,我们需鸣叫七个音调,而音调是受频率影响的,不同的频率产生的音调即不同,所以这里我们需产生七个不同的频率以发出七个音调,而占空比主要是对音调的音量有影响,这里占空比我们保持为 50%即可。七个音调所对应的频率,如下表格:
由实验目标可知,刚开始我们是鸣叫音调“Do”,该音调的频率为 262,所以这里我们需输出频率为 262,占空比为 50%的 PWM 方波。首先是音调的频率我们该如何产生?我们先计算该频率单个方波的时间:1 / 262 ≈ 0.003816794s=3816794ns;而我们单个系统时钟(50MHz)的时间为:1 / 50000000 = 0.00000002s = 20ns;所以我们需用:3816794 / 20 ≈ 190840 个系统时钟去产生一个 PWM波,该 PWM 波形的频率即为 262。故我们需先对 190840 个系统时钟进行计数,这里我们
声明 freq_cnt 信号进行计数。
频率对应表(计数器怎么数):
3. 波形图绘制
1)cnt_500ms:该信号我们定义为蜂鸣器的鸣叫状态计数。由于我们需鸣叫七个音调,所以我们需要计 7 个数(0~6),而每个音调的鸣叫时间即计数值的持续时间。所以这里我们需要用一个鸣叫持续时间计数器去控制蜂鸣器各音调的鸣叫持续时间。
2)cnt:蜂鸣器各音调鸣叫持续时间计数器。在“计数器”章节我们详细的介绍了该如何用计数器进行时间计数,这里就不详细的介绍了。本次实验我们设计让每个音调持续鸣叫0.5s,故这里我们计数到 24999999(0.5s)时让鸣叫状态计数器加 1。当最后一个音调(cnt_500ms = 3’d6)鸣叫了 0.5s 时,我们让状态计数器跳转回第一个音调鸣叫状态(cnt_500ms = 3’d0),以此循环,我们就能实现蜂鸣器七个基本音调的循环鸣叫了。
“Do”音调 PWM 波产生波形图:
“Re”音调 PWM 波产生波形图:
3)freq_cnt:音调计数器。我们使用该计数器进行计数,当要鸣叫什么频率的音调我们就让计数器计到该音调所对应的音调分频计数器值即可。这里计数 0~190839 即为 190840 个系统时钟,该计数范围内即为一个 262 频率波形。
4)freq_data:音调分频计数值。该信号我们用于定义各音调频率的计数值,当我们需要鸣叫什么频率的值时,我们就让该信号的值定义为该频率的计数值。如上图所示我们此时需要输出的 PWM 波形频率为 262,按上面所述的计算方法,计数值即为 190839。
5)duty_data:占空比计数值。通过改变该值我们可改变 PWM 波的占空比。前面说到我们是产生占空比为 50%的波形,即高电平时间所占信号周期的百分比为 50%。信号周期为190840 话,50%占空比的高电平持续时间即为信号周期的一半,即为 95420。
6)beep:输出控制蜂鸣器信号,该信号即为我们前面说的 PWM 方波。当我们需要输出频率为 262 时,我们需要产生波形的一个方波时间为 190840 个系统时钟,而高电平的持续时间即为 95420,所以如上波形图所示,当音阶计数器(freq_cnt)中的值大于等于占空比计数值时,我们将 beep 信号输出高电平,小于时输出低电平,这样我们输出的信号即为频率为 262,占空比为 50%的 PWM 波了,我们将该信号接入到驱动蜂鸣器的 I/O 引脚即能驱动蜂鸣器发出“Do”的音调。
4. RTL
module beep
#(
parameter TIME_500MS = 25'd24000000, //0.5s 计数值
parameter DO = 18'd190840 , //"哆"音调分频计数值(频率 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 //输出蜂鸣器控制信号
);
reg [24:0] cnt ; //0.5s 计数器
reg [2:0] cnt_500ms ; //0.5s 个数计数
// 后面 在第几个0.5秒 会对应着不同的频率
reg [17:0] freq_data ; //音调分频计数值
// 每个频率对应的计数器
reg [17:0] freq_cnt ; //音调计数器
//计数每个音符的频率
wire [16:0] duty_data ; //占空比计数值
// duty_data = freq_data / 2
//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 - 25'd1)
cnt <= 25'd0;
else
cnt <= cnt + 25'd1;
//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_500ms == 3'd7 - 3'd1) && (cnt == TIME_500MS - 25'd1))
cnt_500ms <= 3'd0;
else if(cnt == TIME_500MS - 25'd1)
cnt_500ms <= cnt_500ms + 3'd1;
else
cnt_500ms <= cnt_500ms ;
//不同时间鸣叫不同的音阶
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
freq_data <= DO;
else
begin
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
end
//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 - 25'd1)
freq_cnt <= 18'd0;
else
freq_cnt <= freq_cnt + 18'd1;
//设置 50%占空比:音阶分频计数值的一半即为占空比的高电平数
assign duty_data = freq_data >> 1'b1; // 右移就是除法
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
5. testbench
`timescale 1ns/1ns
module tb_beep();
//因为 testbench 不对外进行信号的输入输出,只是自己产生
//激励信号提供给内部实例化待测 RTL 模块使用,所以端口列表
//中没有内容,只是列出“()”,当然可以将“()”省略,括号
//后有个“;”不要忘记
//要在 initial 块和 always 块中被赋值的变量一定要是 reg 型
//在 testbench 中待测试 RTL 模块的输入永远是 reg 型变量
//输出信号,我们直接观察,也不用在任何地方进行赋值
//所以是 wire 型变量(在 testbench 中待测试 RTL 模块的输出永远是 wire 型变量)
reg sys_clk;
reg sys_rst_n;
//输出信号,我们直接观察,也不用在任何地方进行赋值
//所以是 wire 型变量(在 testbench 中待测试 RTL 模块的输出永远是 wire 型变量)
wire beep;
//初始化值在没有特殊要求的情况下给 0 或 1 都可以。如果不赋初值,仿真时信号
//会显示为不定态(ModelSim 中的波形显示红色)
initial
//initial 只在通电执行一次
//在仿真中 begin...end 块中的内容都是顺序执行的,
//在没有延时的情况下几乎没有差别,看上去是同时执行的,
//如果有延时才能表达的比较明了;
//而在 rtl 代码中 begin...end 相当于括号的作用, begin...end 在 Testbench 中的用法及意义(区别 -----------------------------------------------------)
//在同一个 always 块中给多个变量赋值的时候要加上
begin
sys_clk = 1'b1; //时钟信号的初始化为 1,且使用“=”赋值,
//其他信号的赋值都是用“<=”
sys_rst_n <= 1'b0; //因为低电平复位,所以复位信号的初始化为 0
#20 //延时20ns
sys_rst_n <= 1'b1; //初始化 20ns 后,复位释放,因为是低电平复位
//都是顺序执行的
end
//产生时钟信号
always #10 sys_clk = ~sys_clk;
beep
#(
.TIME_500MS(25'd24999), //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
(
//前面的“in1”表示被实例化模块中的信号,后面的“in1”表示实例化该模块并要和这个
//模块的该信号相连接的信号(可以取名不同,一般取名相同,方便连接和观察)
//“.”可以理解为将这两个信号连接在一起
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.beep (beep )
);
endmodule