[FPGA 学习记录] 无源蜂鸣器驱动实验

无源蜂鸣器驱动实验


本小节的内容就是完成无源蜂鸣器的驱动实验,目的是教会大家如何驱动我们的无源蜂鸣器。

本讲视频的主要内容分为两个部分:理论学习和实战演练。那么在理论部分,我们会对蜂鸣器的相关知识以及无源蜂鸣器的驱动原理做一个详细的讲解;那么在实战演练部分,我们会通过实验工程实现无源蜂鸣器的驱动实验。

首先是理论学习。

1 理论学习

1.1 蜂鸣器简介

那么蜂鸣器大家都见过,它是一种一体化结构的电子讯响器,具有体积小、重量轻、价格低、结构牢靠的特点,被广泛应用在报警器、电子玩具、定时器等电子产品当中,作为一个发声器件;那么蜂鸣器按照它的结构可以分为电磁式蜂鸣器、压电式蜂鸣器两种,如果说按照其是否带有信号源又可以分为有源蜂鸣器和无源蜂鸣器,那么有源蜂鸣器它内部是装有集成电路的,不需要音频驱动电路,只要接通直流电源就能够直接发出声响;而我们的无源蜂鸣器只有外部加音频驱动信号才能发出声响,我们征途系列开发板上使用的就是我们的无源蜂鸣器,那么下面这张图就是有源蜂鸣器与无源蜂鸣器,左边的是有源蜂鸣器、右边的是无源蜂鸣器

有源蜂鸣器和无源蜂鸣器

单单从外观上来看,我们的有源蜂鸣器它的高度高一些,在引脚部位它是黑胶封闭;而我们的无源蜂鸣器引脚部位是有裸露的电路板。我们征途系列开发板使用的是无源蜂鸣器,我们的实验也是无源蜂鸣器的驱动实验,所以说我们要说一下无源蜂鸣器的驱动原理。

1.2 无源蜂鸣器的驱动原理

无源蜂鸣器与有源蜂鸣器不同,它内部不带振荡源,所以说它无法像有源蜂鸣器那样直接用直流信号就可以驱动,它这里需要使用 PWM 方波才能驱动其发声。如果我们给无源蜂鸣器输入不同频率和占空比的 PWM 方波,它发出的声音是不同的;其中的频率对它的音调是有影响的,方波的占空比对音量大小是有影响的,我们来看一下下面这张表

音调1(Do)2(Re)3(Mi)4(Fa)5(So)6(La)7(Si)
频率(Hz)262294330349392440494

这张表展示的是七个音阶:Do、Re、Mi、Fa、So、La、Si 对应的七个频率值。它表示什么意思呢?我们来举个例子
比如说音调 Fa 它对应的频率值是 349Hz,这就表示:如果给我们的无源蜂鸣器输入一个频率为 349Hz 的 PWM 方波,它就可以发出 Fa 的声音;如果输入的信号频率值是 392Hz,它就会发出 So 的声音。

以上内容就是理论部分对蜂鸣器以及无源蜂鸣器驱动原理的讲解,那么下面开始我们的实战演练

2 实战演练

在实战演练部分,我们将完成无源蜂鸣器的驱动实验。

2.1 实验目标

我们的实验目标是驱动开发板上的无源蜂鸣器,进行七个基本音调:Do、Re、Mi、Fa、So、La、Si 的循环发声,每个音调发声 0.5s 占空比是 50%。

2.2 硬件资源

我们使用开发板上的无源蜂鸣器进行该实验的验证,如下图所示

20231120164758_FshfjzPO945

由原理图可知,我们只需要在 BEEP 端输入 PWM 方波即可驱动无源蜂鸣器进行鸣叫了。如下图所示

20231120165039_EJIyd4vw5p9

2.3 程序设计

了解了实验目标之后,下面就开始程序的设计。

首先,建立一个文件体系

20231115153648_ySs69NQnPN7

然后打开 doc 文件夹建立一个 Visio 文件,绘制我们的框图和我们的波形图

20231115153947_ii962LjXVm5

2.3.1 框图绘制

那么首先是模块框图的绘制

20231115154118_xC12C41wQJ8

下面就是端口列表。我们来看一下我们的实验目标:我们的目的是驱动我们的无源蜂鸣器进行发声,其实就是输出不同频率的 PWM 方波。那么这个感觉就和我们的分频器非常类似,所以说它的输入、输出信号也应该和分频器相同,输入就是时钟和复位信号;那么输出就是不同频率的信号,因为是输出到蜂鸣器,那么信号命名为 beep

20231115154318_TZP0KK4zU55

2.3.2 波形绘制

那么下面就是波形图的绘制。首先是我们的输入信号

20231115154556_wOtOS0cpf40

那么这样输入信号的波形绘制完成。下面应该怎么绘制呢?我们先来分析一下:

我们在实验目标中提到:我们要驱动无源蜂鸣器进行七个基本音调的发声,那么每个音调发声是 0.5 s 0.5\text{s} 0.5s。那么这儿就可以确定要使用一个计数器,计时 0.5 s 0.5\text{s} 0.5s,我们把它取名为 cnt;那么它的初值应该是 0 0 0 初值是 0 0 0 每个周期自加一,计数到最大值就归零,进行下一个循环的计数,那么它的计数最大值是多少呢?之前已经教过大家如何计算了,那么经过计算,在 50 MHz 50\text{MHz} 50MHz 的频率下完成 0.5 s 0.5\text{s} 0.5s 的计数,需要计数 2.5 × 1 0 7 2.5\times10^{7} 2.5×107 个周期;因为是从 0 0 0 开始计数,所以说计数的最大值应该是 2.5 × 1 0 7 − 1 2.5\times10^{7}-1 2.5×1071 那么这儿采用省略的画法,我们画一下

20231115155148_chNv1mI6xR9

那么当计数到最大值它就归零,然后开始下一个周期的计数

20231115155406_Awn42xbkdC3

那么到了这里,0.5s 计数器的波形绘制完成。

我们继续分析我们的实验目标。我们的实验目标是驱动我们的蜂鸣器进行七个基本音调:Do、Re、Mi、Fa、So、La、Si 的循环发声,每个音调发声 0.5s;也就是说,在计数的第一个 0.5s 发 Do 第二个 0.5s 发 Re 然后第七个 0.5s 是发 Si,然后发完 Si 之后下一个 0.5s 又回到了 Do 那么怎么实现这个循环呢?

我们就想到了可以对我们的 0.5s 周期进行计数。我们声明一个计数器对 0.5s 的计数器进行一个计数,那么这样就可以了

20231115162105_hlD0nbkjYv0

它的初值是 0,当我们的 0.5s 计数器完成第一个 0.5s 的计数之后它加一;那么 0 到 6 就完成了七个周期的一个计数。我们的计数器 cnt_500ms 是对我们的 0.5s 计数器进行一个计数,那么它每加一就代表完成了一个 0.5s 的一个计数;当它的计数值为 0 就代表这个周期要发 Do 音,为 1 就是 Re 音,然后后面就是 Mi、Fa、So、La、Si;那么发完 Si 音之后,再下一个 0.5s 继续发 Do 音,就完成了下一个七个音调的一个循环。

那么声明了这两个计数器,就能保证完成七个音调:Do、Re、Mi、Fa、So、La、Si 的发声,而且保证每个音调发声的时间为 0.5s。

那么接下来我们继续分析。我们的目的是使我们的蜂鸣器发出 Do、Re、Mi、Fa、So、La、Si 七个音调的声音;我们已经知道,我们的无源蜂鸣器想要让它发出不同音调的声音,就要输出不同频率的 PWM 方波。那么在前面的讲解当中我们已经提到了 Do、Re、Mi、Fa、So、La、Si 七个音调对应的各自的频率,那么知道了它们各自对应的频率,怎么产生这个频率信号呢?

其实它的产生就和分频是一样的。那么既然是分频,就需要一个分频计数器

20231120091146_KJ6BpWFdgz7

那么有了系统时钟和我们的分频计数器,就可以产生我们想要的频率的信号。怎么产生呢?我们来看一下

首先,我们的计数器有一个初值是 0 然后它每个周期会自加一,那么它计数的最大值是多少呢?这儿就需要计算一下

我们以 Do 为例:那么音调 Do 它对应的频率信号频率是 262 Hz 262\text{Hz} 262Hz,而我们的系统时钟是 50 MHz 50\text{MHz} 50MHz 50 MHz 50\text{MHz} 50MHz 对应的时钟周期是 20 ns 20\text{ns} 20ns 262 Hz 262\text{Hz} 262Hz 对应的时钟周期经过计算 1 262 Hz ≈ 3816794 ns \frac{1}{262\text{Hz}}\approx3816794\text{ns} 262Hz13816794ns 得到 3816794 ns 3816794\text{ns} 3816794ns;那么得到这两个参数,使用 3816794 ns 3816794\text{ns} 3816794ns 这个值除以 20 ns 20\text{ns} 20ns 就可以得到我们计数器计数的最大值: 3816794 ns / 20 ns ≈ 190840 3816794\text{ns}/20\text{ns}\approx190840 3816794ns/20ns190840。也就是说在 50 MHz 50\text{MHz} 50MHz 的时钟频率下要输出频率为 262 Hz 262\text{Hz} 262Hz 的信号,我们的计数器需要计数 190840 190840 190840 个时钟周期。

因为我们的计数器是从 0 开始计数,所以说计数的最大值应该是 190840 − 1 = 190839 190840-1=190839 1908401=190839 那么这儿就应该这样画

20231120093005_uqJyfsYJLq2

如果发出其他音调,freq_cnt 这个计数值是会改变的。那么经过计算,我们得到下面这样一张表

频率(Hz)音调分频计数值(freq_cnt)占空比计数值(duty_cnt)
26219084095420
29417006885034
33015151575757
34914326671633
39212755163775
44011363656818
49410121450607

那么这张表显示的是 Do、Re、Mi、Fa、So、La、Si 这七个音调频率对应的计数最大值;那么这儿也计算出了占空比的计数值,我们前面实验目标已经提到了,我们的占空比是 50%,所以说 duty_cnt 这个值是 freq_cnt 的二分之一。

那么根据上面这张表格,我们可以完善我们的频率计数器的波形

20231120095541_ybrXvzKK305

那么这样,频率计数器的波形绘制完成。由绘制的波形图可知,我们的频率计数器它计数的最大值有 7 种情况,我们的频率计数器只有一个;那么为了能够准确的产生不同频率的 PWM 方波,我们需要灵活的控制清零的条件,那么这个清零条件我们就需要用一个中间变量 freq_data 来进行传递;那么它的初值我们就让它等于 Do 对应的这个值,然后当我们的计数器计数到 1 我们就让它等于 Re 对应的这个最大值,然后参照这个规律绘制我们的波形图

20231120100316_fL0YNQYFwk6

那么这样频率计数最大值的波形绘制完成。下面我们再声明一个变量,声明一个占空比计数值,使用这个变量来控制我们的占空比;我们的占空比已经设置为 50% 所以说它的值应该等于 freq_data 这个变量的一半,如果说你想要输出其他占空比的波形,就可以通过计算对它进行修改

20231120101128_nRHb1nhg7Y4

那么这样,占空比计数器的波形也绘制完成了。那么下面就可以开始绘制输出信号的波形,我们单单拿出一个周期来看,我们拿出 Do 这个周期来看

20231120102018_5WkAjpi06P7

那么在音阶 Do 这个发声范围内,我们的输出信号应该怎么变化呢?我们来看一下。

首先,要给它一个初值,初值是低电平;当我们的频率计数器它的计数值小于占空比计数值时,让它保持低电平,当我们的频率计数器它的计数值大于占空比计数值时,把低电平拉高,让它保持高电平;然后下一个周期也是这样变化,那么参照这个规律,我们完善我们的波形图

20231120102922_ym3Hcodz5O6

那么这样,在音阶 Do 发声周期内它的输出波形我们绘制完成。在其他音阶发声周期内它的波形变化也是类似的:当计数值小于占空比计数值时保持低电平,大于时保持高电平;那么这样就能够得到,各个音阶对应的频率信号,这样就可以驱动我们的蜂鸣器实现 Do、Re、Mi、Fa、So、La、Si 七个循环的发声。

2.3.3 RTL 代码编写

那么接下来,参照我们绘制的波形图进行代码的编写

20231120104149_XsWFo2qizW5

那么首先是我们的模块开始,然后是模块名称、端口列表,然后是模块结束

20231120104329_eDssQl6XsQ1

由图可知输入信号有两路:时钟信号和复位信号;然后有一路输出信号

20231120104618_dYPKBUs1NC2

那么接下来定义我们的变量,首先是 0.5s 的计数器 cnt 我们后面将使用 always 语句对它进行赋值,所以说它的变量类型是 reg 型;它计数的最大值是 ( 24 _ 999 _ 999 ) 10 (24\_999\_999)_{10} (24_999_999)10 换算成二进制就是 ( 1 _ 0111 _ 1101 _ 0111 _ 1000 _ 0011 _ 1111 ) 2 (1\_0111\_1101\_0111\_1000\_0011\_1111)_{2} (1_0111_1101_0111_1000_0011_1111)2 那么它的位宽是 25 位宽,就是 [24:0]然;后是变量 cnt_500ms 对 0.5s 的个数进行计数,那么计数的最大值是 6 对应位宽就应该是 3 位宽就,是 [2:0];然后是我们的频率计数器 freq_cnt,最大值应该是 ( 190 _ 839 ) 10 (190\_839)_{10} (190_839)10 然后换算成二进制就是 ( 10 _ 1110 _ 1001 _ 0111 _ 0111 ) 2 (10\_1110\_1001\_0111\_0111)_{2} (10_1110_1001_0111_0111)2 位宽是 18 位宽,就是 [17:0];那么下面是计数最大值 freq_data 它的位宽应该和 freq_cnt 是一样的;然后是占空比计数值 duty_cnt 我们的占空比是 50%,它的值等于 freq_data 的一半,最大值应该是 ( 95 _ 419 ) 10 (95\_419)_{10} (95_419)10 换算成二进制就是 ( 1 _ 0111 _ 0100 _ 1011 _ 1011 ) 2 (1\_0111\_0100\_1011\_1011)_{2} (1_0111_0100_1011_1011)2 位宽是 17 位宽,就是 [16:0]

20231120110322_1Yhj5Jhj5q0

那么这样变量声明完成,下面开始变量的赋值。

那么首先是我们的计数器 cnt 使用 always 语句进行赋值

20231120111059_feWFqX1I261

那么下面开始变量 cnt_500ms 的赋值,同样使用 always 语句

20231120111549_PFVfAC65uK1

那么下面开始频率计数器的赋值,我们同样使用 always 语句

20231120112052_YkTmoEr23B1

那么接下来就是变量计数最大值的赋值,就是 freq_data 这个变量,它的赋值我们要使用我们的 case 条件分支语句

20231120120832_9CuVQnjMQp1

那么这样,变量计数器最大值它的赋值就完成了。那么接下来就是占空比计数值的赋值,就是 duty_cnt。我们的占空比已经规定是 50% 它的值就是 freq_data 的二分之一,我们可以使用除法来实现;但是这儿我们使用移位的方法来实现,我们使用 assign 语句

20231120121419_t8nkKmtRKm6

那么最后就是输出信号的赋值,我们同样使用 always 语句

20231120121843_nYKC3vqs739

那么这样,代码编写完成,我们保存。

2.3.4 RTL 代码编译

那么保存之后我们回到桌面,建立一个新的实验工程,然后添加我们的文件就是我们编写的代码

20231120122213_HOTdMCxoNr5

然后全编译

20231120122353_TD3mlM68Xq6

编译通过点击 OK

查看警告信息,提示位宽问题,回到代码发现忘记编写参数的位宽,修改完成并且保存后回到工程重新进行编译

20231120122534_lRKI079HGi5

beep.v

module beep
#(
    parameter   CNT_MAX = 25'd24_999_999,
    parameter   DO = 18'd190839,
    parameter   RE = 18'd170067,
    parameter   MI = 18'd151514,
    parameter   FA = 18'd143265,
    parameter   SO = 18'd127550,
    parameter   LA = 18'd113635,
    parameter   SI = 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_cnt;
reg [17:0]  freq_data;
wire[16:0]  duty_cnt;

always@(posedge sys_clk or negedge sys_rst_n)
    if (sys_rst_n == 1'b0)
        cnt <= 25'd0;
    else if (cnt == CNT_MAX)
        cnt <= 25'd0;
    else
        cnt <= cnt + 25'd1;

always@(posedge sys_clk or negedge sys_rst_n)
    if (sys_rst_n == 1'b0)
        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 + 3'd1;
    else
        cnt_500ms <= cnt_500ms;

always@(posedge sys_clk or negedge sys_rst_n)
    if (sys_rst_n == 1'b0)
        freq_cnt <= 18'd0;
    else if ((cnt == CNT_MAX)
           ||(freq_cnt == freq_data))
        freq_cnt <= 18'd0;
    else
        freq_cnt <= freq_cnt + 18'd1;

always@(posedge sys_clk or negedge sys_rst_n)
    if (sys_rst_n == 1'b0)
        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 <= SI;
        default:freq_data <= DO;
    endcase

assign duty_cnt = freq_data >> 1;

always@(posedge sys_clk or negedge sys_rst_n)
    if (sys_rst_n == 1'b0)
        beep <= 1'b0;
    else if (freq_cnt >= duty_cnt)
        beep <= 1'b1;
    else
        beep <= 1'b0;

endmodule

2.3.5 仿真验证

2.3.5.1 仿真代码编写

然后接下来编写我们的仿真代码

20231120122919_DAnDbOTurm1

那么首先是我们的时间参数,然后是模块开始、模块名称、端口列表(空的),然后是模块结束

20231120123041_MNjpI34N0g2

然后是变量的定义

20231120123155_V5EN6Aiq687

然后是初始化

20231120123251_FmbhNV12Hw9

然后是我们的时钟

20231120123433_G67yh4Afd35

然后是实例化。在这个位置我们对所有的参数缩小十倍;在这个位置,还要将我们的信号引出来

20231120123653_KGDepEHBDD5

那么这样,我们的仿真代码编写完成,保存。

tb_beep.v

`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_999_99),
    .DO      (18'd19083),
    .RE      (18'd17006),
    .MI      (18'd15151),
    .FA      (18'd14326),
    .SO      (18'd12755),
    .LA      (18'd11363),
    .SI      (18'd10121)
)
beep_inst
(
    .sys_clk  (sys_clk),
    .sys_rst_n(sys_rst_n),
    
    .beep     (beep)
);

endmodule

2.3.5.2 仿真代码编译

回到我们的实验工程,然后添加我们的文件,然后全编译检查语法错误

20231120124007_XRDmSg0Zd09

2.3.5.3 仿真设置

编译通过之后,点击 OK。然后进行仿真设置

20231120124137_e0cCegqyro2

2.3.5.4 波形仿真

然后开始仿真

20231120124254_HIRUtqFqBI3

仿真编译完成之后,打开 sim 选项卡,添加我们的模块波形;然后回到波形界面,全选、分组、消除前缀

20231120124548_aHrva3qrn79

然后点击 Restart,那么这儿有一点要注意:我们的代码当中参数的数值是比较大的,所以说,它的运行时间稍微长一点;我们这儿先设置为 100ms 先运行一次

20231120124703_DVZ4iaAFf70

那么 100ms 运行完成,全局视图。我们已经运行了 100ms,这儿只跑了两个周期,我们需要至少八个周期;那么这儿先改为 400ms,运行一次。那么 400ms 运行完成,我们全局视图

20231120125119_W0asj5gjWr7

来看一下,首先调整一下进制

20231120125713_iFSgLr32wk3

2.3.5.5 波形查看对比

接下来参照我们的绘制波形图,看一下我们的仿真波形。

那么首先看一下变量 cnt

20231120130809_MrgLwoxpsM7

那么变量 cnt 它的波形是没有问题的

那么接下来看一下变量 cnt_500ms

20231120143725_f0U15IwPe53

那么变量 cnt_500ms 它的波形也没有问题
那么下面看一下频率计数器 freq_cnt

20231120144957_9cTbgbLfyn3

那么接下来看一下输出信号 beep

20231120145738_3rSj5UFCrO2

那么这样,模块仿真验证通过。

2.4 上板验证

2.4.1 管脚绑定

我们回到我们的实验工程,开始绑定我们的管脚。蜂鸣器——>J11、时钟信号——>E1、复位信号——>M15

20231120150019_d3KuCJ1vn76

那么这样绑定完成,回到我们的实验工程,全编译

20231120150108_DErapDREN41

2.4.2 结果验证

那么在这个时间,我们可以连接我们的开发板。

开发板连接电源和下载器,那么下载器的另一端连接到我们的电脑

上板验证前的硬件连线

那么编译完成点击 OK。打开下载界面,然后添加 SOF 文件,给我们的开发板上电,点击 Start 下载程序

20231120150535_keaoVPO0dR5

蜂鸣器实验上板验证

那么刚才我们就听到了我们的开发板发出了 Do、Re、Mi、Fa、So、La、Si 循环发声。

那么这样,上板验证成功


参考资料:

18. 无源蜂鸣器驱动实验

29-第二十一讲-无源蜂鸣器驱动实验(一)

30-第二十一讲-无源蜂鸣器驱动实验(二)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值