FPGA VerilogHDL语言 数字钟 按键消抖
1.描述
一個簡單的基于FPGA的數字鐘,語言用的是VerilogHDL,可以實現以下功能:
1. 數碼管顯示0-59(秒表)
2. 數碼管顯示:時-分-秒
3. 數碼管顯示時分秒并且可以設置時間(小時和分鐘)
4. 在3的基礎上,當分鐘為59時,秒數從56-59依次對應不同LED亮
5. 在4的基礎上,55秒、57秒、59秒警報,且59秒高音。
開發板上有四個LED燈(腳管輸出高點亮)、一個長的數碼管(有六個單個的,型號是LG3661BH)、四個按鍵(按下是低電平)
2.環境
硬件 :altera公司的Cyclone IV --EP4CE6F17C8N
軟件: Quartus Prime Lite Edition 17.1
操作系統:Windows10
3.代碼示例
1)按鍵消抖模塊
按鍵消抖模塊主要實現原理:采用兩級觸發器儲存兩個時鐘節拍下連續讀到的管教電平信息,通過比較兩級觸發器的數值來確定按鍵按下的狀態是否穩定,穩定的話(兩級觸發器的值相同),啟動定時器開始定時,期間如果值不穩定的話定時器會清零,若是定時器計數到指定的值后,輸出鍵值。其具體代碼如下
//按鍵消抖模塊 module Btn_debounce(input Btn_Clk, //輸入時鐘input Btn_in, //鍵值輸入input Btn_nRst, //復位output reg Btn_out //輸出 );parameter BTN_FRE = 50; //50MHZ parameter DLY_TIME = 20; //20ms延時計數 parameter DLY_TIME_VAL = 1000*DLY_TIME*BTN_FRE;//計數值reg[31:0] store_cnt; reg[31:0] next_cnt; //采用兩級觸發器來比較前后鍵值 reg DFF1; reg DFF2;wire IsReachTime; //穩定時間到達信號 wire IsKeyValSame; //觸發器兩次值相同信號//獲取當前按鍵狀態 always@(posedge Btn_Clk or negedge Btn_nRst) beginif(1'b0 == Btn_nRst)beginDFF1 <= 1'b1;DFF2 <= 1'b1;store_cnt <= 1'b0 ;endelsebeginDFF1 <= Btn_in ; //獲取當前鍵值DFF2 <= DFF1;store_cnt <= next_cnt;end end //輸出穩定鍵值 always@(posedge Btn_Clk or negedge Btn_nRst) beginif(1'b0 == Btn_nRst) Btn_out <= 1'b1 ;//復位輸出值//到達時間(按鍵以穩定一段時間了)輸出鍵值else if(store_cnt == DLY_TIME_VAL) Btn_out <= DFF2 ;else Btn_out <= Btn_out ; end//鍵值穩定時間計數 assign IsKeyValSame = (DFF1^DFF2);//異或,不同為1相同為0 assign IsReachTime = ~(store_cnt == DLY_TIME_VAL); //always@(IsKeyValSame or IsReachTime or store_cnt) beginif({IsKeyValSame,IsReachTime} == 2'b00) //時間到了并且穩定,可以輸出next_cnt <= store_cnt; //鎖定計數值;else if({IsKeyValSame,IsReachTime} == 2'b01) //計數時間沒到next_cnt <= store_cnt+1;elsenext_cnt <= 32'b0; //鍵值不穩定,計數值清零 endendmodule2)數碼管譯碼模塊
//共陽極數碼管譯碼器 module seg_decoder(input nEn,input[3:0] bin_data, //4位二進制輸入output reg[6:0] seg_data //段碼輸出 );always@(seg_data or nEn) beginif(nEn == 1'b1) seg_data <=7'b111_1111; elsebegincase(bin_data)4'd0:seg_data <= 7'b100_0000; //04'd1:seg_data <= 7'b111_1001;4'd2:seg_data <= 7'b010_0100;4'd3:seg_data <= 7'b011_0000;4'd4:seg_data <= 7'b001_1001;4'd5:seg_data <= 7'b001_0010;4'd6:seg_data <= 7'b000_0010;4'd7:seg_data <= 7'b111_1000;4'd8:seg_data <= 7'b000_0000;4'd9:seg_data <= 7'b001_0000;4'ha:seg_data <= 7'b000_1000;4'hb:seg_data <= 7'b000_0011;4'hc:seg_data <= 7'b100_0110;4'hd:seg_data <= 7'b010_0001;4'he:seg_data <= 7'b000_0110;4'hf:seg_data <= 7'b000_1110; //Fdefault:seg_data <= 7'b111_1111;endcaseend end endmodule3)數碼管掃描模塊
module seg_scaner(input Clk, //時鐘信號input nRst,output[3:0] seg_cnt, //output[5:0] seg_sel );//parameter SCAN_FRE = 10000000 ; //用于波形仿真測試 parameter SCAN_FRE = 10000 ; parameter SYS_FRE = 50000000; parameter SCAN_COUNT = SYS_FRE/SCAN_FRE*6-1; //6個數碼管reg[31:0] scan_counter; reg[3:0] Tscan_sel; reg[5:0] Tseg_sel; assign seg_sel = Tseg_sel; assign seg_cnt = Tscan_sel;always@(posedge Clk or negedge nRst) beginif(1'b0 == nRst) beginscan_counter <= 32'd0;Tscan_sel <= 4'd0;endelse if(scan_counter >= SCAN_COUNT)beginscan_counter <= 32'd0;if(Tscan_sel == 4'd5) Tscan_sel <= 4'd0;else Tscan_sel <= Tscan_sel+4'd1;endelse beginscan_counter <= scan_counter+32'd10;end end//類似于譯碼器 always@(posedge Clk or negedge nRst) beginif(1'b0 == nRst) Tseg_sel <= 6'b111111; //共陽極,位選端elsebegincase(Tscan_sel)4'd0:Tseg_sel <= 6'b111110;4'd1:Tseg_sel <= 6'b111101;4'd2:Tseg_sel <= 6'b111011;4'd3:Tseg_sel <= 6'b110111;4'd4:Tseg_sel <= 6'b101111;4'd5:Tseg_sel <= 6'b011111;default:Tseg_sel <= 6'b111111;endcaseend end endmodule4)計數器模塊
//計數器模塊 module MyNcounter #(parameter BIT=4,parameter N = 10)(input CntClk, input CntnRst,output[BIT-1:0] CntDout, //數據輸出output CntCout //進位輸出);reg[BIT-1:0] rDout; reg rCout;assign CntDout = rDout; assign CntCout = rCout;always@(posedge CntClk or negedge CntnRst) beginif(1'b0 == CntnRst) beginrDout <= {BIT {1'b0}};rCout <= 1'b0;endelsebeginif(rDout < N-1)begin//rDout <= rDout+{BIT {1'b1}};rDout <= rDout+1;rCout <= 1'b0;endelsebeginrDout <= {BIT {1'b0}};rCout <= 1'b1;endend endendmodule5)兩位計數器
//23點計數器 module My2BitNcounter#(parameter BIT=8)(input CntClk, input CntnRst,output[BIT/2-1:0] CntHDout, //數據輸出output[BIT/2-1:0] CntLDout,output CntCout //進位輸出);reg[BIT/2-1:0] rHDout; reg[BIT/2-1:0] rLDout; reg rCout;assign CntHDout = rHDout; assign CntLDout = rLDout; assign CntCout = rCout;always@(posedge CntClk or negedge CntnRst) beginif(1'b0 == CntnRst) beginrHDout <= {BIT/2 {1'b0}};rLDout <= {BIT/2 {1'b0}};rCout <= 1'b0;endelsebegin //23if({rHDout,rLDout} < 8'b0010_0011) //正常//if({rHDout,rLDout} <= 8'd23) //錯誤代碼beginif(rLDout < 9)beginrLDout <= rLDout+1;rCout <= 1'b0;endelsebeginrLDout <= {BIT/2 {1'b0}};if(rHDout < 9)beginrHDout <= rHDout+1;rCout <= 1'b0;endelsebeginrHDout <= {BIT/2 {1'b0}};rCout <= 1'b0;endendendelsebeginrHDout <= {BIT/2 {1'b0}};rLDout <= {BIT/2 {1'b0}};rCout <= 1'b1;endendendendmodule####6)分頻器模塊
//分頻器獨立小模塊 module DigCtrlFre #(parameter N=8) (input PreClk,input DivnRst,input[N-1:0] PreNum,output DivClk );reg[N-1:0] rCnt; reg rDivClk; reg[N-1:0] rPreNum;assign DivClk = rDivClk;always@(posedge PreClk or negedge DivnRst) beginif(1'b0 == DivnRst)begin rDivClk <= {N {1'b0}};rPreNum <= {N {1'b0}};rCnt <= {N {1'b0}};endelsebegin//防止從高分頻系數到低分頻系數變化時需等待1周期if(rPreNum == PreNum) beginrCnt <= rCnt+1;if(rCnt == PreNum)beginrDivClk <= (~rDivClk);rCnt <= {N {1'b0}};endelserDivClk <= rDivClk;endelsebeginrPreNum <= PreNum;rCnt <= {N {1'b0}};endend endendmodule7)頂層代碼
/*====================================================================== 實驗內容:1. 數碼管顯示0-59(秒表)2. 數碼管顯示:時-分-秒3. 數碼管顯示時分秒并且可以設置時間(小時和分鐘)4. 在3的基礎上,當分鐘為59時,秒數從56-59依次對應不同LED亮5. 在4的基礎上,55秒、57秒、59秒警報,且59秒高音 文件說明:top1:實驗內容1的頂層文件 seg_scaner: 6個數碼管掃描顯示文件top2:實驗內容2的頂層文件 seg_decoder: 數碼管段碼譯碼文件top3:實驗內容3的頂層文件 Btn_debounce:按鍵消抖模塊文件top4:實驗內容4的頂層文件 MyNcounter: 任意進制計數器top5:實驗內容5的頂層文件 DigCtrlFre: 時鐘分頻模塊注意事項:管腳分配:CLK: E1NRST: N13SEG_SEL[0-5]: N9,P9,M10,N11,P11,M11SEG_DATA[0-7]: R14,N16,P16,T15,P15,N12,N15,R16KEYTSET: M15KEYMADD: M16KEYHADD: E16LED_OUT[0-3]: E10,F9,C9,D9(高電平亮)BUZ_OUT: C11(無源,低電平響)代碼方面:按設置鍵后,小時和分鐘個位會加一次(已解決) =======================================================================*/module top5(input CLK,input NRST,input KEYTSET,input KEYHADD, //小時加input KEYMADD, //分鐘加output[5:0] SEG_SEL,output[7:0] SEG_DATA,output reg[3:0] LED_OUT,output BUZ_OUT );reg[3:0] rBinData; //reg rSegDecoderEn; reg IsTimeSetState; reg rBUZ_OUT;wire[6:0] rSEG_DATA; //數碼管譯碼器譯碼輸出 wire wClk1HZ; //1HZ時鐘線 wire wClk2kHZ; wire wClk500HZ;wire[3:0] wSegCnt; //秒鐘 wire[3:0] wSecCntGe; wire wSecCntGeCy; //個位計數器進位輸出 wire[3:0] wSecCntShi; //十位計數器數據輸出 wire wSecCntShiCy; //十位計數器進位輸出 //分鐘 wire[3:0] wMinCntGe; wire wMinCntGeCy; //個位計數器進位輸出 wire[3:0] wMinCntShi; //十位計數器數據輸出 wire wMinCntShiCy; //個位計數器進位輸出 //小時 wire[3:0] wHorCntGe; wire whorCntGeCy; //個位計數器進位輸出 wire[3:0] wHorCntShi; //十位計數器數據輸出//按鍵消抖模塊輸出 wire wKeyBdSet; wire wKeyBdHadd; wire wKeyBdMadd;reg rHorDrivClk; //小時驅動時鐘 reg rMinDrivClk; //分鐘驅動時鐘 reg rSecDrivClk;assign BUZ_OUT = rBUZ_OUT;//顯示時間格式為23.00.00(時 分 秒) assign SEG_DATA = (wSegCnt%2==0)?{1'b1,rSEG_DATA}:{1'b0,rSEG_DATA};//1HZ時間顯示更新時鐘 DigCtrlFre #(.N(32)) DigCtrlFre_m4(.PreClk(CLK),.DivnRst(NRST),.PreNum(25000000),//.PreNum(1), //用于波形仿真測試.DivClk(wClk1HZ)); //2khz蜂鳴器驅動時鐘 DigCtrlFre #(.N(32))DigCtrlFre_m41(.PreClk(CLK),.DivnRst(NRST),.PreNum(12500),//.PreNum(1), //用于波形仿真測試.DivClk(wClk2kHZ)); //500hz蜂鳴器驅動時鐘 DigCtrlFre #(.N(32))DigCtrlFre_m42(.PreClk(CLK),.DivnRst(NRST),.PreNum(50000),//.PreNum(1), //用于波形仿真測試.DivClk(wClk500HZ)); //數碼管掃描 seg_scaner seg_scaner_m4(.Clk(CLK),.nRst(NRST),.seg_cnt(wSegCnt),.seg_sel(SEG_SEL));//位選 always@(wSegCnt) begincase(wSegCnt)3'b000:rBinData <= wHorCntShi;3'b001:rBinData <= wHorCntGe;3'b010:rBinData <= wMinCntShi;3'b011:rBinData <= wMinCntGe;3'b100:rBinData <= wSecCntShi;3'b101:rBinData <= wSecCntGe;default:rBinData <= 1'b0;endcase end//秒個位計數器 MyNcounter #(.BIT(4), //計數器位寬.N(10) //計數器進制)MyNcounter_m40(.CntClk(rSecDrivClk),.CntnRst(NRST),.CntDout(wSecCntGe),.CntCout(wSecCntGeCy));//秒十位計數器 MyNcounter #(.BIT(4), //計數器位寬.N(6) //計數器進制)MyNcounter_m41(.CntClk(wSecCntGeCy),.CntnRst(NRST),.CntDout(wSecCntShi),.CntCout(wSecCntShiCy));//分個位計數器 MyNcounter #(.BIT(4), //計數器位寬.N(10) //計數器進制)MyNcounter_m42(.CntClk(rMinDrivClk),.CntnRst(NRST),.CntDout(wMinCntGe),.CntCout(wMinCntGeCy));//分十位計數器 MyNcounter #(.BIT(4), //計數器位寬.N(6) //計數器進制)MyNcounter_m43(.CntClk(wMinCntGeCy),.CntnRst(NRST),.CntDout(wMinCntShi),.CntCout(wMinCntShiCy));//時個位計數器 //MyNcounter #(.BIT(4), //計數器位寬 // .N(10) //計數器進制 // ) // MyNcounter_m44( // .CntClk(rHorDrivClk), // .CntnRst(NRST), // .CntDout(wHorCntGe), // .CntCout(wHorCntGeCy) // // ); // // 時十位計數器 //MyNcounter #(.BIT(4), //計數器位寬 // .N(3) //計數器進制 // ) // MyNcounter_m45( // .CntClk(wHorCntGeCy), // .CntnRst(NRST), // .CntDout(wHorCntShi), // .CntCout() // // ); //時分 My2BitNcounter #(.BIT(8)) My2BitNcounter_m4(.CntClk(rHorDrivClk), .CntnRst(NRST),.CntHDout(wHorCntShi), //數據輸出.CntLDout(wHorCntGe),.CntCout() //進位輸出);//時間設置模塊 always@(posedge wKeyBdSet or negedge NRST) beginif(1'b0 == NRST) IsTimeSetState <= 1'b0; //防止初次啟動不計數elsebeginif(wKeyBdSet)beginIsTimeSetState <= ~IsTimeSetState;endelse IsTimeSetState <= IsTimeSetState;end endalways@(IsTimeSetState) begin//(1'b1 == IsTimeSetState)//問題代碼if((1'b1 == IsTimeSetState) && (wKeyBdHadd == 1'b0 || wKeyBdMadd == 1'b0)) //正常 beginrHorDrivClk <= wKeyBdHadd;rMinDrivClk <= wKeyBdMadd;rSecDrivClk <= 1'b0;endelse if(1'b0 == IsTimeSetState)beginrHorDrivClk <= wMinCntShiCy;rMinDrivClk <= wSecCntShiCy;rSecDrivClk <= wClk1HZ;end end//LED驅動模塊 always@(posedge wClk1HZ or negedge NRST) beginif(1'b0 == NRST) LED_OUT <= 4'b0000;elsebegin//if((wMinCntGe == 9) && (wMinCntShi == 1)) //測試用if((wMinCntGe == 9) && (wMinCntShi == 5))beginif(wSecCntShi == 5) beginif(wSecCntGe == 6) LED_OUT <= 4'b0001;else LED_OUT <= (LED_OUT<<1);endelse LED_OUT <= 4'b0000;endelse LED_OUT <= 4'b0000;end end//蜂鳴器驅動模塊 always@(posedge CLK) beginif((wMinCntGe == 9) && (wMinCntShi == 5))beginif(wSecCntShi == 5)begincase(wSecCntGe)4'd5:rBUZ_OUT <= wClk500HZ;4'd7:rBUZ_OUT <= wClk500HZ; //低音4'd9:rBUZ_OUT <= wClk2kHZ; //高音default:rBUZ_OUT <= 1'b1;endcaseendelse rBUZ_OUT <= 1'b1;endelse rBUZ_OUT <= 1'b1; end//按鍵消抖模塊 Btn_debounce Btn_debounce_m40(.Btn_Clk(CLK), .Btn_in(KEYTSET), .Btn_nRst(NRST), .Btn_out(wKeyBdSet) );Btn_debounce Btn_debounce_m41(.Btn_Clk(CLK), .Btn_in(KEYHADD), .Btn_nRst(NRST), .Btn_out(wKeyBdHadd));Btn_debounce Btn_debounce_m42(.Btn_Clk(CLK), .Btn_in(KEYMADD), .Btn_nRst(NRST), .Btn_out(wKeyBdMadd));//數碼管譯碼器 seg_decoder seg_decoder_m4(.nEn(1'b0),.bin_data(rBinData),.seg_data(rSEG_DATA)); endmodule4思路描述
設計具體實現思路是:通過分頻器模塊將50Mhz的輸入時鐘分別分為:
wClk1HZ(1HZ)給時分秒計數器
wClk2kHZ(2KHZ)驅動蜂鳴器發出高音
wClk500HZ(500HZ)驅動蜂鳴器發出低音
然后通過wClk1HZ驅動四個計數器級聯實現分和秒的功能,使用兩位24進制計數器實現時的功能。利用快速掃描和視覺殘留效果實現6個數碼管的同時顯示(seg_scaner模塊),在通過多路選擇器(頂層文件中的case語句)實現不同數碼管顯示不同的數值。
對于時間設置的功能,設置按鍵按下,將分鐘個位和小時個位的驅動時鐘源換為按鍵按下抬起過程中產生的上升沿驅動,當設置按鍵再次按下時,時鐘源再次切換為經過分頻器分頻后的1HZ時鐘,時間加按鍵失效。
LED和蜂鳴器功能實現的方法差不多,通過判斷秒數個位來進行不同的操作,LED模塊通過移位實現不同燈的點亮,蜂鳴器硬件上采用的是有源蜂鳴器(接上高電平就叫),通過切換不同頻率的驅動時鐘源,實現不同聲調的聲音輸出。
5總結
因為剛開始學FPGA 和verilogHDL語言,所以代碼在資源節約利用和規范性上有所欠缺,但是基本的功能都已實現;按照慣例,每次新學一個東西總是想發個博客記錄一下。各位參考著看,若是有些可以改進之處也歡迎大家討論。
總結
以上是生活随笔為你收集整理的FPGA VerilogHDL语言 数字钟 按键消抖的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 国家示范性高职院校名单(109所)
- 下一篇: 电脑桌面图标不见了怎么办如果电脑图标不见