一,硬件介绍;
1.工作原理;
图1.电子琴硬件框图
(1)电子琴的硬件框图中,核心部分是小脚丫的step开发板,本次活动搭载的是STEP-MXO2-C专用板,是专为FPGA大赛推出的,可以使用网页版FPGA开发系统Web IDE来编程,无需下载、安装FPGA的编程软件,当然熟悉Lattice Diamond编程工具的同学仍然可以使用Diamond对其进行编程。
(2)输入端共15个按键,其中13个被定义为琴键,剩下两个按键用来扩展音程;
(3)输出端可以选择喇叭发声,也可以是蜂鸣器发声,通过中间拨动开关来选择。蜂鸣器与喇叭的驱动方式和音色响度略有不同。
2.硬件原理图
硬禾的原理图格式一直是可以的,比起学校里那些堆砌一通的原理图好太多了。这种清爽的布局,清晰的连线,易于阅读也好查错。在对一个事物还没建立大局观的时候,顺藤摸瓜胜过碎片化的反复检索。
图2.硬件原理图
3.蜂鸣器和喇叭的区别和驱动方法
蜂鸣器发声主要靠电压驱动压电陶瓷发声:给压电陶瓷材料施加交变电压时,材料发生规律性膨胀收缩,从而带动金属振膜震动发出声音。压电陶瓷一般工作在谐振频率附近,频带较窄。因为阻抗较大,电流较小,所以蜂鸣器功率较小。本应用FPGA给出一路PWM波来控制三极管开关,从而驱动蜂鸣器发声。通过调节占空比来控制音量大小。
喇叭的发声是利用永磁体和线圈磁场相互作用发声:变化的电信号流经线圈产生变化的磁场与永磁体产生相作用力,致使磁体吸引排斥线圈产生震动,从而带动纸盆发出声音。喇叭的阻抗较低,功率可以做的很高。本应用采用PWM模拟DAC来生成需要的模拟信号,经过放大电路放大后驱动喇叭发声,通过调节模拟信号的幅值来控制声音大小。
4.蜂鸣器和喇叭的声音区别
由于蜂鸣器的谐振频率较高,频带较窄,所以发出的声音较尖锐,一般做报警用。喇叭的频带较宽,频率响应优于蜂鸣器,在人耳听力范围内可以表现出更多的细节,适合播放音乐。
5.模拟放大电路的仿真及分析:
小脚丫的IO口只有两种状态,要么是高电平,要么低电平。要想获得模拟信号,就需要用到小脚丫的“独臂神通”PWM模拟DAC了,即通过调节PWM的占空比,来获得与之等效的模拟电压信号。此处PWM的频率要与后端R16,R17,C3所组成的滤波电路频率相匹配,频率过高驱不动,频率低了又会引起失真。
图3.功放芯片框图
图3是功放驱动芯片的框图可以看到内部输入端是一个放大器件,它可以将输入信号按照一定倍数放大,这个放大倍数为,一级放大后的信号接到喇叭的一端、再经过反向后接到喇叭另一端来驱动喇叭。
图4.放大电路仿真
图4是根据放大电路内部框架搭建的仿真图,图中绿色是输入信号,黄、蓝色曲线是放大器输出端信号。
二,功能介绍
经过这段时间的学习,实现了存储一段琴谱自动播放,按不同琴键切换不同音调。上下按键扩展音程,以及同时按下两个琴键生成和弦等功能。
三,实现过程
1.正弦波的生成:本应用使用了查表法来实现。即事先将目标基频的一段波表存在小脚丫中,每过一段时间读取一个值来执行累加操作,溢出后将相应IO口置高。
波表部分:
module sin_table(address,sin);
output [8:0] sin; //实际波形表为9位分辨率(1/4周期)
input [5:0] address; //64个点来生成1/4个周期的波形,完整的一个周期为256个点
reg [8:0] sin;
always @(address)
begin
case(address)
6'h0: sin=9'h0;
6'h1: sin=9'h24;
6'h2: sin=9'h4A;
6'h3: sin=9'h6F;
6'h4: sin=9'h93;
6'h5: sin=9'hB6;
6'h6: sin=9'hD8;
6'h7: sin=9'hF8;
6'h8: sin=9'h116;
6'h9: sin=9'h134;
。
。
。
。
。
。
。
endcase
end
endmodule
pwm生成部分:
module pwm(
input clk,
input rst,
input [12:0] duty,
output pwm_out
);
reg [9:0] PWM_accumulatore;
always @(posedge clk or negedge rst)
begin
if(!rst)
PWM_accumulatore <= 0;
else
PWM_accumulatore <= PWM_accumulatore[8:0] + duty[8:0];
end
assign pwm_out = ~PWM_accumulatore[9];
endmodule
(2)生成不同频率的波:通过调节累加器步进值来调节读取播表的快慢成都,从而获得不同正弦波频率。
module synthesizer(
input clk,
input rst,
input [12:0] key,
input [1:0]updown,
output [12:0] wavecnt
);
wire pwm_bit_synthesizer;
wire [9:0] wavec4, waved4b, waved4, wavee4b, wavee4, wavef4, waveg4b, waveg4, wavea4b, wavea4, waveb4b, waveb4, wavec5;
wave #(366) c4(.clk(clk), .rst(rst), .enable(key[0]), .interval(updown), .waveout(wavec4));
wave #(388) d4b(.clk(clk),.rst(rst), .enable(key[1]), .interval(updown),.waveout(waved4b));
wave #(411) d4(.clk(clk), .rst(rst), .enable(key[2]), .interval(updown),.waveout(waved4));
wave #(435) e4b(.clk(clk),.rst(rst), .enable(key[3]), .interval(updown),.waveout(wavee4b));
wave #(461) e4(.clk(clk), .rst(rst), .enable(key[4]), .interval(updown),.waveout(wavee4));
wave #(488) f4(.clk(clk), .rst(rst), .enable(key[5]), .interval(updown),.waveout(wavef4));
wave #(517) g4b(.clk(clk),.rst(rst), .enable(key[6]), .interval(updown),.waveout(waveg4b));
wave #(548) g4(.clk(clk), .rst(rst), .enable(key[7]), .interval(updown),.waveout(waveg4));
wave #(581) a4b(.clk(clk),.rst(rst), .enable(key[8]), .interval(updown),.waveout(wavea4b));
wave #(615) a4(.clk(clk), .rst(rst), .enable(key[9]), .interval(updown),.waveout(wavea4));
wave #(652) b4b(.clk(clk),.rst(rst), .enable(key[10]),.interval(updown), .waveout(waveb4b));
wave #(690) b4(.clk(clk), .rst(rst), .enable(key[11]),.interval(updown), .waveout(waveb4));
wave #(732) c5(.clk(clk), .rst(rst), .enable(key[12]),.interval(updown), .waveout(wavec5));
assign wavecnt = (wavec4+waved4b+waved4+wavee4b+wavee4+wavef4+waveg4b+waveg4+wavea4b+wavea4+waveb4b+waveb4+wavec5)/4;
endmodule
(3)谐波分量:为了实现谐波分量要求,我事先使用excel生成了一段带有3倍,5倍谐波分量的波表如下图:
图5.谐波分量波表
(4)两个琴键的和弦,此处使用了简单的相加操作来生成和弦。但同时引出另一个问题,两个波相加后的值超出寄存器上限,这样就会出现失真的问题。解决方法是将相加的波值进行了除4处理。
assign wavecnt = (wavec4+waved4b+waved4+wavee4b+wavee4+wavef4+waveg4b+waveg4+wavea4b+wavea4+waveb4b+waveb4+wavec5)/4;
(5)自动播放一段音乐:这里再次使用了万能的查表法,即先将琴谱存储起来。然后再定义一个节拍模块,每四分之一秒读取一次琴谱,循环一圈播放一遍音乐。
always@(posedge clk or negedge rst) //自动播放音乐节拍计数
begin
if(!rst)
tempo[23:0]<= 0;
else
tempo[23:0]<=tempo[23:0]+24'b1;
if(tempo>=1500000)
begin
tempo[23:0]<= 0;
tempo_flag=~tempo_flag; //计数到节拍标志翻转
end
end
always@(posedge tempo_flag or negedge rst) //自动播放音节累加
begin
if(!rst)
music_count[7:0]<= 0;
else if(music_flag==1)
begin
music_count[7:0]<=music_count[7:0]+8'b1;
if(music_count>=96)
begin
music_count[7:0]<= 0;
end
end
else
music_count[7:0]<= 0;
end
musicable musicable_u0(
.micount(music_count),
.musickey(music)
);
autokey autokey_u0( //将单个音节映射到13个按键
.key_value (music),
.key_bit(music_keybit)
);
四,不足与改进
1.按键开始和结束的时候,能听到声音突变,代码还有待优化。现在想来可以这么处理:按键按下时做缓启动设计,即振幅由小慢慢变到最大,弹起时增加振幅慢慢减小的过程;
2.蜂鸣器的声音大小还不能调节,暂时还没摸索出蜂鸣器声音的调节方法;
3.自动播放功能只能播放单音节,还不能播放和弦,后续继续探索。
五,心得体会
1.了解了fpga的设计流程;
2.认识到功放电路的驱动原理;