进阶项目(4)蜂鸣器程序设计讲解

写在前面的话

经过前面内容的学习,梦翼师兄相信大家的基础知识水平一定已经很扎实。那么本节,我们就一起来庆祝一下,用播放器奏响一曲《欢乐颂》,奏响我们凯旋的乐章。

什么是蜂鸣器

蜂鸣器是一种一体化结构的电子讯响器,采用直流电压供电,广泛应用于计算机、打印机、电子玩具、定时器等电子产品中作为发声器件。蜂鸣器分为有源蜂鸣器和无源蜂鸣器两种,在电路中用字母“H”或“HA”(旧标准用“FM”、“ZZG”、“LB”、“JD”等)表示。那么,怎么区分有源蜂鸣器和无源蜂鸣器呢?有源蜂鸣器内部带震荡源,所以只要一通电就会叫;而无源内部不带震荡源,所以如果用直流信号无法令其鸣叫。必须用2K-5K的方波去驱动它,有源蜂鸣器往往比无源的贵,就是因为里面多个震荡电路。无源蜂鸣器的优点是:

1. 便宜;

2. 声音频率可控,可以做出“多来米发索拉西”的效果;

3. 在一些特例中,可以和LED复用一个控制口

有源蜂鸣器的优点是:程序控制方便。

在FPGA应用的设计上,很多方案都会用到蜂鸣器,大部分都是使用蜂鸣器来做提示或报警,比如按键按下、开始工作、工作结束或是故障等等。当然,我们也可以让它为我们演奏喜欢的音乐。在设计之前,我们先来了解一下声音是怎么播放出来的。我们在本次设计中,用到的是一个无源蜂鸣器,如下图所示:

由于蜂鸣器的工作电流一般比较大,以至于FPGA的I/O 口是不能很好地直接驱动它的,所以要利用三极管的开关特性来引入电源电压信号,我们知道无源蜂鸣器的主要特点是内部不带振荡源,所以如果使用直流信号是无法驱动无源蜂鸣器鸣叫的,因此必须使用方波去驱动它。

现在我们明白了,只要给蜂鸣器发送一定频率的方波,就可以使得蜂鸣器发出声音,然而现在的问题就转化为-我们究竟要给蜂鸣器发送什么频率的方波信号呢?具体的频率可以查表如下:

现在我们知道了如何让蜂鸣器发出声音,又知道发送多大的频率可以让蜂鸣器响起什么样的声音,所以我相信我们已经有能力让蜂鸣器响起我们需要的音乐了。

设计任务

下面我们用FPGA来设计一个蜂鸣器的播放器,梦翼师兄开发板上的晶振是50Mhz的,所以我们需要一个锁相环模块(PLL)来得到低频率的时钟,然后再用这个比较低的频率来分出所需要的频率来驱动蜂鸣器。有了锁相环(PLL)后,我们还需要一个空间来保存乐谱,由于乐谱是确定的,不需要更改,所以我们选择一个简单的存储器就可以了,这里我们使用的是 ROM,可以直接通过IP 核来创建。

既然是播放音乐,那么就需要节拍(一般为 4 拍),因此我们还需要一个节拍控制器, 例如,我们现在需要发出一个低音 1 并维持一秒,那怎么办呢? 我们可以设计一个模块,控制每 0.25s, ROM 的地址加一,若要使发出的低音 1 维持 1 秒 钟,我们仅需在 ROM 的四个连续地址中写入低音 1 的对应信息即可

具体的设计架构图如下所示:

由上图可知,整个FPGA设计里面一共有6个模块,其中PLL模块,用来将50Mhz的晶振时钟信号分频得到1Mhz的系统时钟。ROM模块用来存储想要播放的音乐数据,time_counter模块是一个计数器模块,当计数到0.25秒之后,time_counter输出的time_finsh信号会变一个时钟周期的高电平,发送给 addr_gen模块。当addr_gen接收到这个高电平之后,就会给Rom发送一个地址信号,让ROM输出数据,简单来说,就是在0.25秒之后,把ROM的地址加一,读取下一个数据。 decode解码模块,是把从Rom中读出来的数据解码然后发送到music_gen模块,产生特定的方波频率。

为了方便我们往ROM里保存数据,比如保存的数据是8’hAB,其中A设定只有3个值,分别是1,2,4来表示低音、中音和高音,而B设定有7个值,分别是1,2,3,4,5,6,7,比如现在要产生一个低音1,仅仅需要在ROM里面写8’h11,需要产生一个高音5,则需要在ROM里面写8’h45依次类推。但是实际上,你要产生一个低音1,然后把ROM里面的8’h11发给music_gen模块是不行的,因为在ROM里面的数据是为了方便我们才定义成这样的格式,所以我们这里需要一个decode模块(解码模块)来把ROM的数据还原成music_gen所需要的数据。

time_counter的功能很简单,就不断的计数,当计数到249_999(1Mhz/4Hz=250_000-1,4Hz=0.25S)的时候数据选择器选择18’d0,让count归零,然后又继续累加,直到计数到249_999,不断这样循环。

下面附上一首《欢乐颂》的ROM数据:

端口介绍

顶层端口及其意义如下:


端口名


端口说明


clk_50M


系统50MHz时钟输入


rst_n


系统低电平复位


beep_out


蜂鸣器输入

内部连线及其意义如下


端口名


端口说明


clk_1M


系统驱动时钟


time_finish


0.25s节拍定时标志


addr


Rom读数据地址


rom_data


解码前音符编码数据


music_data


分频后解码预置数

代码解释

time_counter 模块


/****************************************************

*   Engineer      :   梦翼师兄

*   QQ             :   761664056

*   The module function : 产生节拍定时模块 *****************************************************/

01  module time_counter(

02                  clk,        //输入1Mhz时钟信号

03                  rst_n,      //输入复位信号

04                  time_finsh  //输出时间计数标志位(没0.25s变高电平一次)

05                  );

06

07      input clk, rst_n;       //输入1Mhz时钟信号,复位信号

08      output time_finsh;      //输出时间计数标志位(没0.25s变高电平一次)

09

10      reg [17:0]count;        //计数器count

11

12      always@(posedge clk or negedge rst_n)

13      begin

14          if(!rst_n)

15              count <= 18‘d0;     //计数器复位

16          else if(time_finsh)

17              count <= 18‘d0;     //每到0.25s计数器归零

18          else

19              count <= count + 1‘d1;  //未到0.25s,计数器继续累加

20      end

21

22      //每到0.25s,time_finsh拉高,表示已经达到0.25s

23      assign time_finsh = (count == 18‘d249_999)? 1‘d1 : 1‘d0;

24      //用于仿真,因为真正的0.25s会仿真很长

25      //assign time_finsh =   (count == 22‘d25_00)? 1‘d1 : 1‘d0;

26

27  endmodule

这个模块是一个计数模块,每次当计数到0.25s的时候,计数器清零,并输出一个周期的高电平用作标志信号。

addr_gen模块


/****************************************************

*   Engineer      :   梦翼师兄

*   QQ             :   761664056

*   The module function : 地址发生器模块 *****************************************************/

01  module addr_gen(

02                  clk,        //输入1Mhz时钟信号

03                  rst_n,      //输入复位信号

04

05                  addr,       //输出给ROM的地址信号

06                  time_finsh  //输入时间计数标记位(每0.25s变高电平一次)

07                  );

08

09      input clk, rst_n;   //输入1Mhz时钟信号,复位信号

10      input time_finsh;   //输入时间计数标记位(每0.25s变高电平一次)

11

12      output reg [6:0]addr;   //输出给ROM的地址信号

13

14      always@(posedge clk or negedge rst_n)

15      begin

16          if(!rst_n)

17              addr <= 7‘d0;   //输出给ROM的地址信号复位

18          else if(time_finsh)

19              addr <= addr + 1‘d1;

20          else   //输出给ROM的地址信号自加1(每0.25s自加1)

21              addr <= addr;   //未够0.25s,ROM的地址信号不变

22      end

23

24  endmodule

这个模块功能是当接收到计数到0.25s的标志信号time_finish之后,输出一个新的地址,没有接受到标志信号的时候,输出地址不变。

decode模块


/****************************************************

*   Engineer      :   梦翼师兄

*   QQ             :   761664056 *   The module function : 音符数据解码模块 *****************************************************/

01  module decode(

02              clk,        //输入1Mhz时钟信号

03              rst_n,      //输入复位信号

04

05              rom_data,   //输入的ROM的数据

06              music_data  //输出ROM的解码数据

07              );

08

09  input clk, rst_n;       //输入1Mhz时钟信号,复位信号

10  input [7:0]rom_data;    //输入的ROM的数据

11

12  output reg [10:0]music_data;  //输出ROM的解码数据

13

14  always@(posedge clk or negedge rst_n)

15  begin

16      if(!rst_n)

17          music_data  <=  11‘d0;  //输出ROM的解码数据复位

18      else case (rom_data)

19      8‘h11 : music_data <= 11‘d1911; //(1Mhz/261.63Hz)/2)=1191 低音1

20      8‘h12 : music_data <= 11‘d1702; //(1Mhz/293.67Hz)/2)=1702 低音2

21      8‘h13 : music_data <= 11‘d1517; //(1Mhz/329.63Hz)/2)=1517 低音3

22      8‘h14 : music_data <= 11‘d1431; //(1Mhz/349.23Hz)/2)=1431 低音4

23      8‘h15 : music_data <= 11‘d1276; //(1Mhz/391.99Hz)/2)=1276 低音5

24      8‘h16 : music_data <= 11‘d1136; //(1Mhz/440.00Hz)/2)=1136 低音6

25      8‘h17 : music_data <= 11‘d1012; //(1Mhz/493.88Hz)/2)=1012 低音7

26

27      8‘h21 : music_data <= 11‘d939;  //(1Mhz/532.25Hz)/2)=939  中音1

28      8‘h22 : music_data <= 11‘d851;  //(1Mhz/587.33Hz)/2)=851  中音2

29      8‘h23 : music_data <= 11‘d758;  //(1Mhz/659.25Hz)/2)=758  中音3

30      8‘h24 : music_data <= 11‘d716;  //(1Mhz/698.46Hz)/2)=716  中音4

31      8‘h25 : music_data <= 11‘d638;  //(1Mhz/783.99Hz)/2)=638  中音5

32      8‘h26 : music_data <= 11‘d568;  //(1Mhz/880.00Hz)/2)=568  中音6

33      8‘h27 : music_data <= 11‘d506;  //(1Mhz/987.76Hz)/2)=506  中音7

34

35      8‘h41 : music_data <= 11‘d478;  //(1Mhz/1046.50Hz)/2)=478 高音1

36      8‘h42 : music_data <= 11‘d425;  //(1Mhz/1174.66Hz)/2)=425 高音2

37      8‘h43 : music_data <= 11‘d379;  //(1Mhz/1318.51Hz)/2)=379 高音3

38      8‘h44 : music_data <= 11‘d358;  //(1Mhz/1396.51Hz)/2)=358 高音4

39      8‘h45 : music_data <= 11‘d319;  //(1Mhz/1567.98Hz)/2)=319 高音5

40      8‘h46 : music_data <= 11‘d284;  //(1Mhz/1760.00Hz)/2)=284 高音6

41      8‘h47 : music_data <= 11‘d253;  //(1Mhz/1975.52Hz)/2)=253 高音7

42

43      8‘h00 : music_data <= 11‘d0;    //0HZ,停止节拍

44      endcase

45  end

46

47  endmodule

这个模块是把从ROM中读出来的数据进行解码并输出,其实就是一个数据选择器,每一个音符对应一个解码数据,那么这个解码数据是怎么计算的呢?比如高音5,从ROM中读出来的数据是45,它的方波驱动频率是 1567.98Hz,一个周期是637763ns,本模块的时钟频率是1MHz一个周期是1000ns,所以需要计数637763/1000=637.763次,因为是方波,我们取637.763的一半318.8,这里我们取319就可以产生频率是1567.98Hz的方波了。

music_gen模块


/****************************************************

*   Engineer      :   梦翼师兄

*   QQ             :   761664056

*   The module function : 驱动频率输出模块 *****************************************************/

01  module music_gen(

02                  clk,        //输入1Mhz时钟信号

03                  rst_n,      //输入复位信号

04

05                  music_data, //输入音乐频率控制字

06                  beep         //输出方波

07                  );

08

09      input clk, rst_n;        //输入1Mhz时钟信号,复位信号

10      input [10:0] music_data; //输入音乐频率控制字

11

12      output reg beep;        //输出方波

13

14      reg [10:0] data, count; //寄存音乐控制字的data,计数器count

15

16      always@(posedge clk or negedge rst_n)

17      begin

18          if(!rst_n)

19              data <= 11‘d0;      //寄存器data复位

20          else

21              data <= music_data; //data寄存音乐控制字

22      end

23

24      always@(posedge clk or negedge rst_n)

25      begin

26          if(!rst_n)

27              begin

28                  count <= 11‘d1;     //计数器复位

29                  beep <= 1‘d0;       //输出方波复位

30              end

31          else if(data == 11‘d0)      //当data==11‘d0,(停止节拍)

32              begin

33                  count <= 11‘d1;     //计数器归一

34                  beep <= 1‘d0;       //输出方波归零

35              end

36          else if(count <= data)      //当计数器小于等于data的值

37              count <= count + 1‘d1;  //计数器继续累加

38          else

39              begin

40                  count <= 11‘d1;   //当计数器大于data的值,计数器归一

41                  beep <= ~beep;    //输出方波取反

42              end

43      end

44

45  endmodule

在music_gen模块中,music_data的值会寄存在data寄存器里面,当data=11’d0(停止节拍)的时候,计数器count归1,输出的方波信号beep归零,当data!=11’d0的时候,就判断当前计数器的值是否小于等于data的值,如果计数器的值小于等于data的值,计数器继续累加,输出的方波信号beep不变,当计数器的值大于等于data的值,计数器归1,输出的方波信号beep取反,通过改变data的值来改变输出的方波频率。

顶层模块


/****************************************************

*   Engineer      :   梦翼师兄

*   QQ             :   761664056

*   The module function : 顶层模块 *****************************************************/

01  module beep(

02              clk,        //输入50Mhz时钟信号

03              rst_n,      //输入低电平复位信号

04

05              beep_data   //输出的方波

06              );

07

08      input clk, rst_n;        //输入50Mhz时钟信号,复位信号

09

10      output beep_data;        //输出的方波

11

12      wire clk_1m, time_finsh; //1Mhz时钟信号线,0.25s时间计数标记位

13      wire [6:0]addr;          //ROM地址线

14      wire [7:0]rom_data;      //ROM数据线

15      wire [10:0]music_data;   //ROM数据解码数据线

16

17      my_pll my_pll(              //PLL模块

18                  .areset(!rst_n),

19                  .inclk0(clk),

20                  .c0(clk_1m)

21                    );

22

23      my_rom my_rom(              //ROM模块

24                  .address(addr),

25                  .clock(clk_1m),

26                  .q(rom_data)

27                    );

28

29      decode decode(        //解码模块

30                  .clk(clk_1m),

31                  .rst_n(rst_n),

32                  .rom_data(rom_data),

33                  .music_data(music_data)

34                      );

35

36      music_gen music_gen(      //音乐发生器模块

37                          .clk(clk_1m),

38                          .rst_n(rst_n),

39                          .music_data(music_data),

40                          .beep(beep_data)

41                          );

42

43      time_counter time_counter(  //0.25s时间计数器模块

44                          .clk(clk_1m),

45                          .rst_n(rst_n),

46                          .time_finsh(time_finsh)

47                          );

48

49      addr_gen addr_gen(      //ROM地址发生器

50                      .clk(clk_1m),

51                      .rst_n(rst_n),

52                      .addr(addr),

53                      .time_finsh(time_finsh)

54                      );

55

56  endmodule

编写完可综合代码之后,查看RTL视图如下:

由RTL视图可以看出,代码综合生成的电路和我们设计的系统框图一致,说明顶层连接关系正确,接下来编写测试代码如下:


/****************************************************

*   Engineer      :   梦翼师兄

*   QQ             :   761664056

*   The module function : 测试模块 *****************************************************/

01  `timescale 1ns/1ps  //仿真时间单位是ns,精度是ps

02  module beep_tb;

03

04  reg clk, rst_n;     //仿真激励的时钟与复位信号

05

06  wire beep_data;     //输出的方波信号

07

08  initial

09      begin

10          clk = 0;            //时钟信号初始化

11          rst_n = 0;          //复位信号有效

12          #200.1 rst_n=1;    //复位结束

13      end

14

15  always #10 clk = ~clk;  //产生50Mhz时钟信号

16

17  beep beep(          //把激励信号送进beep模块

18          .clk(clk),

19          .rst_n(rst_n),

20          .beep_data(beep_data)

21          );

22

23  endmodule

仿真分析

由仿真波形可知,当ROM输出的数据q等于23的时候,代表着输出中音3,解码后的结果等于758,输出的方波周期等于1.518ms,方波频率等于658.76Hz,约等于中音3的659.25Hz;当ROM输出的数据q等于00的时候,代表着输出停止节拍,解码后的结果等于0,输出一个低电平;当ROM输出的数据q等于24的时候,代表着输出中音4,解码后的结果等于716,输出的方波周期等于1.434ms,等于697.35Hz,约等于中音4的698.46Hz,虽然有误差存在,但并不影响音乐播放器的播放效果,若大家想把精度做的更高一点,可以把锁相环的输出时钟频率1Mhz增大,时钟频率越高,分频精度就越高,误差就越小。

原文地址:https://www.cnblogs.com/mengyi1989/p/11518282.html

时间: 2024-10-31 18:22:57

进阶项目(4)蜂鸣器程序设计讲解的相关文章

【思库教育】2017PHP项目实战基础+进阶+项目之基础篇

下载链接: [思库教育]2017PHP项目实战基础+进阶+项目之基础篇 小白变大牛,您的专属资源库! 小白变大牛,您的专属资源库! 内容非常充实,可以看目录,设计的面多,项目多,技能多,如果掌握好,找一份PHP的工作,易如反掌!学完后可以到PHP小白变大牛精华区查找更加符合你的资源或者项目! [思库教育]2017PHP项目实战基础+进阶+项目之基础篇[思库教育]2017PHP项目实战基础+进阶+项目之进阶篇[思库教育]2017PHP项目实战基础+进阶+项目之项目篇小白变大牛!Python小白,J

重磅回归-SSM整合进阶项目实战之个人博客系统

历经一个多月的重新设计,需求分析以及前后端开发,终于有了一定的输出:我自己实现的spring4+springmvc+mybatis3整合的进阶项目实战-个人博客系统 已然完成了,系统采用mvc三层模式进行整体的开发,涉及到技术一下子很难全部列出,其中不得不提的有:整合shiro实现登录安全认证,整合lucene实现全文信息检索,基于Spring的事件驱动模型实现业务服务模块之间的异步解耦(在RabbitMQ视频教程中我也会重提这个技术点!),爬虫框架Jsoup解析html文本中的图片,整合ued

android项目的目录结构讲解

参考书籍:<第一行代码Android> 一:android项目的目录结构讲解 1..gradle和.idea ? ??? ? 这两个目录下放置的都是Android Studio自动生成的一些文件,我们无须关心,也不要去手动编辑. 2.app ? ??? ? 项目中的代码.资源等内容几乎都是放置在这个目录下的,我们后面的开发工作也基本都是在这个目录下进行的,待会儿还会对这个目录单独展开进行讲解. 3.build ? ??? ? 这个目录你也不需要过多关心,它主要包含了一些在编译时自动生成的文件.

进阶项目(3)UART串口通信程序设计讲解

写在前面的话 UART串行接口简称串口,是我们各类芯片最常用的一种异步通信接口,通过串口我们就可以建立起计算机和我们实验板之间的通信和控制关系,也就是我们通常所说的上下位机通信.串口可以说是不同平台互相通信.控制的一个最基本的接口. 项目需求 设计一个UART控制器,当控制器从上位机接收到数据以后,马上将数据输出,发送回上位机,完成“回环测试”.  UART的原理分析 要实现UART通信,首先我们需要用到一个外部的电平转换芯片MAX232,其具体配置电路如下: 注解: MAX232芯片是美信(M

进阶项目(11) 矩阵键盘程序设计讲解

写在前面的话 在使用按键的时候,如果按键不多的话,我们可以直接让按键与FPGA相连接,但是如果按键比较多的时候,如果还继续使用直接让按键与FPGA相连接的话,会大量增加FPGA端口的消耗,为了减少FPGA端口的消耗,我们可以把按键设计成矩阵的形式.接下来,梦翼师兄将和大家一起学习扫描键盘的电路原理以及驱动方式. 项目需求 设计4*4矩阵键盘按键扫描模块,正确解析按键值. 矩阵键盘的原理 由上图可以知道,矩阵键盘的行row(行)与col(列)的交点,都是通过一个按键相连接.传统的一个按键一个端口的

进阶项目(12)PS2键盘驱动程序设计讲解

写在前面的话 我们从小就开始接触电脑,曾经多么羡慕那些在键盘上洋洋洒洒的人,手指轻柔的飞舞,刻画出一章章美丽的篇幅…那么作为工程师的我们,同样拥有着属于我们的情怀.如果曾经的向往变成我们喜欢的玩具:如果曾经的神秘变成我们夜以继日的痴迷.那么,一切又将如何?梦翼师兄携手大家一起来欣赏.来品味. 项目需求 设计一个ps2键盘的接口驱动电路. 原理分析 ps2的接口如下图所示: 其中,1是数据线DATA: 2是预留N/C: 3是GND: 4是VCC(+5V): 5是时钟信号线CLK: 6是预留N/C:

进阶项目(7)VGA显示程序设计讲解

 写在前面的话 可能大家觉得之前设计都是各种看波形,比较的单一.乏味.那么本节的内容,一定可以带给大家全新的感受,现代电子技术中,图像处理技术可谓是发展迅猛,其带给大家独特的视觉感受也总是那么的赏心悦目.这里,梦翼师兄和大家一起敲开图像世界的大门,让我们一起为之痴迷,为之陶醉. 什么是VGA? VGA(Video Graphics Array,视频图形阵列),是 IBM 于1987年提出的一个使用类比讯号的电脑显示标准.这个标准已对于现今的个人电脑市场已经十分过 时.即使如此,VGA 仍然是很多

基础项目(6)基于尖峰脉冲的按键消抖程序设计讲解

 写在前面的话 我们通常所用的按键开关为机械弹性开关,当机械触点断开.闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会马上断开.因而在闭合及断开的瞬间均伴随有一连串的抖动,为了避免这种现象造成的干扰而作的措施就是按键消抖. 抖动时间的长短由按键的机械特性决定,一般为5ms-10ms.按键稳定闭合时间的长短则是由操作人员的按键动作决定的,一般为零点几秒至数秒.键抖动会引起一次按键被误读多次.为确保智能单元对按键的一次闭合仅作一次处理,必须消除键抖动.在按键闭合

进阶项目(6)LCD12864液晶屏幕设计讲解

 写在前面的话 液晶(LCD)显示具有功耗低.体积小.重量轻.超薄等许多其他显示器无法比拟的优点,近几年被广泛应用于FPGA控制的智能仪器.仪表和低功耗的电子产品中.LCD可分为段位式LCD.字符式LCD和点阵式LCD.其中段位式LCD和字符式LCD只能用于字符和数字的简单显示,不能满足图像曲线和汉字显示的要求:而点阵式LCD不仅可以显示字符.数字,还可以显示各种图形.曲线及汉字,并且可以实现屏幕上下左右滚动.动画功能.分区开窗口.反转.闪烁等功能,用途十分广泛. 基本概念 LCD12864 是