verilog实验1:基于FPGA蜂鸣器演奏乐曲并数码管显示

时间:2024-03-01 18:17:38

一、实验任务

       利用FPGA进行代码开发,使蜂鸣器演奏出乐曲《生日快乐》,将音调显示在数码管。原理为蜂鸣器为交流源蜂鸣器,在引脚上加一定频率的方波就可以发声,而且发声的频率由所加方波决定。这样我们就可以根据无源蜂鸣器的原理进行发声练习了。

二、代码实现

       由于需要蜂鸣器发声且数码管显示音调,所以我们将代码分为两部分。

       第一部分用于产生音调的方波。第二部分为数码管显示。

       (一)产生音调

       (1)PreDiv 预置分频数模块

       将48M晶振分频12M,再计算得出各个音调的频率,公式为12M÷音调频率÷2,所得即为预置分频数。程序中只编写了低音和中音的14个音。

 

module prediv(
    input [3:0]Index,
    input clk,
    input Reset_n,
    output reg[15:0] PreDiv
    );
        
    always @ (negedge Reset_n or posedge clk)
            if(!Reset_n)
            begin
            PreDiv<=16\'h5997;
            end
        else
            begin
            case(Index)
            4\'d1:PreDiv<=16\'h5997;
            4\'d2:PreDiv<=16\'h4FCD;
            4\'d3:PreDiv<=16\'h471B;
            4\'d4:PreDiv<=16\'h431E;
            4\'d5:PreDiv<=16\'h3BCA;
            4\'d6:PreDiv<=16\'h3544;
            4\'d7:PreDiv<=16\'h2F74;
            4\'d8:PreDiv<=16\'h2CCA;
            4\'d9:PreDiv<=16\'h27E8;
            4\'d10:PreDiv<=16\'h238D;
            4\'d11:PreDiv<=16\'h218E;
            4\'d12:PreDiv<=16\'h1DE5;
            4\'d13:PreDiv<=16\'h1AA2;
            4\'d14:PreDiv<=16\'h17BA;
        endcase
        end
endmodule

       (2)Index 索引数模块

        为了便于代码书写,需要引用索引数(其实加一个ROM更为方便),即使用“5“为低音”so",如简谱一样,可以更为方便的编写乐曲。毕竟乐曲有很多音符,如果每次都用预置数编写程序,程序书写和查错会非常不方便。当然这也是verilog语言的魅力之处。

module index(
    input clk,
    input reset_n,
    output reg[3:0]index
    );
    
    reg[5:0]cnt;
    wire clk2m;
    wire clk2000;
    wire clk2;
    
    defparam clk_rhythm1.divdWIDTH=3,clk_rhythm1.divdFACTOR=12;//24分频2M
    div clk_rhythm1(
            .reset(reset_n),
            .clkin(clk),
            .clkout(clk2m)
            );
    defparam clk_rhythm2.divdWIDTH=8,clk_rhythm2.divdFACTOR=500;//1000分频2000hz
    div clk_rhythm2(
            .reset(reset_n),
            .clkin(clk2m),
            .clkout(clk2000)
            );
    defparam clk_rhythm3.divdWIDTH=8,clk_rhythm3.divdFACTOR=500;//1000分频2hz
    div clk_rhythm3(
            .reset(reset_n),
            .clkin(clk2000),
            .clkout(clk2)
            );
    always @ (negedge reset_n or posedge clk2)
        if(!reset_n)
            begin
            index<=4\'d0;
            cnt<=6\'h0;
            end
        else
            begin
            if(cnt==6\'d42)
                cnt<=6\'h0;
            else
            cnt<=cnt+1\'b1;
            case(cnt)
            6\'d1:index<=4\'d5;
            6\'d2:index<=4\'d5;
            6\'d3:index<=4\'d6;
            6\'d4:index<=4\'d6;
            6\'d5:index<=4\'d5;
            6\'d6:index<=4\'d5;
            6\'d7:index<=4\'d8;
            6\'d8:index<=4\'d8;
            6\'d9:index<=4\'d7;
            6\'d10:index<=4\'d7;
            6\'d11:index<=4\'d5;
            6\'d12:index<=4\'d5;
            6\'d13:index<=4\'d6;
            6\'d14:index<=4\'d6;
            6\'d15:index<=4\'d5;
            6\'d16:index<=4\'d5;
            6\'d17:index<=4\'d9;
            6\'d18:index<=4\'d9;
            6\'d19:index<=4\'d8;
            6\'d20:index<=4\'d8;
            6\'d21:index<=4\'d5;
            6\'d22:index<=4\'d5;
            6\'d23:index<=4\'d12;
            6\'d24:index<=4\'d12;
            6\'d25:index<=4\'d10;
            6\'d26:index<=4\'d10;
            6\'d27:index<=4\'d8;
            6\'d28:index<=4\'d8;
            6\'d29:index<=4\'d7;
            6\'d30:index<=4\'d7;
            6\'d31:index<=4\'d6;
            6\'d32:index<=4\'d6;
            6\'d33:index<=4\'d11;
            6\'d34:index<=4\'d11;
            6\'d35:index<=4\'d10;
            6\'d36:index<=4\'d10;
            6\'d37:index<=4\'d8;
            6\'d38:index<=4\'d8;
            6\'d39:index<=4\'d9;
            6\'d40:index<=4\'d9;
            6\'d41:index<=4\'d8;
            6\'d42:index<=4\'d8;
        endcase
        end
endmodule    

         (3)节拍

           歌曲中不仅有音调还要有快慢,这个快慢即为节拍。所以我们还是分频分出节拍为0.5s一拍。具体代码见上一部分。

          (二)数码管显示

          数码管显示的原理是利用人眼的视觉暂留0.05s,数码管动态扫描时间小于等于0.05s,人眼看到数码管就是一直在显示,根据这个原理,我们将音调显示在数码管上。音调即为上文提到的索引数,所以我们编写代码的思路就为将索引数输入,进行一些运算,在数码管上显示。道理十分简单。

module smdisplay(clk,rst,index,dataout,en);

input clk,rst;
input [3:0]index;
output[7:0] dataout;
output[3:0] en;//COM使能输出

reg[7:0] dataout;//各段数据输出
reg[3:0] en;

reg[15:0] cnt_scan;//扫描频率计数器
reg[3:0] dataout_buf;

always@(posedge clk or negedge  rst)
begin
    if(!rst) 
        begin //低电平复位
            cnt_scan<=0;
         end
    else 
        begin
            cnt_scan<=cnt_scan+1;
        end
end

always @(cnt_scan)//段码扫描频率
begin 
  case(cnt_scan[15:14])
      2\'b00 :
          en = 4\'b1110;
      2\'b01 :
          en = 4\'b1101;
      2\'b10 :
          en = 4\'b1011;
      2\'b11 :
          en = 4\'b0111;
      default :
          en = 4\'b1110;
    endcase
end

always@(en or index) //对应COM信号给出各段数据,段码
begin
    if(index>=1 && index<=7)
    case(en)
        4\'b1110:
            dataout_buf<=index;
        4\'b1101:
            dataout_buf<=0;
        4\'b1011:
            dataout_buf<=0;
        4\'b0111:
            dataout_buf<=0;   
        default:
            dataout_buf<=8;
     endcase
    else if(index >=8 && index <=14)
     case(en)
        4\'b1110:
            dataout_buf<=0;
        4\'b1101:
            dataout_buf<=index - 4\'d7;
        4\'b1011:
            dataout_buf<=0;
        4\'b0111:
            dataout_buf<=0;   
        default:
            dataout_buf<=8;
     endcase
    else
     case(en)
        4\'b1110:
            dataout_buf<=0;
        4\'b1101:
            dataout_buf<=0;
        4\'b1011:
            dataout_buf<=index - 4\'d14;
        4\'b0111:
            dataout_buf<=0;   
        default:
            dataout_buf<=8;
     endcase
end

always@(dataout_buf)
begin
    case(dataout_buf)  //将要显示的数字译成段码
        4\'b0000://0
            dataout=8\'b0000_0011;
        4\'b0001://1
            dataout=8\'b1001_1111;
        4\'b0010://2
            dataout=8\'b0010_0101;
        4\'b0011://3
            dataout=8\'b0000_1101;
        4\'b0100://4
            dataout=8\'b1001_1001;
        4\'b0101://5
            dataout=8\'b0100_1001;
        4\'b0110://6
            dataout=8\'b0100_0001;
        4\'b0111://7
            dataout=8\'b0001_1111;
        4\'b1000://8
            dataout=8\'b0000_0001;
        4\'b1001://9
            dataout=8\'b0000_1001;
       default://这里仅编译了0-9这几个数字
            dataout=8\'b1111_1111;//全灭
     endcase
end

endmodule

      之后,我们在顶层编写,将各个模块连接起来。输入输出分清楚。再进行管脚的绑定,我们就可以进行程序的烧写了!烧写如我们所料,可以演奏音乐,并且在数码管上显示音调。

三、感悟

      这是在一年前大学里学了EDA课程后,再一次拾起。年轻时候由于EDA是选修课程,尽管很感兴趣也很喜欢讲授课程的老师,但是还是没有很重视起来,觉得这些课程像高数大物之类般看看书完成完成课后作业便可以了,可是在每一次完成老师布置的大作业时的吃力,至今想想还是不容小觑的。那时的我不会查资料,不会从网上看别人发的经验帖,更是看代码也十分粗糙,也不会用仿真软件。总结起来就是不知道这些与时俱进的技术怎么学。现在很高兴的是,我又一次的拾起来这些大概还没忘记的知识。由于实习,我又一次接触了FPGA,并且将其作为我的毕设题目。这次,我一定要吸取经验,努力提高自己。如果不知道该努力什么,那么就把自己现在所面临的每一件事情做好。加油。