最近做EDA课设,看到自己的买的板子上有蜂鸣器,所以就打算做一个FPGA控制蜂鸣器播放音乐。
这里我使用的板子是睿智助学的FPGA开发板,板子上的芯片是EP4CE6E22C8,如果是你使用的是其他开发板或者是自己做的板子,就根据原理图,在写完代码时绑定相应的引脚下载代码即可。
在FPGA上编写代码来完成播放音乐与使用STM32来实现此功能的思想不同。FPGA是用的硬件描述语言(HDL)去写的,因此在写代码的时候,心里其实就应该有一个硬件结构。根据硬件结构,通过HDL编写代码描述硬件功能,才是正确的思想。【如果把硬件描述语言单纯的认为是纯代码编写是错误的思想,与硬件贴合就是其特点】
首先还是对音乐进行描述,这一部分我之前在用STM32蜂鸣器播放音乐的地方已经说了,这里就贴一下链接,大家如果对音乐有不明白的地方,可以先去那边文章看看。
【stm32蜂鸣器播放音乐】
这里我就在简短的说明一下,蜂鸣器的要实现的效果。众所周知,每个音符因为频率不同,所以声音不一样。因为在FPGA上我是用占空比50%的方波来控制蜂鸣器,所以,方波的频率的不同,可以实现产生不同的声音。同时方波的个数,就相当于音符的持续时间。所以乐器要演奏,本质上控制控制方波的频率和方波的个数。
因此,产生不同的声音就要产生不同频率的方波,不同频率就要对时钟分频。那么唯一的做法就是找一个基准频率时钟,通过修改分频比来产生不同的时钟频率。可是由于音阶频率多为非整数,而分频系数又不能为小数,所以必须将计算得到的分频数进行四舍五入取整。取整就会有误差,所以基准频率不能太小;如果基准频率过大也会导致分频数变大。在实际的设计中要综合考虑两个方面,在尽量减少频率误差的前提下取合适的基准频率。这里推荐的频率是以6MHz为基准频率,对于6MHz的频率要求并不是特别严格,在6MHz左右的基准频率也是可以的,如果想自己用其他的基准频率也是可以的,就是分频数就得自己再算一算。
这里我参照的是王金明老师的《EDA技术与VerilogHDL设计》这本书。这里摘抄一下书的片段。
为了减少输出的偶次谐波分量,最后输出到蜂鸣器的波形应为对称方波,因此在到达蜂鸣器之前,有一个二分频的分频器。下表的分频比就是在由6MHz频率二分频得到的3MHz频率的基础上计算得出的。如果用正弦波替代方波驱动蜂鸣器,将会有更好的效果。
从表中可以看出,最大的分频系数为14468,故采用14位二进制计数器分频即可满足需要。除了给出分频比以外,还给出了对应于各个音阶频率时计数器不同的预置数。对于的分频系数,只要加载不同的预置数即可,对于乐曲中的休止符,只要将分频系数为0,即初始值为
2
14
−
1
=
16383
2^{14}-1=16383
214−1=16383即可,此时扬声器不会发声。采用加载预置数实现分频的方法比采用反馈零法节省资源,实现起来也容易一些。
音符的持续时间根据乐曲的速度及每个音符的节拍数来确定。这里的细节也可以参考我上面的文章。因为我的曲子有32分音符,同时4分音符的持续时间为
0.5
s
0.5s
0.5s,所以我需要
8
/
0.5
=
16
8/0.5=16
8/0.5=16Hz的频率来产生32分音符的时长。
关于音符差不多就到这了,接下来就是硬件的构思。首先我们需要一个分频器来产生6MHz的时钟,同时还需要一个分频器来产生16Hz的时钟,然后就是乐谱产生电路,最后一个数码管显示音符。
以下图片是相关模块的设计框图
这里解释一下为什么我上面的模块多了一个分频器,在教材中,它是直接对6MHz分频得到4Hz的频率,而我的曲子有32分音符,所以需要16Hz的频率,如果对6MHz的分频,分频系数就是小数,必然会产生不必要的误差,所以我就把50MHz再送到另一个分频器分频得到500KHz的频率,对这个频率再分频就能得到准确的16Hz时钟了。
关于为什么是对50MHz的时钟分频,这取决于你的板子所采用的时钟源频率,具体可以看你板子的原理图,这里我的板子所使用的晶振是50MHz,所以我是对50MHz进行分频,如果你的板子使用的是不同频率的晶振,就要根据你板子的晶振频率来计算相应的分频系数来到6MHz,以及16Hz(或者其他的频率值)
接下来就是对数码管模块的说明,因为我的数码管是公用8个段的,通过位选来控制点亮相应的数码管,所以我需要动态点亮数码管来在相应位置的数码管显示指定的音符。
如果你的数码管是独立的,即每个数码管有各自的8根段码线连接,那么你的数码管模块输出也是8个为一组作为一数码管的段码,如果要用3个数码管,也就是要24根线(这里也就是数码管的静态显示与动态显示的区别,如果不明白可以在网上查阅其他文章,其他文章应该有把这方面讲明白的,这里我就不再赘述了)
好了,模块都已经讲清楚了,剩下的就是根据模块来写相应的代码。在实际的编写中,对模块的划分也是相当重要的,比起把代码都写在一个文件里,将不同的功能硬件代码划分出来,可以使代码条理更加清晰,功能明确,同时在后期的检查也十分方便,容易定位错误,也便于测试各个模块的功能。
下面就是贴代码了,具体细节我就都写在代码的注释里了
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;//曲子长度的度量
//通过置数改变分频比
always @(posedge clk6MHz) begin
if(divider == 16383) begin
carry <= 1;
divider <= origin;
end
else begin
divider <= divider+1;
carry <= 0;
end
end
//2分频产生方波信号
always @(posedge carry) begin
speaker <= ~speaker;
end
//根据不同的音符,预置分频比
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的位数,避免数值溢出
else counter <= counter+1;
//曲子的音符与时间
case(counter)//下面是我的乐谱,如果是要播放其他乐曲,在这里修改乐谱
/*第1小节*/
//低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小节*/
//中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小节*/
//低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小节*/
//中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小节*/
//高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小节*/
//中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小节*/
//中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小节*/
//低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小节*/
//低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文件文章来源:https://www.toymoban.com/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;//位选线低电平有效,根据原理图来写
assign med = ~|dataM;
assign low = ~|dataL;
always @(*) begin
case(dataH|dataM|dataL)//译码,将相应的数字译码使其在数码管上显示相应的数字
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文件),然后引脚绑定再编译一次,最后下载到板子里就能看到结果了。文章来源地址https://www.toymoban.com/news/detail-458418.html
到了这里,关于FPGA蜂鸣器播放音乐的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!