verilog實驗1:基於FPGA蜂鳴器演奏樂曲並數碼管顯示


一、實驗任務

       利用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,並且將其作為我的畢設題目。這次,我一定要吸取經驗,努力提高自己。如果不知道該努力什么,那么就把自己現在所面臨的每一件事情做好。加油。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM