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、仿真波形图

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

相关推荐
a4962986855 小时前
Xilinx FPGA的Bitstream比特流加密设置方法
fpga开发·代码保护·xilinx
乐闻x6 小时前
VSCode 插件开发实战(十四):创建交互式引导教程
ide·vscode·编辑器
LabVIEW开发6 小时前
LabVIEW条件配置对话框
fpga开发·labview数学
乐闻x9 小时前
VSCode 插件开发实战(十三):如何添加个性化欢迎信息
ide·vscode·编辑器
szxinmai主板定制专家12 小时前
【国产NI替代】基于全国产FPGA的16振动+2转速+8路IO口输入输出(24bits)256k采样率,高精度终端采集板卡
大数据·人工智能·fpga开发
高性能服务器12 小时前
《异构计算:多元算力聚变,点燃高性能计算新引擎 – CPU、GPU与FPGA算力融合》
深度学习·fpga开发·gpu算力·hpc·高性能计算·异构计算·通用计算
floenrce14 小时前
vscode remote-ssh 免密登录不生效的问题
vscode·ubuntu·ssh
mahuifa14 小时前
windows下vscode使用msvc编译器出现中文乱码
windows·vscode·中文乱码·msvc
涛ing14 小时前
Vscode + gdbserver远程调试开发板指南:
ide·vscode·编辑器
热爱生活的五柒14 小时前
vscode打开下一个文件的时候上一个文件会关闭
ide·vscode·编辑器