基于FPGA完成的电子琴设计
基于FPGA技术与小脚丫电子琴板,实现了扬声器与蜂鸣器自动播放音乐,按键演奏等功能。
标签
FPGA
DDS
2022暑假在家练
PLL
赖明宇
更新2022-09-07
华中科技大学
887

                        基于FPGA的电子琴设计的项目总结报告

                                                                                 华中科技大学 20级本科生赖明宇

一、电子琴的工作原理和框图

(一)工作原理

上电后,当开关拨向A方,电子琴由扬声器发声。此时若小脚丫核心板上的开关key_1拨向上方,进入演奏模式。初始化的音程为C4,C4#,......,C5,可通过上下键拓展音程,最大音程为C6,C6#,......,C7,每按下13个键其中的一个,便发出对应频率的声音,并且声音包含主频和一个谐波分量。若开关key_1拨向下方,进入自动播放模式,循环播放《天空之城》开头部分,并且每发出的一个音都包含主频以及谐波分量;当开关拨向P方,电子琴由蜂鸣器发声。若key_1拨向上方,进入演奏模式,可通过上下键拓展音程,每按下13个键其中的一个,便发出对应频率的声音。若开关key_1拨向下方,进入自动播放模式,循环播放《天空之城》开头部分。

(二)工作原理框图

FhAWUcyQnDLP634WoxbLqU2kyUdQ

二、蜂鸣器和模拟喇叭的差别

本次实验用到两个发声源,分别是蜂鸣器与扬声器,下面介绍二者的区别。

(一)蜂鸣器

Fpwrkpx4Wafewyr-4It-dXP2k1RS

                                                  Piano Kit扩展板上的蜂鸣器资源

本项目扬声器型号为PUI Audio SMT-0825-S-4-R,为无源蜂鸣器。无源蜂鸣器本身没有内部振荡电路,所以需要一个外部的振荡信号才能将其驱动、发声。当NPN型BJT三极管基极为高电平时,三极管导通,集电极上有较大电流,蜂鸣器两端电压近似为电源电压减去三极管导通压降,二极管截止;当基极为低电平时,三极管截止,基极电流、发射极电流、集电极电流为0,蜂鸣器两端电压近似为二极管电压,而蜂鸣器作为感性元件,电流不能突变,故此时二极管作为续流二极管的作用,使电流较平缓的变化。当基极在高低电平之间变化时,蜂鸣器两端电压也相应在高低电平之间以相同的频率变化,从而发出对应频率的声音。

(二)扬声器

FmRvNEqx0xdwX7MBKL0JHRQ6T_CZ

                                             Piano Kit扩展板上的扬声器资源

FrnDeb6_fle7HSV31Algb6HUBpXH

这部分电路是低通滤波器,可以滤去高频分量,通过推导表达式并由matlab作图得到低通滤波器的幅频特性曲线为:

FuoqNoOcSxfAlEdRlUrNoRc6lz6b低通滤波器后一级是音频功率放大器,为扬声器speaker提供高质量的输出功率。

(三)蜂鸣器和模拟喇叭的实现方法差别以及音效差别分析

1、蜂鸣器部分:

我参考了电子森林上的驱动蜂鸣器的例程。若想蜂鸣器产生某一频率的信号,比如C4(261.6Hz)音调,则给它一个261.6Hz频率的方波信号,占空比为1/2。实现方法为将12MHz的时钟信号分频,分频到对应的频率。

2、扬声器部分:

由于项目要求扬声器发声能够实现和弦,并且播放时每个单音由主频加至少一个谐波分量构成,故采用spwm方式。我参考了直播老师的讲解,利用DDS方法,建立sin波表来产生sin波形,利用相位累加器的方法,改变查表速度来改变sin波形的频率,再将得到的sin值与一个锯齿波比较,采用向上计数的方式得到spwm波形,将spwm传给扬声器便发出对应频率的声音。将主频和谐波频率对应的sin值相加再和锯齿波比较产生的spwm传给扬声器后,扬声器实现每个单音由主频和至少一个谐波分量构成,并且按两个键不失真。

3、音效差别分析

Fmg4z_kC4jieK3c4goboSPKNZbHq

                                                       扬声器发出的B6频率声音

FtOY1us919_Hrss05cQGOjOaEa7G

                                                             蜂鸣器发出的声音

由图可以发现,扬声器发出的声音除了主频和所加的二次谐波以外,其余频率的波强度很小,较好的实现了项目中谐波的要求;蜂鸣器得到的是方波信号,所以有很多谐波分量,而且偶次谐波强度大致相等,奇次谐波强度大致相等。

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

FhkgwKejT7OIk3ZQWfse2sFXioj3

Fmqih73kDfTDuqgRZHoImEy0cgRA

                                                          使用LTspice仿真的电路

FsU8g8CKzoLnIL34QvdHFdOQAOOX

                                                 交流扫描得到的扬声器两端电压值

分析:

由于SD接口一直接地,始终不关断,所以电路图中未将其画出。由电路图可知8002b功率放大器由两个运放组成。第二个运放不能改变增益,第一个运放可以通过改变-IN与VO1接口两端电阻电容以及-IN端口的输入电阻来改变增益,从而改变8002b的增益大小,达到功率放大的作用。C4-C7频率范围为261.6Hz-2092.8Hz,由交流扫描分析可知,在该频率范围内,20lg(v0/vi)=19.47。Av=v0/vi=10^(19.47/20)=9.41。电压放大倍数较大且衰减很小。

四、主要代码片段及说明

(一)顶层文件

module top(
		input clk,
		input rst,
		input [12:0] key,
		input key_choose,
		input [1:0] judge_key,
		input sel,
		output spwm_out,
		output square_out
		);
	//******dds******
	wire [31:0] wave_out1;
	wire [12:0] delay_key;
	wire [1:0]  delay_judge_key;
	
	Key_Delay judge_key_0(.clk(clk),.key_in(judge_key[0]),.rst(rst),.key_out(delay_judge_key[0]));
	Key_Delay judge_key_1(.clk(clk),.key_in(judge_key[1]),.rst(rst),.key_out(delay_judge_key[1]));
		
	synthesizer_pwm u0_synthesizer_pwm(.clk(clk),.rst(rst),.sel(sel),.key(key),.judge_key(delay_judge_key),.key_choose(key_choose),.pwm_out(spwm_out));
	beeper u0_beeper(.clk(clk),.rst(rst),.sel(sel),.key(key),.judge_key(delay_judge_key),.key_choose(key_choose),.beeper_wave(square_out));

endmodule

输入端口功能说明:clk接12MHz时钟,rst为复位键,key[12:0]为13个电子琴按键,key_choose为播放/弹奏模式的选择,judge_key为按键拓展音程,sel为选择声源,选择扬声器发声或者蜂鸣器发声,spwm_out为传给扬声器的信号,square_out为传给蜂鸣器的信号。Key_Delay为按键消抖模块,为judge_key按键消抖,synthesizer_pwm为产生扬声器信号模块,beeper为产生蜂鸣器信号模块。

(二)引脚分配

Fvp5vy34ec2Lk45zQag-wwxMpjbm

(三)按键消抖模块

module  Key_Delay(
    input clk,
    input key_in,
    input rst,
    output reg key_out
);
	reg [31:0] count1;
	reg clk150Hz;
	always @(posedge clk or negedge rst)begin
		if(!rst) begin count1 <= 0;
		end else if(count1 < 32'd40000) begin count1 <= count1 + 32'd1; 
		end else begin
			count1 <= 32'd0;
			clk150Hz <= ~clk150Hz;  //得到2Hzclk
		end
	end
	
	reg [2:0] shifter;
	always @(posedge clk150Hz) begin
		shifter[2:1] <= shifter[1:0];
		shifter[0] <= key_in;
	end

	always @(posedge clk150Hz) begin
		if(shifter[2:0]==3'b000) begin key_out <= 1'b0;end
		else if(shifter[2:0]==3'b111) begin key_out<=1'b1;end
		else begin
			key_out <= key_out;	
		end
	end
endmodule

先通过分频,得到12MHz/80000=150Hz信号,当key_in按键电平改变后,每过3个clk150Hz的上升沿,将key_in传给key_out。用时3/(150Hz)=0.02s=20ms。从而实现按键20ms消抖。

(四)产生spwm_out信号

module synthesizer_pwm(
		input clk,
        input rst,
		input sel,
        input [12:0] key,
		input [1:0] judge_key,
		input key_choose,
        output pwm_out
        );
                
    wire [10:0] wavec,wavecJ,waved,wavedJ,wavee,wavef,wavefJ,waveg,wavegJ,wavea,waveaJ,waveb,wavecJJ;
	wire [23:0] stepc,stepcJ,stepd,stepdJ,stepe,stepf,stepfJ,stepg,stepgJ,stepa,stepaJ,stepb,stepcJJ;
	
	enlarge u0_enlarge(.CLKI(clk),.CLKOP(clk240));
	
	reg [1:0]  judge_key_last;
	wire[1:0]  judge_key_press;
	 
	always @(posedge clk) begin judge_key_last <= judge_key; end
	assign judge_key_press = judge_key_last & ~judge_key;
	 
	reg [2:0] chose_cnt = 2;
	
	always @(posedge clk) begin
		if(!rst) begin chose_cnt <= 2; end 
		else if(judge_key_press[0]) begin 
			if(chose_cnt != 3'd2) begin chose_cnt <= chose_cnt + 3'd1; end    //音调降八度
		end 
		else if(judge_key_press[1])  begin
			if(chose_cnt != 3'd0) begin chose_cnt <= (chose_cnt - 1+60) % 60; end				//音调升八度
		end
	end
  
      assign stepc = (24'd1461 >> chose_cnt) ; //C6
	  assign stepcJ = (24'd1547 >> chose_cnt);   
	  assign stepd = (24'd1643 >> chose_cnt); 
	  assign stepdJ = (24'd1739 >> chose_cnt); 
	  assign stepe = (24'd1845 >> chose_cnt); 
	  assign stepf = (24'd1952 >> chose_cnt); 
	  assign stepfJ = (24'd2069 >> chose_cnt); 
	  assign stepg = (24'd2197 >> chose_cnt); 
	  assign stepgJ = (24'd2315 >> chose_cnt); 
	  assign stepa = (24'd2464 >> chose_cnt); 
	  assign stepaJ = (24'd2603 >> chose_cnt); 
	  assign stepb = (24'd2763 >> chose_cnt); 
	  assign stepcJJ = stepc<<1  ; 			
			
	wave u0_c(.clk(clk),.rst(rst),.step(stepc),.enable(key[0]),.waveout(wavec));
	wave u0_cJ(.clk(clk),.rst(rst),.step(stepcJ),.enable(key[1]),.waveout(wavecJ));
	wave u0_d(.clk(clk),.rst(rst),.step(stepd),.enable(key[2]),.waveout(waved));
	wave u0_dJ(.clk(clk),.rst(rst),.step(stepdJ),.enable(key[3]),.waveout(wavedJ));
	wave u0_e(.clk(clk),.rst(rst),.step(stepe),.enable(key[4]),.waveout(wavee));
	wave u0_f(.clk(clk),.rst(rst),.step(stepf),.enable(key[5]),.waveout(wavef));
	wave u0_fJ(.clk(clk),.rst(rst),.step(stepfJ),.enable(key[6]),.waveout(wavefJ));
	wave u0_g(.clk(clk),.rst(rst),.step(stepg),.enable(key[7]),.waveout(waveg));
	wave u0_gJ(.clk(clk),.rst(rst),.step(stepgJ),.enable(key[8]),.waveout(wavegJ));
	wave u0_a(.clk(clk),.rst(rst),.step(stepa),.enable(key[9]),.waveout(wavea));
	wave u0_aJ(.clk(clk),.rst(rst),.step(stepaJ),.enable(key[10]),.waveout(waveaJ));
	wave u0_b(.clk(clk),.rst(rst),.step(stepb),.enable(key[11]),.waveout(waveb));
	wave u0_cJJ(.clk(clk),.rst(rst),.step(stepcJJ),.enable(key[12]),.waveout(wavecJJ));		
	
	wire [31:0] wavecnt;
	wire [31:0] wavecnt_1;
    assign wavecnt = wavec+wavecJ+waved+wavedJ+wavee+wavef+wavefJ+waveg+wavegJ+wavea+waveaJ+waveb+wavecJJ;
	
	wire [10:0] sin_song; //得到播放的sin
    song my_song(.clk(clk),.rst(rst),.sin_song(sin_song));
	assign wavecnt_1 = sel?((key_choose)? (wavecnt >> 4):(sin_song >> 3)):0;//sel为1时扬声器发声,当key_choose为1时为演奏模式,当key_choose为0时自动播放
	
	spwm u0_spwm(.clk(clk240),.rst(rst),.wave_out1(wavecnt_1),.pwm_out(pwm_out));	
endmodule

这个模块有较多部分。考虑到C4,D4,......,C5与C5,D5,......C6与C6,D6,......,D7每隔一个八度频率翻倍,用chose_cnt来控制相位累加器中相位累加的步长。每当按下上键,judge_key_press[1]产生一个正脉冲,使chose_cnt减一,步长乘二,频率翻倍,从而使每个音提高一个音程,每当按下下键,judge_key_press[0]产生一个正脉冲,使chose_cnt加一,步长除以二,频率除以二,从而使每个音降低一个音程。每当按下电子琴键时,key[i]则会变成0,该模块会根据输入的步长输出对应的wave,而未按下的键对应的enable端口输入的为1,该模块输出恒为0,wavcnt将这13个例化的模块的输出加在一起,作为按键产生的sin波形。

通过调用song模块,得到sin_song。sin_song是扬声器自动播放的sin波形信号。

通过assign wavecnt_1 = sel?((key_choose)? (wavecnt >> 4):(sin_song >> 3)):0;语句,确定将被pwm调制的波形。若sel为0,wavecnt_1为0,则由蜂鸣器发声;若sel为1,key_choose为1,则wavecnt_1=wavecnt>>4,对应演奏模式;若sel为1,key_choose为0,则wavecnt=sin_song>>3,对应自动播放模式。

通过enlarge模块,得到240MHz的时钟信号作为例化spwm模块的时钟输入,使得到的spwm更加精细,产生的正弦波不需要的谐波分量强度更低。

通过spwm模块将wavecnt_1信号与锯齿波信号相比较,得到输出至扬声器端的pwm_out信号。

这里的步长计算方法:f=12000000/((2^24)/M)。通过sin波的频率f推得对应的M值。

(五)扬声器自动播放模块

module song(           //演奏天空之城,四分音符为一拍,每小节有四拍
	input clk,         //12MHz 
	input rst,
	output [10:0] sin_song
	);
	
	reg clk2Hz;
	reg [31:0] count1;
	
	always @(posedge clk or negedge rst)begin
		if(!rst) begin count1 <= 0; end 
		else if(count1 < 32'd3000000)begin count1 <= count1 + 32'd1; end 
		else begin
			count1 <= 32'd0;
			clk2Hz <= ~clk2Hz;  //得到2Hzclk
		end
	end
	
	wire [23:0] stepc4,stepc4J,stepd4,stepd4J,stepe4,stepf4,stepf4J,stepg4,stepg4J,stepa4,stepa4J,stepb4,stepc4JJ;
	wire [23:0] stepc5,stepc5J,stepd5,stepd5J,stepe5,stepf5,stepf5J,stepg5,stepg5J,stepa5,stepa5J,stepb5,stepc5JJ;

	  assign stepc4 = (24'd1461 >> 2);  //C4
	  assign stepc4J = (24'd1547 >> 2);  
	  assign stepd4 = (24'd1643 >> 2);
	  assign stepd4J = (24'd1739 >> 2);
	  assign stepe4 = (24'd1845 >> 2);
	  assign stepf4 = (24'd1952 >> 2);
	  assign stepf4J = (24'd2069 >> 2);
	  assign stepg4 = (24'd2197 >> 2);
	  assign stepg4J = (24'd2315 >> 2);
	  assign stepa4 = (24'd2464 >> 2);
	  assign stepa4J = (24'd2603 >> 2);
	  assign stepb4 = (24'd2763 >> 2);
	  assign stepc4JJ = (stepc4 << 1); 
	  
	  assign stepc5 = (24'd1461 >> 1) ; //C5
	  assign stepc5J = (24'd1547 >> 1) ; 
	  assign stepd5 = (24'd1643 >> 1);
	  assign stepd5J = (24'd1739 >> 1);
	  assign stepe5 = (24'd1845 >> 1);
	  assign stepf5 = (24'd1952 >> 1);
	  assign stepf5J = (24'd2069 >> 1);
	  assign stepg5 = (24'd2197 >> 1);
	  assign stepg5J = (24'd2315 >> 1);
	  assign stepa5 = (24'd2464 >> 1);
	  assign stepa5J = (24'd2603 >> 1);
	  assign stepb5 = (24'd2763 >> 1);
	  assign stepc5JJ = (stepc4 << 2); 
	
	reg [6:0] state;
	reg enable;
	reg [23:0] step;
	always @(posedge clk2Hz or negedge rst)begin
		if(!rst) begin state = 0; end 
		else if(state == 7'd28) begin state = 0; end 
		else begin state = state + 7'd1; end
	end
	
	always @(posedge clk2Hz) begin
		case(state)
			7'd1:begin enable = 0;step = stepa4; end
			7'd2:step = stepb4;
			7'd3,7'd4,7'd5:step = stepc5;
			7'd6:step = stepb4;
			7'd7,7'd8:step = stepc5;
			7'd9,7'd10:step = stepe5;
			7'd11,7'd12,7'd13,7'd14:step = stepb4;
			7'd15,7'd16:enable = 1;
			7'd17,7'd18:begin enable =0;step = stepe4; end
			7'd19,7'd20,7'd21:step = stepa4;
			7'd22:step = stepg4;
			7'd23,7'd24:step = stepa4;
			7'd25,7'd26:step = stepc5;
			7'd27,7'd28:step = stepg4;
		endcase	
	end
	
	wave my_song(.clk(clk),.rst(rst),.step(step),.enable(enable),.waveout(sin_song));
endmodule	

《天空之城》四分音符为一拍,每小节四拍,加下划线的音符演奏时间减半,故通过对12MHz时钟clk分频产生2Hz时钟信号,使得加下划线的音符发音0.5s。我采用例化wave模块的方法得到想要的波形。每当有clk2Hz的上升沿则将state+1,同时在第二个always时序电路中根据state的值,对step赋以音调对应的步长,从而通过wave模块得到sin_song,即自动播放的sin波形。并且wave模块输出的波形已经包含了主频和二次谐波,符合项目要求。

(六)wave模块产生波形

module wave(
		input clk,
		input rst,
		input  [23:0] step,
		input  enable,
		output reg [10:0] waveout
		);
	
    reg [23:0] phase_acc,two_phase_acc;     //前者为主频,后者为加上的谐波
	wire [7:0] phase_acc1,two_phase_acc1;
    wire [31:0] sin_out1,two_sin_out1;
	
	wire [23:0] two_step;
	assign two_step = step << 1;            //谐波频率为主频率的两倍                   
	
    always @(posedge clk or negedge rst) begin
        if(!rst) begin
            phase_acc <= 0;                 //主频和谐波相位累加器的初始化
			two_phase_acc <= 0;
            waveout <= 0;
        end
        else if(!enable)begin
            phase_acc <= phase_acc + step;
			two_phase_acc <= two_phase_acc +two_step;
            waveout <= (!enable)?((sin_out1*7+two_sin_out1*2)>>3):0;
        end
		else begin waveout <= 11'd0; end
	end
            
    lookup_tables u0_lookuptable(.phase(phase_acc[23:16]),.sin_out(sin_out1));//根据相位得到对应的sin值sin_out1
	lookup_tables u2_lookuptable(.phase(two_phase_acc[23:16]),.sin_out(two_sin_out1));//根据相位得到对应的sin值two_sin_out1
	
endmodule

我采用直播老师讲解的相位累加法。主频步长为输入的step,二次谐波步长为step的两倍,即:step<<1。每当琴键按下,使得enable为0后,当clk上升沿来时phase_acc与two_phase_acc便会增加相应的步长,利用例化lookup_tables模块的方法得到主频sin_out1以及二次谐波two_sin_out1波形。再通过waveout <= (!enable)?((sin_out1*7+two_sin_out1*2)>>3):0;得到二者叠加后的波形并输出。

(七)脉冲宽度调制pwm实现过程

module spwm (
		input clk,
		input rst,
		input [31:0] wave_out1,
		output pwm_out
		);
    reg [31:0] cnt;
	wire [31:0] num;
	assign num = 32'hFF;
    always @(posedge clk or negedge rst) begin
        if(!rst)  begin cnt <= 0;  end 
        else begin
            if(cnt >= num-1) cnt <= 0;
            else cnt <= cnt + 1;
        end
    end

    assign pwm_out = (cnt < wave_out1) ? 1 : 0 ;
endmodule	

wave_out1为输入的待调制信号,对应synthesizer_pwm模块里的wavecnt_1。当clk上升沿时cnt+1;当cnt等于num-1时,达到了锯齿波的最高点,再将cnt置零。cnt与wave_out1是关于clk实时变化的,将二者相比较,若后者大,则输出1,若前者大,则输出0,从而得到spwm作为pwm_out。

(八)产生square_out信号

module beeper(
		input clk,
		input rst,
		input sel,
		input [12:0] key,
		input [1:0] judge_key,
		input key_choose,
		output beeper_wave
		);
	reg [1:0]  judge_key_last;
	wire[1:0]  judge_key_press;
	 
	always @(posedge clk) begin judge_key_last <= judge_key; end
	assign judge_key_press = judge_key_last & ~judge_key;
	 
	reg [2:0] chose_cnt = 2;
	
	always @(posedge clk) begin
		if(!rst)
			chose_cnt <= 2;
		if(judge_key_press[0])  chose_cnt <= chose_cnt - 3'd1;     //音调降八度
		if(judge_key_press[1])  chose_cnt <= chose_cnt + 3'd1;     //音调升八度
	end
	
	reg [15:0] time_end;
	always @(posedge clk) begin
		case(key) 
			13'h0FFF:time_end = (16'd11464>>chose_cnt)<<2;
			13'h17FF:time_end = (16'd12147>>chose_cnt)<<2;
			13'h1BFF:time_end = (16'd12876>>chose_cnt)<<2;
			13'h1DFF:time_end = (16'd13635>>chose_cnt)<<2;
			13'h1EFF:time_end = (16'd14458>>chose_cnt)<<2;
			13'h1F7F:time_end = (16'd15305>>chose_cnt)<<2;
			13'h1FBF:time_end = (16'd17182>>chose_cnt)<<2;
			13'h1FDF:time_end = (16'd17181>>chose_cnt)<<2;
			13'h1FEF:time_end = (16'd18203>>chose_cnt)<<2;
			13'h1FF7:time_end = (16'd19293>>chose_cnt)<<2;
			13'h1FFB:time_end = (16'd20428>>chose_cnt)<<2;
			13'h1FFD:time_end = (16'd21661>>chose_cnt)<<2;
			13'h1FFE:time_end = (16'd22935>>chose_cnt)<<2;
			default:time_end = 0;
		endcase
	end
	wire square_song;//得到自动播放的方波信号
	beeper_song u0_beeper_song(.clk(clk),.rst(rst),.square_song(square_song));
	assign beeper_wave = (~sel)?((key_choose)?beeper_wave1:square_song):0;               //sel为0时蜂鸣器发声,当key_choose为1时为演奏模式,当key_choose为0时自动播放
	square_wave u0_square_wave(.clk(clk),.rst(rst),.time_end(time_end),.beeper_wave(beeper_wave1));
endmodule

Judge_key_press,chose_cnt与synthesizer_pwm模块中的作用相同,不再赘述。每当clk上升沿来时,根据按键[12:0]key的值,确定给time_end赋以对应频率的分频值,并通过例化square_wave模块得到蜂鸣器驱动信号beeper_wave1。

通过例化beeper_song模块,得到蜂鸣器自动播放信号square_song。

通过assign beeper_wave = (~sel)?((key_choose)?beeper_wave1:square_song):0;确定将被输出的波形。若sel为1,则beeper_wave为0,由扬声器发声;若sel为0,key_choose为1,则beeper_wave=beeper_wave1,对应演奏模式;若sel为0,key_choose为0,则beeper_wave=square_song,对应自动播放模式。

这里time_end计算方式为:time_end=12000000/(2*f),根据音调对应的f推得time_end。

(九)蜂鸣器自动播放模块

module beeper_song(
		input clk,     //12MHz主时钟
		input rst,
		output square_song
		);
	reg	clk2Hz;       //单音最低演奏时长0.5s,故需要2Hz的时钟
	reg [31:0] count1;
	
	always @(posedge clk or negedge rst)begin
		if(!rst) begin count1 <= 0; end 
		else if(count1 < 32'd3000000)begin count1 <= count1 + 32'd1; end 
		else begin
			count1 <= 32'd0;
			clk2Hz <= ~clk2Hz;  //得到2Hzclk
		end
	end
	
	wire [15:0] time_endC4,time_endC4J,time_endD4,time_endD4J,time_endE4,
	time_endF4,time_endF4J,time_endG4,time_endG4J,time_endA4,time_endA4J,
	time_endB4,time_endC4JJ;
	
	assign time_endC4 = 16'd22935;
	assign time_endC4J = 16'd21661;
	assign time_endD4 = 16'd20428;
	assign time_endD4J = 16'd19293;
	assign time_endE4 = 16'd18203;
	assign time_endF4 = 16'd17181;
	assign time_endF4J = 16'd17182;
	assign time_endG4 = 16'd15305;
	assign time_endG4J = 16'd14458;
	assign time_endA4 = 16'd13635;
	assign time_endA4J = 16'd12876;
	assign time_endB4 = 16'd12147;
	assign time_endC4JJ = 16'd11464;

	reg [15:0] time_end;
	reg [6:0] state;
	always @(posedge clk2Hz or negedge rst)begin
		if(!rst) begin state = 0;end
		else if(state == 7'd28) begin state = 0; end
		else begin
			state <= state + 7'd1;
			case(state)
				7'd1:time_end = time_endA4;
				7'd2:time_end = time_endB4;
				7'd3,7'd4,7'd5:time_end = time_endC4 >> 1;
				7'd6:time_end = time_endB4;
				7'd7,7'd8:time_end = time_endC4 >> 1;
				7'd9,7'd10:time_end = time_endE4 >> 1;
				7'd11,7'd12,7'd13,7'd14:time_end = time_endB4;
				7'd17,7'd18:time_end = time_endE4;
				7'd19,7'd20,7'd21:time_end = time_endA4;
				7'd22:time_end = time_endG4;
				7'd23,7'd24:time_end = time_endA4;
				7'd25,7'd26:time_end = time_endC4 >> 1;
				7'd27,7'd28:time_end = time_endG4;
			endcase	
		end
	end
	
	square_wave u1_square_wave(.clk(clk),.rst(rst),.time_end(time_end),.beeper_wave(square_song));
endmodule

原理同扬声器自动播放类似。首先通过分频产生一个2Hz时钟clk2Hz。然后每当clk2Hz上升沿使state+1,同时根据state的值,给time_end赋以音调对应的分频值。

通过例化square_wave模块,不断随着clk2Hz上升沿改变time_end的方法得到蜂鸣器自动播放的驱动信号square_song。

(十)产生蜂鸣器驱动信号

module square_wave(
		input clk,
		input rst,
		input [15:0] time_end,
		output reg beeper_wave
		);
	reg [17:0] time_cnt;
	//计数器按照计数终值计数
	always@(posedge clk or negedge rst) begin
		if(!rst) begin
			time_cnt <= 1'd0;
			beeper_wave <= 1'b0;
		end else if(time_cnt == time_end) begin
			time_cnt <= 1'd0;
			beeper_wave <= ~beeper_wave;
		end else begin
			time_cnt <= time_cnt + 1'd1;
			beeper_wave <= beeper_wave;
		end
	end
endmodule

time_end作为输入端,对应于beeper里的time_end分频值。每当clk上升沿来临时,time_cnt加1,当time_cnt等于输入的分频值时,将time_cnt清零,同时beeper_wave取反。当time_cnt两次达到最大值时,beeper_wave经历一个周期,beeper_wave作为蜂鸣器的驱动信号输出。

五、遇到的主要难题及解决方法

作为一个从未用FPGA开发过项目的小萌新,我一开始遇到了很多很多问题。比如不知道顶层文件是什么,输入输出端口不知道由哪个模块决定,不清楚如何给扬声器输入正弦波信号(明明电平只有0、1两个值),不知道如何实现和弦,甚至很多语法都不熟练......但这个暑假通过直播老师指引的方向,遇到困难时小脚丫群友和好朋友的帮助,我实现了项目要求的功能。下面是我遇到的主要问题:

1、不知如何给扬声器合适的正弦波信号。通过上网查资料我了解到了spwm技术:在载波的不同周期内,spwm波形的占空比随着时间呈正弦规律变化,从而等效为正弦信号。经过仿真,我成功实现了spwm。

FnyO6ldqkQTWuQLa38aijq5Oiawa

                                                          第一次调出的spwm波形

不过这个由改变方波占空比得到的spwm信号精度不够高,发出的声音谐波分量过多,经过群友指点,我开始尝试提高载波频率。我采用了pll的方式,将载波频率升到了240MHz。并且将双向pwm改为了单向pwm(不用三角波而使用锯齿波调制),使得调制信号频率翻倍。从而锯齿波频率为0.941MHz。在保证了载波频率足够高的同时,每个周期内pwm有240MHz/0.941Mhz约为255个取值。这样调制出的波形不需要的谐波分量强度低,声音细腻好听~

2、和弦的实现

扬声器发声。最开始两个键的和弦总是有电流噪音,百思不得其解。后来发现是因为有溢出。于是我研究了每个音的幅值。单音幅值为1FFH+FFH=2FEH,算上最高位的符号位0,一共需要十一位,若同时按下三个键,则最大幅值为2FEH+2FEH+2FEH=8FAH,算上最高位的符号位0,一共需要13位(12位数值位),而我要将其与幅值为FFH的8位锯齿波比较,故将wavecnt右移4位,得到8位有效位的待调制波形;对于不存在和弦的自动播放,移位也是为了防止溢出。

六、改进建议

1、资源使用问题:当我把所有程序写好之后,lut过载:Fm5piFsQU-b8n411PKxPC0xx0F-9

后来尝试更改、注释了不少模块,对lut占用量的减少仍是杯水车薪,于是我把电子琴13个键的按键消抖取消了,缩短了自动播放的内容,lut勉强够用。

FtLVx1qeAy-jtJvCeeoF23hu6_Ge

造成lut资源不够用的原因我认为有两点。首先是我的写法可能不规范。FPGA要被综合成各种电路,就要考虑很多实际的问题,比如竞争冒险、资源共用等等。所以有可能代码逻辑正确,但不符合实际的电路要求,造成了lut过多无意义的使用。其次是我还未能充分利用小脚丫核心板上的资源,比如96Kbit 用户闪存,92Kbit RAM等等,它们可以存放很多数据,但我使用起来仍很吃力。

2、蜂鸣器的发音音调:当我给蜂鸣器261Hz的方波信号时,蜂鸣器发出的声音并非261Hz,而是很高的频率,这一点不知道为何。

3、扬声器的音色:虽然扬声器实现了单频正弦波+二次谐波的声音,但是还可以参考真实乐器发音的音谱,利用DDS产生对应的波形,从而使扬声器发出的音色更贴近乐器。

4、考虑到有两个声源。可以给蜂鸣器一种乐器的波形信号,再给扬声器另一种乐器的波形信号,然后让它们同时播放乐曲,我想声音会很好听。

附件下载
电子琴.zip
内含project_2文件夹和11 .v文件代码,文件夹中除代码外还有pll配置。
团队介绍
华中科技大学-赖明宇
团队成员
赖明宇
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号