赞
踩
一个简单的基于FPGA的数字钟,语言用的是VerilogHDL,可以实现以下功能:
1. 数码管显示0-59(秒表)
2. 数码管显示:时-分-秒
3. 数码管显示时分秒并且可以设置时间(小时和分钟)
4. 在3的基础上,当分钟为59时,秒数从56-59依次对应不同LED亮
5. 在4的基础上,55秒、57秒、59秒警报,且59秒高音。
开发板上有四个LED灯(脚管输出高点亮)、一个长的数码管(有六个单个的,型号是LG3661BH)、四个按键(按下是低电平)
硬件 :altera公司的Cyclone IV --EP4CE6F17C8N
软件: Quartus Prime Lite Edition 17.1
操作系统:Windows10
按键消抖模块主要实现原理:采用两级触发器储存两个时钟节拍下连续读到的管教电平信息,通过比较两级触发器的数值来确定按键按下的状态是否稳定,稳定的话(两级触发器的值相同),启动定时器开始定时,期间如果值不稳定的话定时器会清零,若是定时器计数到指定的值后,输出键值。其具体代码如下
//按键消抖模块 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) begin if(1'b0 == Btn_nRst) begin DFF1 <= 1'b1; DFF2 <= 1'b1; store_cnt <= 1'b0 ; end else begin DFF1 <= Btn_in ; //获取当前键值 DFF2 <= DFF1; store_cnt <= next_cnt; end end //输出稳定键值 always@(posedge Btn_Clk or negedge Btn_nRst) begin if(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) begin if({IsKeyValSame,IsReachTime} == 2'b00) //时间到了并且稳定,可以输出 next_cnt <= store_cnt; //锁定计数值; else if({IsKeyValSame,IsReachTime} == 2'b01) //计数时间没到 next_cnt <= store_cnt+1; else next_cnt <= 32'b0; //键值不稳定,计数值清零 end endmodule
//共阳极数码管译码器 module seg_decoder( input nEn, input[3:0] bin_data, //4位二进制输入 output reg[6:0] seg_data //段码输出 ); always@(seg_data or nEn) begin if(nEn == 1'b1) seg_data <=7'b111_1111; else begin case(bin_data) 4'd0:seg_data <= 7'b100_0000; //0 4'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; //F default:seg_data <= 7'b111_1111; endcase end end endmodule
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) begin if(1'b0 == nRst) begin scan_counter <= 32'd0; Tscan_sel <= 4'd0; end else if(scan_counter >= SCAN_COUNT) begin scan_counter <= 32'd0; if(Tscan_sel == 4'd5) Tscan_sel <= 4'd0; else Tscan_sel <= Tscan_sel+4'd1; end else begin scan_counter <= scan_counter+32'd10; end end //类似于译码器 always@(posedge Clk or negedge nRst) begin if(1'b0 == nRst) Tseg_sel <= 6'b111111; //共阳极,位选端 else begin case(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; endcase end end endmodule
//计数器模块 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) begin if(1'b0 == CntnRst) begin rDout <= {BIT {1'b0}}; rCout <= 1'b0; end else begin if(rDout < N-1) begin //rDout <= rDout+{BIT {1'b1}}; rDout <= rDout+1; rCout <= 1'b0; end else begin rDout <= {BIT {1'b0}}; rCout <= 1'b1; end end end endmodule
//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) begin if(1'b0 == CntnRst) begin rHDout <= {BIT/2 {1'b0}}; rLDout <= {BIT/2 {1'b0}}; rCout <= 1'b0; end else begin //23 if({rHDout,rLDout} < 8'b0010_0011) //正常 //if({rHDout,rLDout} <= 8'd23) //错误代码 begin if(rLDout < 9) begin rLDout <= rLDout+1; rCout <= 1'b0; end else begin rLDout <= {BIT/2 {1'b0}}; if(rHDout < 9) begin rHDout <= rHDout+1; rCout <= 1'b0; end else begin rHDout <= {BIT/2 {1'b0}}; rCout <= 1'b0; end end end else begin rHDout <= {BIT/2 {1'b0}}; rLDout <= {BIT/2 {1'b0}}; rCout <= 1'b1; end end end endmodule
####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) begin if(1'b0 == DivnRst) begin rDivClk <= {N {1'b0}}; rPreNum <= {N {1'b0}}; rCnt <= {N {1'b0}}; end else begin //防止从高分频系数到低分频系数变化时需等待1周期 if(rPreNum == PreNum) begin rCnt <= rCnt+1; if(rCnt == PreNum) begin rDivClk <= (~rDivClk); rCnt <= {N {1'b0}}; end else rDivClk <= rDivClk; end else begin rPreNum <= PreNum; rCnt <= {N {1'b0}}; end end end endmodule
/*====================================================================== 实验内容: 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: E1 NRST: N13 SEG_SEL[0-5]: N9,P9,M10,N11,P11,M11 SEG_DATA[0-7]: R14,N16,P16,T15,P15,N12,N15,R16 KEYTSET: M15 KEYMADD: M16 KEYHADD: E16 LED_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) begin case(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) begin if(1'b0 == NRST) IsTimeSetState <= 1'b0; //防止初次启动不计数 else begin if(wKeyBdSet) begin IsTimeSetState <= ~IsTimeSetState; end else IsTimeSetState <= IsTimeSetState; end end always@(IsTimeSetState) begin //(1'b1 == IsTimeSetState)//问题代码 if((1'b1 == IsTimeSetState) && (wKeyBdHadd == 1'b0 || wKeyBdMadd == 1'b0)) //正常 begin rHorDrivClk <= wKeyBdHadd; rMinDrivClk <= wKeyBdMadd; rSecDrivClk <= 1'b0; end else if(1'b0 == IsTimeSetState) begin rHorDrivClk <= wMinCntShiCy; rMinDrivClk <= wSecCntShiCy; rSecDrivClk <= wClk1HZ; end end //LED驱动模块 always@(posedge wClk1HZ or negedge NRST) begin if(1'b0 == NRST) LED_OUT <= 4'b0000; else begin //if((wMinCntGe == 9) && (wMinCntShi == 1)) //测试用 if((wMinCntGe == 9) && (wMinCntShi == 5)) begin if(wSecCntShi == 5) begin if(wSecCntGe == 6) LED_OUT <= 4'b0001; else LED_OUT <= (LED_OUT<<1); end else LED_OUT <= 4'b0000; end else LED_OUT <= 4'b0000; end end //蜂鸣器驱动模块 always@(posedge CLK) begin if((wMinCntGe == 9) && (wMinCntShi == 5)) begin if(wSecCntShi == 5) begin case(wSecCntGe) 4'd5:rBUZ_OUT <= wClk500HZ; 4'd7:rBUZ_OUT <= wClk500HZ; //低音 4'd9:rBUZ_OUT <= wClk2kHZ; //高音 default:rBUZ_OUT <= 1'b1; endcase end else rBUZ_OUT <= 1'b1; end else 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) ); endmodule
设计具体实现思路是:通过分频器模块将50Mhz的输入时钟分别分为:
wClk1HZ(1HZ)给时分秒计数器
wClk2kHZ(2KHZ)驱动蜂鸣器发出高音
wClk500HZ(500HZ)驱动蜂鸣器发出低音
然后通过wClk1HZ驱动四个计数器级联实现分和秒的功能,使用两位24进制计数器实现时的功能。利用快速扫描和视觉残留效果实现6个数码管的同时显示(seg_scaner模块),在通过多路选择器(顶层文件中的case语句)实现不同数码管显示不同的数值。
对于时间设置的功能,设置按键按下,将分钟个位和小时个位的驱动时钟源换为按键按下抬起过程中产生的上升沿驱动,当设置按键再次按下时,时钟源再次切换为经过分频器分频后的1HZ时钟,时间加按键失效。
LED和蜂鸣器功能实现的方法差不多,通过判断秒数个位来进行不同的操作,LED模块通过移位实现不同灯的点亮,蜂鸣器硬件上采用的是有源蜂鸣器(接上高电平就叫),通过切换不同频率的驱动时钟源,实现不同声调的声音输出。
因为刚开始学FPGA 和verilogHDL语言,所以代码在资源节约利用和规范性上有所欠缺,但是基本的功能都已实现;按照惯例,每次新学一个东西总是想发个博客记录一下。各位参考着看,若是有些可以改进之处也欢迎大家讨论。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。