2022暑期在家一起练(3) - 基于FPGA的电子琴设计实现音乐功能
通过小脚丫FPGA核心板(Lattice MXO2-C)和Piano Kit扩展板的结合,制作一个电子琴,增加使用小脚丫FPGA的趣味性和成就感。
标签
FPGA
测试
数字逻辑
杨不止
更新2022-09-06
安徽师范大学
784

一、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语言和软件的使用。

电子琴的工作原理和框图:

FtaTd7ELlqIWMJL1IhMmNdMxaB_KFhOTCUIJbRz1GKaxHqIMbEXgBWuhFlfMa5oXQ0XqUfY74gDjMF3O72zE

二、FPGA的电子琴板卡任务完成

1、自己组装电子琴

Fqcnq4wF8vsTVF42ilsxpbhz7bU7

 

2、自己编程基于FPGA实现:存储一段音乐,并可以进行音乐播放;可以自己通过板上的按键进行弹奏,支持两个按键同时按下(和弦)并且声音不能失真,板上的按键只有13个,可以通过有上方的“上“、”下”两个按键对音程进行扩展;使用扬声器进行播放时,输出的音调信号除了对应于该音调的单频正弦波外,还必须包含至少一个谐波分量;音乐的播放支持两种方式,这两种方式可以通过开关进行切换: ;当开关切换到蜂鸣器端,可以通过蜂鸣器来进行音乐播放;当开关切换到扬声器端,可以通过模拟扬声器来进行音乐播放,每个音符都必须包含基频 + 至少一个谐波分量。

3、使用官网软件Lattice Diamond编程,观察资源使用情况

FmnDVvR2rl6N6W0r1XIaQdsvoPE2

(注:因为实现功能是音频的形式,所以,请观看视频的项目演示,这里图片也不利于表达。)

三、分析蜂鸣器和模拟喇叭

1、蜂鸣器和模拟喇叭的区别

蜂鸣器的原理是电压式蜂鸣器,由压电式蜂鸣器主要由多谐振荡器、压电蜂鸣片、阻抗匹配器及共鸣箱、外壳等组成。扬声器,即模拟喇叭的原理其实是一种电能转换成声音的一种转换设备,当不同的电子能量传至线圈时,线圈产生一种能量与磁铁的磁场互动,这种互动造成纸盘振动,因为电子能量随时变化,喇叭的线圈会往前或往后运动,因此喇叭的纸盘就会跟着运动,这此动作使空气的疏密程度产生变化而产生声音。

2、蜂鸣器和模拟喇叭的实现方法及音效差别分析

FmIPYSkuUsSoXNfUSNIgTOO4DDMH

原理图可以看到此次用到的蜂鸣器最基本的组成电路情况,由一个S8050三极管驱动,和一个1N4148保护蜂鸣器的二极管组成。

Fr1yAISX5M4gphEPFCp_CaW_qbts

从模拟喇叭的原理图可以看出,GPIO口输出信号,经过一个RC滤波,再经过一个RV1可调音量的电位器,再经过一个RC滤波,送到8002b音频功率放大器,最后送到模拟喇叭,输出人耳可以听到的音频。

蜂鸣器和模拟喇叭的音频输出的实现方法都是利用PWM波驱动,用wave、synthesizer和一个looktable查表子模块改变来改变音频频率,,类似于SPWM的一个实际应用。它们输出的音频的差别是在于,蜂鸣器输出音频噪声小,但是带宽较窄,并且同样的GPIO驱动输出下,蜂鸣器的输出音频声音会比模拟喇叭的音频声音小,模拟喇叭的音频输出声音洪亮,但会有一些噪声,在调节电位器,改变声音大小时,会有噪声,但是模拟喇叭的带宽比较宽。

3、模拟放大电路的仿真及分析

对其输入的信号进行模拟,计算;

FiMVFbfnU7oEohFjLAsWg-kgBPUM

第五列即wave子模块的频率参数。然后对原始参数进行添加偶次谐波,使声音的音色更好听;

FitUfbPunVvFpfTTYQ8OF6sg3YSF

FmJdlQwxyBVtQO1DsPG6tohSYMcF

图一为增加过一个2倍频的偶次谐波的图形,图二则对图一进行归一化处理,因为实际数值是0-511,当然,bit的设置可以自己根据需求调节。

4、工作框图

这里为整体运行的一个流程图。

FuHN7AtHw_HgVaFlCRBt3_ixe9RW

四、代码讲解

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软件进行编程结合使用,可以说是新的收获。

由衷地希望硬禾学堂越来越好!!!

附件下载
PianoSourceCode_1.zip
源代码
团队介绍
渔火
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号