基于FPGA的电子琴设计——项目总结报告
一、项目概况及需求
利用小脚丫FPGA核心板(Lattice MXO2-C)和Piano Kit拓展套件,组装并编程基于FPGA实现以下功能:存储一段音乐,并可以进行音乐播放;可以自己通过板上的按键进行弹奏,支持两个按键同时按下(和弦)并且声音不能失真,板上的按键只有13个,可以通过有上方的“上“、”下”两个按键对音程进行扩展;使用扬声器进行播放时,输出的音调信号除了对应于该音调的单频正弦波外,还必须包含至少一个谐波分量;音乐的播放支持两种方式,这两种方式可以通过开关进行切换。
二、项目理论分析
1、电子琴工作原理
根据项目要求,设计的电子琴应有手动弹奏和自动播放功能,则电子琴工作的大致框图如下:
本电子琴电路系统可大致分为控制模块、FPGA模块和音频模块三大部分。控制模块主要反映了用户对电子琴的功能选择操作,通过琴键上的控制按钮完成。播放模式选择按钮高电平时为自动播放模式,读取预设的音符信息并通过不同频率输出,实现自动播放,当按钮低电平时为手动弹奏模式,用户可按键进行弹奏。电子琴发音原理于后代码分析中展示。FPGA模块是核心模块,存有编写好的代码内容,控制电子琴所有功能。音频模块中,本次项目有扬声器和蜂鸣器两个发音装置,都能够将电信号转化为声音信号。
2、蜂鸣器、扬声器原理和区别
本项目中的蜂鸣器采用的是无源蜂鸣器,通过不同频率的脉冲驱动,能够发出不同频率的声音信号。蜂鸣器分压电式蜂鸣器和电磁式蜂鸣器,前者在通电后,振动膜片在电磁线圈和磁铁的相互作用下周期性地振动发电,后者通过多谐振荡器起振,输出1.5~2.5kHZ的音频信号,阻抗匹配器推动压电蜂鸣片发声。
而扬声器是一种把电信号转变为声信号的换能器件,在音响设备中是一个最薄弱的器件,而对于音响效果而言,它又是一个最重要的部件。其音频电能通过电磁、压电或静电效应,使其纸盆或膜片振动并与周围的空气产生共振而发出声音。
蜂鸣器和扬声器最大的区别是喇叭的频率响应要比蜂鸣器好得多,蜂鸣器只在一个很窄的频率范围内电声功率转换比较高。播放音乐时,最直观的感受是扬声器的音色要比蜂鸣器好很多。
在本项目中,通过beeper模块产生弹奏的各个音符频率控制字,输入到dds信号发生器中,实现弹奏、播放以及谐波对应频率的波形,再将数据输入值pwm模块中,通过控制pwm占空比产生不同的pwm波形,最后选择输出到蜂鸣器或喇叭。
三、实现过程说明和主要代码展示
1、顶层模块top
例化clk_wiz, debounce, btn_coding, beeper, pwm, music_box模块,将所有功能整合,各个模块功能稍后说明,
2、btn_coding模块
always@(posedge clk)
2. if(!rst_n)
3. begin
4. sel_speaker <= 0;
5. mode <= 0;
6. end
7. else
8. begin
9. if(sel_speaker_n)
10. sel_speaker <= ~sel_speaker;
11. if(mode_n)
12. mode <= ~mode;
13. end
对输入的模式切换按键即声音输出设备选择按键进行处理(经测试需按两次才可切换),同时控制自动播放和弹奏模式的选择。
3、music_box模块
播放音乐控制模块,产生要播放的音乐的各个节拍的频率控制字,输出给dds模块。选用曲目《梁祝》,设置寄存器存放其简谱
1. reg [31:0] freq [0:21]; //do re mi fa so la si do 低音、中音、高音,共21个
2. reg [7:0] PUA [0:136];//存放曲1的简谱,一个寄存器放半拍
3. initial begin
4. freq[0]=0;
5. freq[1]= 366;
6. freq[2]= 411;
7. freq[3]= 461;
8. freq[4]= 488;
9. freq[5]= 548;
10. freq[6]= 615;
11. freq[7]= 691;
12. freq[8]= 731;
13. freq[9]= 821;
14. freq[10]=921;
15. freq[11]=976;
16. freq[12]=1096;
17. freq[13]=1230;
18. freq[14]=1381;
19. freq[15]=1464;
20. freq[16]=1643;
21. freq[17]=1844;
22. freq[18]=1953;
23. freq[19]=2192;
24. freq[20]=2461;
25. freq[21]=2750;
26. end
27. initial begin //梁祝化蝶部分的简谱,一个寄存器存半拍
28. PUA[0] = 3; PUA[01] = 3; PUA[02] = 3; PUA[03] =3; PUA[04] =5; PUA[05] =5; PUA[06] =5; PUA[07] =6;
29. PUA[08] = 8; PUA[09] = 8; PUA[10] = 8; PUA[11] =9; PUA[12] =6; PUA[13] =8; PUA[14] =5; PUA[15] =5;
30. PUA[16] = 12; PUA[17] = 12; PUA[18] = 15; PUA[19] =15; PUA[20] =13; PUA[21] =12; PUA[22] =10; PUA[23] =12;
31. PUA[24] = 9; PUA[25] = 9; PUA[26] = 9; PUA[27] =9; PUA[28] =9; PUA[29] =9; PUA[30] =9; PUA[31] =0;
32. PUA[32] = 9; PUA[33] = 9; PUA[34] = 9; PUA[35] =10; PUA[36] =7; PUA[37] =7; PUA[38] =6; PUA[39] =6;
33. PUA[40] = 5; PUA[41] = 5; PUA[42] = 5; PUA[43] =6; PUA[44] =8; PUA[45] =8; PUA[46] =9; PUA[47] =9;
34. PUA[48] = 3; PUA[49] = 3; PUA[50] = 8; PUA[51] =8; PUA[52] =6; PUA[53] =5; PUA[54] =6; PUA[55] =8;
35. PUA[56] = 5; PUA[57] = 5; PUA[58] = 5; PUA[59] =5; PUA[60] =5; PUA[61] =5; PUA[62] =5; PUA[63] =0;
36. PUA[64] = 10; PUA[65] = 10; PUA[66] = 10; PUA[67] =12; PUA[68] =7; PUA[69] =7; PUA[70] =9; PUA[71] =9;
37. PUA[72] = 6; PUA[73] = 8; PUA[74] = 5; PUA[75] =5; PUA[76] =5; PUA[77] =5; PUA[78] =5; PUA[79] =5;
38. PUA[80] = 3; PUA[81] = 5; PUA[82] = 3; PUA[83] =3; PUA[84] =5; PUA[85] =6; PUA[86] =7; PUA[87] =9;
39. PUA[88] = 6; PUA[89] = 6; PUA[90] = 6; PUA[91] =6; PUA[92] =6; PUA[93] =6; PUA[94] =5; PUA[95] =6;
40. PUA[96] = 8; PUA[97] = 8; PUA[98] = 8; PUA[99] =9; PUA[100]=12; PUA[101]=12; PUA[102]=12; PUA[103]=10;
41. PUA[104]= 9; PUA[105]= 9; PUA[106]= 10; PUA[107]=9; PUA[108]=8; PUA[109]=8; PUA[110]=6; PUA[111]=5;
42. PUA[112]= 3; PUA[113]= 3; PUA[114]= 3; PUA[115]=3; PUA[116]=8; PUA[117]=8; PUA[118]=8; PUA[119]=8;
43. PUA[120]= 6; PUA[121]= 8; PUA[122]= 6; PUA[123]=5; PUA[124]=3; PUA[125]=5; PUA[126]=6; PUA[127]=8;
44. PUA[128]= 5; PUA[129]= 5; PUA[130]= 5; PUA[131]=5; PUA[132]=5; PUA[133]=5; PUA[134]=5; PUA[135]=5;
45. end
4、dds_main模块
作为dds模块的顶层,控制dds产生波形的频率
1. module dds_main(clk_in,rst_n,en, FTwoRD,dac_data_out);
2. input clk_in; // 小脚丫FPGA的外部时钟频率位 12MHz,内部可以通过PLL产生
3. //12MHz整数倍的高速时钟 ,比如96MHz、108MHz、120MHz...216MHz
4. input rst_n;
5. input en;
6. input [23:0]FTwoRD; //频率控制字
7. output [9:0] dac_data_out; // 10位数据输出送给外部
8. reg [23:0] cnt=0; // 自由运行的计数器,共计24位,最低频率为cnt[23]的12MHz/2^24= 0.7Hz
9. always@(posedge clk_in) cnt <= cnt + 1'b1;
10. wire [9:0]dac_data;
11. reg [23:0] phase_acc = 0;
12. always @(posedge clk_in, negedge rst_n)
13. if(!rst_n)
14. phase_acc <= 0;
15. else
16. phase_acc <= phase_acc + FTwoRD; //主时钟为12MHz,产生20KHz的正弦波信号
17. lookup_tables u_lookup_tables(.phase(phase_acc[23:16]), .sin_out(dac_data));
18. assign dac_data_out = en ? dac_data : 0;
19. endmodule
5、beeper模块
这一模块包括了多个功能。首先是采用键盘弹奏时,各个音符频率控制字的产生;
1. always@(posedge clk_in or negedge rst_n_in) begin
2. if(!rst_n_in)
3. begin
4. FTwoRD_temp[0] <= 0;
5. FTwoRD_temp[1] <= 0;
6. FTwoRD_temp[2] <= 0;
7. FTwoRD_temp[3] <= 0;
8. FTwoRD_temp[4] <= 0;
9. FTwoRD_temp[5] <= 0;
10. FTwoRD_temp[6] <= 0;
11. FTwoRD_temp[7] <= 0;
12. FTwoRD_temp[8] <= 0;
13. FTwoRD_temp[9] <= 0;
14. FTwoRD_temp[10] <= 0;
15. FTwoRD_temp[11] <= 0;
16. FTwoRD_temp[12] <= 0;
17. end
18. else begin
19. if(PB_state[0])
20. begin
21. FTwoRD_temp[0] <= 10'd365; //M1,C
22. end
23. else
24. FTwoRD_temp[0] <= 10'd0; //M1,C
25.
26. if(PB_state[1])
27. begin
28. FTwoRD_temp[1] <= 10'd387; //H1,#C
29. end
30. else
31. FTwoRD_temp[1] <= 10'd0; //H1,#C
32.
33. if(PB_state[2])
34. begin
35. FTwoRD_temp[2] <= 10'd410; //M2,D
36. end
37.
38. else
39. FTwoRD_temp[2] <= 10'd0; //M2,D
40.
41. if(PB_state[3])
42. begin
43. FTwoRD_temp[3] <= 10'd434; //H2,#D
44. end
45. else
46. FTwoRD_temp[3] <= 10'd0; //H2,#D
47.
48. if(PB_state[4])
49. begin
50. FTwoRD_temp[4] <= 10'd460; //M3,E
51. end
52. else
53. FTwoRD_temp[4] <= 10'd0; //M3,E
54.
55. if(PB_state[5])
56. begin
57. FTwoRD_temp[5] <= 10'd488; //M4,F
58. end
59. else
60. FTwoRD_temp[5] <= 10'd0; //M4,F
61.
62. if(PB_state[6])
63. begin
64. FTwoRD_temp[6] <= 10'd517; //H4,#F
65. end
66. else
67. FTwoRD_temp[6] <= 10'd0; //H4,#F
68.
69. if(PB_state[7])
70. begin
71. FTwoRD_temp[7] <= 10'd548; //M5,G
72. end
73. else
74. FTwoRD_temp[7] <= 10'd0; //M5,G
75.
76. if(PB_state[8])
77. begin
78. FTwoRD_temp[8] <= 10'd580; //H5,#G
79. end
80. else
81. FTwoRD_temp[8] <= 10'd0; //H5,#G
82.
83. if(PB_state[9])
84. begin
85. FTwoRD_temp[9] <= 10'd615; //M6,A
86. end
87. else
88. FTwoRD_temp[9] <= 10'd0; //M6,A
89.
90. if(PB_state[10])
91. begin
92. FTwoRD_temp[10] <= 10'd651; //H6,#A
93. end
94. else
95. FTwoRD_temp[10] <= 10'd0; //H6,#A
96.
97. if(PB_state[11])
98. begin
99. FTwoRD_temp[11] <= 10'd690; //M7,B
100. end
101. else
102. FTwoRD_temp[11] <= 10'd0; //M7,B
103. if(PB_state[12])
104. begin
105. FTwoRD_temp[12] <= 10'd730; //HIGH_C
106. end
107. else
108. FTwoRD_temp[12] <= 10'd0; //HIGH_C
109. end
110. end
其次是和弦的算法,本人乐理知识较为一般,此处采用了取频率平均值的方法解决按多个按键产生和弦的问题,经测试同时按下二、三个按键不会出现失真现象
1. assign FTwoRD_temp_1 = (FTwoRD_temp[0]+FTwoRD_temp[1]+FTwoRD_temp[2]+FTwoRD_temp[3]+FTwoRD_temp[4]+FTwoRD_temp[5]+FTwoRD_temp[6]+FTwoRD_temp[7]+FTwoRD_temp[8]+FTwoRD_temp[9]+FTwoRD_temp[10]+FTwoRD_temp[11]+FTwoRD_temp[12])/(PB_state[0]+PB_state[1]+PB_state[2]+PB_state[3]+PB_state[4]+PB_state[5]+PB_state[6]+PB_state[7]+PB_state[8]+PB_state[9]+PB_state[10]+PB_state[11]+PB_state[12]);
此为测试和弦功能时的仿真效果
还有音程调节部分,此处设定为按住调节键时才会升或降低八度
1. always@(posedge clk_in or negedge rst_n_in) begin
2. if(!rst_n_in) begin
3. FTwoRD <= 24'hffffff;
4. end else begin
5. case(PB_state[14:13])
6. 2'b01: FTwoRD <= FTwoRD_temp_1 >> 1; //升八度
7. 2'b10: FTwoRD <= FTwoRD_temp_1 << 1; //降八度
8.
9. default:FTwoRD <= FTwoRD_temp_1;
10. endcase
11. end
12. end
最后,dds模块的例化也包含在beeper里,
dds_main u_dds_main(
2. .clk_in(clk_in),
3. .rst_n(rst_n_in),
4. .en(1),
5. .FTwoRD(FTwoRD),//输入频率控制字
6. .dac_data_out(dac_data)
7. );
8. dds_main bbox_dac(
9. .clk_in(clk_in),
10. .rst_n(rst_n_in),
11. .en(1),
12. .FTwoRD(box_word),//输入频率控制字
13. .dac_data_out(bbox_dac_data)
14. );
15. dds_main bbox_dac_h(
16. .clk_in(clk_in),
17. .rst_n(rst_n_in),
18. .en(1),
19. .FTwoRD(box_word <<1),//输入频率控制字
20. .dac_data_out(bbox_dac_data_h)
21. );
22. assign dac_data_out = mode ? sel_speaker ? (bbox_dac_data_h + bbox_dac_data) : (bbox_dac_data) : dac_data;
需要特别注意的是后两个例化,实现了在音乐自动播放时,音符产生谐波的功能,第二个例化中的dds模组用以产生基波,第三个对应谐波,此处设置谐波为两倍的基波。
此外,还有debounce模块,实现按键消抖功能;sin_table模块,储存正弦波1/4波表;lookup_table模块,基于正弦波1/4波表,用对称性实现其他3个1/4周期波形,此处不再展示,具体代码见附件。
5、总体布局
各模块布线情况见schematic.pdf文件
四、后记
1、遇到的难题及解决方法
此次项目个人认为工作量较大,从编写代码的数量就可以看出。由于本人是FPGA新手,若是没有任何资料进行参考,直接看项目的话必然是一头雾水。好在电子森林有大量的参考案例可供参考,帮助我理解并写出了电子琴项目的整体框架。
我认为本项目中最难的部分是对声音的处理。在进行学习之前,我以为只需对各个按键绑定发出特定频率的声音而不用加以处理,就可以完成功能。而想要完成发声,还需要学习DDS信号发生器和PWM相关知识,而这方面知识没有信号与系统等相关课程的基础是难以理解的。
2、改进方向
最大的改进方向在于和弦功能的实现。上文提及本人采用的方法是取频率的平均值,相当于将多个按键发出的声音合成为一个,本以为没有什么问题,但直到我听了真正乐器的和弦,我才觉得貌似我理解的“乐理知识”应该与实际有一定偏差。碍于时间关系,留作个人的后续改进项目。
在自动播放音乐方面,实代码写出了不止播放预设的单一歌曲的功能,实际上应该能够导入并切换其他乐曲,但没有进行测试,若成功,则可以实现能够切换歌曲的音乐盒,使功能更丰富。