1.任务目的
1.基于我们提供的套件和工具,自己组装电子琴
2.自己编程基于FPGA实现:
- 存储一段音乐,并可以进行音乐播放,
- 可以自己通过板上的按键进行弹奏,支持两个按键同时按下(和弦)并且声音不能失真,板上的按键只有13个,可以通过有上方的“上“、”下”两个按键对音程进行扩展
- 使用扬声器进行播放时,输出的音调信号除了对应于该音调的单频正弦波外,还必须包含至少一个谐波分量
- 音乐的播放支持两种方式,这两种方式可以通过开关进行切换:
- 当开关切换到蜂鸣器端,可以通过蜂鸣器来进行音乐播放
- 当开关切换到扬声器端,可以通过模拟扬声器来进行音乐播放,每个音符都必须包含基频 + 至少一个谐波分量
2.电子琴的工作原理和框图
电子琴有13个按键作为电子琴的琴键,2个按键用于调节音程,小脚丫上有4个按键,其中一个被我拿来做音乐播放按键,一个拿来做整体的复位按键。有个切换开关,用于切换蜂鸣器或者喇叭。滑动变阻器,用于调节声音大小。
按键接到FPGA上直接处理,接出两路输出,一路经三极管到蜂鸣器,用于控制蜂鸣器的音调。一路经RC滤波到滑动变阻器再到功率放大器,最后接到喇叭,用于控制喇叭的发声。其中滑动变阻器是通过改变输入信号分压的方式,来硬件调节喇叭输出声音大小的。
3.蜂鸣器和模拟喇叭的差别
蜂鸣器:分有源蜂鸣器跟无源蜂鸣器两种,有源蜂鸣器是通电后产生一个固定音调的声音,不可调节。无源蜂鸣器,也是这次电子琴使用的蜂鸣器,可以通过改变驱动PWM的频率使其发出不同音调的声音。
喇叭:按结构和电-声换能的方式不同可分为电动式扬声器、电磁式扬声器、静电式扬声器和压电式扬声器。按扬声器的形状可分为纸盆式、号筒式和球顶式。按频率范围可分为高音扬声器、中音扬声器和低音扬声器。电动式扬声器(又被称为动圈式扬声器)主要由永久磁铁、线圈(又称音圈)和纸盆(与线圈连在一起)构成。当电信号通过引出线流进线圈时,线圈产生磁场。由于流进线圈的电流是变化的,线圈产生的磁场也是变化的,线圈变化的磁场与磁铁的磁场相互作用,线圈和磁铁不断排斥和吸引,使重量轻的线圈产生运动(时而远离磁铁,时而靠近磁铁),线圈的运动带动与它相连的纸盆振动,纸盆就发出声音,就使扬声器产生随音频变化的声音,从而实现了电-声转换。
对于我们控制来说,两者最大的区别是蜂鸣器没法精细控制,只能是pwm信号,导通不导通两个状态,没有中间态,而喇叭的话可以有很多个状态,可以控制纸盆的位置。
4.用蜂鸣器和模拟喇叭的实现方法差别以及音效差别分析
实现方法:
模拟喇叭:FPGA将声音信号(波形)转换为高速的PWM波形,占空比的多少对应着波形每一个点的位置,将此信号输出,外部接一个RC滤波,作为低通滤波器使用,将输出的PWM平滑重新还原为波形,这样就跟常规的DAC驱动是一样的效果了。
之后信号经过滑动变阻器分压(可用于控制输入波形幅值进而控制输出声音大小),然后接音频功率放大器,最后输出到喇叭。
蜂鸣器:只能通过改变输入信号频率进而改变音调,这里我将原始的声音波形信号(虽然叠加谐波单整体接近正弦波),取其接近中点的位置作为输出信号PWM的判断,高于则为高电平,低于则为低电平,这样就能得到一个与原始声音信号频率相同的PWM信号。此PWM信号输出。外部电路用三极管做控制,接收PWM信号使喇叭发声。
音效差别:
模拟喇叭的音色是可以通过控制输入波形的形状来控制,换种说法就是输入波形叠加不同数量大小的谐波,输入的波形不同(频率相同),音色也就不同。而蜂鸣器不能这样控制,音色比较单一。而且喇叭本身的设计就是为了发出不同的声音,本身的频率特性就好,高频低频都支持,而且比较柔和,蜂鸣器的设计则不是,而是更好的去警醒人们,方便作为电路报警提醒,那是怎么刺耳怎么设计,怎么便宜怎么设计,音效就比较刺耳。
5.模拟放大电路的仿真及分析
电子琴音频放大部分是一个典型的反向放大器,将其电路抽象,就是类似下图的电路:
对其分析,运放的同向端接地=0V,反向端和同向端虚短,所以也是0V,反向输入端输入电阻很高,虚断,几乎没有电流注入和流出,那么R1和R2相当于是串联 的,流过一个串联电路中的每一只组件的电流是相同的,即流过R1的电流和流过R2的电流是相同的。流过R1的电流I1 = (Vi - V-)/R1 ……a 流过R2的电流I2 = (V- - Vout)/R2 ……b V- = V+ = 0 ……c I1 = I2 ……d 求解上面的初中代数方程得Vout = (-R2/R1)*Vi 这就是反向放大器的输入输出关系式了。在这里其放大倍数正是47k/10k=4.7倍,与下图仿真结果相同(黄色为运放输入,蓝色为运放输出)。
6.主要代码片段及说明
整体控制
module top
(
input clk, //clk = 12mhz
input rst, //rst_n, active low
input [14:0] key,
input sw,
input auto,
output pwm_out,
output speak_out
);
wire key_up,key_down,key_auto;
reg [25:0] key_out;
wire [10:0] wavecnt;
wire waveout;
reg [1:0]mode;
wire [13:0]music_out;
assign pwm_out = sw?wavecnt[8]:1'b0;
assign speak_out = sw?1'b0:waveout;
debounce key_up_debounce
(
.clk (clk),
.rst_n (rst),
.key_in (key[14]),
.key_flag (key_up)
);
debounce key_down_debounce
(
.clk (clk),
.rst_n (rst),
.key_in (key[13]),
.key_flag (key_down)
);
debounce key_auto_debounce
(
.clk (clk),
.rst_n (rst),
.key_in (auto),
.key_flag (key_auto)
);
music music_u0(
.clk(clk),
.rst(rst),
.enable((mode==2)?1'b1:1'b0),
.music_out(music_out)
);
pwm pwm_u0(
.clk(clk),
.rst(rst),
.duty(wavecnt),
.pwm_out(waveout)
);
synthesizer synthesizer_u0(
.clk(clk),
.rst(rst),
.key(key_out[25:0]),
.wavecnt(wavecnt)
);
always @(posedge clk or negedge rst) begin
if(!rst)
mode <= 0;
else if(key_down)
mode <= 0;
else if(key_up)
mode <= 1;
else if(key_auto)
mode <= 2;
end
always @(*) begin
if(!rst) begin
key_out[25:13] = 13'b0;
key_out[12:0] = ~key[12:0];
end
else if(mode == 0) begin
key_out[25:13] = 13'b0;
key_out[12:0] = ~key[12:0];
end
else if(mode == 1) begin
key_out[25:13] = ~key[12:0];
key_out[12:0] = 13'b0;
end
else if(mode == 2) begin
key_out[25:13] = 13'b0;
key_out[12:0] = music_out;
end
end
endmodule
按键消抖
module debounce(
input clk,//12Mhz
input rst_n,
input key_in,
output key_flag
);
parameter JITTER = 240;//12Mhz / (1/20ms)
reg [1:0] key_r;
wire change;
reg [15:0] delay_cnt;
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
key_r <= 0;
end
else begin
key_r <= {key_r[0],key_in};
end
end
assign change = (~key_r[1] & key_r[0]) | (key_r[1] & ~key_r[0]);
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
delay_cnt <= 0;
end
else if(change == 1'b1)
delay_cnt <= 0;
else if(delay_cnt == JITTER)
delay_cnt <= delay_cnt;
else
delay_cnt <= delay_cnt + 16'd1;
end
assign key_flag = ((delay_cnt == JITTER - 1) && (key_in == 1'b1))? 1'b1:
1'b0;
endmodule
1/4部分基波与谐波存储
module sin15_table(
input [5:0] address,
output reg [8:0] sin
);
always @(address)
begin
case(address)
6'h0: sin=9'h0;
6'h1: sin=9'hD;
6'h2: sin=9'h19;
6'h3: sin=9'h26;
6'h4: sin=9'h32;
6'h5: sin=9'h3D;
6'h6: sin=9'h48;
6'h7: sin=9'h53;
6'h8: sin=9'h5D;
6'h9: sin=9'h66;
6'hA: sin=9'h6E;
6'hB: sin=9'h75;
6'hC: sin=9'h7C;
6'hD: sin=9'h81;
6'hE: sin=9'h86;
6'hF: sin=9'h89;
6'h10: sin=9'h8C;
6'h11: sin=9'h8E;
6'h12: sin=9'h8F;
6'h13: sin=9'h8F;
6'h14: sin=9'h8F;
6'h15: sin=9'h8E;
6'h16: sin=9'h8C;
6'h17: sin=9'h8B;
6'h18: sin=9'h88;
6'h19: sin=9'h86;
6'h1A: sin=9'h83;
6'h1B: sin=9'h81;
6'h1C: sin=9'h7F;
6'h1D: sin=9'h7C;
6'h1E: sin=9'h7A;
6'h1F: sin=9'h79;
6'h20: sin=9'h78;
6'h21: sin=9'h78;
6'h22: sin=9'h78;
6'h23: sin=9'h79;
6'h24: sin=9'h7A;
6'h25: sin=9'h7D;
6'h26: sin=9'h80;
6'h27: sin=9'h84;
6'h28: sin=9'h88;
6'h29: sin=9'h8D;
6'h2A: sin=9'h94;
6'h2B: sin=9'h9A;
6'h2C: sin=9'hA1;
6'h2D: sin=9'hA9;
6'h2E: sin=9'hB1;
6'h2F: sin=9'hBA;
6'h30: sin=9'hC3;
6'h31: sin=9'hCC;
6'h32: sin=9'hD4;
6'h33: sin=9'hDD;
6'h34: sin=9'hE6;
6'h35: sin=9'hEE;
6'h36: sin=9'hF6;
6'h37: sin=9'hFD;
6'h38: sin=9'h104;
6'h39: sin=9'h10A;
6'h3A: sin=9'h10F;
6'h3B: sin=9'h114;
6'h3C: sin=9'h117;
6'h3D: sin=9'h11A;
6'h3E: sin=9'h11B;
6'h3F: sin=9'h11C;
endcase
end
endmodule
计算补充完整波形
通过高两位判断当前是波形的那一段,通过不同的访问顺序来实现完整波形
module look_table(
input [7:0] phase,
output signed [10:0] sin_out
);
reg [5:0] address;
wire [1:0] sel;
wire [8:0] sine_table_out;
reg signed [10:0] sine_onecycle_amp;
assign sin_out = sine_onecycle_amp[10:0];
assign sel = phase[7:6];
sin15_table u_sin_table(address, sine_table_out);
always @(sel or sine_table_out)
begin
case(sel)
2'b00: begin
sine_onecycle_amp = 9'h11C+sine_table_out;
address = phase[5:0];
end
2'b01: begin
sine_onecycle_amp = 9'h11C+sine_table_out[8:0];
address = ~phase[5:0];
end
2'b10: begin
sine_onecycle_amp = 9'h11C-sine_table_out;
address = phase[5:0];
end
2'b11: begin
sine_onecycle_amp = 9'h11C-sine_table_out[8:0];
address = ~phase[5:0];
end
endcase
end
endmodule
产生不同频率波形
通过控制累加器每一次增加的大小来控制频率
module wave(
input clk,
input rst,
input enable,
output reg signed [10:0] waveout
);
parameter FREQ = 24'd336; //C1 261.63Hz ʱ��12MHz =(261.63*2**24) /12000000
reg [23:0] phase_acc;
wire signed [10:0] sinout;
always @(posedge clk or negedge rst)
if(!rst)
phase_acc <= 0;
else
phase_acc <= phase_acc + FREQ;
always @(posedge clk or negedge rst)
if(!rst)
waveout <= 0;
else if(enable)
waveout <= sinout;
else
waveout <= 0;
look_table u0_table(
.phase(phase_acc[23:16]),
.sin_out(sinout)
);
endmodule
生成具体按键对应关系
最后将所有按键输出相加作为最终输出
module synthesizer(
input clk,
input rst,
input [25:0] key,
output [10:0] wavecnt
);
wire signed [10:0] wave_262, wave_227, wave_294, wave_311, wave_330, wave_349, wave_370, wave_392, wave_415, wave_440, wave_446, wave_494, wave_523
, wave_740, wave_784, wave_831, wave_880, wave_932, wave_988, wave_1046, wave_1109, wave_1175, wave_1245, wave_1318, wave_1397, wave_1480;
wave #(366) d1 (.clk(clk), .rst(rst), .enable(key[0]), .waveout(wave_262));
wave #(388) d_1(.clk(clk), .rst(rst), .enable(key[1]), .waveout(wave_227));
wave #(411) d2 (.clk(clk), .rst(rst), .enable(key[2]), .waveout(wave_294));
wave #(435) d_2(.clk(clk), .rst(rst), .enable(key[3]), .waveout(wave_311));
wave #(461) d3 (.clk(clk), .rst(rst), .enable(key[4]), .waveout(wave_330));
wave #(488) d4 (.clk(clk), .rst(rst), .enable(key[5]), .waveout(wave_349));
wave #(517) d_4(.clk(clk), .rst(rst), .enable(key[6]), .waveout(wave_370));
wave #(548) d5 (.clk(clk), .rst(rst), .enable(key[7]), .waveout(wave_392));
wave #(581) d_5(.clk(clk), .rst(rst), .enable(key[8]), .waveout(wave_415));
wave #(615) d6 (.clk(clk), .rst(rst), .enable(key[9]), .waveout(wave_440));
wave #(652) d_6(.clk(clk), .rst(rst), .enable(key[10]), .waveout(wave_446));
wave #(690) d7 (.clk(clk), .rst(rst), .enable(key[11]), .waveout(wave_494));
wave #(732) z1 (.clk(clk), .rst(rst), .enable(key[12]), .waveout(wave_523));
wave #(1034) z_4(.clk(clk), .rst(rst), .enable(key[13]), .waveout(wave_740));
wave #(1096) z5 (.clk(clk), .rst(rst), .enable(key[14]), .waveout(wave_784));
wave #(1162) z_5(.clk(clk), .rst(rst), .enable(key[15]), .waveout(wave_831));
wave #(1230) z6 (.clk(clk), .rst(rst), .enable(key[16]), .waveout(wave_880));
wave #(1303) z_6(.clk(clk), .rst(rst), .enable(key[17]), .waveout(wave_932));
wave #(1381) z7 (.clk(clk), .rst(rst), .enable(key[18]), .waveout(wave_988));
wave #(1462) g1 (.clk(clk), .rst(rst), .enable(key[19]), .waveout(wave_1046));
wave #(1550) g_1(.clk(clk), .rst(rst), .enable(key[20]), .waveout(wave_1109));
wave #(1643) g2 (.clk(clk), .rst(rst), .enable(key[21]), .waveout(wave_1175));
wave #(1741) g_2(.clk(clk), .rst(rst), .enable(key[22]), .waveout(wave_1245));
wave #(1843) g3 (.clk(clk), .rst(rst), .enable(key[23]), .waveout(wave_1318));
wave #(1953) g4 (.clk(clk), .rst(rst), .enable(key[24]), .waveout(wave_1397));
wave #(2069) g_4(.clk(clk), .rst(rst), .enable(key[25]), .waveout(wave_1480));
assign wavecnt = wave_262 + wave_227 + wave_294 + wave_311 + wave_330 + wave_349 + wave_370 + wave_392 + wave_415 + wave_440 + wave_446 + wave_494 + wave_523
+ wave_740 + wave_784 + wave_831 + wave_880 + wave_932 + wave_988 + wave_1046 + wave_1109 + wave_1175 + wave_1245 + wave_1318 + wave_1397 + wave_1480;
endmodule
7.遇到的主要难题及解决方法
无法实现和弦功能,两个键一按下就会破音
输出信号的幅度太大了,两个按键同时按下,相加后就会超范围,通过减小原始存储波形与输出幅度的比值,减小了输出幅度,让多键输出相加后也不会超出硬件输出能力。
8.改进建议
电子琴可用于测试的点太少,而且使用不方便。
建议在板边可以加一个可供示波器夹子夹持的地孔,或者留一个边缘的开窗。关键信号可以将测试孔留到偏板边的位置(主要是顶板的干涉比较大)。测试点可以使用下面这种器件。
板子空白较多,可以多加点丝印标注。
比如将小脚丫核心板的IO连接的内部FPGA引脚标注出来,方便查看。
资源使用:
Number of registers: 1020 out of 4635 (22%)
PFU registers: 1020 out of 4320 (24%)
PIO registers: 0 out of 315 (0%)
Number of SLICEs: 1544 out of 2160 (71%)
SLICEs as Logic/ROM: 1544 out of 2160 (71%)
SLICEs as RAM: 0 out of 1620 (0%)
SLICEs as Carry: 679 out of 2160 (31%)
Number of LUT4s: 3087 out of 4320 (71%)
Number used as logic LUTs: 1729
Number used as distributed RAM: 0
Number used as ripple logic: 1358
Number used as shift registers: 0
Number of PIO sites used: 21 + 4(JTAG) out of 105 (24%)
Number of block RAMs: 0 out of 10 (0%)
Number of GSRs: 1 out of 1 (100%)
EFB used : No
JTAG used : No
Readback used : No
Oscillator used : No
Startup used : No
POR : On
Bandgap : On
Number of Power Controller: 0 out of 1 (0%)
Number of Dynamic Bank Controller (BCINRD): 0 out of 6 (0%)
Number of Dynamic Bank Controller (BCLVDSO): 0 out of 1 (0%)
Number of DCCA: 0 out of 8 (0%)
Number of DCMA: 0 out of 2 (0%)
Number of PLLs: 0 out of 2 (0%)
Number of DQSDLLs: 0 out of 2 (0%)
Number of CLKDIVC: 0 out of 4 (0%)
Number of ECLKSYNCA: 0 out of 4 (0%)
Number of ECLKBRIDGECS: 0 out of 2 (0%)