加入星計劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴散
  • 作品版權(quán)保護
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 設(shè)計背景
    • 設(shè)計原理
    • 設(shè)計框架
    • 設(shè)計代碼
    • 仿真測試
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

源碼系列:基于FPGA的音樂蜂鳴器設(shè)計(附源工程)

15小時前
347
閱讀需 19 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

大俠好,歡迎來到FPGA技術(shù)江湖,江湖偌大,相見即是緣分。大俠可以關(guān)注FPGA技術(shù)江湖,在“闖蕩江湖”、"行俠仗義"欄里獲取其他感興趣的資源,或者一起煮酒言歡。

今天給大俠帶來基于FPGA的音樂蜂鳴器設(shè)計,附源碼,獲取源碼,請在“FPGA技術(shù)江湖”公眾號內(nèi)回復(fù)“音樂蜂鳴器設(shè)計源碼”,可獲取源碼文件。話不多說,上貨。

設(shè)計背景

蜂鳴器是一種一體化結(jié)構(gòu)的電子訊響器,采用直流電壓供電,廣泛應(yīng)用于計算機、打印機、復(fù)印機、報警器、電子玩具、汽車電子設(shè)備、電話機、定時器電子產(chǎn)品中作發(fā)聲器件。在一般設(shè)計中,可利用蜂鳴器檢測有些按鍵是否按下,或者有些功能是否正常等,當然如果足夠浪漫,也可以讓蜂鳴器演奏音樂。

設(shè)計原理

本設(shè)計使用的是無源蜂鳴器,也可稱為聲響器,原理電路圖如下所示。它沒有內(nèi)部驅(qū)動電路,無源蜂鳴器工作的理想信號為方波,如果給直流,蜂鳴器是不響應(yīng)的,因為磁路恒定,鉬片不能震動發(fā)音。

根據(jù)電路圖可知,由于FPGA的驅(qū)動能力不夠,這里增加了一個三極管來驅(qū)動這個無源蜂鳴器。在驅(qū)動時,只需要向蜂鳴器發(fā)送一定頻率的方波,就可以使蜂鳴器發(fā)聲。那么應(yīng)該發(fā)送怎樣的頻率呢?具體則可參考下表(音節(jié)頻率表):

樂曲能連續(xù)演奏所需要的兩個基本數(shù)據(jù)是:組成樂曲的每個音符的頻率值(音調(diào))和每個音符持續(xù)的時間(音長)。因此只要控制FPGA輸出到蜂鳴器的激勵信號頻率的高低和持續(xù)時間,就可以使蜂鳴器發(fā)出連續(xù)的樂曲聲。

在本設(shè)計中,由于開發(fā)板晶振為50MHz,所以我們需要一個一個分頻模塊(PLL)產(chǎn)生一個較低的基準頻率(1MHz)。還需要一個空間儲存樂譜,由于樂譜是固定的不需要更改,所以我們選擇ROM IP 核進行存儲。

基準頻率1MHz可分頻得到所有不同頻率的信號。最大的分頻比為1_000_000/262/2。既然是音樂,那么就需要節(jié)拍,一般采用4拍,即音長為0.25s,所以還需設(shè)計一個模塊,控制每0.25s,ROM地址加1,。如果需要發(fā)送一個低音1并維持1秒,則只需要在ROM的連續(xù)四個地址中寫入低音1的對應(yīng)信息即可。

在設(shè)計中為了方便在ROM中儲存數(shù)據(jù),這里數(shù)據(jù)格式為8’hAB,其中A暫時為三個值1、2、4,分別表示低音、中音、高音。B暫時為七個值1、2、3、4、5、6、7。比如要產(chǎn)生一個低音1,只需在ROM中存儲8’h11,如要產(chǎn)生一個高音7,只需在ROM中存儲8’h47,以此類推即可。這時,就需要一個解碼模塊,將ROM中的數(shù)據(jù)還原成音樂發(fā)生器所需要的數(shù)據(jù)。

設(shè)計框架

設(shè)計架構(gòu)圖:

本設(shè)計包括6個模塊,PLL模塊把50MHz的時鐘信號降到1MHz,rom模塊存儲音樂數(shù)據(jù),time_counter是一個計數(shù)模塊,產(chǎn)生節(jié)拍,每到0.25s,輸出的time_finsh變?yōu)橐粋€周期的高電平。并發(fā)送給addr_gen模塊,產(chǎn)生addr,讓rom輸出下一個地址的數(shù)據(jù)。rom輸出的數(shù)據(jù)rom_data輸入到decode解碼模塊,將解碼后的數(shù)據(jù)music_data輸入到music_gen模塊,通過計數(shù)器,如果計數(shù)器小于music_data的值,則beep保持不變,否則,beep取反,并且計數(shù)器清1,從而產(chǎn)生特定的方波頻率。

設(shè)計代碼

beep頂層模塊代碼如下:

module beep  (clk, rst_n, beep);          input clk, rst_n;      //輸入50Mhz時鐘信號,復(fù)位信號  output beep;      //輸出的方波    wire clk_1M, time_finsh;  //1Mhz時鐘信號線,0.25s時間計數(shù)標記位  wire [6:0]addr;        //rom地址線  wire [7:0]rom_data;      //rom數(shù)據(jù)線  wire [10:0]music_data;    //rom數(shù)據(jù)解碼數(shù)據(jù)線    /*****PLL模塊*****/  my_pll my_pll_inst(            .areset(~rst_n),    .inclk0(clk),    .c0(clk_1M)  );    /*****0.25s時間計數(shù)器模塊*****/  time_counter time_counter_inst(      .clk(clk_1M),    .rst_n(rst_n),    .time_finsh(time_finsh)  );        /*****ROM地址發(fā)生器*****/  addr_gen addr_gen_inst(          .clk(clk_1M),    .rst_n(rst_n),    .addr(addr),    .time_finsh(time_finsh)  );      /*****ROM模塊*****/  my_rom my_rom_inst(            .address(addr),    .clock(clk_1M),    .q(rom_data)  );    /*****解碼模塊*****/  decode decode_inst(            .clk(clk_1M),     .rst_n(rst_n),      .rom_data(rom_data),    .music_data(music_data)  );              /*****音樂發(fā)生器模塊*****/  music_gen music_gen_inst(        .clk(clk_1M),    .rst_n(rst_n),    .music_data(music_data),    .beep(beep)  );              endmodule

time_counter模塊代碼如下:

module time_counter (clk, rst_n, time_finsh);                input clk, rst_n;    //輸入1Mhz時鐘信號,復(fù)位信號  output time_finsh;  //輸出時間計數(shù)標志位(沒0.25s變高電平一次)    reg [17:0]count;    //計數(shù)器count    always@(posedge clk or negedge rst_n)  begin    if(!rst_n)      count  <=  18'd0;  //計數(shù)器復(fù)位    else  if(time_finsh)      count  <=  18'd0;  //每到0.25s計數(shù)器歸零    else      count  <=  count  +  1'd1;  //未到0.25s,計數(shù)器繼續(xù)累加  end  /*****每到0.25s,time_finsh拉高,表示已經(jīng)達到0.25s*****/  //assign time_finsh  =  (count == 18'd249_999)? 1'd1  :  1'd0;  /*****用于仿真,因為真正的0.25是會仿真很長*****/  assign time_finsh  =  (count == 22'd25_00)? 1'd1  :  1'd0;  
endmodule

addr_gen模塊代碼如下:

module addr_gen (clk, rst_n, addr, time_finsh);              input clk, rst_n;  //輸入1Mhz時鐘信號,復(fù)位信號  input time_finsh;  //輸入時間計數(shù)標記位(每0.25s變高電平一次)  output reg [6:0]addr; //輸出給ROM的地址信號    always@(posedge clk or negedge rst_n)  begin    if(!rst_n)      addr  <=  7'd0;  //輸出給ROM的地址信號復(fù)位    else  if(time_finsh) //輸出給ROM的地址信號自加1(每0.25s自加1)      addr  <=  addr  +  1'd1;      else      addr  <=  addr;    //未夠0.25s,ROM的地址信號不變  end  endmodule

decode解碼模塊代碼如下:

module decode (clk, rst_n, rom_data, music_data);            input clk, rst_n;    //輸入1Mhz時鐘信號,復(fù)位信號  input [7:0]rom_data;  //輸入的ROM的數(shù)據(jù)  output reg [10:0]music_data;  //輸出ROM的解碼數(shù)據(jù)    always@(posedge clk or negedge rst_n)  begin    if(!rst_n)      music_data  <=  11'd0;    //輸出ROM的解碼數(shù)據(jù)復(fù)位    else      case (rom_data)      8'h11  :  music_data  <=  11'd1911;  //(1Mhz/261.63Hz)/2)=1191  低音1      8'h12  :  music_data  <=  11'd1702;  //(1Mhz/293.67Hz)/2)=1702  低音2      8'h13  :  music_data  <=  11'd1517;  //(1Mhz/329.63Hz)/2)=1517  低音3      8'h14  :  music_data  <=  11'd1431;  //(1Mhz/349.23Hz)/2)=1431  低音4      8'h15  :  music_data  <=  11'd1276;  //(1Mhz/391.99Hz)/2)=1276  低音5      8'h16  :  music_data  <=  11'd1136;  //(1Mhz/440.00Hz)/2)=1136  低音6      8'h17  :  music_data  <=  11'd1012;  //(1Mhz/493.88Hz)/2)=1012  低音7            8'h21  :  music_data  <=  11'd939;  //(1Mhz/532.25Hz)/2)=939  中音1      8'h22  :  music_data  <=  11'd851;  //(1Mhz/587.33Hz)/2)=851  中音2      8'h23  :  music_data  <=  11'd758;  //(1Mhz/659.25Hz)/2)=758  中音3      8'h24  :  music_data  <=  11'd716;  //(1Mhz/698.46Hz)/2)=716  中音4      8'h25  :  music_data  <=  11'd638;  //(1Mhz/783.99Hz)/2)=638  中音5      8'h26  :  music_data  <=  11'd568;  //(1Mhz/880.00Hz)/2)=568  中音6      8'h27  :  music_data  <=  11'd506;  //(1Mhz/987.76Hz)/2)=506  中音7            8'h41  :  music_data  <=  11'd478;  //(1Mhz/1046.50Hz)/2)=478  高音1      8'h42  :  music_data  <=  11'd425;  //(1Mhz/1174.66Hz)/2)=425  高音2      8'h43  :  music_data  <=  11'd379;  //(1Mhz/1318.51Hz)/2)=379  高音3      8'h44  :  music_data  <=  11'd358;  //(1Mhz/1396.51Hz)/2)=358  高音4      8'h45  :  music_data  <=  11'd319;  //(1Mhz/1567.98Hz)/2)=319  高音5      8'h46  :  music_data  <=  11'd284;  //(1Mhz/1760.00Hz)/2)=284  高音6      8'h47  :  music_data  <=  11'd253;  //(1Mhz/1975.52Hz)/2)=253  高音7              8'h00  :  music_data  <=  11'd0;    //0HZ,停止節(jié)拍    endcase  end      endmodule

music_gen模塊代碼如下:

module music_gen  (clk, rst_n, music_data, beep);                input clk, rst_n;      //輸入1Mhz時鐘信號,復(fù)位信號  input [10:0]music_data;  //輸入音樂頻率控制字  output reg beep;      //輸出方波    reg [10:0]data, count;  //寄存音樂控制字的data,計數(shù)器count    always@(posedge clk or negedge rst_n)  begin    if(!rst_n)      data  <=  11'd0;      //寄存器data復(fù)位    else      data  <=  music_data;    //data寄存音樂控制字  end      always@(posedge clk or negedge rst_n)  begin    if(!rst_n)      begin        count  <=  11'd1;    //計數(shù)器復(fù)位        beep  <=  1'd0;    //輸出方波復(fù)位      end    else  if(data == 11'd0)  //當data==11‘d0,(停止節(jié)拍)      begin        count  <=  11'd1;   //計數(shù)器歸一        beep  <=  1'd0;    //輸出方波歸零      end    else  if(count  <=  data)  //當計數(shù)器小于等于data的值      count  <=  count  +  1'd1;//計數(shù)器繼續(xù)累加    else      begin        count  <=  11'd1;  //當計數(shù)器大于data的值,計數(shù)器歸一        beep  <=  ~beep;  //輸出方波取反      end  end
endmodule

仿真測試

beep_tp頂層測試模塊代碼如下:

`timescale 1ns/1ps          module beep_tb;
  reg clk, rst_n;              wire beep;            
  initial begin    clk = 1;                rst_n = 0;                #200.1 rst_n=1;  
    #100000000 $stop;  end
  beep beep_dut(    .clk(clk),    .rst_n(rst_n),    .beep(beep)    );    always #10 clk = ~clk;
endmodule    

仿真圖:

由仿真圖可知:當rom輸出rom_data為8’h16時,代表輸出低音6,解碼后結(jié)果music_data為1136,輸出的beep頻率為440Hz,與實際低音6的音節(jié)頻率表的值一致;當rom輸出rom_data為8’h22時,代表輸出中音2,解碼后結(jié)果music_data為851,輸出的beep頻率為563Hz,與實際中音2的音節(jié)頻率表的值相差24Hz,存在一定的誤差,但是不影響樂曲的播放。如果想提高beep頻率的精度,減小誤差,則可以將1MHz的基準頻率提高。

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

任何技術(shù)的學習就好比一個江湖,對于每一位俠客都需要不斷的歷練,從初入江湖的小白到歸隱山林的隱世高人,需要不斷的自我感悟自己修煉,讓我們一起仗劍闖FPGA乃至更大的江湖。