Verilog语言fpga小脚丫数字时钟(整点报时,调时,显示秒钟等功能)

本文主要是介绍Verilog语言fpga小脚丫数字时钟(整点报时,调时,显示秒钟等功能),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

学弟加油!                                                                       ———来自科大焯人

最近刚好学习了数电有关知识,就做了这个项目(闹钟过于繁琐就没有做了)

希望给还在学习的大伙一点参考,完整代码在最后

在这里先附上两串代码分别是debounce(按键消抖)和divide(分频)

这两个在小脚丫的示例中都可以找到,但我还是先附在这

//按键消抖
module debounce (clk,rst,key,key_pulse);parameter       N  =  1;                      //要消除的按键的数量input             clk;input             rst;input 	[N-1:0]   key;                        //输入的按键					output  [N-1:0]   key_pulse;                  //按键动作产生的脉冲	reg     [N-1:0]   key_rst_pre;                //定义一个寄存器型变量存储上一个触发时的按键值reg     [N-1:0]   key_rst;                    //定义一个寄存器变量储存储当前时刻触发的按键值wire    [N-1:0]   key_edge;                   //检测到按键由高到低变化是产生一个高脉冲//利用非阻塞赋值特点,将两个时钟触发时按键状态存储在两个寄存器变量中always @(posedge clk  or  negedge rst)beginif (!rst) beginkey_rst <= {N{1'b1}};                //初始化时给key_rst赋值全为1,{}中表示N个1key_rst_pre <= {N{1'b1}};endelse beginkey_rst <= key;                     //第一个时钟上升沿触发之后key的值赋给key_rst,同时key_rst的值赋给key_rst_prekey_rst_pre <= key_rst;             //非阻塞赋值。相当于经过两个时钟触发,key_rst存储的是当前时刻key的值,key_rst_pre存储的是前一个时钟的key的值end    endassign  key_edge = key_rst_pre & (~key_rst);//脉冲边沿检测。当key检测到下降沿时,key_edge产生一个时钟周期的高电平reg	[17:0]	  cnt;                       //产生延时所用的计数器,系统时钟12MHz,要延时20ms左右时间,至少需要18位计数器     //产生20ms延时,当检测到key_edge有效是计数器清零开始计数always @(posedge clk or negedge rst)beginif(!rst)cnt <= 18'h0;else if(key_edge)cnt <= 18'h0;elsecnt <= cnt + 1'h1;end  reg     [N-1:0]   key_sec_pre;                //延时后检测电平寄存器变量reg     [N-1:0]   key_sec;                    //延时后检测key,如果按键状态变低产生一个时钟的高脉冲。如果按键状态是高的话说明按键无效always @(posedge clk  or  negedge rst)beginif (!rst) key_sec <= {N{1'b1}};                else if (cnt==18'h3ffff)key_sec <= key;  endalways @(posedge clk  or  negedge rst)beginif (!rst)key_sec_pre <= {N{1'b1}};else                   key_sec_pre <= key_sec;             end      assign  key_pulse = key_sec_pre & (~key_sec);     endmodule
//分频
module divide (	clk,rst_n,clkout);input 	clk,rst_n;                       //输入信号,其中clk连接到FPGA的C1脚,频率为12MHzoutput	clkout;                          //输出信号,可以连接到LED观察分频的时钟//parameter是verilog里常数语句parameter	WIDTH	= 24;             //计数器的位数,计数的最大值为 2**WIDTH-1parameter	N	= 12_000_000;             //分频系数,请确保 N < 2**WIDTH-1,否则计数会溢出reg 	[WIDTH-1:0]	cnt_p,cnt_n;     //cnt_p为上升沿触发时的计数器,cnt_n为下降沿触发时的计数器reg			clk_p,clk_n;     //clk_p为上升沿触发时分频时钟,clk_n为下降沿触发时分频时钟//上升沿触发时计数器的控制always @ (posedge clk or negedge rst_n )         //posedge和negedge是verilog表示信号上升沿和下降沿//当clk上升沿来临或者rst_n变低的时候执行一次always里的语句beginif(!rst_n)cnt_p<=0;else if (cnt_p==(N-1))cnt_p<=0;else cnt_p<=cnt_p+1;             //计数器一直计数,当计数到N-1的时候清零,这是一个模N的计数器end//上升沿触发的分频时钟输出,如果N为奇数得到的时钟占空比不是50%;如果N为偶数得到的时钟占空比为50%always @ (posedge clk or negedge rst_n)beginif(!rst_n)clk_p<=0;else if (cnt_p<(N>>1))          //N>>1表示右移一位,相当于除以2去掉余数clk_p<=0;else clk_p<=1;               //得到的分频时钟正周期比负周期多一个clk时钟end//下降沿触发时计数器的控制        	always @ (negedge clk or negedge rst_n)beginif(!rst_n)cnt_n<=0;else if (cnt_n==(N-1))cnt_n<=0;else cnt_n<=cnt_n+1;end//下降沿触发的分频时钟输出,和clk_p相差半个时钟always @ (negedge clk)beginif(!rst_n)clk_n<=0;else if (cnt_n<(N>>1))  clk_n<=0;else clk_n<=1;                //得到的分频时钟正周期比负周期多一个clk时钟endassign clkout = (N==1)?clk:(N[0])?(clk_p&clk_n):clk_p;      //条件判断表达式//当N=1时,直接输出clk//当N为偶数也就是N的最低位为0,N(0)=0,输出clk_p//当N为奇数也就是N最低位为1,N(0)=1,输出clk_p&clk_n。正周期多所以是相与
endmodule 

将上述两个代码文件准备好就可以开始编写我们的主体程序了。

既然是数字时钟,首先我们应当通过分频器产生1s中的信号,代码中的clk1h是我用分频器产生的1Hz信号。每经过1Hz的上升沿秒钟进一位,在秒钟个位为9且十位不为5时,下一次进位个位要变为0,十位要加1,若是59秒时则应当都置零。同时,分钟的计时和时钟的计时实现思路与秒钟计时类似,但需要额外进行判断,比如说分钟进位要在秒钟均为0时才产生进位,时钟进位要在分钟秒钟全为0时才进位。

时钟和分钟的敏感信号为clk,这是小脚丫自带的时钟信号12MHz。由于clk频率极高,在1s中内能够触发分钟和时钟计时模块多次,为实现分钟和时钟在进位时仅进位一次,我额外增加了两个判断位,分别为pan(分钟)和pan1(时钟)。在pan和pan1为0时才能分别触发分钟和时钟进位,并且触发一次进位后pan或pan1置1,直到秒钟或者分钟不全为0时pan或pan1才会重新置0。

具体代码如下

//60秒计时控制always @ (posedge clk1h ) beginif(cnt_shi==5 && cnt_ge==9) begincnt_shi <= 0;cnt_ge <= 0;endelse if(cnt_shi==0 && cnt_ge==0) begincnt_shi <= 0;cnt_ge <= 1;endelse if(cnt_ge==9)begincnt_ge <= 0;cnt_shi <= cnt_shi+1;endelsecnt_ge <= cnt_ge +1;end
//60分计时控制always @ (posedge clk)beginif((cnt_ge==0)&&(cnt_shi==0)&&(pan==0))beginpan<=1;if(minute_shi==5 && minute_ge==9) beginminute_shi <= 0;minute_ge <= 0;endelse if(minute_ge==9)beginminute_ge <= 4'd0;minute_shi <= minute_shi+1;endelseminute_ge <= minute_ge +1;endelseif((cnt_ge!=0) || (cnt_shi!=0))beginpan<=0;end
//24小时计时与加减法模块always @ (posedge clk ) beginif ((minute_ge==0)&&(minute_shi==0)&&(pan1==0)&&(cnt_shi==0)&&(cnt_ge==0)) beginpan1<=1;if(hour_shi==2 && hour_ge==3) beginhour_shi <= 0;hour_ge <= 0;endelse if(hour_ge==9)beginhour_ge <= 4'd0;hour_shi <= hour_shi+1;endelsehour_ge <= hour_ge +1;endelseif((minute_ge!=0) || (minute_shi!=0))beginpan1<=0;end

至此,我们已经基本实现了数字时钟的运行,但还需要将时钟信息显示在数码管上。

我将按键触发的信号位记作change和change2。当change2为高电平且change为低电平时,数码管显示分钟;当change2为高电平且change为高电平时,数码管显示时钟;而当change2为低电平时,不论change电平,均显示秒钟。

//数码管显示数字seg[0] = 7'h3f;	   //  0seg[1] = 7'h06;	   //  1seg[2] = 7'h5b;	   //  2seg[3] = 7'h4f;	   //  3seg[4] = 7'h66;	   //  4seg[5] = 7'h6d;	   //  5seg[6] = 7'h7d;	   //  6seg[7] = 7'h07;	   //  7seg[8] = 7'h7f;	   //  8seg[9] = 7'h6f;	   //  9//选择显示always @ (posedge clk)beginif((change==0)&&(change2==1))beginseg_led_1<= seg[hour_ge];seg_led_2<= seg[hour_shi];endelse if((change==1)&&(change2==1))beginseg_led_1<= seg[minute_ge];seg_led_2<= seg[minute_shi];endelse if(change2==0)beginseg_led_1<= seg[cnt_ge];seg_led_2<= seg[cnt_shi];endend

所有基础功能都已经实现,现在还需要进阶,也就是调时和整点报时的实现。

我们先讲调时的实现

我在实现加减法的时候均用了两个计数器add、add1和jian、jian1。

add和jian分别为按键按下次数的计数器,而add1和jian1则分别为add和jian的匹配计数器。

当add1不等于add时,将时钟进一位,并且add1+1;当jian1不等于jian时,将时钟退一位,再将jian1+1,便可以实现调时的功能。

我在写代码的时候是将调时模块整合在计时模块中的,时钟调时与分钟调时的原理是相同的。

        if((add1!=add)&&(change==1)) beginadd1<=add1+1;if(minute_shi==5 && minute_ge==9) beginminute_shi <= 0;minute_ge <= 0;endelse if(minute_ge==9)beginminute_ge <= 4'd0;minute_shi <= minute_shi+1;endelseminute_ge <= minute_ge +1;endif((jian1!=jian)&&(change==1))beginjian1<=jian1+1;if(minute_shi==0 && minute_ge==0)beginminute_ge<=9;minute_shi<=5;endelse if(minute_ge==0)beginminute_ge<=9;minute_shi<=minute_shi-1;endelseminute_ge<=minute_ge-1;end

最后便是整点报时的实现

整点报时需要让灯光按照1Hz的频率闪烁,那么我们便可以用一个2Hz的时钟信号作为敏感信号来触发灯光闪烁。

首先只有在秒钟分钟均为0时才算作整点,在这个条件下我又用到了两个计数器cnt、cnt1与加减法的计数器功能一致,cnt1为当前整点数的两倍(因为一亮一灭需要灯光反转两次),cnt为匹配计数器,当cnt不等于cnt1时灯光反转一次,并且cnt+1。具体代码实现如下。

 //整点报时always @(posedge clk0)beginled=~led;if((minute_shi==0)&&(minute_ge==0)&&(cnt_shi==0)&&(cnt_ge==0))begincnt1=2*(10*hour_shi+hour_ge);endif(cnt!=cnt1)beginrgb=~rgb;cnt<=cnt+1;endelse if(cnt==cnt1)begincnt<=0;cnt1<=0;endend

以上就是整个数字时钟的设计,最后附上数字时钟的全部代码(记得把文首的debounce和devide两个代码也装上,不然数字时钟代码无法成功编译)

module counter
(clk				,    //时钟rst				,    //复位plus            ,    //加法为cut             ,    //减法位change			,    //显示转化按键1change2         ,    //显示转化按键2seg_led_1		,    //数码管1seg_led_2		,    //数码管2rgb             ,    //rgb灯光led                  //led
);input 	clk,rst;input	change;input   change2;input   plus,cut;output 	reg [8:0]	seg_led_1,seg_led_2;output 	reg	[7:0]	led;output  reg [5:0]   rgb;wire        clk0;         //0.5秒时钟wire		clk1h;        //1秒钟时钟wire		change_pulse; //转换按键消抖后信号wire        plus_pulse;   //加法按键消抖后信号wire        cut_pulse;    //减法按键消抖后信号reg			change_flag;  //转换按键标志位reg         plus_flag;    //加法按键标志位reg         cut_flag;     //减法按键标志位reg         pan;          //判断分钟进位reg         pan1;         //判断时钟进位     reg         add;          //分钟加法按键按下计数reg         add1;         //分钟加法按键匹配计数reg         add10;        //时钟加法按键按下计数     reg         add11;        //时钟加法按键匹配计数reg         jian10;       //时钟减法按键按下计数  reg         jian11;       //时钟减法按键匹配计数  reg         jian;         //分钟减法按键按下计数reg         jian1;        //分钟减法按键匹配计数reg         [5:0]   cnt=0;          //记录当前小时数reg         [5:0]   cnt1=0;         //小时匹配数reg   		[6:0]   seg		[9:0];  //数码管reg			[3:0]	cnt_ge;         //秒钟个位reg			[3:0]	cnt_shi;        //秒钟十位reg			[3:0]	minute_ge;      //分钟个位reg			[3:0]	minute_shi;     //分钟十位reg			[3:0]	hour_ge;        //小时个位reg			[3:0]	hour_shi;       //小时十位 initial beginseg[0] = 7'h3f;	   //  0seg[1] = 7'h06;	   //  1seg[2] = 7'h5b;	   //  2seg[3] = 7'h4f;	   //  3seg[4] = 7'h66;	   //  4seg[5] = 7'h6d;	   //  5seg[6] = 7'h7d;	   //  6seg[7] = 7'h07;	   //  7seg[8] = 7'h7f;	   //  8seg[9] = 7'h6f;	   //  9end// 启动/暂停按键进行消抖debounce  U2 (.clk(clk),.rst(rst),.key(change),.key_pulse(change_pulse));debounce  U6 (.clk(clk),.rst(rst),.key(plus),.key_pulse(plus_pulse));debounce  U7 (.clk(clk),.rst(rst),.key(cut),.key_pulse(cut_pulse));// 用于分出一个1Hz的频率	divide #(.WIDTH(32),.N(12000000)) U1 ( .clk(clk),.rst_n(rst),      .clkout(clk1h));// 用于分出一个2Hz的频率divide #(.WIDTH(32),.N(6000000)) U5 ( .clk(clk),.rst_n(rst),      .clkout(clk0));//按键动作标志信号产生always @ (posedge change_pulse)begin	if(!rst==1)change_flag <= 0;elsechange_flag <= ~change_flag;endalways @ (posedge plus_pulse)begin	if(!rst==1)plus_flag <= 0;elseplus_flag <= ~plus_flag;if(change==1)beginadd<=add+1;endelse if(change==0)beginadd10<=add10+1;endendalways @ (posedge cut_pulse)begin	if(!rst==1)cut_flag <= 0;elsecut_flag <= ~cut_flag;if(change==1)beginjian<=jian+1;endelse if(change==0)beginjian10<=jian10+1;endend//60秒计时控制always @ (posedge clk1h ) beginif(cnt_shi==5 && cnt_ge==9) begincnt_shi <= 0;cnt_ge <= 0;endelse if(cnt_shi==0 && cnt_ge==0) begincnt_shi <= 0;cnt_ge <= 1;endelse if(cnt_ge==9)begincnt_ge <= 0;cnt_shi <= cnt_shi+1;endelsecnt_ge <= cnt_ge +1;end//60分钟计时与加减法模块always @ (posedge clk)beginif((cnt_ge==0)&&(cnt_shi==0)&&(pan==0))beginpan<=1;if(minute_shi==5 && minute_ge==9) beginminute_shi <= 0;minute_ge <= 0;endelse if(minute_ge==9)beginminute_ge <= 4'd0;minute_shi <= minute_shi+1;endelseminute_ge <= minute_ge +1;endelseif((cnt_ge!=0) || (cnt_shi!=0))beginpan<=0;endif((add1!=add)&&(change==1)) beginadd1<=add1+1;if(minute_shi==5 && minute_ge==9) beginminute_shi <= 0;minute_ge <= 0;endelse if(minute_ge==9)beginminute_ge <= 4'd0;minute_shi <= minute_shi+1;endelseminute_ge <= minute_ge +1;endif((jian1!=jian)&&(change==1))beginjian1<=jian1+1;if(minute_shi==0 && minute_ge==0)beginminute_ge<=9;minute_shi<=5;endelse if(minute_ge==0)beginminute_ge<=9;minute_shi<=minute_shi-1;endelseminute_ge<=minute_ge-1;endend//24小时计时与加减法模块always @ (posedge clk ) beginif ((minute_ge==0)&&(minute_shi==0)&&(pan1==0)&&(cnt_shi==0)&&(cnt_ge==0)) beginpan1<=1;if(hour_shi==2 && hour_ge==3) beginhour_shi <= 0;hour_ge <= 0;endelse if(hour_ge==9)beginhour_ge <= 4'd0;hour_shi <= hour_shi+1;endelsehour_ge <= hour_ge +1;endelseif((minute_ge!=0) || (minute_shi!=0))beginpan1<=0;endif((add11!=add10)&&(change==0))beginadd11<=add11+1;if(hour_shi==2 && hour_ge==3) beginhour_shi <= 0;hour_ge <= 0;endelse if(hour_ge==9)beginhour_ge <= 4'd0;hour_shi <= hour_shi+1;endelsehour_ge <= hour_ge +1;endif((jian11!=jian10)&&(change==0))beginjian11<=jian11+1;if(hour_shi==0 && hour_ge==0)beginhour_ge<=3;hour_shi<=2;endelse if(hour_ge==0)beginhour_ge<=9;hour_shi<=hour_shi-1;endelsehour_ge<=hour_ge-1;endend//选择显示always @ (posedge clk)beginif((change==0)&&(change2==1))beginseg_led_1<= seg[hour_ge];seg_led_2<= seg[hour_shi];endelse if((change==1)&&(change2==1))beginseg_led_1<= seg[minute_ge];seg_led_2<= seg[minute_shi];endelse if(change2==0)beginseg_led_1<= seg[cnt_ge];seg_led_2<= seg[cnt_shi];endend//整点报时always @(posedge clk0)beginled=~led;if((minute_shi==0)&&(minute_ge==0)&&(cnt_shi==0)&&(cnt_ge==0))begincnt1=2*(10*hour_shi+hour_ge);endif(cnt!=cnt1)beginrgb=~rgb;cnt<=cnt+1;endelse if(cnt==cnt1)begincnt<=0;cnt1<=0;endendendmodule

管脚分配如下

 

有疑惑或者问题欢迎一起讨论

这篇关于Verilog语言fpga小脚丫数字时钟(整点报时,调时,显示秒钟等功能)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/388160

相关文章

从去中心化到智能化:Web3如何与AI共同塑造数字生态

在数字时代的演进中,Web3和人工智能(AI)正成为塑造未来互联网的两大核心力量。Web3的去中心化理念与AI的智能化技术,正相互交织,共同推动数字生态的变革。本文将探讨Web3与AI的融合如何改变数字世界,并展望这一新兴组合如何重塑我们的在线体验。 Web3的去中心化愿景 Web3代表了互联网的第三代发展,它基于去中心化的区块链技术,旨在创建一个开放、透明且用户主导的数字生态。不同于传统

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

usaco 1.2 Name That Number(数字字母转化)

巧妙的利用code[b[0]-'A'] 将字符ABC...Z转换为数字 需要注意的是重新开一个数组 c [ ] 存储字符串 应人为的在末尾附上 ‘ \ 0 ’ 详见代码: /*ID: who jayLANG: C++TASK: namenum*/#include<stdio.h>#include<string.h>int main(){FILE *fin = fopen (

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

Spring框架5 - 容器的扩展功能 (ApplicationContext)

private static ApplicationContext applicationContext;static {applicationContext = new ClassPathXmlApplicationContext("bean.xml");} BeanFactory的功能扩展类ApplicationContext进行深度的分析。ApplicationConext与 BeanF