大俠好,歡迎來到FPGA技術(shù)江湖,江湖偌大,相見即是緣分。大俠可以關(guān)注FPGA技術(shù)江湖,在“闖蕩江湖”、"行俠仗義"欄里獲取其他感興趣的資源,或者一起煮酒言歡。
今天給大俠帶來基于FPGA的音樂蜂鳴器設(shè)計,附源碼,獲取源碼,請在“FPGA技術(shù)江湖”公眾號內(nèi)回復“音樂蜂鳴器設(shè)計源碼”,可獲取源碼文件。話不多說,上貨。
設(shè)計背景
蜂鳴器是一種一體化結(jié)構(gòu)的電子訊響器,采用直流電壓供電,廣泛應(yīng)用于計算機、打印機、復印機、報警器、電子玩具、汽車電子設(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時鐘信號,復位信號
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時鐘信號,復位信號
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ù)器復位
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時鐘信號,復位信號
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的地址信號復位
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時鐘信號,復位信號
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ù)復位
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時鐘信號,復位信號
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復位
else
data <= music_data; //data寄存音樂控制字
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
count <= 11'd1; //計數(shù)器復位
beep <= 1'd0; //輸出方波復位
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的基準頻率提高。