基于FPGA的电子琴设计的项目总结报告
华中科技大学 20级本科生赖明宇
一、电子琴的工作原理和框图
(一)工作原理
上电后,当开关拨向A方,电子琴由扬声器发声。此时若小脚丫核心板上的开关key_1拨向上方,进入演奏模式。初始化的音程为C4,C4#,......,C5,可通过上下键拓展音程,最大音程为C6,C6#,......,C7,每按下13个键其中的一个,便发出对应频率的声音,并且声音包含主频和一个谐波分量。若开关key_1拨向下方,进入自动播放模式,循环播放《天空之城》开头部分,并且每发出的一个音都包含主频以及谐波分量;当开关拨向P方,电子琴由蜂鸣器发声。若key_1拨向上方,进入演奏模式,可通过上下键拓展音程,每按下13个键其中的一个,便发出对应频率的声音。若开关key_1拨向下方,进入自动播放模式,循环播放《天空之城》开头部分。
(二)工作原理框图
二、蜂鸣器和模拟喇叭的差别
本次实验用到两个发声源,分别是蜂鸣器与扬声器,下面介绍二者的区别。
(一)蜂鸣器
Piano Kit扩展板上的蜂鸣器资源
本项目扬声器型号为PUI Audio SMT-0825-S-4-R,为无源蜂鸣器。无源蜂鸣器本身没有内部振荡电路,所以需要一个外部的振荡信号才能将其驱动、发声。当NPN型BJT三极管基极为高电平时,三极管导通,集电极上有较大电流,蜂鸣器两端电压近似为电源电压减去三极管导通压降,二极管截止;当基极为低电平时,三极管截止,基极电流、发射极电流、集电极电流为0,蜂鸣器两端电压近似为二极管电压,而蜂鸣器作为感性元件,电流不能突变,故此时二极管作为续流二极管的作用,使电流较平缓的变化。当基极在高低电平之间变化时,蜂鸣器两端电压也相应在高低电平之间以相同的频率变化,从而发出对应频率的声音。
(二)扬声器
Piano Kit扩展板上的扬声器资源
这部分电路是低通滤波器,可以滤去高频分量,通过推导表达式并由matlab作图得到低通滤波器的幅频特性曲线为:
低通滤波器后一级是音频功率放大器,为扬声器speaker提供高质量的输出功率。
(三)蜂鸣器和模拟喇叭的实现方法差别以及音效差别分析
1、蜂鸣器部分:
我参考了电子森林上的驱动蜂鸣器的例程。若想蜂鸣器产生某一频率的信号,比如C4(261.6Hz)音调,则给它一个261.6Hz频率的方波信号,占空比为1/2。实现方法为将12MHz的时钟信号分频,分频到对应的频率。
2、扬声器部分:
由于项目要求扬声器发声能够实现和弦,并且播放时每个单音由主频加至少一个谐波分量构成,故采用spwm方式。我参考了直播老师的讲解,利用DDS方法,建立sin波表来产生sin波形,利用相位累加器的方法,改变查表速度来改变sin波形的频率,再将得到的sin值与一个锯齿波比较,采用向上计数的方式得到spwm波形,将spwm传给扬声器便发出对应频率的声音。将主频和谐波频率对应的sin值相加再和锯齿波比较产生的spwm传给扬声器后,扬声器实现每个单音由主频和至少一个谐波分量构成,并且按两个键不失真。
3、音效差别分析
扬声器发出的B6频率声音
蜂鸣器发出的声音
由图可以发现,扬声器发出的声音除了主频和所加的二次谐波以外,其余频率的波强度很小,较好的实现了项目中谐波的要求;蜂鸣器得到的是方波信号,所以有很多谐波分量,而且偶次谐波强度大致相等,奇次谐波强度大致相等。
三、模拟放大电路的仿真及分析
使用LTspice仿真的电路
交流扫描得到的扬声器两端电压值
分析:
由于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为产生蜂鸣器信号模块。
(二)引脚分配
(三)按键消抖模块
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。
第一次调出的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过载:
后来尝试更改、注释了不少模块,对lut占用量的减少仍是杯水车薪,于是我把电子琴13个键的按键消抖取消了,缩短了自动播放的内容,lut勉强够用。
造成lut资源不够用的原因我认为有两点。首先是我的写法可能不规范。FPGA要被综合成各种电路,就要考虑很多实际的问题,比如竞争冒险、资源共用等等。所以有可能代码逻辑正确,但不符合实际的电路要求,造成了lut过多无意义的使用。其次是我还未能充分利用小脚丫核心板上的资源,比如96Kbit 用户闪存,92Kbit RAM等等,它们可以存放很多数据,但我使用起来仍很吃力。
2、蜂鸣器的发音音调:当我给蜂鸣器261Hz的方波信号时,蜂鸣器发出的声音并非261Hz,而是很高的频率,这一点不知道为何。
3、扬声器的音色:虽然扬声器实现了单频正弦波+二次谐波的声音,但是还可以参考真实乐器发音的音谱,利用DDS产生对应的波形,从而使扬声器发出的音色更贴近乐器。
4、考虑到有两个声源。可以给蜂鸣器一种乐器的波形信号,再给扬声器另一种乐器的波形信号,然后让它们同时播放乐曲,我想声音会很好听。