基于小脚丫FPGA的电子琴设计
本项目是硬禾学堂提供的暑期在家一起练的一个培训内容,项目基于小脚丫fpga-XO2-4000HC制作电子琴,并完成了相关要求,硬件由硬禾学堂提供。
标签
DDS
2022暑假在家练
小脚丫fpga
电子琴设计
Dog.song
更新2022-09-06
河南工业大学
702

1、设计目标

编程基于FPGA实现:

  1. 存储一段音乐,并可以进行音乐播放,
  2. 可以自己通过板上的按键进行弹奏,支持两个按键同时按下(和弦)并且声音不能失真,板上的按键只有13个,可以通过有上方的“上“、”下”两个按键对音程进行扩展
  3. 使用扬声器进行播放时,输出的音调信号除了对应于该音调的单频正弦波外,还必须包含至少一个谐波分量
  4. 音乐的播放支持两种方式,这两种方式可以通过开关进行切换: 
    1. 当开关切换到蜂鸣器端,可以通过蜂鸣器来进行音乐播放
    2. 当开关切换到扬声器端,可以通过模拟扬声器来进行音乐播放,每个音符都必须包含基频 + 至少一个谐波分量

2、电子琴的工作原理和框图

2.1电子琴的工作原理:

FgL1BU97Zbc9XdDpM0Aq_GMkfZzd

从整体上看,小脚丫FPGA核心板通过获取电子琴按键的情况,输出对应的信号,并且由切换开关控制将信号输出给蜂鸣器或者喇叭上。另外,电子琴上还有用于升八度,降八度的按键,通过按下这两个按键可以实现音阶的扩展。根据任务要求,本项目在小脚丫核心板上也使用了四个按键,功能分别为复位,切换第一首自动播放音乐,切换第二首自动播放音乐,自动播放模式与演奏模式的切换。

 

从更具体上看,核心板输出对应按键产生的信号,是通过DDS产生的正弦波及其谐波复合的信号,本项目的信号是由正弦波及其的二次谐波组成的,占比分别为 66.6% ,33.4% 。下图是由MATLAB生成的信号波形,并生成波表文件,本项目在查找表程序代码,查找的是位宽为9,1/2周期的信号,8位地址的最高位用于选择半周期,低7位用于查找表的查找。8位地址查找的信号,有2^8=256个点,半个周期要查找128个点,因此实际只储存128*9 bit的正弦表。

 

     Fu8P4r7NgpkwBZmkGT_SozFpwWoF

 

上述输出的信号,将会直接输出到rc网络进入放大电路输出给喇叭,或者输出到蜂鸣器电路中。

FtKhpDrAIMc7UX-TNWKLJzJffUe0

本项目另外设置了两位数码管和四个按键的设置,方便调试以及让使用者明白电子琴的工作状态。两位按键中,第一位数码管显示音程的选择,0 表示低音,1表示中音,2表示高音;第二位数码管显示电子琴现在处于自动播放模式还是弹奏模式,0表示弹奏模式,1表示自动播放模式。下图中例化的4个按键除了复位按键,都经过消抖处理,实现控制切换自动播放曲目以及切换模式的功能。

FgQsrLDqRrQftsSNokqhhy2PDVpA

下图展示的是按键弹奏的输入以及蜂鸣器和喇叭的切换,还有信号输出到蜂鸣器和喇叭上。本项目将带有谐波的正弦信号经过滤波输出到功率放大电路进而进入喇叭,也可以直接输到蜂鸣器电路中。结果是由于信号的幅度较小,在蜂鸣器播放时,声音相对较小;而经过放大电路的喇叭播放时,可被滑动变阻器调节到适当音量。

FkoLpg3gFcySLRbu-Q_QfzkJz0dv

本项目的流程图涉及如下:

FqkpxPDiBZ9GntrVCJpexWn_kF1S

3、蜂鸣器和模拟喇叭的差别

蜂鸣器是一体化的电子讯响器,可以在不同驱动波形下发出单调的声音,属于窄频率发声器件。喇叭又称为扬声器,是电声转换器件,它可以把模拟的电信号转换为声音信号,它属于宽频率发声器件。喇叭和蜂鸣器在外在表现来看,最大的区别就是喇叭可以发出各种声音,蜂鸣器只能发出几种单调的声音。从驱动电路和价格方面来看,蜂鸣器都比较简单,一般报警、提示音等简单的声音都有蜂鸣器来做,需要发出音乐等复杂的声音需要使用喇叭。

用蜂鸣器和模拟喇叭的实现方法差别以及音效差别分析:本项目中,在引脚pwmout和speak上输出的信号在程序上是一样的,但是从speak引脚输出的信号经过rc网络会是一个带有谐波的正弦波形。

 4、模拟放大电路的分析

下图是核心板产生变换的pwm波形信号输出到rc电路后得到一个平滑的波形信号,输出给滑动变阻器上,经过一个电容滤除直流分量后进入音频功率放大电路。

FvG025zcVEq5-gK66QN9pQin1Azh

得到的效果是将下面的pwm波形信号转换为测量时示波器显示的波形信号,后者可以在端口TP1放探针,示波器与电路接地后,就可以显示平滑的理想波形。

FuCgVsGVJguGaFORG4yCsmWhS01fFjpF1DVlT754kl_YO6XslUqMjctS

 

5、主要代码片段及说明

4.1查找表

DDS的实现通过查找表实现,表保存了一个带有谐波的正弦信号在不同相位时的信号幅值,当以一定频率取出时即实现了信号的发生。在本项目中,使用了一个长度为256,宽度为9bit的正弦表。由于正弦信号的幅值存在对称性,因此实际只储存128*9 bit的正弦表。实现的代码如下所示,通过地址的最高位来判断信号所处的象限,进而实现对幅值的补偿。

module look_table(phase, sin_out);
    input  	        [7:0]   	phase;
    output 	[9:0] 	sin_out;

reg   	[6:0] 	address;
wire   	    	        sel;
wire   	[8:0] 	sine_table_out;
reg           [9:0]         sine_onecycle_amp;
 
assign sin_out = sine_onecycle_amp[9:0];
 
assign sel = phase[7];
 
sin_table u_sin_table(
                .address(address),
                .sin(sine_table_out)
                );
 
always @(sel or sine_table_out or phase)
begin
	case(sel)
	1'b0: 	begin
			sine_onecycle_amp = 9'h1ff + sine_table_out[8:0];
			address = phase[6:0];
	     	end
  	
  	1'b1: 	begin
			sine_onecycle_amp = 9'h1ff - sine_table_out[8:0];
			address = ~ phase[6:0];
     		end
	endcase
end
 
endmodule

本项目通过Web IDE编程,波表数据是由MATLAB生成的,信号是由正弦波及其的二次谐波组成的,占比分别为 66.6% ,33.4% 。

FjpF1DVlT754kl_YO6XslUqMjctS

module sin_table(address,sin);
output [8:0] sin;         //实际波形表为9位分辨率(1/2周期)
input  [6:0] address;     //128个点来生成1/2个周期的波形,完整的一个周期为256个点
 
reg    [8:0] sin;

always @(address)
	begin
                  case(address)
                    7'h0: sin=9'h0;
                    7'h1: sin=9'h11;
                    7'h2: sin=9'h21;
                    7'h3: sin=9'h32;
                    7'h4: sin=9'h43;
                    7'h5: sin=9'h53;
                    7'h6: sin=9'h63;
                    7'h7: sin=9'h74;
                    7'h8: sin=9'h84;
                    7'h9: sin=9'h94;
                    7'hA: sin=9'hA3;
                    7'hB: sin=9'hB3;
                    7'hC: sin=9'hC2;
                    7'hD: sin=9'hD0;
                    7'hE: sin=9'hDF;
                    7'hF: sin=9'hED;
                    7'h10: sin=9'hFB;
                    7'h11: sin=9'h108;
                    7'h12: sin=9'h115;
                    7'h13: sin=9'h122;
                    7'h14: sin=9'h12E;
                    7'h15: sin=9'h13A;
                    7'h16: sin=9'h145;
                    7'h17: sin=9'h150;
                    7'h18: sin=9'h15B;
                    7'h19: sin=9'h165;
                    7'h1A: sin=9'h16E;
                    7'h1B: sin=9'h177;
                    7'h1C: sin=9'h17F;
                    7'h1D: sin=9'h187;
                    7'h1E: sin=9'h18E;
                    7'h1F: sin=9'h195;
                    7'h20: sin=9'h19B;
                    7'h21: sin=9'h1A1;
                    7'h22: sin=9'h1A6;
                    7'h23: sin=9'h1AB;
                    7'h24: sin=9'h1AE;
                    7'h25: sin=9'h1B2;
                    7'h26: sin=9'h1B5;
                    7'h27: sin=9'h1B7;
                    7'h28: sin=9'h1B9;
                    7'h29: sin=9'h1BA;
                    7'h2A: sin=9'h1BA;
                    7'h2B: sin=9'h1BB;
                    7'h2C: sin=9'h1BA;
                    7'h2D: sin=9'h1B9;
                    7'h2E: sin=9'h1B8;
                    7'h2F: sin=9'h1B6;
                    7'h30: sin=9'h1B3;
                    7'h31: sin=9'h1B0;
                    7'h32: sin=9'h1AD;
                    7'h33: sin=9'h1A9;
                    7'h34: sin=9'h1A4;
                    7'h35: sin=9'h1A0;
                    7'h36: sin=9'h19B;
                    7'h37: sin=9'h195;
                    7'h38: sin=9'h18F;
                    7'h39: sin=9'h189;
                    7'h3A: sin=9'h182;
                    7'h3B: sin=9'h17B;
                    7'h3C: sin=9'h174;
                    7'h3D: sin=9'h16C;
                    7'h3E: sin=9'h165;
                    7'h3F: sin=9'h15D;
                    7'h40: sin=9'h154;
                    7'h41: sin=9'h14C;
                    7'h42: sin=9'h143;
                    7'h43: sin=9'h13A;
                    7'h44: sin=9'h131;
                    7'h45: sin=9'h128;
                    7'h46: sin=9'h11F;
                    7'h47: sin=9'h116;
                    7'h48: sin=9'h10C;
                    7'h49: sin=9'h103;
                    7'h4A: sin=9'hFA;
                    7'h4B: sin=9'hF0;
                    7'h4C: sin=9'hE7;
                    7'h4D: sin=9'hDD;
                    7'h4E: sin=9'hD4;
                    7'h4F: sin=9'hCB;
                    7'h50: sin=9'hC2;
                    7'h51: sin=9'hB9;
                    7'h52: sin=9'hB0;
                    7'h53: sin=9'hA7;
                    7'h54: sin=9'h9E;
                    7'h55: sin=9'h96;
                    7'h56: sin=9'h8D;
                    7'h57: sin=9'h85;
                    7'h58: sin=9'h7D;
                    7'h59: sin=9'h76;
                    7'h5A: sin=9'h6E;
                    7'h5B: sin=9'h67;
                    7'h5C: sin=9'h60;
                    7'h5D: sin=9'h59;
                    7'h5E: sin=9'h52;
                    7'h5F: sin=9'h4C;
                    7'h60: sin=9'h46;
                    7'h61: sin=9'h40;
                    7'h62: sin=9'h3B;
                    7'h63: sin=9'h35;
                    7'h64: sin=9'h31;
                    7'h65: sin=9'h2C;
                    7'h66: sin=9'h27;
                    7'h67: sin=9'h23;
                    7'h68: sin=9'h1F;
                    7'h69: sin=9'h1C;
                    7'h6A: sin=9'h18;
                    7'h6B: sin=9'h15;
                    7'h6C: sin=9'h13;
                    7'h6D: sin=9'h10;
                    7'h6E: sin=9'hE;
                    7'h6F: sin=9'hB;
                    7'h70: sin=9'hA;
                    7'h71: sin=9'h8;
                    7'h72: sin=9'h6;
                    7'h73: sin=9'h5;
                    7'h74: sin=9'h4;
                    7'h75: sin=9'h3;
                    7'h76: sin=9'h2;
                    7'h77: sin=9'h2;
                    7'h78: sin=9'h1;
                    7'h79: sin=9'h1;
                    7'h7A: sin=9'h0;
                    7'h7B: sin=9'h0;
                    7'h7C: sin=9'h0;
                    7'h7D: sin=9'h0;
                    7'h7E: sin=9'h0;
                    7'h7F: sin=9'h0;


                   endcase
              end
endmodule

synthesizer模块是实现按键按下产生对应对应频率的波形,例化了13*3个按键,对应三个音程上的全部按键,调用了tone_chose,输出对应音程(低音,中音,高音)的波形。

module synthesizer(
            input clk,
            input rst,
            input [12:0] key,
            input up,
            input down,
            output waveout,
            output [8:0] seg_led_1
        );
   

    wire [9:0] wavec4,waved4b,waved4,wavee4b,wavee4,wavef4,waveg4b,   //L1-L7
                waveg4,wavea4b,wavea4,waveb4b,waveb4,wavec5;
    wire [9:0] wavec42,waved4b2,waved42,wavee4b2,wavee42,wavef42,waveg4b2,   //M1-M7
                waveg42,wavea4b2,wavea42,waveb4b2,waveb42,wavec52;
    wire [9:0] wavec43,waved4b3,waved43,wavee4b3,wavee43,wavef43,waveg4b3,   //H1-H7
                waveg43,wavea4b3,wavea43,waveb4b3,waveb43,wavec53;
    
    reg [3:0] TONE_CHOSE; 
    
    parameter TONE_L = 2'b00;
    parameter TONE_M = 2'b01;
    parameter TONE_H = 2'b10;
    reg [1:0] state;
    
    always@(posedge clk or negedge rst)begin
    	if(!rst)begin
    		state <= TONE_L;
    	end else if(up)begin
    		state <= state + 1'b1;
    	end else if(down)begin
    		state <= state - 1'b1;
    		end
    end
     
    wave #(366) c4(.clk(clk),.rst(rst),.enable(key[0]),.waveout(wavec4));  //261    L1
    wave #(388) d4b(.clk(clk),.rst(rst),.enable(key[1]),.waveout(waved4b));//277    #L1
    wave #(411) d4(.clk(clk),.rst(rst),.enable(key[2]),.waveout(waved4));  //294    L2
    wave #(435) e4b(.clk(clk),.rst(rst),.enable(key[3]),.waveout(wavee4b));//311    #L2
    wave #(461) e4(.clk(clk),.rst(rst),.enable(key[4]),.waveout(wavee4));  //330    L3
    wave #(488) f4(.clk(clk),.rst(rst),.enable(key[5]),.waveout(wavef4));  //349    L4
    wave #(517) g4b(.clk(clk),.rst(rst),.enable(key[6]),.waveout(waveg4b));//370    #L4
    wave #(548) g4(.clk(clk),.rst(rst),.enable(key[7]),.waveout(waveg4));  //392    L5
    wave #(581) a4b(.clk(clk),.rst(rst),.enable(key[8]),.waveout(wavea4b));//415    #L5
    wave #(615) a4(.clk(clk),.rst(rst),.enable(key[9]),.waveout(wavea4));  //440Hz  L6
    wave #(652) b4b(.clk(clk),.rst(rst),.enable(key[10]),.waveout(waveb4b));//466   #L6
    wave #(690) b4(.clk(clk),.rst(rst),.enable(key[11]),.waveout(waveb4));  //494   L7
    wave #(732) c5(.clk(clk),.rst(rst),.enable(key[12]),.waveout(wavec5));  //523   M1

    wave #(732) c42(.clk(clk),.rst(rst),.enable(key[0]),.waveout(wavec42));  
    wave #(775) d4b2(.clk(clk),.rst(rst),.enable(key[1]),.waveout(waved4b2));
    wave #(821) d42(.clk(clk),.rst(rst),.enable(key[2]),.waveout(waved42));  
    wave #(870) e4b2(.clk(clk),.rst(rst),.enable(key[3]),.waveout(wavee4b2));
    wave #(921) e42(.clk(clk),.rst(rst),.enable(key[4]),.waveout(wavee42));  
    wave #(976) f42(.clk(clk),.rst(rst),.enable(key[5]),.waveout(wavef42));  
    wave #(1035) g4b2(.clk(clk),.rst(rst),.enable(key[6]),.waveout(waveg4b2));
    wave #(1096) g42(.clk(clk),.rst(rst),.enable(key[7]),.waveout(waveg42)); 
    wave #(1162) a4b2(.clk(clk),.rst(rst),.enable(key[8]),.waveout(wavea4b2));
    wave #(1230) a42(.clk(clk),.rst(rst),.enable(key[9]),.waveout(wavea42));  
    wave #(1303) b4b2(.clk(clk),.rst(rst),.enable(key[10]),.waveout(waveb4b2));
    wave #(1381) b42(.clk(clk),.rst(rst),.enable(key[11]),.waveout(waveb42));  
    wave #(1462) c52(.clk(clk),.rst(rst),.enable(key[12]),.waveout(wavec52));      

    wave #(1462) c43(.clk(clk),.rst(rst),.enable(key[0]),.waveout(wavec43));  
    wave #(1550) d4b3(.clk(clk),.rst(rst),.enable(key[1]),.waveout(waved4b3));
    wave #(1643) d43(.clk(clk),.rst(rst),.enable(key[2]),.waveout(waved43));  
    wave #(1741) e4b3(.clk(clk),.rst(rst),.enable(key[3]),.waveout(wavee4b3));
    wave #(1843) e43(.clk(clk),.rst(rst),.enable(key[4]),.waveout(wavee43)); 
    wave #(1953) f43(.clk(clk),.rst(rst),.enable(key[5]),.waveout(wavef43));  
    wave #(2069) g4b3(.clk(clk),.rst(rst),.enable(key[6]),.waveout(waveg4b3));
    wave #(2192) g43(.clk(clk),.rst(rst),.enable(key[7]),.waveout(waveg43));  
    wave #(2322) a4b3(.clk(clk),.rst(rst),.enable(key[8]),.waveout(wavea4b3));
    wave #(2461) a43(.clk(clk),.rst(rst),.enable(key[9]),.waveout(wavea43));  
    wave #(2607) b4b3(.clk(clk),.rst(rst),.enable(key[10]),.waveout(waveb4b3));
    wave #(2763) b43(.clk(clk),.rst(rst),.enable(key[11]),.waveout(waveb43)); 
    wave #(2926) c53(.clk(clk),.rst(rst),.enable(key[12]),.waveout(wavec53));      
                   
    
    wire  [10:0] wavecnt_a,wavecnt_b,wavecnt_c;
    reg  [10:0] wavecnt_d;
    wire [10:0] wavecnt;
    
    
    assign wavecnt_a = wavec4+waved4b+waved4+wavee4b+wavee4+wavef4+waveg4b
                +waveg4+wavea4b+wavea4+waveb4b+waveb4+wavec5;
    assign wavecnt_b = wavec42+waved4b2+waved42+wavee4b2+wavee42+wavef42+waveg4b2
                +waveg42+wavea4b2+wavea42+waveb4b2+waveb42+wavec52;
    assign wavecnt_c = wavec43+waved4b3+waved43+wavee4b3+wavee43+wavef43+waveg4b3
                +waveg43+wavea4b3+wavea43+waveb4b3+waveb43+wavec53;
    
    tone_chose tone_chose_u0(
                    .clk(clk),
                    .rst(rst),
                    .state(state),
                    .wavecnt_a(wavecnt_a),
                    .wavecnt_b(wavecnt_b),
                    .wavecnt_c(wavecnt_c),
                    .waveout(waveout),
                    .seg_led_1(seg_led_1)
                    );

endmodule

wave模块可以再被调用时改变常量FREQ的值,产生对应频率的信号,这里的FREQ就是频率控制字,计算公式为:F_in = F_out*2^24/clk   ,最低输出频率为phase_acc[23]的12MHz/2^24= 0.7Hz。本项目中还有另一个模块wave_2,它用在音乐自动播放时产生波形,与本模块很相似,只是把常量的变化变为变量的输入,方便乐谱中音符的遍历。

module wave(
    input clk,
    input rst,
    input enable,
    output reg [9:0] waveout
);

    parameter FREQ = 24'd366;//C1 261.63Hz       = 261.63*2**24/12000000  
    
    reg [23:0] phase_acc;
    
    wire [9:0] sinout;

always@(posedge clk or negedge rst)
    if(!rst)
        phase_acc <= 0;
    else
        phase_acc <= phase_acc + FREQ;
        
always@(posedge clk or negedge rst)
    if(!rst)
        waveout <= 0;
    else if(!enable)
        waveout <= sinout;
    else
        waveout <= 0;
        
look_table u0_table(
        .phase(phase_acc[23:16]),
        .sin_out(sinout)
);


endmodule
        

本模块只使用在synthesizer模块里,通过state的状态确定输出的音为低音、中音还是高音。同时也例化了一位数码管显示音程的选择,0表示低音,1表示中音,2以及其他表示高音。

module tone_chose(
            input clk,
            input rst,
            input [1:0] state,
            input [10:0] wavecnt_a,
            input [10:0] wavecnt_b,
            input [10:0] wavecnt_c,
            output waveout,
            output [8:0] seg_led_1
        );

    parameter TONE_L = 2'b00;
    parameter TONE_M = 2'b01;
    parameter TONE_H = 2'b10;
    
    wire [10:0] wavecnt;
    reg  [10:0] wavecnt_d;
    
   
    always@(posedge clk or negedge rst)begin
        case(state)
        TONE_L :   wavecnt_d = wavecnt_a;
        TONE_M :   wavecnt_d = wavecnt_b;
        //TONE_H :   wavecnt_d = wavecnt_c;
        endcase
    end
    
    assign  wavecnt = wavecnt_d;

    pwm pwm_u0(
        .clk(clk),
        .rst(rst),
        .duty(wavecnt),
        .pwm_out(waveout)
        );
        
    LED LED_u0(
        .seg_data_1({{2{1'b0}},state}),
        .seg_data_2(),
        .seg_led_1(seg_led_1),
        .seg_led_2()
        );    
endmodule

pwm模块是生成波形的最后一步,pwm_out取累加器的最高位,累加器的位宽要比duty的位宽多一位。具体介绍可参考电子森林用于小脚丫FPGA综合技能训练板的DDS代码 

module pwm
(
input clk,
input rst,
input [10:0] duty,
output pwm_out
);

reg [11:0] PWM_accumulator0;

always@(posedge clk or negedge rst)
    if(!rst)
        PWM_accumulator0 <= 0;
    else
        PWM_accumulator0 <= PWM_accumulator0[10:0] + duty;
        
assign pwm_out = PWM_accumulator0[11];


endmodule

下面是音乐自动播放部分,与弹奏模式相似,写好乐谱后,通过分频器分出一个4Hz的时钟信号,让其进行遍历乐谱,每四个四分音符可组成一个全音符,全音符时间为1s。pua,pub,mod是经过消抖处理后小脚丫核心板上的按键,可以完成切换歌曲和切换演奏模式的功能。

module music_box(
	input    		clk_in,
	input    		rst_n_in,
	input           pua,
	input           pub,
	input           mod,
	output  reg     pwm_en,
	output   		waveout,
	output  [7:0] 	freq_index_out
);
	parameter STATE_IDLE = 2'b00,
			  STATE_QUA  = 2'b01,
			  STATE_QUB  = 2'b10;
			
	wire 		 clk_4Hz;
	wire [10:0]  wavecnt;
	reg  [1:0]   state;
	//reg 		 pwm_en;

	reg  [7:0]	 counter,pucounter_max;
	reg  [4:0]	 freq_index;
	
	reg  [23:0]	 freq [0:21]; //do re mi fa so la si do 低音、中音、高音,共21个
	reg  [7:0]	 PUA  [0:136];//存放曲1的简谱,一个寄存器放半拍
	reg  [7:0]	 PUB  [0:128];//存放曲2的简谱
	//...					  //更改或增加自己的曲子简谱音符
	
	initial begin
			freq[0]=0;
			freq[1]=366;
			freq[2]=411;
			freq[3]=461;
			freq[4]=488;
			freq[5]=548;
			freq[6]=615;
			freq[7]=691;
			freq[8]=731;
			freq[9]=821;
			freq[10]=921;
			freq[11]=976;
			freq[12]=1096;
			freq[13]=1230;
			freq[14]=1381;
			freq[15]=1462;
			freq[16]=1643;
			freq[17]=1843;
			freq[18]=1953;
			freq[19]=2192;
			freq[20]=2461;
			freq[21]=2753;
	end
	//梁祝化蝶部分的简谱,一个寄存器存半拍
	//|3 - 5. 6 | //第一个为例,“低音3 - ”即四拍四个低音mi,“低音5.”即1.5拍,三个so,以此类推
 	//|.   .  . |
	initial begin
		PUA[0]  = 0;  PUA[01] = 3;  PUA[02] = 3;  PUA[03] =3;  PUA[04] =5;  PUA[05] =5;  PUA[06] =5;  PUA[07] =6;
		PUA[08] = 8;  PUA[09] = 8;  PUA[10] = 8;  PUA[11] =9;  PUA[12] =6;  PUA[13] =8;  PUA[14] =5;  PUA[15] =5;
		PUA[16] = 12; PUA[17] = 12; PUA[18] = 15; PUA[19] =15; PUA[20] =13; PUA[21] =12; PUA[22] =10; PUA[23] =12;
		PUA[24] = 9;  PUA[25] = 9;  PUA[26] = 9;  PUA[27] =9;  PUA[28] =9;  PUA[29] =9;  PUA[30] =9;  PUA[31] =0;
		PUA[32] = 9;  PUA[33] = 9;  PUA[34] = 9;  PUA[35] =10; PUA[36] =7;  PUA[37] =7;  PUA[38] =6;  PUA[39] =6;
		PUA[40] = 5;  PUA[41] = 5;  PUA[42] = 5;  PUA[43] =6;  PUA[44] =8;  PUA[45] =8;  PUA[46] =9;  PUA[47] =9;
		PUA[48] = 3;  PUA[49] = 3;  PUA[50] = 8;  PUA[51] =8;  PUA[52] =6;  PUA[53] =5;  PUA[54] =6;  PUA[55] =8;
		PUA[56] = 5;  PUA[57] = 5;  PUA[58] = 5;  PUA[59] =5;  PUA[60] =5;  PUA[61] =5;  PUA[62] =5;  PUA[63] =0;
		PUA[64] = 10; PUA[65] = 10; PUA[66] = 10; PUA[67] =12; PUA[68] =7;  PUA[69] =7;  PUA[70] =9;  PUA[71] =9;
		PUA[72] = 6;  PUA[73] = 8;  PUA[74] = 5;  PUA[75] =5;  PUA[76] =5;  PUA[77] =5;  PUA[78] =5;  PUA[79] =5;
		PUA[80] = 3;  PUA[81] = 5;  PUA[82] = 3;  PUA[83] =3;  PUA[84] =5;  PUA[85] =6;  PUA[86] =7;  PUA[87] =9;
		PUA[88] = 6;  PUA[89] = 6;  PUA[90] = 6;  PUA[91] =6;  PUA[92] =6;  PUA[93] =6;  PUA[94] =5;  PUA[95] =6;
		PUA[96] = 8;  PUA[97] = 8;  PUA[98] = 8;  PUA[99] =9;  PUA[100]=12; PUA[101]=12; PUA[102]=12; PUA[103]=10;
		PUA[104]= 9;  PUA[105]= 9;  PUA[106]= 10; PUA[107]=9;  PUA[108]=8;  PUA[109]=8;  PUA[110]=6;  PUA[111]=5;
		PUA[112]= 3;  PUA[113]= 3;  PUA[114]= 3;  PUA[115]=3;  PUA[116]=8;  PUA[117]=8;  PUA[118]=8;  PUA[119]=8;
		PUA[120]= 6;  PUA[121]= 8;  PUA[122]= 6;  PUA[123]=5;  PUA[124]=3;  PUA[125]=5;  PUA[126]=6;  PUA[127]=8;
		PUA[128]= 5;  PUA[129]= 5;  PUA[130]= 5;  PUA[131]=5;  PUA[132]=5;  PUA[133]=5;  PUA[134]=5;  PUA[135]=5;
	end
	//欢乐颂简谱
	initial begin	                                               
		PUB[0]  =0;  PUB[01] =10; PUB[02] =10; PUB[03] =10;  PUB[04] =11;  PUB[05] =11;  PUB[06] =12;  PUB[07] =12;
		PUB[08] =12;  PUB[09] =12; PUB[10] =11; PUB[11] =11;  PUB[12] =10;  PUB[13] =10;  PUB[14] =9;   PUB[15] =9;  
		PUB[16] =8;   PUB[17] =8;  PUB[18] =8;  PUB[19] =8;   PUB[20] =9;   PUB[21] =9;   PUB[22] =10;  PUB[23] =10;
		PUB[24] =10;  PUB[25] =10; PUB[26] =10; PUB[27] =9;   PUB[28] =9;   PUB[29] =9;   PUB[30] =9;   PUB[31] =9;
		PUB[32] =10;  PUB[33] =10; PUB[34] =10; PUB[35] =10;  PUB[36] =11;  PUB[37] =11;  PUB[38] =12;  PUB[39] =12; 
		PUB[40] =12;  PUB[41] =12; PUB[42] =11; PUB[43] =11;  PUB[44] =10;  PUB[45] =10;  PUB[46] =9;   PUB[47] =9;
		PUB[48] =8;   PUB[49] =8;  PUB[50] =8;  PUB[51] =8;   PUB[52] =9;   PUB[53] =9;   PUB[54] =10;  PUB[55] =10;
		PUB[56] =9;   PUB[57] =9;  PUB[58] =9;  PUB[59] =8;   PUB[60] =8;   PUB[61] =8;   PUB[62] =8;   PUB[63] =8;  
		PUB[64] =9;   PUB[65] =9;  PUB[66] =9;  PUB[67] =9;   PUB[68] =10;  PUB[69] =10;  PUB[70] =8;   PUB[71] =8;
		PUB[72] =9;   PUB[73] =9;  PUB[74] =10; PUB[75] =11;  PUB[76] =10;  PUB[77] =10;  PUB[78] =8;   PUB[79] =8;
		PUB[80] =9;   PUB[81] =9;  PUB[82] =10; PUB[83] =11;  PUB[84] =10;  PUB[85] =10;  PUB[86] =8;   PUB[87] =8;  
		PUB[88] =8;   PUB[89] =8;  PUB[90] =9;  PUB[91] =9;   PUB[92] =5;   PUB[93] =5;   PUB[94] =10;  PUB[95] =10;
		PUB[96] =10;  PUB[97] =10; PUB[98] =10; PUB[99] =10;  PUB[100]=11;  PUB[101]=11;  PUB[102]=12;  PUB[103]=12;
		PUB[104]=12;  PUB[105]=12; PUB[106]=11; PUB[107]=11;  PUB[108]=10;  PUB[109]=10;  PUB[110]=9;   PUB[111]=9;  
		PUB[112]=8;   PUB[113]=8;  PUB[114]=8;  PUB[115]=8;   PUB[116]=9;   PUB[117]=9;   PUB[118]=10;  PUB[119]=10;
		PUB[120]=9;   PUB[121]=9;  PUB[122]=9;  PUB[124]=8;   PUB[124]=8;   PUB[125]=8;   PUB[126]=8;   PUB[127]=8;
	end                                                                                          
	
	//梁祝片段最短的音符为四分音符,如果将全音符的持续时间设为1s,则需要提供一个4Hz的时钟频率信号即可产生四分音符的时长
	divide clk_inst(
		.clk(clk_in),
		.rst_n(rst_n_in),
		.clkout(clk_4Hz)
	);
	//状态切换,根据功能键pua/pub来切换曲目
	always@(posedge clk_in or negedge rst_n_in)
	begin
		if(!rst_n_in) begin
			state <= STATE_IDLE;
		end else begin
			case(state)
				STATE_IDLE : begin
						freq_index <= PUA[counter];
						state <= STATE_QUA;
						pucounter_max <= 136;
					end				
				STATE_QUA : begin 
						freq_index <= PUA[counter];
						if(pua|pub) begin
							pucounter_max <= 128;
							state <= STATE_QUB;
						end else begin pucounter_max <= 136; state <= STATE_QUA; end
					 end
				STATE_QUB : begin 
						freq_index <= PUB[counter];
						if(pua | pub) begin
							pucounter_max <= 136;
							state <= STATE_QUA;
						end else begin pucounter_max <= 128; state <= STATE_QUB; end
					 end
				default : begin state <= STATE_IDLE; pucounter_max <= 136; freq_index <= PUA[counter];end
			endcase 
		end
	end
	always@(posedge clk_4Hz or negedge rst_n_in)
	begin
		if(!rst_n_in) begin
			counter <= 1'b0;
		end else begin		
			if(pwm_en) begin
				if((counter >= pucounter_max) | pua |pub)
					counter <= 1'b0;
				else 
					counter <= counter + 1'b1;
			end else
				counter <= 1'b0;
		end
	end
	//根据MOD键切换播放模式还是弹奏模式,播放模式时使能pwm
	always@(posedge clk_in or negedge rst_n_in) 
	begin
		if(!rst_n_in) begin
			pwm_en  <= 1'b0;
		end else begin
			if(mod) begin  
				pwm_en <= ~pwm_en;
			end
		end
	end
    wire [9:0] wavecnt_a;
	
	wave_2 wave_u0(.clk(clk_in),.rst(rst_n_in),.FREQ(freq[freq_index]),.pwm_en(pwm_en),.waveout(wavecnt_a));
	assign wavecnt = wavecnt_a;
	pwm pwm_u1(
		.clk(clk_in),
		.rst(rst_n_in),
		.duty(wavecnt),
		.pwm_out(waveout)
	);
   
	assign freq_index_out = freq_index;	

endmodule

6、资源占用情况

Fhc3jZQazLvypRR_Y3ZCdE6Lp1sA

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

1.和弦即任意两个按键按下时会出现溢出的现象,通过查阅资料以及向群友求助,解决方法是通过扩大累加器的位宽,以及duty的位宽实现的。

2.谐波的产生,本来是想着在原来的基础上再写一个波表,存储着原来波形的2倍频率的波形,但是合成的波形在相位,幅度上不好控制,最后的解决方法是直接在MATLAB上写出带有谐波的函数,取出256个点,在函数前乘以一个幅度控制因子就行了。

8、 改进建议

1.可以将乐谱写得更复杂一些,完成复杂和弦的使用。

2.可以利用lattice把波表写在ROM里,但本项目全程使用web IDE开发,没使用ROM表。

3.蜂鸣器工作模式下,数字信号的幅度可以更大一点。

 

附件下载
implement - 2022-08-25T150252.516.jed
archive (9).zip
团队介绍
姓名:宋兴旺 学校:河南工业大学
团队成员
Dog.song
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号