FPGA开发——蜂鸣器实现音乐播放器的设计

一、概述

我们在进行蜂鸣器的学习的时候,总会在想既然蜂鸣器能够发出声音,那么它能够播放音乐吗,今天这篇我们文章我们就一起来学习怎样使用使用蜂鸣器来播放音乐,也就是怎样成为一个音乐播放器。

1、蜂鸣器的类型

在设计的时候其实蜂鸣器的类型也对我们最后所实现的效果有一点影响,蜂鸣器分为有源和无源两种,有源蜂鸣器的器件内部本身就带有振荡器,本身来讲完全不用我们进行外部振荡,二无源蜂鸣器是不带振荡器的,所以当涉及到相关频率使用时就需要我们使用外部振荡。这里因为我们使用PWM进行设计实现,所以如果有条件的话使用无源蜂鸣器所实现的效果会更好。

2、PWM介绍

PWM(Pulse Width Modulation),即脉冲宽度调制,是一种模拟信号电平数字编码方法。它通过将有效的电信号分散成离散形式,从而来降低电信号所传递的平均功率。PWM技术的核心在于通过调节脉冲的时间宽度(占空比)来等效地获得所需要合成的相应幅值和频率的波形。通过调整PWM信号的占空比和频率,可以实现声音的音量调节和音调变化。

  • 占空比:占空比是脉冲处于较高电压的时间占整个脉冲周期的百分比。通过改变占空比,可以实现对信号、能量等的调节。例如,在LED电路中,高占空比意味着LED非常明亮,而低占空比则意味着LED较为暗淡。
  • 频率:频率是单位时间内脉冲信号的次数。PWM信号的频率越高,控制效果越接近模拟信号,但也会受到电路响应时长的限制。

3、PWM调制的实现方式

硬件实现 :许多微控制器和数字信号处理器(DSP)已包括了PWM控制器芯片,因此可以更轻松地实施数字化控制。这些芯片通过内部定时器或计数器来生成PWM信号,并通过调整相关参数来改变占空比和频率。
软件实现:在没有内置PWM控制器的系统中,也可以通过软件编程来模拟PWM信号。这通常涉及到对定时器中断的精确控制,以及在高电平和低电平之间快速切换IO口的输出状态。

4、音符的相关设计

这是关于高中低音符的频率和周期表,我们将会参考这个对代码进行设计。

二、工程实现

1、本次播放的音乐

本次我们设计得比较简单,设计了一个《我有一头小毛驴》的简单音乐播放器,具体乐谱如下:

2、基本构建思路

在本次的设计中,采用了一个音符播放播放0.5秒的设计,针对上述乐谱,采用四个字符为一组的方式进行划分,便于代码的赋值。利用三个计数器完成对于音乐播放的相关设计,第一个播放器用于技术每个音符技术的相关频率,第二个计数器用于对于在0.5秒播放时间内对于每个音符重复的次数进行计数,而第三个计数器用于对音符的播放顺序进行一个计数,由此关于音符的设置就设计完成,最后就是对于蜂鸣器进行占空比调制,对每个音符的频率进行一个8分频就完成了最终功能。

3、设计文件的编写

这里新建一个beep.v文件,如下:

cpp 复制代码
//蜂鸣器
module beep(
  input         clk,
  input         rst_n,
  output    reg    beep_out
);
parameter CLK_CLY=50_000_000;//时钟频率
localparam  H1  =CLK_CLY/523,
            H1_L=CLK_CLY/262,
            H1_H=CLK_CLY/1047,
            H2  =CLK_CLY/587,
            H3  =CLK_CLY/659,
            H4  =CLK_CLY/698,
            H5  =CLK_CLY/784,
            H6  =CLK_CLY/880,
            H7  =CLK_CLY/988;


parameter TIME_500MS=25_000_000;//0.5秒

reg [17:0] cnt0;// 每个音符频率计数器
wire       add_cnt0;
wire       end_cnt0;

reg [9:0] cnt1;// 0.5秒内音符周期重复个数
wire       add_cnt1;
wire       end_cnt1;

reg  [5:0] cnt2;// 计数音符播放顺序
wire       add_cnt2;
wire       end_cnt2;
reg     [15:0] cnt_max;

reg   [17:0]  display;//音符计数器的最大值
//cnt0计数
always @(posedge clk or negedge rst_n)begin 
  if(!rst_n)
    cnt0<=0;
  else if(add_cnt0)begin 
    if(end_cnt0)
      cnt0<=0;
    else
      cnt0<=cnt0+1'b1;
  end
end 
assign add_cnt0=1'b1;
assign end_cnt0=add_cnt0 &&(cnt0==display-1);

//cnt1计数
always @(posedge clk or negedge rst_n)begin 
  if(!rst_n)
    cnt1<=0;
  else if(add_cnt1)begin 
    if(end_cnt1)
      cnt1<=0;
    else
      cnt1<=cnt1+1'b1;
  end
end 
assign add_cnt1=end_cnt0;
assign end_cnt1=add_cnt1 &&(cnt1==(TIME_500MS/display-1));

//cnt2计数
always @(posedge clk or negedge rst_n)begin 
  if(!rst_n)
    cnt2<=0;
  else if(add_cnt2)begin 
    if(end_cnt2)
      cnt2<=0;
    else
      cnt2<=cnt2+1'b1;
  end
end 
assign add_cnt2=end_cnt1;
assign end_cnt2=add_cnt2 &&(cnt2==50-1);

always @(posedge clk or negedge rst_n)begin
  if(!rst_n)
    display <=H1;
  else begin
    case (cnt2)
       0:display <=H1_L;
       1:display <=H1;
       2:display <=H1;
       3:display <=H3;

       4:display <=H5;
       5:display <=H5;
       6:display <=H5;
       7:display <=H5;
       
       8:display <=H6;
       9:display <=H6;
      10:display <=H6;
      11:display <=H1_H;

      12:display <=H5;
      13:display <=H5;
      14:display <=H5;
      15:display <=H5;

      16:display <=H4;
      17:display <=H4;
      18:display <=H4;
      19:display <=H6;

      20:display <=H3;
      21:display <=H3;
      22:display <=H3;
      23:display <=H3;

      24:display <=H2;
      25:display <=H2;
      26:display <=H2;
      27:display <=H2;

      28:display <=H5;
      29:display <=H5;
      30:display <=H5;
      31:display <=H5;

      32:display <=H1;
      33:display <=H1_L;
      34:display <=H1;
      35:display <=H3;

      36:display <=H5;
      37:display <=H5;
      38:display <=H5;
      39:display <=H5;

      40:display <=H6;
      41:display <=H6;
      42:display <=H6;
      43:display <=H1_H;

      44:display <=H5;
      45:display <=H5;
      46:display <=H5;
      47:display <=H5;

      40:display <=H4;
      41:display <=H4;
      42:display <=H4;
      43:display <=H6;

      44:display <=H3;
      45:display <=H3;
      46:display <=H3;
      47:display <=H3;
      40:display <=H3;
      41:display <=H3;

      42:display <=H2;
      43:display <=H2;
      44:display <=H2;
      45:display <=H3;

      46:display <=H1;
      47:display <=H1;
      48:display <=H1;
      49:display <=H1;
      default: display <=H1;
    endcase
  end
end

//pwm输出
//蜂鸣器pwm
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        beep_out <= 1'b1;
    end
    else if(cnt0 == (display>>3))begin//设置占空比
        beep_out <= 1'b1;
    end
    else if(cnt0==0)begin
        beep_out<= 1'b0;
    end
end
endmodule 

4、测试文件的编写

新建beep_tb.v文件,这里出来蜂鸣器输出没有任何激励,所以我们只需要进行时钟和复位进行赋值就行。如下:

cpp 复制代码
//定义时间尺度
`timescale 1ns/1ns
module beep_tb ;

//输入信号定义
reg          clk           ;  
reg          rst_n         ; 
wire         beep_out       ;
//模块例化
beep beep_inst(
  /*input        */ .clk        (clk      )   ,
  /*input        */ .rst_n      (rst_n    )   ,
  /*output       */ .beep_out    (beep_out  )   
);

//激励信号产生
parameter CLK_CYC = 20;
//时钟
initial clk=1;
always #(CLK_CYC/2)clk=~clk;

//复位
initial begin
    rst_n= 1'b0;
    #(CLK_CYC*2);
    #3;//复位结束避开时钟上升沿
    rst_n= 1'b1;
end
endmodule

5、仿真波形图

通过对于波形图的观察,我们可以看到在三个计数器的不断计数之下蜂鸣器的输出波形占空比在不断进行变化 ,并且在经过对于计数器进行检查后没有发现什么问题。(这里方针波形图太长,没有截全),感兴趣的可以去自行进行仿真观察。最后进行下板验证之后,蜂鸣器也进行了正常播放,我们的设计成功。

相关推荐
jieshenai2 小时前
使用VSCode远程连接服务器并解决Neo4j无法登陆问题
服务器·vscode·neo4j
ahadee2 小时前
蓝桥杯每日真题 - 第11天
c语言·vscode·算法·蓝桥杯
apple_ttt3 小时前
SystemVerilog学习——虚拟接口(Virtual Interface)
fpga开发·fpga·systemverilog·uvm
ahadee7 小时前
蓝桥杯每日真题 - 第7天
c++·vscode·算法·蓝桥杯
牙买加_小杨10 小时前
用vscode编写verilog时,如何有信号定义提示、信号定义跳转(go to definition)、模块跳转这些功能
ide·vscode·编辑器
南郁10 小时前
06.VSCODE:备战大项目,CMake专项配置
ide·vscode·编辑器
微凉的衣柜12 小时前
使用 VS Code 远程连接时解决 OpenSSL 版本不匹配及权限问题
服务器·vscode·ubuntu
学习路上_write13 小时前
FPGA/Verilog,Quartus环境下if-else语句和case语句RT视图对比/学习记录
单片机·嵌入式硬件·qt·学习·fpga开发·github·硬件工程
jjjxxxhhh12314 小时前
FPGA,使用场景,相比于单片机的优势
单片机·嵌入式硬件·fpga开发