基于小脚丫FPGA的简易电子琴制作总结报告
制作者:张宏宇
学校:南京邮电大学
日期:2020/8/31
摘要:使用小脚丫FPGA设计简易电子琴,拥有十三个可按琴键,基于十二音律的音阶设置,且可以进行自动播放音乐,声音具有基波和一个一次谐波,同时支持扬声器和蜂鸣器两种模式播放音乐。
一、电子琴的工作原理和框图
1、电子琴的硬件是由一块小脚丫核心板、十三个琴键、两个扩展按键、一个蜂鸣器和一个扬声器(喇叭)构成的。电子琴有两种输出声音的方式,一种是蜂鸣器输出,一种是模拟喇叭输出,蜂鸣器只能输出最简单的方波pwm进行播放音乐,而模拟喇叭可以输出正弦波乃至谐波的pwm声音。此外,还可以进行两种硬件的音乐播放。
2、电子琴的工作原理是输出pwm波形,产生对应的声波频率,根据十二音律,共产生 523Hz, 554Hz ,587Hz ,622Hz , 659Hz , 698Hz , 739Hz , 784Hz , 830Hz , 880Hz , 932Hz , 998Hz , 1046Hz的十三个音阶,523Hz开始是音阶的C调依次类推。
无源蜂鸣器需要使用 PWM 方波才能驱动其发声,只要控制输入的 PWM 方波,输入不同的 PWM 方波发出的声音就不一样了。而不同频率和占空比的方波发出的声音是不同的,其中频率对音调有影响,占空比对音量大小有影响。所以只需产生不同频率和占空比的 PWM 方波去驱动无源蜂鸣器就能让无源蜂鸣器发出不同的音调了。
3.播放音乐的工作原理是在两个文件中(或者ROM中)分别存储乐谱之中的每个音节以及每个音节所对应的播放时间。
以下是各音符对应的简谱写法以及时值,4/4 拍是指按四分音符为一拍,每小节有四拍,按照每分钟 60 拍的时间,也就是一拍时间为 1 秒。如果一个音符后面加一个圆点,时值再延长本身时长的二分之一,比如四分音符后加一点,即为 1.5 秒。
简谱名:常见的中音为 1(Do),2(Re),3(Me),4(Fa),5(Sol),6(La),7(Si),在下面加一点表示低音,加两点为超低音,在上面加一点表示高音,加两点为超高音。
音乐频率:在听音乐时,之所以听到音调的高低,是因为声音的频率不同,以下是各简谱名对应的频率。因此在播放到某一个简谱名时,需要转换出相应的频率给蜂鸣器。
- 电子琴的设计框图
- 播放音乐的设计框图
- 蜂鸣器和模拟喇叭的差别:
- 喇叭的原理:喇叭其实是一种电能转换成声音的一种转换设备,当不同的电子能量传至线圈时,线圈产生一种能量与磁铁的磁场互动,这种互动造成纸盘振动,因为电子能量随时变化,喇叭的线圈会往前或往后运动,因此喇叭的纸盘就会跟着运动,这此动作使空气的疏密程度产生变化而产生声音。
- 无源蜂鸣器需要使用 PWM 方波才能驱动其发声,只要控制输入的 PWM 方波,输入不同的 PWM 方波发出的声音就不一样了。而不同频率和占空比的方波发出的声音是不同的,其中频率对音调有影响,占空比对音量大小有影响。所以只需产生不同频率和占空比的 PWM 方波去驱动无源蜂鸣器就能让无源蜂鸣器发出不同的音调了。
- 蜂鸣器发出的声音刺耳,只能输出方波信号,因此无法模拟声音,而喇叭可以通过输出各种波形来实现各种声音的模仿。
- 模拟放大电路的仿真及分析
使用multisim对电路进行仿真
放大电路使用了8002B音频功率放大器,查找8002B音频功率放大器的芯片手册可以看到内部原理框图:
结合小脚丫FPGA原理图,进行仿真电路设计,仿真电路图如下:
信号发生器产生1046Hz的正弦波,开始仿真。
可以看到两个示波器的波形分别是:
二、主要代码片段及说明
- matlab产生带有一次谐波的正弦波代码:
F1=1; %信号频率
F2=2;
P1=0; %信号初始相位
P2=0;
Fs=248; %采样频率
N=248; %采样点数
t=[0:1/Fs:(N-1)/Fs]; %采样时刻
ADC=2^7+35;
A=2^7; %信号幅度
%生成信号
s1=A*sin(2*pi*F1*t+pi*P1/180)+ADC;
s2=A/2*sin(2*pi*F2*t+pi*P2/180)+ADC;
s=s1+s2-ADC;
plot(t,s1,'-',t,s2,'*',t,s,'-');
legend('s1','s2','s3');
grid;
fild = fopen('sin_wave_2.txt','wt');
for i = 1:N
s0(i) = round(s(i)); %对小数四舍五入以取整
if s0(i)<0
s0(i)=0
end
fprintf(fild, '\t%g\t',i-1); %地址编码
fprintf(fild, '%s\t',':'); %冒号
fprintf(fild, '%d',s0(i)); %数据写入
fprintf(fild, '%s\n',';'); %分号,换行
end
fprintf(fild, '%s\n','END;'); %结束
fclose(fild);
代码直接生成正弦波表,进行操作,matlab产生的理想的波形为:
2、取产生波表的一半,作为数据输入,在输入完半个波形周期后,进行反向输入,这样就获得了一个完整的带有一次谐波的正弦波,代码如下:
module lookup_tables(
input wire [7:0]phase,
output wire [9:0]sin_out
);
reg [6:0]address;
wire sel;
wire [8:0]sine_table_out;
reg [9:0]sin_onecycle_amp;
sin_table sin_table_inst(
.address(address),
.sin(sine_table_out)
);
assign sin_out = sin_onecycle_amp[9:0];
assign sel = phase[7];
always @(*)
case(sel)
1'b0: begin
sin_onecycle_amp = 9'h1ff + sine_table_out[8:0];
address = phase[6:0];
end
1'b1: begin
sin_onecycle_amp = 9'h1ff - sine_table_out[8:0];
address = ~phase[6:0];
end
endcase
endmodule
3、dds模块:其中 fre_add 表示相位累加器输出值,位宽为 32 位,系统上电后,fre_add 信号一直执行自加操作,每个时钟周期自加参数 FREQ_CTRL,参数FREQ_CTRL 的计算方法为:FREQ_CTRL= 2N * fOUT / fCLK 。
具体代码为:
module dds
#(
parameter FREQ_CTRL = 24'd731
)
(
input wire clk,
input wire rst_n,
output wire [9:0]data_out
);
reg [23:0]fre_add;
always @(posedge clk or negedge rst_n)
if(!rst_n)
fre_add <= 1'b0;
else
fre_add <= fre_add + FREQ_CTRL;
lookup_tables lookup_tables_inst(
.phase(fre_add[23:16]),
.sin_out(data_out)
);
Endmodule
4、pwm产生模块:使用累加形式进行pwm的输出,就可以得到对应的频率波形的pwm
module speaker_pwm(
input wire clk,
input wire rst_n,
input wire [9:0]duty,
output wire pwm_out
);
reg [10:0]pwm_acc;
always @(posedge clk or negedge rst_n)
if(!rst_n)
pwm_acc <= 1'b0;
else
pwm_acc <= pwm_acc[9:0] + duty;
assign pwm_out = pwm_acc[10];
endmodule
5、按键控制模块:设计了两组三个音阶的和弦,和每个独立按键的声音。
module speaker_top(
input wire clk,
input wire rst_n,
input wire [12:0]beep_key,
output wire pwm_out
);
reg [9:0]duty;
wire [9:0]duty0,duty1,duty2,duty3,duty4,duty5,duty6,duty7,duty8,duty9,duty10,duty11,duty12;
always @(*)
case(beep_key)
13'b1_1111_1111_1110:duty = duty0;
13'b1_1111_1111_1101:duty = duty1;
13'b1_1111_1111_1011:duty = duty2;
13'b1_1111_1111_0111:duty = duty3;
13'b1_1111_1110_1111:duty = duty4;
13'b1_1111_1101_1111:duty = duty5;
13'b1_1111_1011_1111:duty = duty6;
13'b1_1111_0111_1111:duty = duty7;
13'b1_1110_1111_1111:duty = duty8;
13'b1_1101_1111_1111:duty = duty9;
13'b1_1011_1111_1111:duty = duty10;
13'b1_0111_1111_1111:duty = duty11;
13'b0_1111_1111_1111:duty = duty12;
13'b1_1111_1110_1010:duty = duty0 + duty2 + duty4;
13'b1_1101_0101_1111:duty = duty5 + duty7 + duty9;
endcase
speaker_pwm speaker_pwm_inst(
.clk(clk),
.rst_n(rst_n),
.duty(duty),
.pwm_out(pwm_out)
);
dds #( .FREQ_CTRL(24'd731))
dds_inst0( .clk(clk), .rst_n(rst_n), .data_out(duty0));
dds #( .FREQ_CTRL(24'd774))
dds_inst1( .clk(clk), .rst_n(rst_n), .data_out(duty1));
dds #( .FREQ_CTRL(24'd820))
dds_inst2( .clk(clk), .rst_n(rst_n), .data_out(duty2));
dds #( .FREQ_CTRL(24'd869))
dds_inst3( .clk(clk), .rst_n(rst_n), .data_out(duty3));
dds #( .FREQ_CTRL(24'd921))
dds_inst4( .clk(clk), .rst_n(rst_n), .data_out(duty4));
dds #( .FREQ_CTRL(24'd975))
dds_inst5( .clk(clk), .rst_n(rst_n), .data_out(duty5));
dds #( .FREQ_CTRL(24'd1033))
dds_inst6( .clk(clk), .rst_n(rst_n), .data_out(duty6));
dds #( .FREQ_CTRL(24'd1069))
dds_inst7( .clk(clk), .rst_n(rst_n), .data_out(duty7));
dds #( .FREQ_CTRL(24'd1160))
dds_inst8( .clk(clk), .rst_n(rst_n), .data_out(duty8));
dds #( .FREQ_CTRL(24'd1230))
dds_inst9( .clk(clk), .rst_n(rst_n), .data_out(duty9));
dds #( .FREQ_CTRL(24'd1303))
dds_inst10( .clk(clk), .rst_n(rst_n), .data_out(duty10));
dds #( .FREQ_CTRL(24'd1395))
dds_inst11( .clk(clk), .rst_n(rst_n), .data_out(duty11));
dds #( .FREQ_CTRL(24'd1462))
dds_inst12( .clk(clk), .rst_n(rst_n), .data_out(duty12));
endmodule
6、音乐音阶储存模块:储存了低音、中音、高音、超高音的二十八个音阶,足以应对大部分的谱曲。
module hz(
input wire [7:0]hz_sel,
output reg [19:0]cycle
);
parameter CLK_FRE = 12 ;
always @(*)
begin
case(hz_sel)
8'd01 : cycle <= CLK_FRE*1000000/261 ; //low 1 261Hz
8'd02 : cycle <= CLK_FRE*1000000/293 ; //low 2 293Hz
8'd03 : cycle <= CLK_FRE*1000000/329 ; //low 3 329Hz
8'd04 : cycle <= CLK_FRE*1000000/349 ; //low 4 349Hz
8'd05 : cycle <= CLK_FRE*1000000/392 ; //low 5 392Hz
8'd06 : cycle <= CLK_FRE*1000000/440 ; //low 6 440Hz
8'd07 : cycle <= CLK_FRE*1000000/499 ; //low 7 499Hz
8'd11 : cycle <= CLK_FRE*1000000/523 ; //middle 1 523Hz
8'd12 : cycle <= CLK_FRE*1000000/587 ; //middle 2 587Hz
8'd13 : cycle <= CLK_FRE*1000000/659 ; //middle 3 659Hz
8'd14 : cycle <= CLK_FRE*1000000/698 ; //middle 4 698Hz
8'd15 : cycle <= CLK_FRE*1000000/784 ; //middle 5 784Hz
8'd16 : cycle <= CLK_FRE*1000000/880 ; //middle 6 880Hz
8'd17 : cycle <= CLK_FRE*1000000/998 ; //middle 7 998Hz
8'd21 : cycle <= CLK_FRE*1000000/1046 ; //high 1 1046Hz
8'd22 : cycle <= CLK_FRE*1000000/1174 ; //high 2 1174Hz
8'd23 : cycle <= CLK_FRE*1000000/1318 ; //high 3 1318Hz
8'd24 : cycle <= CLK_FRE*1000000/1396 ; //high 4 1396Hz
8'd25 : cycle <= CLK_FRE*1000000/1568 ; //high 5 1568Hz
8'd26 : cycle <= CLK_FRE*1000000/1760 ; //high 6 1760Hz
8'd27 : cycle <= CLK_FRE*1000000/1976 ; //high 7 1976Hz
8'd31 : cycle <= CLK_FRE*1000000/2093 ; //super high 1 2093Hz
8'd32 : cycle <= CLK_FRE*1000000/2349 ; //super high 2 2349Hz
8'd33 : cycle <= CLK_FRE*1000000/2637 ; //super high 3 2637Hz
8'd34 : cycle <= CLK_FRE*1000000/2794 ; //super high 4 2794Hz
8'd35 : cycle <= CLK_FRE*1000000/3136 ; //super high 5 3136Hz
8'd36 : cycle <= CLK_FRE*1000000/3520 ; //super high 6 3520Hz
8'd37 : cycle <= CLK_FRE*1000000/3951 ; //super high 7 3951Hz
default:cycle<=20'd0;
endcase
end
endmodule
7、音乐播放控制模块
module beep_music(
input wire clk,
input wire rst_n,
input wire [1:0]key,
output reg beep_s,
output reg beep_t
);
parameter CLK_FRE = 12 ;
reg [31:0]cnt;
reg [1:0]key_flag;
wire [19:0]duty_data;
reg [6:0]address;
wire [3:0]time_music;
reg [31:0]time_cycle;
wire [7:0]hz_sel;
wire [19:0]cycle;
reg [19:0]cnt_cycle;
always @(posedge clk or negedge rst_n)
if(!rst_n)
key_flag <= 1'b0;
else if(key == 2'b01)
key_flag <= 2'b10;
else if(key == 2'b10)
key_flag <= 2'b01;
else if(key == 2'b00)
key_flag <= 2'b11;
else
key_flag <= key_flag;
//单个音符时间计数值
always @(posedge clk or negedge rst_n)
if(!rst_n)
time_cycle<=32'd0;
else
time_cycle<=time_music*(CLK_FRE*1000000/8) ;
//计时器
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt<=1'b0;
else if(cnt==time_cycle)
cnt<=1'b0;
else
cnt<=cnt+1'b1;
//频率rom计数器加一
always @(posedge clk or negedge rst_n)
if(!rst_n)
address<=1'b0;
else if(address==8'd111 && cnt==time_cycle)
address<=1'b0;
else if(cnt==time_cycle)
address<=address+1'b1;
//频率计数器
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt_cycle<=1'b0;
else if(cnt_cycle==cycle || cnt==time_cycle)
cnt_cycle<=1'b0;
else
cnt_cycle<=cnt_cycle+1'b1;
//占空比
assign duty_data=cycle/4;
//蜂鸣器输出PWM1
always @(posedge clk or negedge rst_n)
if(!rst_n)
beep_s=1'b0;
else if(cnt_cycle>=duty_data && key_flag == 2'b10 && !(key_flag == 2'b11))
beep_s=1'b1;
else
beep_s=1'b0;
//蜂鸣器输出PWM2
always @(posedge clk or negedge rst_n)
if(!rst_n)
beep_t=1'b0;
else if(cnt_cycle>=duty_data && key_flag == 2'b01 && !(key_flag == 2'b11))
beep_t=1'b1;
else
beep_t=1'b0;
time_music time_music0(
.address(address),
.time_music(time_music)
);
hz_sel hz_sel0(
.address(address),
.hz_sel(hz_sel)
);
hz hz0(
.hz_sel(hz_sel),
.cycle(cycle)
);
endmodule
四、资源使用报告:
- 寄存器数量使用了521/4635(11%)
- PFU寄存器数量使用了521/(12%)
- SLICE使用了950/2160(44%)
- 查找表LUT使用了1897/4320(44%)
五、遇到的主要难题及解决方法:
- 使用正弦波输出pwm出错,声音没有显示出正常的正弦波形,经过分析,得出是pwm输出模块的位数有问题,因为对累加输出pwm的方式不是很理解,所以在位数上少了一位,导致出现了问题。
- 在生成谐波的时候,已开始使用谐波的整个波表来进行输出波形,但是声音失真严重,几经摸索也没有找到问题所在,后来使用了半个谐波的波表输入,最终实现了谐波输出。
- 进行音乐播放时,想要使用现有的十二个带有谐波的音阶进行音乐播放,但是出现了波形输出的问题,最终没有解决,便使用了方波进行音乐输出。
六、改进建议:
建议step FPGA网页平台可以支持IP核的使用和进行仿真,苦于电脑无法使用diamond,我只能使用其他软件进行仿真然后再回到网页版进行烧录。