基于FPGA的电子琴制作
基于小脚丫STEP_MXO2 FPGA的电子琴制作,实现两个音键和声并且播放使用扬声器或蜂鸣器两种声音播放小星星
标签
FPGA
数字逻辑
DDS
wuyuwo
更新2022-09-09
南京邮电大学
904

基于小脚丫FPGA的电子琴设计

南京邮电大学21级

本科生微电子科学与工程专业

吴婕

  1. 工作原理
  • 使用蜂鸣器、扬声器进行琴键的播放音乐或支持弹奏电子琴
  • 使用pwm波支持蜂鸣器的发声
  • 使用钢琴音色录入与一位DAC实现扬声器的发声
  1. 框图

三、蜂鸣器与模拟喇叭的差别

  1. 蜂鸣器是产生声音的信号装置,有机械型、机电型及压电型。蜂鸣器的典型应用包括警笛、报警装置、火灾警报器、防空警报器、防盗器、定时器等。常见的电子产品中使用的小型蜂鸣器都是压电式蜂鸣器,主要依靠压电效应来产生振动,发出声音。 此类蜂鸣器一般分为自带震荡源或不带震荡源两种。驱动自带震荡源的蜂鸣器时直接使用直流电即可;而驱动不带震荡源的蜂鸣器则需要使用交流信号。、

  2. 喇叭,也称扬声器、音箱、扩音器,是将电子信号转换成为声音的换能器、电子组件,可以由一个或多个组成音响组。扬声器是由磁铁、线圈、喇叭振膜组成。扬声器把电流频率转化为声音。物理学原理,当电流通过线圈产生电磁场,磁场的方向为右手法则。假设,扬声器播放C调,其频率为256Hz,即每秒振动256次,扬声器输出256Hz的交流电,每秒256次电流改变,发出C当电线圈与扬声器薄膜一起振动,推动周围的空气振动,扬声器由此产生声音。人耳可以听到的声波的频率一般在20赫兹至20000赫兹之间,所以一般的扬声器都会把程序设定在这个范围内。工作原理和上述相同。能量的转换过程是由电能转换为磁能,再由磁能转换为机械能,再从机械能转换为声音。

四、用蜂鸣器和模拟喇叭的实现方法差别以及音效差别分析

  1. 蜂鸣器实现方法和音效

    驱动不带震荡源的无源蜂鸣器则需要使用交流信号,通常采用方波信号。对于数字电路来说,可以采用计数器产生不同频率的方波,来实现不同音符的输出。由于方波的信号特点,发声中带有丰富的杂波信号,音效较差,音色单一。

  2. 模拟喇叭实现方法和音效

    扬声器一般需要模拟电流驱动,响应带宽比蜂鸣器大非常多。通常可采用DAC+放大电路或者集成的音频芯片驱动。本文中是由FPGA的IO口产生PWM信号,使用PWM-DAC技术经过RC低通滤波器,滤除pwm尖峰,再经过调幅变阻器、AC耦合电容到放大电路,转换为较为平滑的低频信号来驱动扬声器。音效比蜂鸣器好,可以表现出模拟信号的细节,本例中改变谐波的种类及含量可以发出不同音色的音符。

五、模拟放大电路的仿真及分析

先经过差分放大器进行音频信号的分离,再经过放大器进行音量的放大

五、主要代码片段及说明

  1. 接受按键信号模块(Key_board)

module KeyBoard(

input clk,

input rst_n,

input [12:0] col,

output [12:0] key_out

);

wire clk_200hz;

reg [12:0]key_out_reg;

clk_maker u15(

.clk(clk),

.rst_n(rst_n),

.CNT(32'd60000),

.clk_changed(clk_200hz)

);

reg [12:0] key_r;

reg [12:0] key;

always@(posedge clk_200hz or negedge rst_n)

begin

if(!rst_n)

key_out_reg<=13'b1_1111_1111_1111;

else

begin

key<=col;

key_r<=key;

key_out_reg<=key_r|key;

end

end

//2-----------------------------------------------------------------

//2--------------------独立按键扫描---------------------------------- -

assign key_out=key_out_reg;

endmodule

实现键盘信号的接受和处理,并且防消抖

  1. 根据键盘信号进行频率选择

module fre(

input clk,

input rst_n,

input [12:0] key_out,

input tone_up,

input tone_down,

output reg [12:0] frequence1,

output reg [15:0] time_end1,

output reg [12:0] frequence2,

output reg [15:0] time_end2

);

reg [1:0] pitch;

wire tone_up_pulse;

wire tone_down_pulse;

debounce u17(

.clk(clk),

.rst_n(rst_n),

.key(tone_up),

.key_pulse(tone_up_pulse)

);

debounce u18(

.clk(clk),

.rst_n(rst_n),

.key(tone_down),

.key_pulse(tone_down_pulse)

);

always@(posedge clk or negedge rst_n) begin //时间控制,上一首或者下一首切换歌曲(手动或者自动)

if(!rst_n) begin

pitch=2'd1; end

else if(tone_up_pulse) begin

case(pitch)

2'd0: begin pitch<=2'd1; end

2'd1: begin pitch <=2'd2;end

2'd2: begin pitch <= 2'd2; end

2'd3: begin pitch<=2'd1;end

default:begin pitch<=pitch; end

endcase

end

else if(tone_down_pulse) begin//按键消抖后会输出一个相反的脉冲,不用!chose_up,下面chose_down同理

case(pitch)

2'd0: begin pitch<=2'd0; end

2'd1: begin pitch <= 2'd0; end

2'd2: begin pitch <= 2'd1; end

2'd3: begin pitch<=2'd1;end

default:begin pitch <= pitch;end

endcase

end

end

always@(key_out or pitch)

begin

case(pitch)

2'b1:

case(key_out[12:0])

13'b0_1111_1111_1111: begin frequence1 <= 13'd523; frequence2<=0; time_end1<=16'd11472; time_end2<=0;end//M1,

13'b1_0111_1111_1111: begin frequence1 <= 13'd554; frequence2<=0; time_end1<=16'd10832; time_end2<=0;end//M1#,

…………

default:

begin

if(key_out[0]==0) begin frequence1 <= 13'd523; time_end1<=16'd11472;end

else if(key_out[1]==0) begin frequence1 <= 13'd554; time_end1<=16'd10832;end

…………(从左往右扫)

 

if(key_out[12]==0) begin frequence2 <= 13'd1047; time_end2<=16'd5730; end

else if(key_out[11]==0) begin frequence2 <= 13'd988; time_end2<=16'd6079; end

………………(从右往左扫)

endcase

2'd2:

case(key_out[12:0])

13'b0_1111_1111_1111: begin frequence1 <= 13'd1047; frequence2<=0; time_end1<=16'd5730; time_end2<=0;end//H1,

……

……

(就像这样扩展音域)

end

endmodule

  1. 制造较慢的时钟信号消抖(使用CNT使能产生的信号的频率不固定,以便制造后面播放音乐时的节奏)

module clk_maker(

input clk,

input rst_n,

input [31:0] CNT,

output clk_changed

);

//1---------------------------------------------------------------

reg [31:0] cnt;

reg clk_changed_reg;

always@(posedge clk or negedge rst_n)

begin

if(!rst_n)

begin

cnt<=16'd0;

clk_changed_reg<=1'b0;

end

else

begin

if(cnt>=((CNT>>1)-1))

begin

cnt<=16'd0;

clk_changed_reg<=~clk_changed_reg;

end

else

begin

cnt<=cnt+1'b1;

clk_changed_reg<=clk_changed_reg;

end

end

end

assign clk_changed=clk_changed_reg;

endmodule

 

  1. 钢琴波形查找表

module piano_table(

input [8:0] address,

output [7:0] piano_table_out_wire

);

reg [8:0] address_reg;

reg [8:0] piano_table_out;

always@(address or address_reg)

begin

address_reg<=address;

case(address_reg)

9'd0:piano_table_out<=9'd256;

9'd1:piano_table_out<=9'd261;

…………

9'd510:piano_table_out<=9'd245;

9'd511:piano_table_out<=9'd251;

default:piano_table_out<=9'd0;

endcase

end

assign piano_table_out_wire=piano_table_out[8:1];

endmodule

  1. 长相位累加器,dac1_en来传递“存在和声”的信号

module long_adder(

input clk,

input [12:0] frequence,

output [7:0] dac_data,

output data_e

);

reg [23:0] phase_acc;

reg [15:0] frequence_ver;

reg data_en;

wire [7:0] dac_data_in;

always@(frequence)

case(frequence)

13'd262:begin frequence_ver<=16'd367;data_en<=1;end

13'd277:begin frequence_ver<=16'd388;data_en<=1;end

13'd294:begin frequence_ver<=16'd412;data_en<=1;end

13'd311:begin frequence_ver<=16'd435;data_en<=1;end

…………

13'd2093:begin frequence_ver<=16'd2930;data_en<=1;end

default: begin frequence_ver<=16'd0; data_en<=0;end

endcase

assign data_e=data_en;

always@(posedge clk)

phase_acc<=phase_acc+frequence_ver;

piano_table u8(

.address(phase_acc[8:0]),

.piano_table_out_wire(dac_data_in)

);

assign dac_data=dac_data_in;

endmodule

7.扬声器,输出音乐信号或音键信号

module outspeaker(

input [7:0] dac_data2,

input [7:0] outspeaker_music,

input dac2_en,

input music_en,

input clk,

input tone_en,

output outspeaker

);

reg [7:0] dac_data2_reg;

reg [7:0] outspeaker_music_reg;

reg [7:0] outspeaker_reg;

reg [8:0] outspeaker_accumulate;

always@(posedge clk )

begin

dac_data2_reg<=dac_data2;

outspeaker_music_reg<=outspeaker_music;

if(dac2_en)

outspeaker_reg<=dac_data2_reg;

else outspeaker_reg<=outspeaker_reg;

if(!tone_en)

if(music_en)

outspeaker_reg<=outspeaker_music_reg;

else outspeaker_reg<=outspeaker_reg;

else outspeaker_reg<=0;

end

always@(posedge clk)

outspeaker_accumulate<=outspeaker_accumulate[7:0]+outspeaker_reg;

assign outspeaker=outspeaker_accumulate[8];

endmodule

  1. 按键消抖,产生脉冲信号

module debounce(

input clk,

input rst_n,

input key,

output key_pulse

);

reg key_rst_n_pre; //定义一个寄存器型变量存储上一个触发时的按键值

reg key_rst_n; //定义一个寄存器变量储存储当前时刻触发的按键值

wire key_edge; //检测到按键由高到低变化是产生一个高脉冲

//利用非阻塞赋值特点,将两个时钟触发时按键状态存储在两个寄存器变量中

always @(posedge clk or negedge rst_n)

begin

if (!rst_n) begin

key_rst_n <= 1'b1; //初始化时给key_rst_n赋值全为1,{}中表示N个1

key_rst_n_pre <= 1'b1;

end

else begin

key_rst_n <= key; //第一个时钟上升沿触发之后key的值赋给key_rst_n,同时key_rst_n的值赋给key_rst_n_pre

key_rst_n_pre <= key_rst_n; //非阻塞赋值。相当于经过两个时钟触发,key_rst_n存储的是当前时刻key的值,key_rst_n_pre存储的是前一个时钟的key的值

end

end

assign key_edge = key_rst_n_pre & (~key_rst_n);//脉冲边沿检测。当key检测到下降沿时,key_edge产生一个时钟周期的高电平

reg [17:0] cnt; //产生延时所用的计数器,系统时钟12MHz,要延时20ms左右时间,至少需要18位计数器

//产生20ms延时,当检测到key_edge有效是计数器清零开始计数

always @(posedge clk or negedge rst_n)

begin

if(!rst_n)

cnt <= 18'h0;

else if(key_edge)

cnt <= 18'h0;

else

cnt <= cnt + 1'h1;

end

 

reg key_sec_pre; //延时后检测电平寄存器变量

reg key_sec;

 

 

//延时后检测key,如果按键状态变低产生一个时钟的高脉冲。如果按键状态是高的话说明按键无效

always @(posedge clk or negedge rst_n)

begin

if (!rst_n)

key_sec <= {1'b1};

else if (cnt==18'h3ffff)

key_sec <= key;

end

always @(posedge clk or negedge rst_n)

begin

if (!rst_n)

key_sec_pre <={1'b1};

else

key_sec_pre <= key_sec;

end

assign key_pulse = key_sec_pre & (~key_sec);

 

endmodule

  1. 产生PWM波信号驱动蜂鸣器

module PWM(

input clk,

input [15:0] time_end,

output pwm

);

reg [15:0] cnt;

always@(posedge clk)

if(cnt>=(time_end<<1))

cnt<=0;

else

cnt<=cnt+1'b1;

assign pwm=(time_end>cnt);

endmodule

  1. 储存并输出音乐信号

module music(

input clk,

input rst_n,

input play,

output [7:0] outspeaker_music,

output beeper_music,

output music_en

);

wire start;

wire clk_1hz;

reg playing;

reg [7:0] flag;

reg [2:0] notation [206:0];

reg [15:0] music_time_end_reg;

reg [12:0] music_frequence_reg;

wire [15:0] music_time_end;

wire [12:0] music_frequence;

wire [7:0] outspeaker_music_wire;

wire music_pwm_wire;

debounce u16_debounce(

.clk(clk),

.key(play),

.rst_n(rst_n),

.key_pulse(start)

);

clk_maker u19_clk_maker(

.clk(clk),

.rst_n(rst_n),

.CNT(100_0000),

.clk_changed(clk_1hz)

);

always@(posedge clk_1hz)

begin

notation[1]={3'd1};

notation[2]={3'd1};

notation[3]={3'd1};

notation[4]={3'd1};

notation[5]={3'd0};//输出一定时间音时也要输出较短时间空隙,不然听着不像

…………

notation[205]={3'd0};

notation[206]={3'd0};

end

always@(posedge clk or negedge rst_n)

if(!rst_n)

begin playing=0;end

else if(start)

begin playing<=1;end

else if(flag>=8'd206)

begin playing=0;end

else playing<=playing;

 

always@(posedge clk_1hz)

begin

if(playing==0)

flag<=0;

else flag<=flag+6'd1;

end

always@(posedge clk)

begin

case(notation[flag])

3'd1: begin music_frequence_reg<= 13'd523; music_time_end_reg<=16'd11472; end//M1,

3'd2: begin music_frequence_reg<= 13'd587; music_time_end_reg<=16'd10221; end//M2,

3'd3: begin music_frequence_reg<= 13'd659; music_time_end_reg<=16'd9104; end//M3,

3'd4: begin music_frequence_reg<= 13'd698; music_time_end_reg<=16'd8596; end//M4,

3'd5: begin music_frequence_reg<= 13'd784; music_time_end_reg<=16'd7653; end//M5,,

3'd6: begin music_frequence_reg<= 13'd880; music_time_end_reg<=16'd6818; end//M6,

3'd7: begin music_frequence_reg<= 13'd987; music_time_end_reg<=16'd6079; end//M7,

default: begin music_frequence_reg<= 13'd0; music_time_end_reg<=16'd0; end//没摁

endcase end

assign music_frequence=music_frequence_reg;

assign music_time_end=music_time_end_reg;

PWM u19_PWM(

.clk(clk),

.time_end(music_time_end),

.pwm(music_pwm_wire)

);

long_adder u20_long_adder(

.clk(clk),

.frequence(music_frequence),

.dac_data(outspeaker_music_wire)

);

assign outspeaker_music=outspeaker_music_wire;

assign beeper_music=music_pwm_wire;

assign music_en=playing;

endmodule

 

  1. 蜂鸣器,选择音乐信号或是按键信号进行播放

module beeper(

input pwm1,

input clk,

input tone_en,

input beeper_music,

input music_en,

output beeper

);

reg music_reg;

reg pwm1_reg;

reg beeper_reg;

always@(posedge clk)

begin

music_reg<=beeper_music;

pwm1_reg<=pwm1;

if( tone_en)

if(music_en)

beeper_reg<=music_reg;

else beeper_reg<=pwm1_reg;

else beeper_reg<=0;

end

assign beeper=beeper_reg;

endmodule

六、主要难题及解决办法

1.怎么样获得钢琴波形的数据

在网上查了非常非常多的资料但是没找到直接数据

解决办法:群里有个大佬推荐autopiano网站并且推荐波形基波和各次谐波比例大概为63%,21%,6%,5%,5%并按这个配出了波形

  1. 蜂鸣器声音太小

找了好久bug没找到,用了几种别的方式测试实验,发现我的这块板子的蜂鸣器本来就相较于扬声器偏小

  1. 播放音乐部分的代码找不到什么bug但是上机摁了play键还是没有声音,解决办法:观察波形图发现我的clk_maker模块里cnt的容量上限太小导致输出不了很慢的时钟信号

七、改进建议

1.多设计几个fre频率选择模块出口来实现更多的键的和声

2.建议拓展板的按键使用静音的按键减少对钢琴音播放的影响

软硬件
电路图
附件下载
piano.rar
团队介绍
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号