一、FPGA的电子琴板卡介绍
FPGA的电子琴板卡包括两个板子构成,一个是基于小脚丫FPGA核心板(Lattice MXO2-C),能够通过Web IDE在线编程(stepfpga)或Lattice官方提供的Diamond软件(Lattice Diamond (latticesemi.com))进行编程,STEP-MXO2-C专用版功能和配置与STEP-MXO2小脚丫FPGA学习模块,板载编程器几乎完全一致,唯一的差别在于FPGA程序下载方式不同,STEP-MXO2-C没有采用USB-JTAG的模式下载,而是通过MCU虚拟U盘,拷贝FPGA配置文件到U盘的方式下载程序,使用更便捷;另一个是Piano Kit扩展板,包含了带电路的底板和一块琴键盖板。
本次题目,考察了数电相关的知识,及逻辑编程的思维,还有对Verilog语言和软件的使用。
电子琴的工作原理和框图:
二、FPGA的电子琴板卡任务完成
1、自己组装电子琴
2、自己编程基于FPGA实现:存储一段音乐,并可以进行音乐播放;可以自己通过板上的按键进行弹奏,支持两个按键同时按下(和弦)并且声音不能失真,板上的按键只有13个,可以通过有上方的“上“、”下”两个按键对音程进行扩展;使用扬声器进行播放时,输出的音调信号除了对应于该音调的单频正弦波外,还必须包含至少一个谐波分量;音乐的播放支持两种方式,这两种方式可以通过开关进行切换: ;当开关切换到蜂鸣器端,可以通过蜂鸣器来进行音乐播放;当开关切换到扬声器端,可以通过模拟扬声器来进行音乐播放,每个音符都必须包含基频 + 至少一个谐波分量。
3、使用官网软件Lattice Diamond编程,观察资源使用情况
(注:因为实现功能是音频的形式,所以,请观看视频的项目演示,这里图片也不利于表达。)
三、分析蜂鸣器和模拟喇叭
1、蜂鸣器和模拟喇叭的区别
蜂鸣器的原理是电压式蜂鸣器,由压电式蜂鸣器主要由多谐振荡器、压电蜂鸣片、阻抗匹配器及共鸣箱、外壳等组成。扬声器,即模拟喇叭的原理其实是一种电能转换成声音的一种转换设备,当不同的电子能量传至线圈时,线圈产生一种能量与磁铁的磁场互动,这种互动造成纸盘振动,因为电子能量随时变化,喇叭的线圈会往前或往后运动,因此喇叭的纸盘就会跟着运动,这此动作使空气的疏密程度产生变化而产生声音。
2、蜂鸣器和模拟喇叭的实现方法及音效差别分析
原理图可以看到此次用到的蜂鸣器最基本的组成电路情况,由一个S8050三极管驱动,和一个1N4148保护蜂鸣器的二极管组成。
从模拟喇叭的原理图可以看出,GPIO口输出信号,经过一个RC滤波,再经过一个RV1可调音量的电位器,再经过一个RC滤波,送到8002b音频功率放大器,最后送到模拟喇叭,输出人耳可以听到的音频。
蜂鸣器和模拟喇叭的音频输出的实现方法都是利用PWM波驱动,用wave、synthesizer和一个looktable查表子模块改变来改变音频频率,,类似于SPWM的一个实际应用。它们输出的音频的差别是在于,蜂鸣器输出音频噪声小,但是带宽较窄,并且同样的GPIO驱动输出下,蜂鸣器的输出音频声音会比模拟喇叭的音频声音小,模拟喇叭的音频输出声音洪亮,但会有一些噪声,在调节电位器,改变声音大小时,会有噪声,但是模拟喇叭的带宽比较宽。
3、模拟放大电路的仿真及分析
对其输入的信号进行模拟,计算;
第五列即wave子模块的频率参数。然后对原始参数进行添加偶次谐波,使声音的音色更好听;
图一为增加过一个2倍频的偶次谐波的图形,图二则对图一进行归一化处理,因为实际数值是0-511,当然,bit的设置可以自己根据需求调节。
4、工作框图
这里为整体运行的一个流程图。
四、代码讲解
module top(
input clk,
input rst,
input [14:0] key,
input [1:0] switch,
input sw,
output pwmout,
output speak,
output [7:0] led
);
顶层文件top的设计,输入口和输出口的定义和使用。
initial begin
freq[0]=19'h7FFFF;
freq[1]=19'h7FFFF;
freq[2]=19'h7FFFF;
freq[3]=19'h7FFFE;
freq[4]=19'h7FFFD;
freq[5]=19'h7FFFB;
freq[6]=19'h7FFF7;
freq[7]=19'h7FFEF;
freq[8]=19'h7FFDE;
freq[9]=19'h7FFBE;
freq[10]=19'h7FF7F;
freq[11]=19'h7FEFF;
freq[12]=19'h7FDFF;
freq[13]=19'h7FBFF;
freq[14]=19'h7F7FF;
freq[15]=19'h7EFFF;
freq[16]=19'h7DFFF;
freq[17]=19'h7BFFF;
freq[18]=19'h77FFF;
freq[19]=19'h6FFFF;
freq[20]=19'h5FFFF;
end
initial
begin
Music[0] =10; Music[01] =10; Music[02] =10; Music[03] =10; Music[04] =11; Music[05] =11; Music[06] =12; Music[07] =12;
Music[08] =12; Music[09] =12; Music[10] =11; Music[11] =11; Music[12] =10; Music[13] =10; Music[14] =9; Music[15] =9;
Music[16] =8; Music[17] =8; Music[18] =8; Music[19] =8; Music[20] =9; Music[21] =9; Music[22] =10; Music[23] =10;
Music[24] =10; Music[25] =10; Music[26] =10; Music[27] =9; Music[28] =9; Music[29] =9; Music[30] =9; Music[31] =9;
Music[32] =10; Music[33] =10; Music[34] =10; Music[35] =10; Music[36] =11; Music[37] =11; Music[38] =12; Music[39] =12;
Music[40] =12; Music[41] =12; Music[42] =11; Music[43] =11; Music[44] =10; Music[45] =10; Music[46] =9; Music[47] =9;
Music[48] =8; Music[49] =8; Music[50] =8; Music[51] =8; Music[52] =9; Music[53] =9; Music[54] =10; Music[55] =10;
Music[56] =9; Music[57] =9; Music[58] =9; Music[59] =8; Music[60] =8; Music[61] =8; Music[62] =8; Music[63] =8;
Music[64] =9; Music[65] =9; Music[66] =9; Music[67] =9; Music[68] =10; Music[69] =10; Music[70] =8; Music[71] =8;
Music[72] =9; Music[73] =9; Music[74] =10; Music[75] =11; Music[76] =10; Music[77] =10; Music[78] =8; Music[79] =8;
Music[80] =9; Music[81] =9; Music[82] =10; Music[83] =11; Music[84] =10; Music[85] =10; Music[86] =8; Music[87] =8;
Music[88] =8; Music[89] =8; Music[90] =9; Music[91] =9; Music[92] =5; Music[93] =5; Music[94] =10; Music[95] =10;
Music[96] =10; Music[97] =10; Music[98] =10; Music[99] =10; Music[100]=11; Music[101]=11; Music[102]=12; Music[103]=12;
Music[104]=12; Music[105]=12; Music[106]=11; Music[107]=11; Music[108]=10; Music[109]=10; Music[110]=9; Music[111]=9;
Music[112]=8; Music[113]=8; Music[114]=8; Music[115]=8; Music[116]=9; Music[117]=9; Music[118]=10; Music[119]=10;
Music[120]=9; Music[121]=9; Music[122]=9; Music[124]=8; Music[124]=8; Music[125]=8; Music[126]=8; Music[127]=8;
end
自动播放文件的音频。
module clk_quarter(
input wire clk_in,rst_n_in,
output reg clk_out
);
localparam counter_max = 32'd3000000; // 25MHz*0.25s = 6250000
reg [31:0]counter;
always@(posedge clk_in or negedge rst_n_in)begin
if(!rst_n_in)begin
counter <= 0;
clk_out <= 1'b0;
end
else begin
if(counter >= counter_max)begin
counter <= 0;
clk_out <= 1'b1;
end
else begin
counter <= counter + 1'b1;
clk_out <= 1'b0;
end
end
end
endmodule
四分之一分频子模块的设计。
module debounce (clk,rst,key,key_out,key_pulse);
parameter N = 1; //瑕佹秷闄ゆ姈鍔ㄧ殑鎸夐敭鐨勬暟閲
input clk;
input rst;
input [N-1:0] key; //杈撳叆鐨勬寜閿
output [N-1:0] key_out;
output [N-1:0] key_pulse; //鎸夐敭鍔ㄤ綔浜х敓鐨勮剦鍐蹭竴涓椂閽熷懆鏈熼珮鐢靛钩
reg [N-1:0] key_rst_pre; //瀹氫箟涓€涓瘎瀛樺櫒鍨嬪彉閲忓瓨鍌ㄤ笂涓€涓Е鍙戞椂鐨勬寜閿€ reg [N-1:0] key_rst; //瀹氫箟涓€涓瘎瀛樺櫒鍙橀噺鍌ㄥ瓨鍌ㄥ綋鍓嶆椂鍒昏Е鍙戠殑鎸夐敭鍊
wire [N-1:0] key_edge; //妫€娴嬪埌鎸夐敭鐢遍珮鍒颁綆鍙樺寲鏃朵骇鐢熶竴涓珮鑴夊啿
//鍒╃敤闈為樆濉炶祴鍊肩壒鐐癸紝灏嗕袱涓椂閽熻Е鍙戞椂鎸夐敭鐘舵€佸瓨鍌ㄥ湪涓や釜瀵勫瓨鍣ㄥ彉閲忎腑
always @(posedge clk or negedge rst)
begin
if (!rst) begin
key_rst <= {N{1'b1}}; //鍒濆鍖栨椂缁檏ey_rst璧嬪€煎叏涓锛寋}涓〃绀篘涓
key_rst_pre <= {N{1'b1}};
end
else begin
key_rst <= key; //绗竴涓椂閽熶笂鍗囨部瑙﹀彂涔嬪悗key鐨勫€艰祴缁檏ey_rst,鍚屾椂key_rst鐨勫€艰祴缁檏ey_rst_pre
key_rst_pre <= key_rst; //闈為樆濉炶祴鍊笺€傜浉褰撲簬缁忚繃涓や釜鏃堕挓瑙﹀彂锛宬ey_rst瀛樺偍鐨勬槸褰撳墠鏃跺埢key鐨勫€硷紝key_rst_pre瀛樺偍鐨勬槸鍓嶄竴涓椂閽熺殑key鐨勫€ end
end
//鑴夊啿杈规部妫€娴嬨€傚綋key妫€娴嬪埌涓嬮檷娌挎椂锛宬ey_edge浜х敓涓€涓椂閽熷懆鏈熺殑楂樼數骞 assign key_edge = key_rst_pre & (~key_rst);
reg [17:0] cnt; //浜х敓寤舵椂鎵€鐢ㄧ殑璁℃暟鍣紝绯荤粺鏃堕挓12MHz锛岃寤舵椂20ms宸﹀彸鏃堕棿锛岃嚦灏戦渶瑕8浣嶈鏁板櫒
//浜х敓20ms寤舵椂锛屽綋妫€娴嬪埌key_edge鏈夋晥鏄鏁板櫒娓呴浂寮€濮嬭鏁 always @(posedge clk or negedge rst)
begin
if(!rst)
cnt <= 18'h0;
else if(key_edge)
cnt <= 18'h0;
else
cnt <= cnt + 1'h1;
end
reg [N-1:0] key_sec_pre; //寤舵椂鍚庢娴嬬數骞冲瘎瀛樺櫒鍙橀噺
reg [N-1:0] key_sec;
//寤舵椂鍚庢娴媖ey锛屽鏋滄寜閿姸鎬佸彉浣庝骇鐢熶竴涓椂閽熺殑楂樿剦鍐层€傚鏋滄寜閿姸鎬佹槸楂樼殑璇濊鏄庢寜閿棤鏁 always @(posedge clk or negedge rst)
begin
if (!rst)
key_sec <= {N{1'b1}};
else if (cnt==18'h3ffff)
key_sec <= key;
end
always @(posedge clk or negedge rst)
begin
if (!rst)
key_sec_pre <= {N{1'b1}};
else
key_sec_pre <= key_sec;
end
assign key_pulse = key_sec_pre & (~key_sec);
assign key_out = key_sec;
endmodule
按键消抖的子程序模块设计。
module synthesizer(
input clk,
input rst,
input [12:0] key,
input up,
input down,
output [10:0] wavecnt
);
wire [9:0] waveldo,wavelre,wavelmi,wavelfa,wavelso,wavella,wavelsi,
wavemdo,wavemre,wavemmi,wavemfa,wavemso,wavemla,wavemsi,
wavehdo,wavehre,wavehmi,wavehfa,wavehso,wavehla,wavesi;
always @(posedge clk or negedge rst) begin
if (!rst) begin
wave #(366) ldo(.clk(clk),.rst(rst),.enable(key[0]),.waveout(waveldo));
wave #(411) lre(.clk(clk),.rst(rst),.enable(key[1]),.waveout(wavelre));
wave #(461) lmi(.clk(clk),.rst(rst),.enable(key[2]),.waveout(wavelmi));
wave #(488) lfa(.clk(clk),.rst(rst),.enable(key[3]),.waveout(wavelfa));
wave #(548) lso(.clk(clk),.rst(rst),.enable(key[4]),.waveout(wavelso));
wave #(615) lla(.clk(clk),.rst(rst),.enable(key[5]),.waveout(wavella));
wave #(691) lsi(.clk(clk),.rst(rst),.enable(key[6]),.waveout(wavelsi));
wave #(732) mdo(.clk(clk),.rst(rst),.enable(key[7]),.waveout(wavemdo));
wave #(821) mre(.clk(clk),.rst(rst),.enable(key[8]),.waveout(wavemre));
wave #(922) mmi(.clk(clk),.rst(rst),.enable(key[9]),.waveout(wavemmi));
wave #(977) mfa(.clk(clk),.rst(rst),.enable(key[10]),.waveout(wavemfa));
wave #(1096) mso(.clk(clk),.rst(rst),.enable(key[11]),.waveout(wavemso));
wave #(1230) mla(.clk(clk),.rst(rst),.enable(key[12]),.waveout(wavemla));
assign wavecnt = waveldo+wavelre+wavelmi+wavelfa+wavelso+wavella+wavelsi+wavemdo+wavemre+wavemmi+wavemfa+wavemso+wavemla;
end
else
if (~down) begin //低电平有效
wave #(366) ldo(.clk(clk),.rst(rst),.enable(key[0]),.waveout(waveldo));
wave #(411) lre(.clk(clk),.rst(rst),.enable(key[1]),.waveout(wavelre));
wave #(461) lmi(.clk(clk),.rst(rst),.enable(key[2]),.waveout(wavelmi));
wave #(488) lfa(.clk(clk),.rst(rst),.enable(key[3]),.waveout(wavelfa));
wave #(548) lso(.clk(clk),.rst(rst),.enable(key[4]),.waveout(wavelso));
wave #(615) lla(.clk(clk),.rst(rst),.enable(key[5]),.waveout(wavella));
wave #(691) lsi(.clk(clk),.rst(rst),.enable(key[6]),.waveout(wavelsi));
wave #(732) mdo(.clk(clk),.rst(rst),.enable(key[7]),.waveout(wavemdo));
wave #(821) mre(.clk(clk),.rst(rst),.enable(key[8]),.waveout(wavemre));
wave #(922) mmi(.clk(clk),.rst(rst),.enable(key[9]),.waveout(wavemmi));
wave #(977) mfa(.clk(clk),.rst(rst),.enable(key[10]),.waveout(wavemfa));
wave #(1096) mso(.clk(clk),.rst(rst),.enable(key[11]),.waveout(wavemso));
wave #(1230) mla(.clk(clk),.rst(rst),.enable(key[12]),.waveout(wavemla));
assign wavecnt = waveldo+wavelre+wavelmi+wavelfa+wavelso+wavella+wavelsi+wavemdo+wavemre+wavemmi+wavemfa+wavemso+wavemla;
end
else (~up) begin // 高电平有效
wave #(821) mre(.clk(clk),.rst(rst),.enable(key[8]),.waveout(wavemre));
wave #(922) mmi(.clk(clk),.rst(rst),.enable(key[9]),.waveout(wavemmi));
wave #(977) mfa(.clk(clk),.rst(rst),.enable(key[10]),.waveout(wavemfa));
wave #(1096) mso(.clk(clk),.rst(rst),.enable(key[11]),.waveout(wavemso));
wave #(1230) mla(.clk(clk),.rst(rst),.enable(key[12]),.waveout(wavemla));
wave #(1381) msi(.clk(clk),.rst(rst),.enable(key[0]),.waveout(wavemsi));
wave #(1474) hdo(.clk(clk),.rst(rst),.enable(key[1]),.waveout(wavehdo));
wave #(1642) hre(.clk(clk),.rst(rst),.enable(key[2]),.waveout(wavehre));
wave #(1843) hmi(.clk(clk),.rst(rst),.enable(key[3]),.waveout(wavehmi));
wave #(1953) hfa(.clk(clk),.rst(rst),.enable(key[4]),.waveout(wavehfa));
wave #(2192) hso(.clk(clk),.rst(rst),.enable(key[5]),.waveout(wavehso));
wave #(2461) hla(.clk(clk),.rst(rst),.enable(key[6]),.waveout(wavehla));
wave #(2762) hsi(.clk(clk),.rst(rst),.enable(key[7]),.waveout(wavehsi));
assign wavecnt = wavemre+wavemmi+wavemfa+wavemso+wavemla+wavemsi+wavehdo+wavehre+wavehmi+wavehfa+wavehso+wavehla+wavehsi;
end
end
endmodule
波形频率的设计子模块。
module breath_led(clk, rst, led);
input clk;
input rst;
output wire [7:0] led;
reg [24:0] cnt1;
reg [24:0] cnt2;
reg flag;
wire led_breath;
assign X = 7'b1111111;
parameter CNT_NUM = 3464;
always @ (posedge clk or negedge rst) begin
if(!rst) begin
cnt1 <= 13'd0;
end
else if(cnt1>=CNT_NUM - 1)
cnt1 <= 1'b0;
else
cnt1 <= cnt1 + 1'b1;
end
always @ (posedge clk or negedge rst) begin
if(!rst) begin
cnt2 <= 13'd0;
flag <= 1'b0;
end
else if(cnt1>=CNT_NUM-1) begin
if(!flag) begin
if(cnt2 >= CNT_NUM - 1)
flag <= 1'b1;
else
cnt2 <= cnt2 + 1'b1;
end
else begin
if(cnt2 <= 0)
flag <= 1'b0;
else
cnt2 <= cnt2 - 1'b1;
end
end
else
cnt2 <= cnt2;
end
assign led_breath = (cnt1 < cnt2) ? 1'b0 : 1'b1;
assign led = {8{led_breath}};
endmodule
呼吸灯的代码,用于提供氛围灯的效果。
module pwm
(
input clk,
input rst,
input [10:0] duty,
output pwm_out
);
reg [11:0] PWM_accumulator0;
always @(posedge clk or negedge rst)
if(!rst)
PWM_accumulator0 <= 0;
else
PWM_accumulator0 <= PWM_accumulator0[10:0] + duty;
assign pwm_out = PWM_accumulator0[11];
endmodule
使用了教程中推荐的PWM设计模块。
五、实现功能展示
请观看视频中的项目演示,图片不易于表达音频,谢谢!
六、心得体会
其中主要遇到的问题,主要是官方提供的软件学习资料较少,只能自己慢慢摸索学习。可以从增大音程和分别仿真蜂鸣器和扬声器的波表,进行一个音色的优化,增加部分显示模块来观察按键按下的键数,由于纯纯的理工生,所以对音乐一窍不通,所以对音程的了解也需要进一步了解,且不同乐器之间的频率也是有很大区别的,
非常感谢硬禾学堂举办的2022暑期在家一起练(3) - 基于FPGA的电子琴设计 ,这也是我参加的第5个硬禾学堂活动,每一次参加都收获满满,无论是硬件知识还是软件知识。相比于之前参加的活动,这一次学习FPGA数字电路方面的知识,对数字电路的设计有了更深刻的使用和了解。同时,通过Web IDE编程和Lattice官方提供的Diamond软件进行编程结合使用,可以说是新的收获。
由衷地希望硬禾学堂越来越好!!!