??最近做EDA課設,看到自己的買的板子上有蜂鳴器,所以就打算做一個FPGA控制蜂鳴器播放音樂。
??這里我使用的板子是睿智助學的FPGA開發(fā)板,板子上的芯片是EP4CE6E22C8,如果是你使用的是其他開發(fā)板或者是自己做的板子,就根據(jù)原理圖,在寫完代碼時綁定相應的引腳下載代碼即可。
??在FPGA上編寫代碼來完成播放音樂與使用STM32來實現(xiàn)此功能的思想不同。FPGA是用的硬件描述語言(HDL)去寫的,因此在寫代碼的時候,心里其實就應該有一個硬件結(jié)構(gòu)。根據(jù)硬件結(jié)構(gòu),通過HDL編寫代碼描述硬件功能,才是正確的思想?!救绻延布枋稣Z言單純的認為是純代碼編寫是錯誤的思想,與硬件貼合就是其特點】
??首先還是對音樂進行描述,這一部分我之前在用STM32蜂鳴器播放音樂的地方已經(jīng)說了,這里就貼一下鏈接,大家如果對音樂有不明白的地方,可以先去那邊文章看看。
【stm32蜂鳴器播放音樂】
??這里我就在簡短的說明一下,蜂鳴器的要實現(xiàn)的效果。眾所周知,每個音符因為頻率不同,所以聲音不一樣。因為在FPGA上我是用占空比50%的方波來控制蜂鳴器,所以,方波的頻率的不同,可以實現(xiàn)產(chǎn)生不同的聲音。同時方波的個數(shù),就相當于音符的持續(xù)時間。所以樂器要演奏,本質(zhì)上控制控制方波的頻率和方波的個數(shù)。
??因此,產(chǎn)生不同的聲音就要產(chǎn)生不同頻率的方波,不同頻率就要對時鐘分頻。那么唯一的做法就是找一個基準頻率時鐘,通過修改分頻比來產(chǎn)生不同的時鐘頻率??墒怯捎谝綦A頻率多為非整數(shù),而分頻系數(shù)又不能為小數(shù),所以必須將計算得到的分頻數(shù)進行四舍五入取整。取整就會有誤差,所以基準頻率不能太??;如果基準頻率過大也會導致分頻數(shù)變大。在實際的設計中要綜合考慮兩個方面,在盡量減少頻率誤差的前提下取合適的基準頻率。這里推薦的頻率是以6MHz為基準頻率,對于6MHz的頻率要求并不是特別嚴格,在6MHz左右的基準頻率也是可以的,如果想自己用其他的基準頻率也是可以的,就是分頻數(shù)就得自己再算一算。
??這里我參照的是王金明老師的《EDA技術與VerilogHDL設計》這本書。這里摘抄一下書的片段。
??為了減少輸出的偶次諧波分量,最后輸出到蜂鳴器的波形應為對稱方波,因此在到達蜂鳴器之前,有一個二分頻的分頻器。下表的分頻比就是在由6MHz頻率二分頻得到的3MHz頻率的基礎上計算得出的。如果用正弦波替代方波驅(qū)動蜂鳴器,將會有更好的效果。
??從表中可以看出,最大的分頻系數(shù)為14468,故采用14位二進制計數(shù)器分頻即可滿足需要。除了給出分頻比以外,還給出了對應于各個音階頻率時計數(shù)器不同的預置數(shù)。對于的分頻系數(shù),只要加載不同的預置數(shù)即可,對于樂曲中的休止符,只要將分頻系數(shù)為0,即初始值為
2
14
?
1
=
16383
2^{14}-1=16383
214?1=16383即可,此時揚聲器不會發(fā)聲。采用加載預置數(shù)實現(xiàn)分頻的方法比采用反饋零法節(jié)省資源,實現(xiàn)起來也容易一些。
??音符的持續(xù)時間根據(jù)樂曲的速度及每個音符的節(jié)拍數(shù)來確定。這里的細節(jié)也可以參考我上面的文章。因為我的曲子有32分音符,同時4分音符的持續(xù)時間為
0.5
s
0.5s
0.5s,所以我需要
8
/
0.5
=
16
8/0.5=16
8/0.5=16Hz的頻率來產(chǎn)生32分音符的時長。
??關于音符差不多就到這了,接下來就是硬件的構(gòu)思。首先我們需要一個分頻器來產(chǎn)生6MHz的時鐘,同時還需要一個分頻器來產(chǎn)生16Hz的時鐘,然后就是樂譜產(chǎn)生電路,最后一個數(shù)碼管顯示音符。
以下圖片是相關模塊的設計框圖
這里解釋一下為什么我上面的模塊多了一個分頻器,在教材中,它是直接對6MHz分頻得到4Hz的頻率,而我的曲子有32分音符,所以需要16Hz的頻率,如果對6MHz的分頻,分頻系數(shù)就是小數(shù),必然會產(chǎn)生不必要的誤差,所以我就把50MHz再送到另一個分頻器分頻得到500KHz的頻率,對這個頻率再分頻就能得到準確的16Hz時鐘了。
關于為什么是對50MHz的時鐘分頻,這取決于你的板子所采用的時鐘源頻率,具體可以看你板子的原理圖,這里我的板子所使用的晶振是50MHz,所以我是對50MHz進行分頻,如果你的板子使用的是不同頻率的晶振,就要根據(jù)你板子的晶振頻率來計算相應的分頻系數(shù)來到6MHz,以及16Hz(或者其他的頻率值)
接下來就是對數(shù)碼管模塊的說明,因為我的數(shù)碼管是公用8個段的,通過位選來控制點亮相應的數(shù)碼管,所以我需要動態(tài)點亮數(shù)碼管來在相應位置的數(shù)碼管顯示指定的音符。
如果你的數(shù)碼管是獨立的,即每個數(shù)碼管有各自的8根段碼線連接,那么你的數(shù)碼管模塊輸出也是8個為一組作為一數(shù)碼管的段碼,如果要用3個數(shù)碼管,也就是要24根線(這里也就是數(shù)碼管的靜態(tài)顯示與動態(tài)顯示的區(qū)別,如果不明白可以在網(wǎng)上查閱其他文章,其他文章應該有把這方面講明白的,這里我就不再贅述了)
??好了,模塊都已經(jīng)講清楚了,剩下的就是根據(jù)模塊來寫相應的代碼。在實際的編寫中,對模塊的劃分也是相當重要的,比起把代碼都寫在一個文件里,將不同的功能硬件代碼劃分出來,可以使代碼條理更加清晰,功能明確,同時在后期的檢查也十分方便,容易定位錯誤,也便于測試各個模塊的功能。
下面就是貼代碼了,具體細節(jié)我就都寫在代碼的注釋里了
BrokenMoon2.v文件
module BrokenMoon2(clk50MHz,speaker,dataLed,High,Med,Low);
input clk50MHz;
output speaker;
output[7:0] dataLed;
output High,Med,Low;
wire clk500KHz_to_clk16Hz;
wire clk6MHz_to_music;
wire clk16Hz_to_music;
wire[3:0] musicHigh_to_LED8s,musicMed_to_LED8s,musicLow_to_LED8s;
CLK6MHz myCLK6MHz(.clk50MHz(clk50MHz),.clk6MHz(clk6MHz_to_music));
CLK500KHz myCLK500KHz(.clk50MHz(clk50MHz),.clk500KHz(clk500KHz_to_clk16Hz));
CLK16Hz myCLK16Hz(.clk500KHz(clk500KHz_to_clk16Hz),.clk16Hz(clk16Hz_to_music));
MUSIC myMUSIC(.clk6MHz(clk6MHz_to_music),.clk16Hz(clk16Hz_to_music),.speaker(speaker),.high(musicHigh_to_LED8s),.med(musicMed_to_LED8s),.low(musicLow_to_LED8s));
LED8s myLED(.dataH(musicHigh_to_LED8s),.dataM(musicMed_to_LED8s),.dataL(musicLow_to_LED8s),.ledout(dataLed),.high(High),.med(Med),.low(Low));
endmodule
CLK6MHz.v文件
/*通過時鐘頻率50MHz的時鐘分頻得到6MHz的時鐘(6MHz是近似,實際是6.25MHz)*/
module CLK6MHz(clk50MHz,clk6MHz);
input clk50MHz;//輸入時鐘50MHz
output reg clk6MHz;//輸出時鐘6MHz
reg[2:0] count8;
always @(posedge clk50MHz) begin
if(count8 == 7) begin
count8 <= 0;
clk6MHz <= 1;
end
else begin
count8 <= count8+1;
clk6MHz <= 0;
end
end
endmodule
CLK500KHz.v文件
/*將50MHz時鐘分頻為500KHz時鐘,為得到16Hz時鐘做準備*/
module CLK500KHz(clk50MHz,clk500KHz);
input clk50MHz;
output reg clk500KHz;
reg[6:0] count100;
always @(posedge clk50MHz) begin
if(count100 == 99) begin
count100 <= 0;
clk500KHz <= 1;
end
else begin
count100 <= count100+1;
clk500KHz <= 0;
end
end
endmodule
CLK16Hz.v文件
/*通過對500KHz時鐘分頻得到16Hz時鐘*/
module CLK16Hz(clk500KHz,clk16Hz);
input clk500KHz;
output reg clk16Hz;
reg[15:0] count16;
always @(posedge clk500KHz) begin
if(count16 == 15625) begin
clk16Hz <= ~clk16Hz;
count16 <= 0;
end
else count16 <= count16+1;
end
endmodule
MUSIC.v文件
module MUSIC(clk6MHz,clk16Hz,speaker,high,med,low);
input clk6MHz,clk16Hz;
output reg speaker;
output reg[3:0] high,med,low;
reg[13:0] divider,origin;
reg carry;
reg[7:0] counter;//曲子長度的度量
//通過置數(shù)改變分頻比
always @(posedge clk6MHz) begin
if(divider == 16383) begin
carry <= 1;
divider <= origin;
end
else begin
divider <= divider+1;
carry <= 0;
end
end
//2分頻產(chǎn)生方波信號
always @(posedge carry) begin
speaker <= ~speaker;
end
//根據(jù)不同的音符,預置分頻比
always @(posedge clk16Hz) begin
case({high,med,low})
'h001: origin <= 4915; 'h002: origin <= 6168;
'h003: origin <= 7281; 'h004: origin <= 7792;
'h005: origin <= 8730; 'h006: origin <= 9565;
'h007: origin <= 10310; 'h010: origin <= 10647;
'h020: origin <= 11272; 'h030: origin <= 11831;
'h040: origin <= 12094; 'h050: origin <= 12556;
'h060: origin <= 12947; 'h070: origin <= 13346;
'h100: origin <= 13516; 'h200: origin <= 13829;
'h300: origin <= 14109; 'h400: origin <= 14235;
'h500: origin <= 14470; 'h600: origin <= 14678;
'h700: origin <= 14864; 'h000: origin <= 16383;
endcase
end
always @(posedge clk16Hz) begin
if(counter == 255) counter <= 0;//注意counter的取值范圍,跟你的曲子長度有關,不同的長度,記得修改上面聲明counter的位數(shù),避免數(shù)值溢出
else counter <= counter+1;
//曲子的音符與時間
case(counter)//下面是我的樂譜,如果是要播放其他樂曲,在這里修改樂譜
/*第1小節(jié)*/
//低3 長度4
0: {high,med,low} <= 'h003;
1: {high,med,low} <= 'h003;
2: {high,med,low} <= 'h003;
3: {high,med,low} <= 'h003;
//低5 長度4
4: {high,med,low} <= 'h005;
5: {high,med,low} <= 'h005;
6: {high,med,low} <= 'h005;
7: {high,med,low} <= 'h005;
//低6 長度4
8: {high,med,low} <= 'h006;
9: {high,med,low} <= 'h006;
10: {high,med,low} <= 'h006;
11: {high,med,low} <= 'h006;
//中1 長度4
12: {high,med,low} <= 'h010;
13: {high,med,low} <= 'h010;
14: {high,med,low} <= 'h010;
15: {high,med,low} <= 'h010;
/*第2小節(jié)*/
//中2 長度8
16: {high,med,low} <= 'h020;
17: {high,med,low} <= 'h020;
18: {high,med,low} <= 'h020;
19: {high,med,low} <= 'h020;
20: {high,med,low} <= 'h020;
21: {high,med,low} <= 'h020;
22: {high,med,low} <= 'h020;
23: {high,med,low} <= 'h020;
//中1 長度4
24: {high,med,low} <= 'h010;
25: {high,med,low} <= 'h010;
26: {high,med,low} <= 'h010;
27: {high,med,low} <= 'h010;
//中2 長度4
28: {high,med,low} <= 'h020;
29: {high,med,low} <= 'h020;
30: {high,med,low} <= 'h020;
31: {high,med,low} <= 'h020;
//中3 長度8
32: {high,med,low} <= 'h030;
33: {high,med,low} <= 'h030;
34: {high,med,low} <= 'h030;
35: {high,med,low} <= 'h030;
36: {high,med,low} <= 'h030;
37: {high,med,low} <= 'h030;
38: {high,med,low} <= 'h030;
39: {high,med,low} <= 'h030;
//中1 長度4
40: {high,med,low} <= 'h010;
41: {high,med,low} <= 'h010;
42: {high,med,low} <= 'h010;
43: {high,med,low} <= 'h010;
//低6 長度4
44: {high,med,low} <= 'h006;
45: {high,med,low} <= 'h006;
46: {high,med,low} <= 'h006;
47: {high,med,low} <= 'h006;
/*第3小節(jié)*/
//低5 長度4
48: {high,med,low} <= 'h005;
49: {high,med,low} <= 'h005;
50: {high,med,low} <= 'h005;
51: {high,med,low} <= 'h005;
//低3 長度4
52: {high,med,low} <= 'h003;
53: {high,med,low} <= 'h003;
54: {high,med,low} <= 'h003;
55: {high,med,low} <= 'h003;
//中1 長度4
56: {high,med,low} <= 'h010;
57: {high,med,low} <= 'h010;
58: {high,med,low} <= 'h010;
59: {high,med,low} <= 'h010;
//中2 長度4
60: {high,med,low} <= 'h020;
61: {high,med,low} <= 'h020;
62: {high,med,low} <= 'h020;
63: {high,med,low} <= 'h020;
//低6 長度8
64: {high,med,low} <= 'h006;
65: {high,med,low} <= 'h006;
66: {high,med,low} <= 'h006;
67: {high,med,low} <= 'h006;
68: {high,med,low} <= 'h006;
69: {high,med,low} <= 'h006;
70: {high,med,low} <= 'h006;
71: {high,med,low} <= 'h006;
//低6 長度4
72: {high,med,low} <= 'h006;
73: {high,med,low} <= 'h006;
74: {high,med,low} <= 'h006;
75: {high,med,low} <= 'h006;
//中1 長度4
76: {high,med,low} <= 'h010;
77: {high,med,low} <= 'h010;
78: {high,med,low} <= 'h010;
79: {high,med,low} <= 'h010;
/*第4小節(jié)*/
//中2 長度8
80: {high,med,low} <= 'h020;
81: {high,med,low} <= 'h020;
82: {high,med,low} <= 'h020;
83: {high,med,low} <= 'h020;
84: {high,med,low} <= 'h020;
85: {high,med,low} <= 'h020;
86: {high,med,low} <= 'h020;
87: {high,med,low} <= 'h020;
//中1 長度4
88: {high,med,low} <= 'h010;
89: {high,med,low} <= 'h010;
90: {high,med,low} <= 'h010;
91: {high,med,low} <= 'h010;
//中2 長度4
92: {high,med,low} <= 'h020;
93: {high,med,low} <= 'h020;
94: {high,med,low} <= 'h020;
95: {high,med,low} <= 'h020;
//中3 長度8
96: {high,med,low} <= 'h030;
97: {high,med,low} <= 'h030;
98: {high,med,low} <= 'h030;
99: {high,med,low} <= 'h030;
100: {high,med,low} <= 'h030;
101: {high,med,low} <= 'h030;
102: {high,med,low} <= 'h030;
103: {high,med,low} <= 'h030;
//中5 長度4
104: {high,med,low} <= 'h050;
105: {high,med,low} <= 'h050;
106: {high,med,low} <= 'h050;
107: {high,med,low} <= 'h050;
//中6 長度4
108: {high,med,low} <= 'h060;
109: {high,med,low} <= 'h060;
110: {high,med,low} <= 'h060;
111: {high,med,low} <= 'h060;
/*第5小節(jié)*/
//高1 長度4
112: {high,med,low} <= 'h100;
113: {high,med,low} <= 'h100;
114: {high,med,low} <= 'h100;
115: {high,med,low} <= 'h100;
//中7 長度4
116: {high,med,low} <= 'h070;
117: {high,med,low} <= 'h070;
118: {high,med,low} <= 'h070;
119: {high,med,low} <= 'h070;
//中6 長度1
120: {high,med,low} <= 'h060;
//中7 長度1
121: {high,med,low} <= 'h070;
//中6 長度2
122: {high,med,low} <= 'h060;
123: {high,med,low} <= 'h060;
//中5 長度4
124: {high,med,low} <= 'h050;
125: {high,med,low} <= 'h050;
126: {high,med,low} <= 'h050;
127: {high,med,low} <= 'h050;
//中6 長度8
128: {high,med,low} <= 'h060;
129: {high,med,low} <= 'h060;
130: {high,med,low} <= 'h060;
131: {high,med,low} <= 'h060;
132: {high,med,low} <= 'h060;
133: {high,med,low} <= 'h060;
134: {high,med,low} <= 'h060;
135: {high,med,low} <= 'h060;
//中5 長度4
136: {high,med,low} <= 'h050;
137: {high,med,low} <= 'h050;
138: {high,med,low} <= 'h050;
139: {high,med,low} <= 'h050;
//中3 長度4
140: {high,med,low} <= 'h030;
141: {high,med,low} <= 'h030;
142: {high,med,low} <= 'h030;
143: {high,med,low} <= 'h030;
/*第6小節(jié)*/
//中2 長度8
144: {high,med,low} <= 'h020;
145: {high,med,low} <= 'h020;
146: {high,med,low} <= 'h020;
147: {high,med,low} <= 'h020;
148: {high,med,low} <= 'h020;
149: {high,med,low} <= 'h020;
150: {high,med,low} <= 'h020;
151: {high,med,low} <= 'h020;
//中3 長度4
152: {high,med,low} <= 'h030;
153: {high,med,low} <= 'h030;
154: {high,med,low} <= 'h030;
155: {high,med,low} <= 'h030;
//中1 長度4
156: {high,med,low} <= 'h010;
157: {high,med,low} <= 'h010;
158: {high,med,low} <= 'h010;
159: {high,med,low} <= 'h010;
//中2 長度8
160: {high,med,low} <= 'h020;
161: {high,med,low} <= 'h020;
162: {high,med,low} <= 'h020;
163: {high,med,low} <= 'h020;
164: {high,med,low} <= 'h020;
165: {high,med,low} <= 'h020;
166: {high,med,low} <= 'h020;
167: {high,med,low} <= 'h020;
//中1 長度4
168: {high,med,low} <= 'h010;
169: {high,med,low} <= 'h010;
170: {high,med,low} <= 'h010;
171: {high,med,low} <= 'h010;
//中2 長度4
172: {high,med,low} <= 'h020;
173: {high,med,low} <= 'h020;
174: {high,med,low} <= 'h020;
175: {high,med,low} <= 'h020;
/*第7小節(jié)*/
//中3 長度6
176: {high,med,low} <= 'h030;
177: {high,med,low} <= 'h030;
178: {high,med,low} <= 'h030;
179: {high,med,low} <= 'h030;
180: {high,med,low} <= 'h030;
181: {high,med,low} <= 'h030;
//低6 長度2
182: {high,med,low} <= 'h006;
183: {high,med,low} <= 'h006;
//中1 長度2
184: {high,med,low} <= 'h010;
185: {high,med,low} <= 'h010;
//中2 長度2
186: {high,med,low} <= 'h020;
187: {high,med,low} <= 'h020;
//中1 長度4
188: {high,med,low} <= 'h010;
189: {high,med,low} <= 'h010;
190: {high,med,low} <= 'h010;
191: {high,med,low} <= 'h010;
//低6 長度8
192: {high,med,low} <= 'h006;
193: {high,med,low} <= 'h006;
194: {high,med,low} <= 'h006;
195: {high,med,low} <= 'h006;
196: {high,med,low} <= 'h006;
197: {high,med,low} <= 'h006;
198: {high,med,low} <= 'h006;
199: {high,med,low} <= 'h006;
//低6 長度4
200: {high,med,low} <= 'h006;
201: {high,med,low} <= 'h006;
202: {high,med,low} <= 'h006;
203: {high,med,low} <= 'h006;
//低5 長度4
204: {high,med,low} <= 'h005;
205: {high,med,low} <= 'h005;
206: {high,med,low} <= 'h005;
207: {high,med,low} <= 'h005;
/*第8小節(jié)*/
//低6 長度6
208: {high,med,low} <= 'h006;
209: {high,med,low} <= 'h006;
210: {high,med,low} <= 'h006;
211: {high,med,low} <= 'h006;
212: {high,med,low} <= 'h006;
213: {high,med,low} <= 'h006;
//低5 長度1
214: {high,med,low} <= 'h005;
//低6 長度1
215: {high,med,low} <= 'h006;
//中1 長度4
216: {high,med,low} <= 'h010;
217: {high,med,low} <= 'h010;
218: {high,med,low} <= 'h010;
219: {high,med,low} <= 'h010;
//中2 長度4
220: {high,med,low} <= 'h020;
221: {high,med,low} <= 'h020;
222: {high,med,low} <= 'h020;
223: {high,med,low} <= 'h020;
//中3 長度4
224: {high,med,low} <= 'h030;
225: {high,med,low} <= 'h030;
226: {high,med,low} <= 'h030;
227: {high,med,low} <= 'h030;
//中2 長度4
228: {high,med,low} <= 'h020;
229: {high,med,low} <= 'h020;
230: {high,med,low} <= 'h020;
231: {high,med,low} <= 'h020;
//低5 長度8
232: {high,med,low} <= 'h005;
233: {high,med,low} <= 'h005;
234: {high,med,low} <= 'h005;
235: {high,med,low} <= 'h005;
236: {high,med,low} <= 'h005;
237: {high,med,low} <= 'h005;
238: {high,med,low} <= 'h005;
239: {high,med,low} <= 'h005;
/*第9小節(jié)*/
//低6 長度16
240: {high,med,low} <= 'h006;
241: {high,med,low} <= 'h006;
242: {high,med,low} <= 'h006;
243: {high,med,low} <= 'h006;
244: {high,med,low} <= 'h006;
245: {high,med,low} <= 'h006;
246: {high,med,low} <= 'h006;
247: {high,med,low} <= 'h006;
248: {high,med,low} <= 'h006;
249: {high,med,low} <= 'h006;
250: {high,med,low} <= 'h006;
251: {high,med,low} <= 'h006;
252: {high,med,low} <= 'h006;
253: {high,med,low} <= 'h006;
254: {high,med,low} <= 'h006;
255: {high,med,low} <= 'h006;
default: {high,med,low} <= 'h000;
endcase
end
endmodule
LED8s.v文件文章來源:http://www.zghlxwxcb.cn/news/detail-458418.html
module LED8s(dataH,dataM,dataL,ledout,high,med,low);
input[3:0] dataH,dataM,dataL;
output reg[7:0] ledout;//輸出位分別是abcdefg
output high,med,low;//位選
assign high = ~|dataH;//位選線低電平有效,根據(jù)原理圖來寫
assign med = ~|dataM;
assign low = ~|dataL;
always @(*) begin
case(dataH|dataM|dataL)//譯碼,將相應的數(shù)字譯碼使其在數(shù)碼管上顯示相應的數(shù)字
0: ledout <= 8'b0000_0011;
1: ledout <= 8'b1001_1111;
2: ledout <= 8'b0010_0101;
3: ledout <= 8'b0000_1101;
4: ledout <= 8'b1001_1001;
5: ledout <= 8'b0100_1001;
6: ledout <= 8'b0100_0001;
7: ledout <= 8'b0001_1111;
8: ledout <= 8'b0000_0001;
9: ledout <= 8'b0000_1001;
default: ledout <= 8'b1111_1111;
endcase
end
endmodule
這些都完成之后,就是在quartus中進行編譯(記得設置頂層文件,這里我的頂層文件是BrokenMoon2.v文件),然后引腳綁定再編譯一次,最后下載到板子里就能看到結(jié)果了。文章來源地址http://www.zghlxwxcb.cn/news/detail-458418.html
到了這里,關于FPGA蜂鳴器播放音樂的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!