项目总结报告
LJ@2022年8月31日
项目需求:
存储一段音乐,并可以进行音乐播放,
可以自己通过板上的按键进行弹奏,支持两个按键同时按下(和弦)并且声音不能失真,板上的按键只有13个,可以通过有上方的“上“、”下”两个按键对音程进行扩展
使用扬声器进行播放时,输出的音调信号除了对应于该音调的单频正弦波外,还必须包含至少一个谐波分量
音乐的播放支持两种方式,这两种方式可以通过开关进行切换:
当开关切换到蜂鸣器端,可以通过蜂鸣器来进行音乐播放
当开关切换到扬声器端,可以通过模拟扬声器来进行音乐播放,每个音符都必须包含基频 + 至少一个谐波分量
项目内容:
电子琴的工作原理和框图
本例中的电子琴工作原理,检测到键盘输入后,fpga从rom中取出对于的波形文件,并以PWM调制的形式发出去,出去后经过低通滤波器过滤掉PWM调制信号的频率,将保留的信号有效成分组合形成音频波形,再经过音频放大电路提高输出能力,到扬声器后电信号转化为我们可听到的声音。
分析蜂鸣器和模拟喇叭的差别
蜂鸣器是产生声音的信号装置,有机械型、机电型及压电型。蜂鸣器的典型应用包括警笛、报警装置、火灾警报器、防空警报器、防盗器、定时器等。常见的电子产品中使用的小型蜂鸣器都是压电式蜂鸣器,主要依靠压电效应来产生振动,发出声音。 此类蜂鸣器一般分为自带震荡源或不带震荡源两种。驱动自带震荡源的蜂鸣器时直接使用直流电即可;而驱动不带震荡源的蜂鸣器则需要使用交流信号。
喇叭,也称扬声器、音箱、扩音器,是将电子信号转换成为声音的换能器、电子组件,可以由一个或多个组成音响组。扬声器是由磁铁、线圈、喇叭振膜组成。扬声器把电流频率转化为声音。物理学原理,当电流通过线圈产生电磁场,磁场的方向为右手法则。假设,扬声器播放C调,其频率为256Hz,即每秒振动256次,扬声器输出256Hz的交流电,每秒256次电流改变,发出C当电线圈与扬声器薄膜一起振动,推动周围的空气振动,扬声器由此产生声音。人耳可以听到的声波的频率一般在20赫兹至20000赫兹之间,所以一般的扬声器都会把程序设定在这个范围内。工作原理和上述相同。能量的转换过程是由电能转换为磁能,再由磁能转换为机械能,再从机械能转换为声音。
本例采用蜂鸣器(SMT-0825-S-4-R)为无源蜂鸣器,线圈电阻 15±3 欧姆,谐振频率 2,500±500Hz,经实测在375KHz基本没有响应,自身相当于一个低通滤波器。
用蜂鸣器和模拟喇叭的实现方法差别以及音效差别分析
蜂鸣器实现方法和音效
驱动不带震荡源的无源蜂鸣器则需要使用交流信号,通常采用方波信号。对于数字电路来说,可以采用计数器产生不同频率的方波,来实现不同音符的输出。由于方波的信号特点,发声中带有丰富的杂波信号,音效较差,音色单一。
模拟喇叭实现方法和音效
扬声器一般需要模拟电流驱动,响应带宽比蜂鸣器大非常多。通常可采用DAC+放大电路或者集成的音频芯片驱动。本例中是由FPGA的IO口产生PWM信号,经过RC低通滤波器,再经过调幅变阻器、AC耦合电容到放大电路,转换为较为平滑的低频信号来驱动扬声器。音效比蜂鸣器好,可以表现出模拟信号的细节,本例中改变谐波的种类及含量可以发出不同音色的音符。
本例的实现方法和音效
针对本例采用蜂鸣器(SMT-0825-S-4-R)为无源蜂鸣器,线圈电阻 15±3 欧姆,谐振频率 2,500±500Hz,经实测在375KHz基本没有响应,自身相当于一个低通滤波器。综合上边分析,FPGA内部设计思路如下,结合蜂鸣器(SMT-0825-S-4-R)的频响范围,决定蜂鸣器和扬声器采用同源驱动,首先将12MHz的时钟输入经PLL倍频至96MHz,这个96MHz的时钟将作为全局时钟来使用。音频的深度设置为8bit,则PWM的调制频率为375KHz。这样输出的信号对于扬声器和蜂鸣器都将是一个低频的模拟交流信号。
模拟放大电路的仿真及分析
利用LTspice工具对放大电路进行仿真
R1=300Ω与C1=100nF构成一个低通滤波器,f=1/(2*pi*R1*C1)=5.3KHz,经过滤波后在测量点TP_ANA波形如图中青色波形,Vpp=0.7V。
再经过音量调节,仿真用R3=5KΩ、R4=5KΩ模拟调阻器,得到测量点Ain_P波形如图中绿色波形,Vpp=0.35V。
再经过耦合电容C4=10μF,得到测量点sin_ac_2.5V波形如图中蓝色波形,Vpp=0.35V,并将直流偏置调至2.42V。
最后一部分是音频功放,用来增大功率驱动扬声器。由于没有板上音频功放的spice模型,所以用一个AB类功放来替代,波形图中红色波形为扬声器驱动电流。结果符合理论预期,但与板上实际情况有差别。
补充1:不同放大器类别的比较
然后,放大器类始终定义如下:
A类: –放大器的单个输出晶体管在输入波形的整个360 o周期内导通。
B类: –放大器的两个输出晶体管仅导通一半,即输入波形的180o 。
AB类: –放大器的两个输出晶体管在输入波形的180 o至360 o之间导通。
补充2:本例音频功放介绍
8002B 是一款 AB 类,单声道带关断模式,桥式音频功率放大器。在输入 1KHz,5V 工作电压 时 , 最 大 驱动功 率 为 : 3W,(4Ω 负 载 ,THD<10%),2W,(4Ω负载,THD<1%); 音频范围内总谐波失真噪音小于 1%(20Hz·20KHz);8002B 应用电路简单,只需要极少数外围器件,就能提供高品质的输出功率。8002B 输出不需要外接耦合电容或上举电容、缓冲网络、反馈电阻。Ø 8002B 采样 SOP 封装,特别适用于低功耗、小体积的便携式系统。8002B 可以通过控制进入休眠模式,从而减少功耗:8002B 内部有过热自动关断保护机制。8002B 工作稳定,并且单位增益稳定。通过配置外围电阻可以调整放大器的电压增益,方便应用。是一款深受市场欢迎,用户认可度高的典型芯片。
主要代码片段及说明
音阶生成模块
例如:do音阶的生成,do_a_max是do在波形表的最大地址,即读取的结束;pitch_c是高音、中音、低音的选择,即读取波形表的步进长度;pwm_fb_c是单PWM周期的GOOD信号,代表可以继续读取波形表。
其他音符读取亦是如此。
PWM调制模块
本模块是将8bit深度的音频信息转换为PWM信号,实现方式是通过计数器控制电平序列的产生。
混频器模块
本模块是产生和弦并且声音不失真,方法是多个音阶输出时,进行降幅处理。
PWM_C是混频后的信号,PWM_C1是降幅后的信号,例如:当有4个琴键被按下时,混频后的信号会超出幅度限制,这里我将它的幅度减为混频后的四分之一。
音阶存储模块
本模块是用来存储每个音阶的波形表的,do re mi fa sol la是用EBR 的ROM IP核实现,si是用片上逻辑资源的ROM IP核实现,宽度均为8bit 。
遇到的主要难题及解决方法
本次主要遇到的主要难题是谐波的添加,谐波强度过大不仅会影响每个音阶的音色,设置会影响音调的准确性。
为了解决这一问题,用matlab建立查找表时,先绘制单周期的波形图,在matlab确保整个波形的峰峰值大于次高峰的峰峰值后,再导入FPGA的程序程序中。
改进建议
目前没有找到片上信号抓取的方法,如PLL倍频至400MHz时无法验证工作是否稳定可靠(软件report时钟可能会不稳定,384MHz仍然提示),建议开发板预留JTAG调试口。
实测情况
实测波形,Vspeaker1-gnd
实测波形,Vspeaker2-gnd
实测波形,扬声器两端相对电压
实测波形,do-re的和弦
Design Summary
Number of registers: 242 out of 4635 (5%)
PFU registers: 242 out of 4320 (6%)
PIO registers: 0 out of 315 (0%)
Number of SLICEs: 356 out of 2160 (16%)
SLICEs as Logic/ROM: 356 out of 2160 (16%)
SLICEs as RAM: 0 out of 1620 (0%)
SLICEs as Carry: 101 out of 2160 (5%)
Number of LUT4s: 647 out of 4320 (15%)
Number used as logic LUTs: 445
Number used as distributed RAM: 0
Number used as ripple logic: 202
Number used as shift registers: 0
Number of PIO sites used: 47 + 4(JTAG) out of 105 (49%)
Number of block RAMs: 10 out of 10 (100%)
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: 1 out of 2 (50%)
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%)
顶层代码如下:
module simu(
input clk_i,rst_n_i,
input [14:0] key_i,
input sw_i,sw1_i,
output speaker_o,
output buzzer_o,
output [13:0] led_o,
output [9:0] test_o,
output [7:0] test1_o
);
//parameter SIN_O_DEPTH = 8;
parameter ADDRESS_DEPTH = 11;
parameter PWM_DEPTH = 8;
//wire [ADDRESS_DEPTH-1:0] Sine;
wire [ADDRESS_DEPTH-1:0] pwm_val;
wire [ADDRESS_DEPTH-1:0] do_addr_c;
wire [ADDRESS_DEPTH-1:0] re_addr_c;
wire [ADDRESS_DEPTH-1:0] mi_addr_c;
wire [ADDRESS_DEPTH-1:0] fa_addr_c;
wire [ADDRESS_DEPTH-1:0] so_addr_c;
wire [ADDRESS_DEPTH-1:0] la_addr_c;
wire [ADDRESS_DEPTH-1:0] si_addr_c;
wire [PWM_DEPTH-1:0] pwm_do;
wire [PWM_DEPTH-1:0] pwm_re;
wire [PWM_DEPTH-1:0] pwm_mi;
wire [PWM_DEPTH-1:0] pwm_fa;
wire [PWM_DEPTH-1:0] pwm_so;
wire [PWM_DEPTH-1:0] pwm_la;
wire [PWM_DEPTH-1:0] pwm_si;
wire [PWM_DEPTH-1:0] pwm_val;
wire [6:0] sel_c;
//wire [SIN_O_DEPTH-2:0] pwm_val0 = ~0;
wire clk_c;
wire sin_table_en;
wire rst_n_c;
wire speaker_c;
wire buzzer_c;
wire dc_cyc_done;
wire [ADDRESS_DEPTH-1:0] Theta;
wire [7:0] test1_c;
wire pwm_out_c;
//led
wire led_1_c ;
wire led_2_c ;
wire led_3_c ;
wire led_4_c ;
wire led_5_c ;
wire led_6_c ;
wire led_7_c ;
wire led_8_c ;
wire led_r1_c ;
wire led_g1_c ;
wire led_b1_c ;
wire led_r2_c ;
wire led_g2_c ;
wire led_b2_c ;
wire [13:0] led_c ;
//keyboard
wire [14:0] key_c;
wire rom_en = 1;
wire [6:0] song;
assign test_o = clk_c;
assign rst_n_c = rst_n_i;
//assign clk_c = clk_i;
//assign test_o = Sine;
assign speaker_o = speaker_c;
assign buzzer_o = buzzer_c;
assign test1_o = test1_c;
//assign led_o = led_c;
assign led_r1_c = key_c[2];
assign led_g1_c = key_c[1];
assign led_b1_c = key_c[0];
assign led_o[6:0] = ~key_c[6:0];
assign led_o[13:12] = ~key_c[14:13];
assign led_o[11:8] = ~0;
/////////////////////////////////////////////////////////////////////////////////
re re_inst(
.Address(re_addr_c),
.OutClock(clk_c),
.OutClockEn(rom_en),
.Reset(!rst_n_c),
.Q(pwm_re)
);
mi mi_inst(
.Address(mi_addr_c),
.OutClock(clk_c),
.OutClockEn(rom_en),
.Reset(!rst_n_c),
.Q(pwm_mi)
);
fa fa_inst(
.Address(fa_addr_c),
.OutClock(clk_c),
.OutClockEn(rom_en),
.Reset(!rst_n_c),
.Q(pwm_fa)
);
so so_inst(
.Address(so_addr_c),
.OutClock(clk_c),
.OutClockEn(rom_en),
.Reset(!rst_n_c),
.Q(pwm_so)
);
la la_inst(
.Address(la_addr_c),
.OutClock(clk_c),
.OutClockEn(rom_en),
.Reset(!rst_n_c),
.Q(pwm_la)
);
si si_inst(
.Address(si_addr_c),
.OutClock(clk_c),
.OutClockEn(rom_en),
.Reset(!rst_n_c),
.Q(pwm_si)
);
m_do do_inst(
.Address(do_addr_c),
.OutClock(clk_c),
.OutClockEn(rom_en),
.Reset(!rst_n_c),
.Q(pwm_do)
);
////////////////////////////////////////////////////////////////////////////////////////
pwm2sin
#(
.DEPTH(PWM_DEPTH)
)
pwm_inst
(
.clk_i(clk_c),
.rst_n_i(rst_n_c),
.pwm_p(pwm_val),
.pwm_out(pwm_out_c),
.pwm_done_o(dc_cyc_done)
);
// module pwm2sin
scale_ctrl scale_inst(
.clk_i(clk_c),
.rst_n_i(rst_n_c),
.pwm_fb_i(dc_cyc_done),
.scale_up_i(key_c[14]),
.scale_down_i(key_c[13]),
//.sin_wrt_o(sin_table_en),
//.test_o(test1_c),
.do_addr_o(do_addr_c),
.re_addr_o(re_addr_c),
.mi_addr_o(mi_addr_c),
.fa_addr_o(fa_addr_c),
.so_addr_o(so_addr_c),
.la_addr_o(la_addr_c),
.si_addr_o(si_addr_c)
);
// Instantiate the key_input_inst
keyboard key_input_inst (
.clk_i(clk_c),
.rst_n_i(rst_n_c),
.key_i(key_i), //[14:0]
.key_o(key_c) //[14:0]
);
//assign key_c = ~key_i;
mix mix_inst (
.clk_i(clk_c),
.rst_n_i(rst_n_c),
.sel_i(sel_c), //[6:0]
.pwm_do_i(pwm_do), //[7:0]
.pwm_re_i(pwm_re),
.pwm_mi_i(pwm_mi),
.pwm_fa_i(pwm_fa),
.pwm_so_i(pwm_so),
.pwm_la_i(pwm_la),
.pwm_si_i(pwm_si),
.pwm_o(pwm_val) //[7:0]
);
//assign pwm_val = pwm_re;
mini_star star_inst (
.clk_i(clk_c),
.rst_n_i(rst_n_c),
.en_i(1),
.key_o(song),
.test_o(led_o[7])
);
///////////////////////////////////////////////////////////////////////////////////////
sw sw_inst (
.clk_i(clk_c),
.rst_n_i(rst_n_c),
.swich_i(sw_i),
.pwm_i(pwm_out_c),
.pwm1_o(speaker_c),
.pwm2_o(buzzer_c)
);
sw1 sw1_inst (
.clk_i(clk_c),
.rst_n_i(rst_n_c),
.swich_i(sw1_i),
.song_i(song),
.piano_i(key_c[6:0]),
.key_o(sel_c)
);
/* module mix
#(
parameter DEPTH = 8
)
(
input clk_i,rst_n_i,
input [6:0] sel_i,
input [DEPTH-1:0] pwm_do_i,
pwm_re_i,
pwm_mi_i,
pwm_fa_i,
pwm_so_i,
pwm_la_i,
pwm_si_i,
output [DEPTH-1:0] pwm_o
) */;
endmodule