FPGA学习——蜂鸣器实现音乐播放器并播放两只老虎

一、蜂鸣器简介

1.1 蜂鸣器分类

蜂鸣器一般分为有源蜂鸣器和无源蜂鸣器。二者的区别在于,有源蜂鸣器内部含有振动源和功放电路,只需上电便可发出鸣叫。而无源蜂鸣器内部不含振动源和功放电路,因此我们需要给予其PWM方波,才能驱动无源蜂鸣器正常工作。

1.2 PWM

PWM即脉冲脉宽调制,PWM的占空比是指在一个完整的时钟周期内,高电平所占时间占整个时钟周期的比例(50%占空比即高低电平各占一半时钟周期),为方便代码编写,本项目产生的PWM均为50%占空比。

二、C4开发板原理图

在这里插入图片描述

博主所用开发板为Cyclone Ⅳ开发板,开发板芯片为:EP4CE6F17C8,由开发板原理图可以看出,本开发板蜂鸣器低电平有效。

三、如何产生不同的音调

我们可以通过给予无源蜂鸣器不同频率的PWM方波信号从而实现不同的音调音符。
相关数据参考可见下图:
在这里插入图片描述
举个例子:博主所用开发板晶振为50MHz,而低音Do的频率为262,因此我们需要在一秒内产生50MHz/262次PWM方波信号,此时蜂鸣器变会发出低音Do(如有错误请指正,博主是这样理解的,乐理知识匮乏,请见谅)。

两只老虎的乐谱如下:

在这里插入图片描述

四、代码实现及分析

本次项目较为简单,仅有一个蜂鸣器模块。

源码分析:

  • 本次项目首先需要一个音符计数器,用来存放歌曲中的音符数目,由乐谱可知两只老虎共含有34个音符,因此我们需要一个6位宽的计数器用来计数到34(后续发现好像乐谱中间隔比较大的是空拍?各位可以自行调整)
  • 除此之外,本次项目需要一个节拍计数器,不过这方面也是乐理知识,博主不太懂,两只老虎好像是一秒四拍,所以我们需要设计一个250ms计数器用来记一拍(也就是一个音符播放的时间)
  • 同时我们自然需要一个计数音符频率的计数器。由于不同音符的频率不尽相同,因此我们可以很自然地想到需要引入一个中间信号,通过case第几个音符,给予中间信号不同的频率值。
  • 另外由于本项目生成的PWM均为50%占空比,因此需要一个中间信号duty,duty为音符频率的一半,音符频率计数器小于duty时蜂鸣器输出0,否则输出1.
module beep (
    input       wire        clk     ,
    input       wire        rst_n   ,
    input       wire        beep_en ,

    output      reg         beep     //输出蜂鸣器
);

//内部参数定义
parameter   CYCLE = 26'd50_000_000  ;
parameter   TIME  = 24'd12_500_000  ;
parameter   NUM   = 6'd34           ;
parameter   DOL   = CYCLE/262       ,
            REL   = CYCLE/294       ,
            MIL   = CYCLE/330       ,
            FAL   = CYCLE/349       ,
            SOL   = CYCLE/392       ,
            LAL   = CYCLE/440       ,
            XIL   = CYCLE/494       ,
            DOM   = CYCLE/523       ,
            REM   = CYCLE/587       ,
            MIM   = CYCLE/659       ,
            FAM   = CYCLE/698       ,
            SOM   = CYCLE/784       ,
            LAM   = CYCLE/880       ,
            XIM   = CYCLE/988       ,
            DOH   = CYCLE/1047      ,
            REH   = CYCLE/1175      ,
            MIH   = CYCLE/1319      ,
            FAH   = CYCLE/1397      ,
            SOH   = CYCLE/1568      ,
            LAH   = CYCLE/1760      ,
            XIH   = CYCLE/1967      ;


//内部信号定义
reg    [5:0]        cnt_num     ;//音符个数寄存器
wire                add_cnt_num ;
wire                end_cnt_num ;

reg    [23:0]       cnt_250     ;//一拍时间寄存器
wire                add_cnt_250 ;
wire                end_cnt_250 ;

reg     [17:0]      cnt_frq     ;//音符频率寄存器
wire                add_cnt_frq ;
wire                end_cnt_frq ;


reg     [17:0]      frq         ;//中间信号,存储音符频率
wire    [16:0]      duty        ;//中间信号,用于比较产生50%PWM

//250ms计数器
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        cnt_250 <= 1'b0;
    end
    else if(add_cnt_250)begin
        if(end_cnt_250)begin
            cnt_250 <= 1'b0;
        end
        else begin
            cnt_250 <= cnt_250 + 1'b1;
        end
    end
    else begin
        cnt_250 <= cnt_250;
    end
end

assign add_cnt_250 = beep_en;
assign end_cnt_250 = add_cnt_250 && cnt_250 == TIME - 1'b1;

//音符个数寄存器
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        cnt_num <= 1'b0;
    end
    else if(add_cnt_num)begin
        if(end_cnt_num)begin
            cnt_num <= 1'b0;
        end
        else begin
            cnt_num <= cnt_num + 1'b1;
        end
    end
    else begin
        cnt_num <= cnt_num;
    end
end

assign add_cnt_num = end_cnt_250;
assign end_cnt_num = add_cnt_num && cnt_num == NUM - 1'b1;


//音符频率计数器
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        cnt_frq <= 1'b0;
    end
    else if(add_cnt_frq)begin
        if(end_cnt_frq || end_cnt_250)begin
            cnt_frq <= 1'b0;
        end
        else begin
            cnt_frq <= cnt_frq + 1'b1;
        end
    end
    else begin
        cnt_frq <= cnt_frq;
    end
end

assign add_cnt_frq = beep_en;
assign end_cnt_frq = add_cnt_frq && cnt_frq == frq - 1'b1;

//音频赋值
always@(*)begin
    case(cnt_num)
    6'd0     :   frq = DOM;
    6'd1     :   frq = REM;
    6'd2     :   frq = MIM;
    6'd3     :   frq = DOM;
    6'd4     :   frq = DOM;
    6'd5     :   frq = REM;
    6'd6     :   frq = MIM;
    6'd7     :   frq = DOM;
    6'd8     :   frq = MIM;
    6'd9     :   frq = FAM;
    6'd10    :   frq = SOM;
    6'd11    :   frq = MIM;
    6'd12    :   frq = FAM;
    6'd13    :   frq = SOM;
    6'd14    :   frq = SOM;
    6'd15    :   frq = LAM;
    6'd16    :   frq = SOM;
    6'd17    :   frq = FAM;
    6'd18    :   frq = MIM;
    6'd19    :   frq = DOM;
    6'd20    :   frq = SOM;
    6'd21    :   frq = LAM;
    6'd22    :   frq = SOM;
    6'd23    :   frq = FAM;
    6'd24    :   frq = MIM;
    6'd25    :   frq = DOM;
    6'd26    :   frq = REM;
    6'd27    :   frq = SOL;
    6'd28    :   frq = DOM;
    6'd29    :   frq = 0  ;
    6'd30    :   frq = REM;
    6'd31    :   frq = SOL;
    6'd32    :   frq = DOM;
    6'd33    :   frq = 0  ;
    default  :   frq = 0  ;
    endcase
end

//beep输出赋值
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        beep <= 1'b1;
    end
    else if(cnt_frq < duty && beep_en)begin
        beep <= 1'b0;
    end
    else begin
        beep <= 1'b1;
    end
end

//生成占空比为50%的音频PWM方波的比较信号
assign duty = frq >> 1;

endmodule

五、总结

本项目较为简单,基本是在做计数器的练习和PWM方波信号产生练习,希望大家能够掌握。

如有没有讲清楚的地方还请大家指正。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值