FPGA学习—通过数码管实现电子秒表模拟

文章目录

  • 一、数码管简介
  • 二、项目分析
  • 三、项目源码及分析
  • 四、实现效果
  • 五、总结

一、数码管简介

请参阅博主以前写过的一篇电子时钟模拟,在此不再赘述。

https://blog.csdn.net/qq_54347584/article/details/130402287

二、项目分析

  • 项目说明:本次项目是为了通过数码管实现秒表模拟。其中,六位数码管分别显示秒表的分位,秒位,毫秒位(由于毫秒有三位,在此只取百位和十位),其中分位和秒位,秒位和毫秒位之间用小数点隔开
  • 本次项目拟设置四个模块,分别为:按键消抖模块,计数模块,数码管驱动模块,以及顶层模块
  • 按键消抖模块要求:传出两个按键的脉冲信号,一个用来暂停/开始秒表的计数,一个用来清空秒表的计数
  • 计数模块要求:能传出秒表的各位值以及小数点位置
  • 数码管驱动模块要求:能正常显示秒表各位值

三、项目源码及分析

数码管驱动模块:

代码分析:

  • 由于本开发板有六位数码管,每位数码管设置显示0-F,因此在此模块中,博主定义了一个24位的din信号(六位每位能显示十六个字符(因此每位需要四位位宽))以此来给每位数码管赋值
  • 同时博主将八段式数码管拆分为七段加一段小数点,因此设计了一个六位的point_n以此来控制小数点的显示
  • 其余代码与博主以前写的数码管动态显示0-F类似,在此不再赘述(如果是FPGA初学者,没有数码管开发经验,请仔细阅读博主动态显示电子时钟两篇博客!!)
/**************************************功能介绍***********************************
Date	: 2023-08-01 11:08:11
Author	: majiko
Version	: 1.0
Description: 动态数码管模块(动态扫描)
*********************************************************************************///---------<模块及端口声名>------------------------------------------------------
module seg_driver( input				clk		,input				rst_n	,input		[23:0]	din		,//输入6位数码管显示数据,每位数码管占4位input       [5:0]   point_n ,//输入小数点控制位output	reg	[5:0]	seg_sel	,//输出位选output	reg	[7:0]	seg_dig  //输出段选
);								 
//---------<参数定义>--------------------------------------------------------- parameter TIME_1MS = 50_000;//1ms//数码管显示字符编码localparam NUM_0 = 7'b100_0000,//0NUM_1 = 7'b111_1001,//1NUM_2 = 7'b010_0100,//NUM_3 = 7'b011_0000,//NUM_4 = 7'b001_1001,//NUM_5 = 7'b001_0010,//NUM_6 = 7'b000_0010,//NUM_7 = 7'b111_1000,//NUM_8 = 7'b000_0000,//NUM_9 = 7'b001_1000,//A     = 7'b000_1000,//B     = 7'b000_0011,//bC     = 7'b100_0110,//D     = 7'b010_0001,//dE     = 7'b000_1110,//E替换为FF     = 7'b011_1111,//F替换为--DIV   = 7'b011_1111;//---------<内部信号定义>-----------------------------------------------------reg			[15:0]	cnt_1ms	   	;//1ms计数器(扫描间隔计数器)wire				add_cnt_1ms	;wire				end_cnt_1ms	;reg         [3:0]   disp_data   ;//每一位数码管显示的数值reg                 point_n_r   ;//每一位数码管显示的小数点//****************************************************************
//--cnt_1ms
//****************************************************************always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt_1ms <= 'd0;end else if(add_cnt_1ms)begin if(end_cnt_1ms)begin cnt_1ms <= 'd0;endelse begin cnt_1ms <= cnt_1ms + 1'b1;end endend assign add_cnt_1ms = 1'b1;//数码管一直亮assign end_cnt_1ms = add_cnt_1ms && cnt_1ms == TIME_1MS - 1;//****************************************************************
//--seg_sel
//****************************************************************always @(posedge clk or negedge rst_n)begin if(!rst_n)beginseg_sel <= 6'b111_110;//循环移位实现时,需要给位选赋初值end else if(end_cnt_1ms)begin seg_sel <= {seg_sel[4:0],seg_sel[5]};//循环左移end end//****************************************************************
//--disp_data
//****************************************************************always @(posedge clk or negedge rst_n)begin if(!rst_n)begindisp_data <= 'd0;point_n_r <= 1'b1;end else begin case (seg_sel)6'b111_110 : begin disp_data <= din[3:0]  ; point_n_r <= point_n[0]; end//第一位数码管显示的数值6'b111_101 : begin disp_data <= din[7:4]  ; point_n_r <= point_n[1]; end6'b111_011 : begin disp_data <= din[11:8] ; point_n_r <= point_n[2]; end6'b110_111 : begin disp_data <= din[15:12]; point_n_r <= point_n[3]; end6'b101_111 : begin disp_data <= din[19:16]; point_n_r <= point_n[4]; end6'b011_111 : begin disp_data <= din[23:20]; point_n_r <= point_n[5]; enddefault: disp_data <= 'd0;endcaseend end//****************************************************************
//--seg_dig
//****************************************************************// always @(posedge clk or negedge rst_n)begin //     if(!rst_n)begin//         seg_dig <= 8'hff;//数码管的段选如何赋值好?//     end //     else begin //         case (disp_data)//             0 :  seg_dig <= {point_n_r,NUM_0};//             1 :  seg_dig <= {point_n_r,NUM_1};//             2 :  seg_dig <= {point_n_r,NUM_2};//             3 :  seg_dig <= {point_n_r,NUM_3};//             4 :  seg_dig <= {point_n_r,NUM_4};//             5 :  seg_dig <= {point_n_r,NUM_5};//             6 :  seg_dig <= {point_n_r,NUM_6};//             7 :  seg_dig <= {point_n_r,NUM_7};//             8 :  seg_dig <= {point_n_r,NUM_8};//             9 :  seg_dig <= {point_n_r,NUM_9};//             10 : seg_dig <= {point_n_r,A    };//             11 : seg_dig <= {point_n_r,B    };//             12 : seg_dig <= {point_n_r,C    };//             13 : seg_dig <= {point_n_r,D    };//             14 : seg_dig <= {point_n_r,E    };//             15 : seg_dig <= {point_n_r,F    };//             default: seg_dig <= 8'hff;//         endcase//     end // endalways @(*)begin case (disp_data)0 :  seg_dig <= {point_n_r,NUM_0};1 :  seg_dig <= {point_n_r,NUM_1};2 :  seg_dig <= {point_n_r,NUM_2};3 :  seg_dig <= {point_n_r,NUM_3};4 :  seg_dig <= {point_n_r,NUM_4};5 :  seg_dig <= {point_n_r,NUM_5};6 :  seg_dig <= {point_n_r,NUM_6};7 :  seg_dig <= {point_n_r,NUM_7};8 :  seg_dig <= {point_n_r,NUM_8};9 :  seg_dig <= {point_n_r,NUM_9};10 : seg_dig <= {point_n_r,A    };11 : seg_dig <= {point_n_r,B    };12 : seg_dig <= {point_n_r,C    };13 : seg_dig <= {point_n_r,D    };14 : seg_dig <= {point_n_r,E    };15 : seg_dig <= {point_n_r,F    };16 : seg_dig <= {point_n_r,DIV  };default: seg_dig <= 8'b1101_1111;endcaseendendmodule

计数器模块:

代码分析:

  • 由项目分析可以得出,由于数码管资源有限,毫秒位只能实现百位和十位的显示,因此我们不妨设置一个基准单位为10ms,每当计数到10ms时,毫秒计数器才进行加一(这样毫秒计数器只需加到100次即可)
  • 除此之外,秒位计数器和分位计数器的技术条件分别为毫秒计数器计满和秒位计数器计满
  • 由于本次项目需要引入按键信号进行秒表的暂停、继续以及清空,因此博主引入了两位按键信号。
  • 一位按键控制秒表的暂停与继续。由上述分析可知,如果想要暂停秒表的计数,我们只需暂停基准单位10ms的计数即可(其余三个计数器,均要在10ms计数器工作的前提下才能逐级工作),因此我们只需要引入一个中间信号flag,用flag作为其计数的条件即可(博主将flag初值设为0不计数,按键按下后flag反转为1开始计数,再次按下再次反转,停止计数)
  • 剩余一位按键用于四个计数器的清零,一旦四个计数器全部清零,传出的数码管数据自然为0
  • 在代码的最后博主将毫秒,秒,分计数器的值赋给dout,再将其传入数码管驱动模块即可(之所以是这个顺序是因为博主前面数码管驱动模块对位选信号赋值好像写反了,不过影响不大)
module counter (input   wire            clk         ,input   wire            rst_n       ,input   wire    [1:0]   key_in      ,//按键信号输入output  wire    [23:0]  dout        ,//数码管各位值输出output  wire    [5:0]   point_out    //小数点输出
);//内部参数定义
parameter TIME_10ms  = 19'd500_000;//10ms计数器,计满秒表毫秒位加1
parameter TIME_990ms = 7'd100     ;//计数器毫秒位,以10ms为单位
parameter TIME_1s    = 6'd60      ;//计数器秒位,计满清零
parameter TIME_1min  = 6'd60      ;//计数器分位,计满清零//内部信号定义
reg     [18:0]  cnt_10ms    ;//10毫秒计数器寄存器
reg     [6:0]   cnt_99ms    ;//毫秒位计数寄存器
reg     [5:0]   cnt_1s      ;//秒位计数器寄存器
reg     [5:0]   cnt_1min    ;//分位计数器寄存器wire    add_cnt_10ms        ;
wire    end_cnt_10ms        ;wire    add_cnt_990ms       ;
wire    end_cnt_990ms       ;wire    add_cnt_1s          ;
wire    end_cnt_1s          ;wire    add_cnt_1min        ;
wire    end_cnt_1min        ;reg     flag                ;//运行/暂停标志信号寄存器
reg     flag_0              ;//清零信号标志寄存器//10毫秒计数器
always @(posedge clk or negedge rst_n)beginif(!rst_n)begincnt_10ms <= 1'b0;endelse if(add_cnt_10ms)beginif(end_cnt_10ms)begincnt_10ms <= 1'b0;endelse begincnt_10ms <= cnt_10ms + 1'b1;endendelse begincnt_10ms <= cnt_10ms;end
endassign add_cnt_10ms = 1'b1 && flag;//运行标志位有效
assign end_cnt_10ms = (add_cnt_10ms && cnt_10ms == TIME_10ms - 1'b1) || key_in[1];//按下清零按键也会清零//毫秒位计数器
always@(posedge clk or negedge rst_n)beginif(!rst_n)begincnt_99ms <= 1'b0;endelse if(add_cnt_990ms)beginif(end_cnt_990ms)begincnt_99ms <= 1'b0;endelse begincnt_99ms <= cnt_99ms + 1'b1;endendelse begincnt_99ms <= cnt_99ms;end
endassign add_cnt_990ms = end_cnt_10ms;
assign end_cnt_990ms = (add_cnt_990ms && cnt_99ms == TIME_990ms - 1'b1) || key_in[1];//秒位计数器
always@(posedge clk or negedge rst_n)beginif(!rst_n)begincnt_1s <= 1'b0;endelse if(add_cnt_1s)beginif(end_cnt_1s)begincnt_1s <= 1'b0;endelse begincnt_1s <= cnt_1s + 1'b1;endendelse begincnt_1s <= cnt_1s;end
endassign add_cnt_1s = end_cnt_990ms;
assign end_cnt_1s = (add_cnt_1s && cnt_1s == TIME_1s - 1'b1) || key_in[1];//分位计数器
always@(posedge clk or negedge rst_n)beginif(!rst_n)begincnt_1min <= 1'b0;endelse if(add_cnt_1min)beginif(end_cnt_1min)begincnt_1min <= 1'b0;endelse begincnt_1min <= cnt_1min + 1'b1;endendelse begincnt_1min <= cnt_1min;end
endassign add_cnt_1min = end_cnt_1s;
assign end_cnt_1min = (add_cnt_1min && cnt_1min == TIME_1min - 1'b1) || key_in[1];//flag信号控制秒表运行/暂停
always@(posedge clk or negedge rst_n)beginif(!rst_n)beginflag <= 1'b0;endelse if(key_in[0])beginflag <= ~flag;endelse beginflag <= flag;end
end//输出值赋值
assign dout[23:20] = cnt_99ms % 10;
assign dout[19:16] = cnt_99ms / 10;
assign dout[15:12] = cnt_1s   % 10;
assign dout[11:8]  = cnt_1s   / 10;
assign dout[7:4]   = cnt_1min % 10;
assign dout[3:0]   = cnt_1min / 10;
assign point_out   = 6'b110_101   ;endmodule

按键消抖模块:

代码分析:请详细参阅博主所写的按键消抖模块博文,在此不再赘述

module key_filter#(parameter WIDTH = 2)//参数化按键位宽
(input       wire            clk     ,input       wire            rst_n   ,input       wire  [WIDTH - 1:0]     key_in  ,//按键输入信号output      reg   [WIDTH - 1:0]     key_out  //输出稳定的脉冲信号
);parameter MAX = 20'd1_000_000;reg     [19:0]                  cnt_delay       ; //20ms延时计数寄存器
wire                            add_cnt_delay   ; //开始计数的标志
wire                            end_cnt_delay   ; //结束计数的标志reg     [WIDTH - 1:0]           key_r0          ; //同步
reg     [WIDTH - 1:0]           key_r1          ; //打一拍
reg     [WIDTH - 1:0]           key_r2          ; //打两拍wire    [WIDTH - 1:0]           nedge           ; //下降沿寄存器//同步打拍
always @(posedge clk or negedge rst_n) beginif(!rst_n)beginkey_r0 <= {WIDTH{1'b1}};key_r1 <= {WIDTH{1'b1}};key_r2 <= {WIDTH{1'b1}};endelse beginkey_r0 <= key_in; //同步key_r1 <= key_r0; //寄存一拍key_r2 <= key_r1; //寄存两拍end
end//20ms计数器
always @(posedge clk or negedge rst_n)beginif(!rst_n)begincnt_delay <= 1'b0;endelse if(add_cnt_delay )beginif(nedge)begin //检测到下降沿从0开始计数cnt_delay <= 1'b0;endelse if(cnt_delay == MAX - 1'b1)begincnt_delay <= cnt_delay; //计数计满结束后保持,避免产生多个输出脉冲endelse begincnt_delay <= cnt_delay + 1'b1;endendelse begincnt_delay <= 1'b0;end
endassign nedge = ~key_r1 & key_r2; //下降沿检测
assign add_cnt_delay = 1'b1; 
assign end_cnt_delay = add_cnt_delay && cnt_delay == MAX - 1'b1;//key_out脉冲信号赋值
always@(posedge clk or negedge rst_n)beginif(!rst_n)beginkey_out <= 'd0;endelse if(cnt_delay == MAX - 2'd2)begin //计数计满前一个脉冲时产生按键脉冲key_out <= ~key_in;endelse beginkey_out <= 'd0;end
endendmodule

顶层模块:

/****
项目说明:本次项目是为了通过数码管实现秒表模拟。其中,六位数码管分别显示秒表的分位,秒位,毫秒位(百位和十位)。本次项目拟设置四个模块,分别为:按键消抖模块,计数模块,数码管驱动模块,以及顶层模块。本项目按键消抖模块要求传出两个按键的脉冲信号,一个用来暂停/开始秒表的计数,一个用来清空秒表的计数。本项目计数模块要求能传出秒表的各位值以及小数点位置本项目数码管驱动模块要求能正常显示秒表各位值
****/
module top(input   wire            clk     ,input   wire            rst_n   ,input   wire    [1:0]   key_in  ,output  wire    [5:0]   sel     ,output  wire    [7:0]   seg    
);wire       [1:0]    key_out ;
wire	   [23:0]	din		;
wire       [5:0]    point_n ;seg_driver u_seg_driver( .clk		(clk	),.rst_n	    (rst_n	),.din		(din	),.point_n    (point_n),.seg_sel	(sel    ),.seg_dig    (seg    )
);key_filter u_key_filter(.clk     (clk    ),.rst_n   (rst_n  ),.key_in  (key_in ),.key_out (key_out)
);counter u_counter(.clk         (clk         ),.rst_n       (rst_n       ),.key_in      (key_out     ),.dout        (din         ),.point_out   (point_n     ) 
);endmodule

四、实现效果

在这里插入图片描述

五、总结

本项目主要锻炼了FPGA的数码管开发和计数器编写,实际仍未FPGA学习基础,博主学习时常常被数码管位选信号的段选信号的赋值绕晕,解决方法也只有自己亲自编写几遍,否则仍然无法理解余晖效应和动态扫面是如何让数码管同时显示不同字符的。后续对于基础部分的学习博主应该还会写几篇关于蜂鸣器的博文,至此算是基础语法学习结束。

再往后博主学习到IP核HLS,通讯协议时,也许会继续编写博客,但也要看博主是否有足够的精力,以及能否自己理解并讲解清楚,否则话的还是请各位自行上网寻找视频教学资料,不管是野火还是正点原子,都有针对的FPGA学习视频。

学海无涯,大家有缘再见。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/53854.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

51单片机学习--串口通信

首先需要配置寄存器&#xff1a; 下面这里SCON配0x40和0x50都可以&#xff0c;因为暂时还不需要接受信息&#xff0c;所以REN置1置0都可 void Uart_Init(void) //4800bps11.0592MHz {PCON | 0x80; //使能波特率倍速位SMODSCON 0x50; //8位数据,可变波特率TMOD & 0x0F…

高通滤波器,低通滤波器

1.高通滤波器是根据像素与邻近像素的亮度差值来提升该像素的亮度。 import cv2 import numpy as np from scipy import ndimagekernel_3_3 np.array([[-1,-1,-1],[-1,8,-1],[-1,-1,-1]]) print(kernel_3_3) kernel_5_5 np.array([[-1,-1,-1,-1,-1],[-1,1,2,1,-1],[-1,2,4,2,-…

【Markdown入门及使用】

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

Simulink仿真模块 - Pulse Generator

目录 说明 基于时间的模式 基于样本的模式 实例 模块特性 Pulse Generator是按固定间隔生成方波脉冲。 在仿真库中的位置为:Simulink / Source模型为: 说明 Pulse Ge

使用ubuntu-base制作根文件系统

1&#xff1a;ubuntu官网下载最小根文件系统&#xff1a; 放置到电脑的ubuntu中&#xff0c; Mkdir Ubuntu_rootfs Cd Ubuntu_rootfs Sudo tar –zxvf Ubuntu-bash-xxxxxx.tar.gz 2&#xff1a;电脑的ubuntu安装qemu搭建arm模拟系统 将/usr/bin/qemu-arm-static/(64位拷贝…

机器学习笔记之优化算法(五)线搜索方法(步长角度;非精确搜索;Armijo Condition)

机器学习笔记之优化算法——线搜索方法[步长角度&#xff0c;非精确搜索&#xff0c;Armijo Condition] 引言回顾&#xff1a;关于 f ( x k 1 ) ϕ ( α ) f(x_{k1}) \phi(\alpha) f(xk1​)ϕ(α)的一些特性非精确搜索近似求解最优步长的条件 Armijo Condition \text{Armijo…

mongodb-win32-x86_64-2008plus-3.4.24-signed.msi

Microsoft Windows [版本 6.1.7601] 版权所有 (c) 2009 Microsoft Corporation。保留所有权利。C:\Users\Administrator>cd C:\MongoDB\Server\3.4\binC:\MongoDB\Server\3.4\bin>C:\MongoDB\Server\3.4\bin>mongod --help Options:General options:-h [ --help ] …

SQLI_LABS攻击

目录 报错的处理方式 Less1 首先来爆字段 联合注入 判断注入点 爆数据库名 爆破表名 information_schema information_schmea.tables group_concat() 爆破列名 information_schema.columns 爆值 SQLMAP Less-2 -4 Less -5 布尔 数据库 表名 字段名 爆破值 …

python爬虫 获取简单的get请求

打印结果&#xff1a; 原博主写的很厉害额&#xff0c;写的比较全面&#xff0c;大家可以去学习看看 参考原文&#xff1a; Python调用get或post请求外部接口_python调用post接口_纯洁的小魔鬼的博客-CSDN博客

19、springboot引用配置属性或引用生成文件的属性或引用随机值

引用配置属性或引用生成文件的属性或引用随机值 ★ 使用占位符引用其他配置属性&#xff1a; 配置文件中可用${}占位符引用已有的属性&#xff0c;被引用的属性可以是&#xff1a; - 已定义的属性。 - 来自其他配置源&#xff08;比如命令行的选项参数、环境变量、系统属性等…

devops-发布vue前端项目

回到目录 将使用jenkinsk8s发布前端项目 1 环境准备 node环境 在部署jenkins的服务器上搭建node环境 node版本 # 1.拉取 https://nodejs.org/download/release/v20.4.0/node-v20.4.0-linux-x64.tar.gz# 2.解压到/usr/local目录下 sudo tar xf v20.4.0.tar.gz -C /usr/loc…

No111.精选前端面试题,享受每天的挑战和学习

文章目录 map和foreach的区别在组件中如何获取vuex的action对象中的属性怎么去获取封装在vuex的某个接口数据有没有抓包过&#xff1f;你如何跟踪某一个特定的请求&#xff1f;比如一个特定的URL&#xff0c;你如何把有关这部分的url数据提取出来&#xff1f;1. 使用网络抓包工…