一.项目要求
- 基于提供的套件和工具,自己组装电子琴
- 自己编程基于FPGA实现:
- 存储一段音乐,并可以进行音乐播放,
- 可以自己通过板上的按键进行弹奏,支持两个按键同时按下(和弦)并且声音不能失真,板上的按键只有13个,可以通过有上方的“上“、”下”两个按键对音程进行扩展
- 使用扬声器进行播放时,输出的音调信号除了对应于该音调的单频正弦波外,还必须包含至少一个谐波分量
- 音乐的播放支持两种方式,这两种方式可以通过开关进行切换:
- 当开关切换到蜂鸣器端,可以通过蜂鸣器来进行音乐播放
- 当开关切换到扬声器端,可以通过模拟扬声器来进行音乐播放,每个音符都必须包含基频 + 至少一个谐波分量
二.电子琴的工作原理和框图
本次项目基于小脚丫FPGA核心板和Piano Kit扩展板,利用pwm实现蜂鸣器的音乐播放,利用波形ROM和DDS-PWM实现模拟扬声器音乐播放,并实现存储一段音乐,可以进行音乐播放,最终完成所有项目要求,成功实现电子琴的设计。
三.蜂鸣器和模拟喇叭的差别
喇叭与蜂鸣器均属于电声器件,均可将电信号转换为声音,但两者在结构和用途等方面不同。
1.蜂鸣器
蜂鸣器(Buzzer)是一种电子讯响器,用作发声器件,广泛用于计算机、打印机、复印机、报警器、电子玩具、汽车电子设备、定时器等电子产品中。
按其结构主要分为压电式蜂鸣器和电磁式蜂鸣器两种类型:
电磁式蜂鸣器由振荡器、电磁线圈、磁铁、振动膜片及外壳等组成。接通电源后,振荡器产生的音频信号电流通过电磁线圈,使电磁线圈产生磁场,振动膜片在电磁线圈和磁铁的相互作用下,周期性地振动发声。
压电式蜂鸣器主要由多谐振荡器、压电蜂鸣片、阻抗匹配器、共鸣箱及外壳等组成。多谐振荡器由晶体管和集成电路构成,当接通直流电源(1.5-15V)时,多谐振荡器起振,产生1.5-2.5kHz的音频信号,经阻抗匹配器推动压电蜂鸣片发声。压电蜂鸣片由锆钛酸铅或铌镁酸铅压电陶瓷材料制成,在陶瓷片的两面镀上银电极,经极化和老化处理后,再与黄铜片或不锈钢片粘在一起。
2.模拟喇叭
喇叭的种类较多,其中应用最为广泛的是电动式扬声器。
电动式扬声器(又被称为动圈式扬声器)主要由永久磁铁、线圈(又称音圈)和纸盆(与线圈连在一起)构成。当电信号通过引出线流进线圈时,线圈产生磁场。由于流进线圈的电流是变化的,线圈产生的磁场也是变化的,线圈变化的磁场与磁铁的磁场相互作用,线圈和磁铁不断排斥和吸引,使重量轻的线圈产生运动(时而远离磁铁,时而靠近磁铁),线圈的运动带动与它相连的纸盆振动,纸盆就发出声音,就使扬声器产生随音频变化的声音,从而实现了电-声转换。
3.区别
从两者的结构和原理上比较与分析,喇叭输入的信号为音频信号,是不断变化的交流电,所以在结构上无振荡器;有源蜂鸣器输入的为直流电,为使其发出声音,在结构上必须配有振荡器(振荡器是一种产生交流信号的电路),将直流电转换为交流电,从而发出声音;而无源蜂鸣器没有集成振荡器,需要外部提供震荡激励,当震荡频率不同时发出不同的音调。在用途上,喇叭由于音色丰富,能够发出多种音调,多用于播放音频信号,而蜂鸣器多用于报警。
四. 用蜂鸣器和模拟喇叭的实现方法差别以及音效差别分析
1.硬件电路差别
对于无源蜂鸣器,以方波信号作为外部震荡激励的首选,扩展板上采用的电磁式蜂鸣器需要的驱动电流较高,一般单片机和FPGA管脚驱动能力有限不能直接驱动,常用三极管增加驱动能力,另外电磁式蜂鸣器内部含有感应线圈,在电路通断瞬间会产生感应电势,为保证电路长期稳定的工作,最好增加续流二极管设计,扩展板上蜂鸣器驱动电路如下:
对于模拟喇叭,扩展板上驱动电路如下:
FPGA端对数字音乐信号PWM调制,再通过由R16,R17,C3组成的RC滤波器解调得到模拟的音乐信号,经过放大电路放大后驱动模拟喇叭。
2.软件实现方法差别
对蜂鸣器模块,只需设计一个PWM信号发生器模块,模块根据信号cycle控制产生周期可控的脉冲信号(pwm_out),可以用来驱动无源蜂鸣器电路。而cycle信号则受状态标志stat,音符信号note和按键信号控制。具体程序实现如下:
module buzzer(
input clk,//输入时钟(48MHZ)
input rst_n,//复位信号
input [12:0] key_value,//按键消抖后的数据
input [12:0] key_flag,//按键数据有效信号
input [2:0] yinjie,//电子琴状态下音调
input stat,//状态标志
input [5:0] note,//音符
output PWM_out//pwm输出
);
reg [17:0] CNT; //时钟计数器
reg [17:0] cycle; //pwm周期值
reg PWM;
reg [2:0] yinjie_box;//音乐盒状态下根据note改变音调
initial begin
cycle = 18'd0;
yinjie_box = 3'd1;
end
//计数器计数,输出的pwm频率为:48MHz/cycle
always @(negedge clk or negedge rst_n) begin
if(!rst_n)
CNT <= 18'd0;
else if(CNT >= cycle - 1)
CNT <= 18'd0;
else begin
if(stat == 1'b0)
CNT <= CNT + yinjie;
if(stat == 1'b1)
CNT <= CNT + yinjie_box;
end
end
//改变频率
always@(negedge clk) begin
if(stat == 1'b0) begin//电子琴状态
if(key_flag[0] && ~key_value[0])
cycle <= 18'd183465;//C1
if(key_flag[1] && ~key_value[1])
cycle <= 18'd173172;//C1#/D1B
if(key_flag[2] && ~key_value[2])
cycle <= 18'd163454;//D1
if(key_flag[3] && ~key_value[3])
cycle <= 18'd154276;//D1#/E1B
if(key_flag[4] && ~key_value[4])
cycle <= 18'd145618;//E1
if(key_flag[5] && ~key_value[5])
cycle <= 18'd137288;//F1
if(key_flag[6] && ~key_value[6])
cycle <= 18'd129733;//F1#/G1B
if(key_flag[7] && ~key_value[7])
cycle <= 18'd122449;//G1
if(key_flag[8] && ~key_value[8])
cycle <= 18'd115579;//G1#/A1B
if(key_flag[9] && ~key_value[9])
cycle <= 18'd109091;//A1
if(key_flag[10] && ~key_value[10])
cycle <= 18'd102969;//A1#/B1B
if(key_flag[11] && ~key_value[11])
cycle <= 18'd97190;//B1
if(key_flag[12] && ~key_value[12])
cycle <= 18'd91734;//C2
end
if(stat == 1'b1) begin//音乐盒状态
case(note%12)
6'd0:begin
if(note/12)
cycle <= 18'd97190;
else
cycle <= 0;
end
6'd1:cycle <= 18'd183465;
6'd2:cycle <= 18'd173172;
6'd3:cycle <= 18'd163454;
6'd4:cycle <= 18'd154276;
6'd5:cycle <= 18'd145618;
6'd6:cycle <= 18'd137288;
6'd7:cycle <= 18'd129733;
6'd8:cycle <= 18'd122449;
6'd9:cycle <= 18'd115579;
6'd10:cycle <= 18'd109091;
6'd11:cycle <= 18'd102969;
default:cycle <= 0;
endcase
end
end
//音乐盒状态下根据note改变音调
always @(negedge clk)
begin
if(stat == 1'b1) begin
if(note > 6'd36)
yinjie_box <= 3'd4;
else if(note > 6'd24)
yinjie_box <= 3'd3;
else if(note > 6'd12)
yinjie_box <= 3'd2;
else
yinjie_box <= 3'd1;
end
end
//调节占空比
always @(negedge clk) begin
if(CNT == 16'd0)
PWM <= 1'b1;
else if(CNT < cycle/2) //50%占空比
PWM <= 1'b1;
else
PWM <= 1'b0;
end
assign PWM_out = (key_value[12:0] != 13'h1FFF || stat == 1'b1)? PWM:1'b0;//按键按下时输出pwm
endmodule
对于模拟喇叭模块,则需要设计一个DDS信号发生器。
DDS(Direct Digital Synthesis)是一种把一系列数字信号通过D/A转换器转换成模拟信号的数字合成技术。它有查表法和计算法两种基本合成方法。由于ROM查询法结构简单,只需要在ROM中存放不同相位对应的幅度序列,然后通过相位累加器的输出对其寻址,经过数/模转换和低通滤波(LPF)输出便可以得到所需要的模拟信号,这里我选用ROM查表法。
- 波形ROM
波形ROM中存储了波形在一个周期内各个相位对应的波形幅值,由相位累加器输出的结果选择输出的幅值。在FPGA的应用中,可通过matlab量化波形各个相位对应幅值,生成.mem文件,再通过.mif文件生成对应的ROM,以下是对sin(x)+0.2sin(2x)+0.3sin(3x)+0.1sin(4x)一个周期取样2048个点的matlab程序。取样波形包含了基频和二次到四次谐波分量。
%% sin_mem
close all,clc,clear
x=0:1:2047;
Y=zeros(1,2048);
p=[1 0.2 0.3 0.1];
for i=1:1:4
Y=Y+p(i)*sin(2*pi*i*x/2048);
end
A=round(2048+(Y/1.135)*2047);
fid=fopen ('D:\sin_2048.mem','w');
fprintf (fid,'%X\r\n',A);
fclose (fid );
plot (x,A)
- 相位累加器
相位累加器由一个加法器和一个寄存器构成,输入信号为频率控制字(fcw)和时钟信号,每当一个时钟信号来临时,加法器把上一个时钟产生的累加结果与频率控制字相加,再输出到寄存器,从而使寄存器输出的值每过一个时钟信号就增加一个fcw。模块程序如下:
module DDS(
input clk,
input rst_n,
input key_value,
input [2:0] yinjie,
input [2:0] yinjie_box,
input stat,
input [15:0] fcw,
output reg [24:0] count
);//相位累加器(输出的频率分辨率=48MHZ/(2^(11)*2^(14))=1.4305HZ)
reg [15:0] fcw_r;
always @( negedge clk )//根据状态和音程改变音调
begin
if(stat == 1'b0)
fcw_r <= fcw*yinjie;
if(stat == 1'b1)
fcw_r <= fcw*yinjie_box;
end
always @( negedge clk or negedge rst_n)//地址累加,下降沿改变
begin
if(!rst_n)
count <= 0;
else if(~key_value)
count<=count+fcw_r;//改变累加数字可改变输出频率
else
count <= 0;
end
endmodule
- 频率控制
当频率控制字为1,一个周期的波形取样2048(211)个点时,25位的相位累加器(输入的时钟信号为480MHz,有
f==48MHZ/(2^(11)*2^(14))=1.4305HZ
将频率控制字改为N时,则是通过跳过一些点的方式来将输出波形的频率提高为上式的N倍。
3.音效差别
由蜂鸣器产生的声音单调,刺耳,而模拟喇叭产生的声音更加圆润,音质更加洪亮。
五.模拟放大电路的仿真及分析
扩展板中功放为8002b,由于multisim元件库中没有8002b,于是我选择根据8002b原理图搭建电路进行仿真。电路图和仿真结果如下:
通过理论计算,该功放放大倍数为9.4倍;仿真得到放大倍数约为9.61倍且输出波形与输入反向。
六.主要代码片段及说明
按键消抖模块:主要是利用延时计数器计时20ms,在按键状态稳定时,计数器递减,当计数器递减到1时,说明按键稳定状态维持了20ms,此时消抖过程结束,给出一个时钟周期的标志信号,并寄存此时按键的值。
module key(
input sys_clk, //48M时钟
input sys_rst_n, //复位信号,低有效
input key, //外部按键输入
output reg key_flag, //按键数据有效信号
output reg key_value //按键消抖后的数据
);
reg [31:0] delay_cnt; //延时计数
reg key_reg;
always @(negedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
key_reg <= 1'b1;
delay_cnt <= 32'd0;
end
else begin
key_reg <= key;
if(key_reg != key) //一旦检测到按键状态发生变化(有按键被按下或释放)
delay_cnt <= 32'd1000000; //给延时计数器重新装载初始值(计数时间约为20ms)
else if(key_reg == key) begin //在按键状态稳定时,计数器递减,开始20ms倒计时
if(delay_cnt > 32'd0)
delay_cnt <= delay_cnt - 1'b1;
else
delay_cnt <= delay_cnt;
end
end
end
always @(negedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
key_flag <= 1'b0;
key_value <= 4'b1;
end
else begin
if(delay_cnt == 32'd1) begin //当计数器递减到1时,说明按键稳定状态维持了20ms
key_flag <= 1'b1; //此时消抖过程结束,给出一个时钟周期的标志信号
key_value <= key; //并寄存此时按键的值
end
else begin
key_flag <= 1'b0;
key_value <= key_value;
end
end
end
endmodule
顶层模块piano.v:
module piano
(
input sys_clk, //系统输入时钟(12MHZ)
input rst_n, //复位信号
input [14:0] key, //按键信号
input key_pa, //Speaker/Buzzer转换按键信号
input key_state, //状态转换按键信号
output pwm_out1, //buzzer端pwm输出
output pwm_out2 //speaker端pwm输出
);
wire [14:0] key_value;
wire [14:0] key_flag;
wire clk; //倍频时钟(48MHZ)
wire key_state_value;
wire key_state_flag;
reg stat; //说明当前状态,0为电子琴状态,1为音乐盒状态
reg [2:0] yinjie; //用于音程扩展
reg [31:0] cnt;//用于时间计数
reg clk_beat;//节拍时钟信号
reg [4:0] beat;//音符
reg [5:0] note;//节拍
reg [4:0] count_beat;//音符计数
reg [7:0] count_note;//节拍计数
其中节拍计数部分:
先产生四分之一拍的节拍脉冲信号,再根据脉冲信号递增节拍计数,当节拍计数达到当前播放音符的节拍数时音符计数加一,再以音符计数为地址通过节拍表和音符表获得当前要播放的音符及其节拍。
// 时钟计数
always @(negedge clk or negedge rst_n) begin
if(!rst_n)
cnt <= 0;
else if(cnt == 4500001)
cnt <= 0;
else
cnt <= cnt+1;
end
//生成四分之一拍的节拍脉冲信号
always@(negedge clk) begin
if(cnt==4500000) //可通过更改条件值改变脉冲周期
clk_beat<=1'b1;
else
clk_beat<=1'b0;
end
always@(negedge clk) begin
if(stat == 1'b1) begin
if(clk_beat == 1'b1) begin
if(count_beat < beat)
count_beat <= count_beat+5'd1;
else begin
count_beat <= 5'd1;
if(count_note < 8'd214)
count_note <= count_note+8'd1;
else
count_note <= 8'd0;
end
end
end
end
七.FPGA使用资源情况
通过lattice Diamond编译出Design Summary结果如下,
八.遇到的主要难题及解决方法
和弦的实现:如果为每一个琴键都配置一个romIP用于输出音符波形信号,则会由于波形数据过多导致ROM容量不够,因此我选择配置两个romIP,当有琴键按下时,利用选择器将该琴键对应的累加器产生的地址赋予空闲的romIP。利用这种方法可以完美实现两个琴键的和弦。
九..改进建议
由于真实乐器产生的不同音调信号包含的高次谐波分量的大小不同,可以在FPGA上改进算法以实现。