基于小脚丫FPGA的简易电子琴制作
使用小脚丫FPGA设计简易电子琴,拥有十三个可按琴键,基于十二音律的音阶设置,且可以进行自动播放音乐,声音具有基波和一个一次谐波,同时支持扬声器和蜂鸣器两种模式播放音乐。
标签
FPGA
DDS
2022暑假在家练
njupt-zhang
更新2022-09-06
南京邮电大学
831

                                    基于小脚丫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),在下面加一点表示低音,加两点为超低音,在上面加一点表示高音,加两点为超高音。

音乐频率:在听音乐时,之所以听到音调的高低,是因为声音的频率不同,以下是各简谱名对应的频率。因此在播放到某一个简谱名时,需要转换出相应的频率给蜂鸣器。

  1. 电子琴的设计框图
  1. 播放音乐的设计框图
  1. 蜂鸣器和模拟喇叭的差别:
  2. 喇叭的原理:喇叭其实是一种电能转换成声音的一种转换设备,当不同的电子能量传至线圈时,线圈产生一种能量与磁铁的磁场互动,这种互动造成纸盘振动,因为电子能量随时变化,喇叭的线圈会往前或往后运动,因此喇叭的纸盘就会跟着运动,这此动作使空气的疏密程度产生变化而产生声音。
  3. 无源蜂鸣器需要使用 PWM 方波才能驱动其发声,只要控制输入的 PWM 方波,输入不同的 PWM 方波发出的声音就不一样了。而不同频率和占空比的方波发出的声音是不同的,其中频率对音调有影响,占空比对音量大小有影响。所以只需产生不同频率和占空比的 PWM 方波去驱动无源蜂鸣器就能让无源蜂鸣器发出不同的音调了。
  4. 蜂鸣器发出的声音刺耳,只能输出方波信号,因此无法模拟声音,而喇叭可以通过输出各种波形来实现各种声音的模仿。
  5. 模拟放大电路的仿真及分析

使用multisim对电路进行仿真

放大电路使用了8002B音频功率放大器,查找8002B音频功率放大器的芯片手册可以看到内部原理框图:

结合小脚丫FPGA原理图,进行仿真电路设计,仿真电路图如下:

信号发生器产生1046Hz的正弦波,开始仿真。

可以看到两个示波器的波形分别是:

二、主要代码片段及说明

  1. 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

四、资源使用报告:

  1. 寄存器数量使用了521/4635(11%)
  2. PFU寄存器数量使用了521/(12%)
  3. SLICE使用了950/2160(44%)
  4. 查找表LUT使用了1897/4320(44%)

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

  1. 使用正弦波输出pwm出错,声音没有显示出正常的正弦波形,经过分析,得出是pwm输出模块的位数有问题,因为对累加输出pwm的方式不是很理解,所以在位数上少了一位,导致出现了问题。
  2. 在生成谐波的时候,已开始使用谐波的整个波表来进行输出波形,但是声音失真严重,几经摸索也没有找到问题所在,后来使用了半个谐波的波表输入,最终实现了谐波输出。
  3. 进行音乐播放时,想要使用现有的十二个带有谐波的音阶进行音乐播放,但是出现了波形输出的问题,最终没有解决,便使用了方波进行音乐输出。

六、改进建议:

建议step FPGA网页平台可以支持IP核的使用和进行仿真,苦于电脑无法使用diamond,我只能使用其他软件进行仿真然后再回到网页版进行烧录。

附件下载
piano.jed
电子琴程序
music.jed
播放音乐程序
团队介绍
南京邮电大学张宏宇
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号