无源蜂鸣器 verilog FPGA 基础练习9
发现问题,用技术解决问题。兴趣是自己的源动力 !
前言
无源蜂鸣器的练习,就是对计数器和状态机练习的变种,学会用计数器和状态机的思想来设计。
一、无源蜂鸣器原理
无源蜂鸣器与有缘蜂鸣器不同,因其内部不带震荡源,所以其无法向有缘蜂鸣器那样直接用直流信号驱动,这里需要使用PWM方波才能驱动其发声。
如何发出不同的声音呢?上面说到需要使用PWM方波才能驱动其发声,所以这里我们只要控制输入的PWM方波,输入不同的PWM方波发出的声音就不一样了。而不同频率和占空比的方波发出的声音是不同的,其中频率对音调有影响,占空比对音量大小有影响。所以我们只需产生不同频率和占空比的PWM方波去驱动无源蜂鸣器就能让 无源蜂鸣器发出不同的音调了。
二、无源蜂鸣器设计方案
本次实验我们驱动无源蜂鸣器进行七个基本音调“哆来咪发梭拉西”的循环鸣叫,每个音阶持续鸣叫0.5s后鸣叫下一个音阶,本练习设计占空比为50%。
需要注意的是,设计并不复杂,只需输出一个beep的输出即可。由要输出beep倒推需要设计什么电路
2.1 无源蜂鸣器代码
代码主要有几个组成(从倒推的逻辑):
- 输出一个50%占空比的beep,需要一个判断50%占空比的计数器,和输出beep的always语句(这里是对分频和倍频的练习)
- 因为要输出7种音调,所以要设计一个状态机来表示7种音调的频率,用于输出当前状态的beep(考察状态机的思想)
- 因为每一种音调持续0.5s,所以设计0.5s计数器
- 需要产生一个目前的发音状态,即0.5s个数计数
2.1.1 功能代码
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 //输出蜂鸣器控制信号
);
//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 ; //占空比计数值
// 0.5s计数
always@(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
cnt <= 'b0;
else if( cnt == TIME_500MS )
cnt <= 'b0;
else
cnt <= cnt + 1'b1 ;
end
//0.5s个数计数,7个音节一循环
always@(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
cnt_500ms <= 'b0;
else if( cnt_500ms == 'd6 & cnt == TIME_500MS ) // 这里就要考虑此时基本计数单位的问题了
cnt_500ms <= 'b0;
else if(cnt == TIME_500MS)
cnt_500ms <= cnt_500ms + 1'b1 ;
end
// 设定一个case语句,用于状态转移
always@(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
freq_data <= DO;
// 频率归零条件,一个是一个频率周期,另一个是下一个0.5s
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
end
// 上面是设计实现当前输出的音调的状态(音调频率输出的状态)
// 下面是设计实现设定频率的计数器和50%占空比的输出,可以定义为输出层(功能)
// 设计一个频率计数器,用一个频率计数就可以了,7个音调共用一个计数器
always@(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
freq_cnt <= 'b0;
// 频率归零条件,一个是一个频率周期,另一个是下一个0.5s
else if( (freq_cnt == freq_data) || (cnt == TIME_500MS) )
freq_cnt <= 'b0;
else
freq_cnt <= freq_cnt + 1'b1 ;
end
assign duty_data = freq_data >> 1'b1 ;
// *占空比输出beep
always@(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
beep <= 'b0;
else if( freq_cnt >= duty_data)
beep <= 'b1;
else
beep <= 'b0;
end
endmodule
2.1.2 仿真代码
`timescale 1ns/1ns
module tb_top();
//\* 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'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
(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n ), //系统复位,低有效
.beep (beep ) //输出蜂鸣器控制信号
);
endmodule
2.1.3 仿真结果
总结
- 核心思想:理清楚设计思路,从到推的角度来分层次设计和功能来设计
- 欢迎一起交流学习,如有错误之处,还请各位指正。
参考资料
[1] FPGA系列教学