当前位置: 首页 > news >正文

FPGA开发——按键控制数码管的设计

一、概述

按键控制数码管是一种常见的电子显示技术,它结合了按键输入与数码管显示的功能。在这一设计中,用户通过按下不同的按键来发送指令,这些指令随后被处理并转换为数码管上显示的数字或字符。按键通常作为输入设备,通过电路连接到微控制器(如FPGA、单片机等)的输入引脚,而数码管则作为输出设备,其显示内容由微控制器控制。

按键控制数码管的设计可以包括以下几个步骤:

  1. 按键输入处理:微控制器不断扫描按键的状态,当检测到按键被按下时,根据按键的编号或功能执行相应的操作。

  2. 指令转换:将按键的输入转换为数码管能够理解的显示指令。这通常涉及将按键编号或功能映射到特定的数字或字符编码。

  3. 数码管显示控制:微控制器根据转换后的显示指令,通过控制数码管的驱动电路来点亮或熄灭数码管中的不同段,从而显示出所需的数字或字符。

  4. 循环扫描与更新:为了保持数码管显示内容的实时性,微控制器需要不断重复上述步骤,形成一个循环扫描和更新的过程。

二、工程实现

1、基本思路构建

在本篇文章中我们以按键控制单个数码管为例讲解通过按键改变数码管显示的值的设计,基本思路如下:

在默认情况下数码管正常显示,当按下按键1时,数码管从正常显示的状态跳转到设置的状态,接着通过按键二队数码管显示的值进行一个改变,最后在按下按键1使得数码管从设置状态有重新回到正常显示状态,如此往复设计。

2、设计文件的编写

新建一个seg0.v文件

module seg0(input  clk,input  rst_n,input  [1:0] key_in,input  seg_sel,//位选output reg [7:0] seg_dual//段选
);
localparam  ZERO  = 8'b1100_0000, //共阳极段码ONE   = 8'b1111_1001,TWO   = 8'b1010_0100,THREE = 8'b1011_0000,FOUR  = 8'b1001_1001,FIVE  = 8'b1001_0010,SIX   = 8'b1000_0010,SEVEN = 8'b1111_1000,EIGHT = 8'b1000_0000,NINE  = 8'b1001_0000,A     = 8'b1000_1000,b     = 8'b1000_0011,c     = 8'b1100_0110,d     = 8'b1010_0001,E     = 8'b1000_0110,f     = 8'b1000_1110;
reg [26:0] cnt;
wire       add_cnt;
wire       end_cnt;
reg     [4:0]   flag;
wire            add_flag;
wire            end_flag;reg    seg_flag;//数码管状态切换标志位
always @(posedge clk or negedge rst_n)beginif(!rst_n)cnt<=0;else if(add_cnt)beginif(end_cnt)cnt<=0;elsecnt<=cnt+1'b1;end
end
assign add_cnt=1'b1;
assign end_cnt=add_cnt && (cnt==50_000_000-1);always @(posedge clk or negedge rst_n)beginif(!rst_n)flag<=0;else if(seg_flag==1 && key_in[1])flag<=flag+1'b1;else if(add_flag)beginif(end_flag)flag<=0;elseflag<=flag+1'b1;end
end
assign add_flag=end_cnt;
assign end_flag=add_flag && (flag==16-1);always @(posedge clk or negedge rst_n)beginif(!rst_n)seg_flag<=0;else if(key_in[0])seg_flag<=~seg_flag;else seg_flag<=seg_flag;
endalways @(posedge clk or negedge rst_n)beginif(!rst_n)seg_dual<=8'b1111_1111;else if(seg_flag==0)begincase (flag)4'd0:seg_dual <=ZERO ;4'd1:seg_dual <=ONE  ;4'd2:seg_dual <=TWO  ;4'd3:seg_dual <=THREE; 4'd4:seg_dual <=FOUR ;4'd5:seg_dual <=FIVE ;4'd6:seg_dual <=SIX  ;4'd7:seg_dual <=SEVEN;4'd8:seg_dual <=EIGHT;4'd9:seg_dual <=NINE ;4'd10:seg_dual<=A    ;4'd11:seg_dual<=b    ;4'd12:seg_dual<=c    ;4'd13:seg_dual<=d    ;4'd14:seg_dual<=E    ;4'd15:seg_dual<=f    ;default: ;endcaseend 
end
endmodule 

新建一个key.v文件

//状态机实现
module key (input           clk     ,input           rst_n   ,input        [1:0]   key_in  ,   //输入原始的按键信号output  reg  [1:0]   key_out     //输出处理之后的按键信号
);
//参数定义localparam  IDLE        = 4'b0001,//空闲JITTLE0     = 4'b0010,//滤除第一次抖动DOWN        = 4'b0100,//稳定JITTLE1     = 4'b1000;//滤除第二次抖动parameter   TIME_20MS = 1_000_000;//需要计数的值,20ms//内部信号reg  [3:0]   state_c;//现态reg  [3:0]   state_n;//次态reg  [19:0] cnt_20ms;//计数20mswire        add_cnt_20ms;wire        end_cnt_20ms;wire  [1:0]      nedge   ;//下降沿信号wire  [1:0]      pedge   ;//上升沿信号reg   [1:0]      key_in_r1  ;//打两拍  同步打拍reg   [1:0]      key_in_r2  ;//状态转移 同步时序逻辑描述状态转移always @(posedge clk or negedge rst_n) beginif(!rst_n)state_c <= IDLE;elsestate_c <= state_n;end//状态转移条件 组合逻辑always @(*) begincase (state_c)//一定是case  现态IDLE:beginif(nedge)state_n = JITTLE0;elsestate_n = state_c;endJITTLE0:beginif(end_cnt_20ms)state_n = DOWN;elsestate_n = state_c; endDOWN:beginif(pedge)state_n = JITTLE1;elsestate_n = state_c;endJITTLE1  :beginif(end_cnt_20ms)state_n = IDLE;elsestate_n = state_c;  end   default: state_n = IDLE;endcaseend//20ms计数器
always @(posedge clk or negedge rst_n) beginif(!rst_n)cnt_20ms <= 0;else if(add_cnt_20ms)beginif(end_cnt_20ms)cnt_20ms <= 0;elsecnt_20ms <= cnt_20ms + 1;end
endassign add_cnt_20ms = (state_c == JITTLE0) || (state_c == JITTLE1);
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == TIME_20MS - 1;//下降沿  上升沿
//同步  打拍
always @(posedge clk or negedge rst_n)begin if(!rst_n)beginkey_in_r1 <= 2'b11;key_in_r2 <= 2'b11;end else begin key_in_r1 <= key_in;      //同步按键输入信号key_in_r2 <= key_in_r1;   //打拍end 
end//r1当前状态,r2上一个状态
assign nedge = ~key_in_r1 && key_in_r2;
assign pedge = key_in_r1 && ~key_in_r2;//key_outalways @(posedge clk or negedge rst_n) beginif(!rst_n)key_out <= 0;else if(end_cnt_20ms &&(state_c== JITTLE1))key_out <= ~key_in_r2;//有效脉冲 20nselse key_out <= 0;endendmodule

3、新建一个顶层文件top.v,用于将数码管和按键连接起来。

module top(input  clk,input  rst_n,input [1:0] key_in,output  seg_sel,output [7:0] seg_dual);
wire [1:0] key_flag;
key key_inst(/*input   */  .clk     (clk        ),/*input   */  .rst_n   (rst_n      ),/*input   */  .key_in  (key_in     ),   //输入原始的按键信号/*output  */  .key_out (key_flag    )     //输出处理之后的按键信号
);seg0 seg_inst(/*input */ .clk       (clk         ),/*input */ .rst_n     (rst_n       ),.key_in    (key_flag), ///*input */ .seg_sel   (seg_sel     ),//位选/*output*/ .seg_dual (seg_dual   )//段选
);endmodule

4、测试文件的编写

其实这里我不推荐进行波形图仿真的,因为仿真需要编写的条件非常多,相比之下使用在线调试工具进行实时调试更好。这里我也给一个测试文件,需要的小伙伴可以加以完善。

//定义时间尺度
`timescale 1ns/1ns
module top_tb ;//输入信号定义
reg  clk;
reg rst_n;
reg  [1:0] key_in;
wire [7:0] seg_dual;
wire  seg_sel;
defparam  top_inst.cnt_inst.TIME_1s=500;
//模块例化
top top_inst(/*input */.clk      (clk     ),/*input */.rst_n    (rst_n   ),/*input */.key_in   (key_in  ),/*output*/.seg_sel  (seg_sel ),/*output*/.seg_dual (seg_dual));
//激励信号产生
parameter CLK_CLY = 20;
//时钟
initial clk=1;
always #(CLK_CLY/2)clk=~clk;//复位
initial beginrst_n= 1'b0;#(CLK_CLY*3);#5;//复位结束避开时钟上升沿rst_n= 1'b1;
end//激励
//激励
integer i;
initial repeat(5)beginkey_in[1] = 1;//模拟按键未按下i ={$random}%6;//给i赋值0-5#(CLK_CLY*500);//等待复位时间结束#3;key_in[1] = 0;//前按键抖动开始#(CLK_CLY*1);//一个5-10ms的抖动时间repeat ((i+5)*50)beginkey_in[1] = $random;#(CLK_CLY*1);end key_in[1] = 0;//按键稳定#(CLK_CLY*100*500);//后抖动开始key_in[1] = 1;#(CLK_CLY*1);repeat ((i+5)*50)beginkey_in[1] = $random;#(CLK_CLY*1);end key_in[1] = 1;//按键稳定#(CLK_CLY*10*5000);key_in[2] = 1;//模拟按键未按下i ={$random}%6;//给i赋值0-5#(CLK_CLY*500);//等待复位时间结束#3;key_in[2] = 0;//前按键抖动开始#(CLK_CLY*1);//一个5-10ms的抖动时间repeat ((i+5)*50)beginkey_in[2] = $random;#(CLK_CLY*1);end key_in[2] = 0;//按键稳定#(CLK_CLY*100*500);//后抖动开始key_in[2] = 1;#(CLK_CLY*1);repeat ((i+5)*50)beginkey_in[2] = $random;#(CLK_CLY*1);end key_in[2] = 1;//按键稳定#(CLK_CLY*10*5000);key_in[0] = 1;//模拟按键未按下i ={$random}%6;//给i赋值0-5#(CLK_CLY*500);//等待复位时间结束#3;key_in[0] = 0;//前按键抖动开始#(CLK_CLY*1);//一个5-10ms的抖动时间repeat ((i+5)*50)beginkey_in[0] = $random;#(CLK_CLY*1);end key_in[0] = 0;//按键稳定#(CLK_CLY*100*500);//后抖动开始key_in[0] = 1;#(CLK_CLY*1);repeat ((i+5)*50)beginkey_in[0] = $random;#(CLK_CLY*1);end key_in[0] = 1;//按键稳定#(CLK_CLY*10*5000);key_in[0] = 1;//模拟按键未按下i ={$random}%6;//给i赋值0-5#(CLK_CLY*500);//等待复位时间结束#3;key_in[0] = 0;//前按键抖动开始#(CLK_CLY*1);//一个5-10ms的抖动时间repeat ((i+5)*50)beginkey_in[0] = $random;#(CLK_CLY*1);end key_in[0] = 0;//按键稳定#(CLK_CLY*100*500);//后抖动开始key_in[0] = 1;#(CLK_CLY*1);repeat ((i+5)*50)beginkey_in[0] = $random;#(CLK_CLY*1);end key_in[0] = 1;//按键稳定#(CLK_CLY*10*5000);key_in[1] = 1;//模拟按键未按下i ={$random}%6;//给i赋值0-5#(CLK_CLY*500);//等待复位时间结束#3;key_in[1] = 0;//前按键抖动开始#(CLK_CLY*1);//一个5-10ms的抖动时间repeat ((i+5)*50)beginkey_in[1] = $random;#(CLK_CLY*1);end key_in[1] = 0;//按键稳定#(CLK_CLY*100*500);//后抖动开始key_in[1] = 1;#(CLK_CLY*1);repeat ((i+5)*50)beginkey_in[1] = $random;#(CLK_CLY*1);end key_in[1] = 1;//按键稳定#(CLK_CLY*10*5000);//模拟意外抖动repeat (3)begin repeat ((i+5)*50)beginkey_in = $random;#(CLK_CLY*1);end key_in = 1;//按键稳定#(CLK_CLY*5000);end $stop;
end
endmodule

5、下板验证

在进行下板验证之后最终实现的效果和我们所改造的预期结果一致,这里格式限制上传不了视频,就不展示了。在下板验证时设计的引脚配置等由用户自己根据需求去设置。

三、不足之处

经过最后的检查之后,发现在开发板上进行验证时,在使用按键进行数码管显示数值的改变的这个过程当中我们看不到,因为是在标志位位1时计数里面进行改变,而我们的输出是在标志位为0时进行显示的,所以看不到,这里我们对seg0设计进行一个最简单粗暴的方法进行修改。这里修改的方法很low了,在后面的数字时钟的文章中我会加以完善,不足之处多多包涵。

修改之后的seg0文件:

//分频器
module seg0(input  clk,input  rst_n,input  [1:0] key_in,input  seg_sel,//位选output reg [7:0] seg_dual//段选
);
localparam  ZERO  = 8'b1100_0000, //共阳极段码ONE   = 8'b1111_1001,TWO   = 8'b1010_0100,THREE = 8'b1011_0000,FOUR  = 8'b1001_1001,FIVE  = 8'b1001_0010,SIX   = 8'b1000_0010,SEVEN = 8'b1111_1000,EIGHT = 8'b1000_0000,NINE  = 8'b1001_0000,A     = 8'b1000_1000,b     = 8'b1000_0011,c     = 8'b1100_0110,d     = 8'b1010_0001,E     = 8'b1000_0110,f     = 8'b1000_1110;
reg [26:0] cnt;
wire       add_cnt;
wire       end_cnt;
reg     [4:0]   flag;
wire            add_flag;
wire            end_flag;reg    seg_flag;//数码管状态切换标志位
always @(posedge clk or negedge rst_n)beginif(!rst_n)cnt<=0;else if(add_cnt)beginif(end_cnt)cnt<=0;elsecnt<=cnt+1'b1;end
end
assign add_cnt=1'b1;
assign end_cnt=add_cnt && (cnt==50_000_000-1);always @(posedge clk or negedge rst_n)beginif(!rst_n)flag<=0;else if(add_flag)beginif(end_flag)flag<=0;elseflag<=flag+1'b1;end
end
assign add_flag=end_cnt;
assign end_flag=add_flag && (flag==16-1);always @(posedge clk or negedge rst_n)beginif(!rst_n)seg_flag<=0;else if(key_in[0])seg_flag<=~seg_flag;else seg_flag<=seg_flag;
endalways @(posedge clk or negedge rst_n)beginif(!rst_n)seg_dual<=8'b1111_1111;else if(seg_flag==0)begincase (flag)4'd0:seg_dual <=ZERO ;4'd1:seg_dual <=ONE  ;4'd2:seg_dual <=TWO  ;4'd3:seg_dual <=THREE; 4'd4:seg_dual <=FOUR ;4'd5:seg_dual <=FIVE ;4'd6:seg_dual <=SIX  ;4'd7:seg_dual <=SEVEN;4'd8:seg_dual <=EIGHT;4'd9:seg_dual <=NINE ;4'd10:seg_dual<=A    ;4'd11:seg_dual<=b    ;4'd12:seg_dual<=c    ;4'd13:seg_dual<=d    ;4'd14:seg_dual<=E    ;4'd15:seg_dual<=f    ;default: ;endcaseend else if(seg_flag==1)beginif(key_in[1])begincase (flag)4'd0:seg_dual <=ZERO ;4'd1:seg_dual <=ONE  ;4'd2:seg_dual <=TWO  ;4'd3:seg_dual <=THREE; 4'd4:seg_dual <=FOUR ;4'd5:seg_dual <=FIVE ;4'd6:seg_dual <=SIX  ;4'd7:seg_dual <=SEVEN;4'd8:seg_dual <=EIGHT;4'd9:seg_dual <=NINE ;4'd10:seg_dual<=A    ;4'd11:seg_dual<=b    ;4'd12:seg_dual<=c    ;4'd13:seg_dual<=d    ;4'd14:seg_dual<=E    ;4'd15:seg_dual<=f    ;default: ;endcaseendend 
end
endmodule 

经过本次修改之后就可以观察得到数码管显示数值的一个修改过程,到这里本次设计也就结束了。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • MybatisPlus(二)
  • 卢松松:分享6个短视频拍摄设备
  • Linux Vim教程(十四):使用Vim进行项目管理
  • 基因克隆技术在医学领域的应用有哪些局限性?
  • 异常 29
  • Win10安装node版本管理工具nvm
  • MySQL学习笔记第三天
  • Prompt提示工程上手指南:基础原理及实践-Prompt个性知识库引导
  • 【Golang】短链接系统
  • matlab中的双层数值积分
  • 这本vue3编译原理开源电子书,初中级前端竟然都能看懂
  • 3.插件化系列之动态加载class示例
  • OverlayFS 文件系统介绍
  • 聊聊《思考,快与慢》
  • Synthesia——虚拟人物视频生成
  • 08.Android之View事件问题
  • Java 最常见的 200+ 面试题:面试必备
  • JavaScript标准库系列——Math对象和Date对象(二)
  • JavaScript工作原理(五):深入了解WebSockets,HTTP/2和SSE,以及如何选择
  • js算法-归并排序(merge_sort)
  • Linux各目录及每个目录的详细介绍
  • php的插入排序,通过双层for循环
  • SOFAMosn配置模型
  • Spark VS Hadoop:两大大数据分析系统深度解读
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 使用putty远程连接linux
  • 探索 JS 中的模块化
  • 写给高年级小学生看的《Bash 指南》
  • Salesforce和SAP Netweaver里数据库表的元数据设计
  • ​决定德拉瓦州地区版图的关键历史事件
  • ​软考-高级-系统架构设计师教程(清华第2版)【第20章 系统架构设计师论文写作要点(P717~728)-思维导图】​
  • #经典论文 异质山坡的物理模型 2 有效导水率
  • (07)Hive——窗口函数详解
  • (160)时序收敛--->(10)时序收敛十
  • (java版)排序算法----【冒泡,选择,插入,希尔,快速排序,归并排序,基数排序】超详细~~
  • (LNMP) How To Install Linux, nginx, MySQL, PHP
  • (阿里巴巴 dubbo,有数据库,可执行 )dubbo zookeeper spring demo
  • (办公)springboot配置aop处理请求.
  • (回溯) LeetCode 78. 子集
  • (简单有案例)前端实现主题切换、动态换肤的两种简单方式
  • (免费领源码)python+django+mysql线上兼职平台系统83320-计算机毕业设计项目选题推荐
  • (十一)c52学习之旅-动态数码管
  • (学习日记)2024.01.09
  • (一)SvelteKit教程:hello world
  • (转)程序员技术练级攻略
  • .net core webapi 大文件上传到wwwroot文件夹
  • .NET Core、DNX、DNU、DNVM、MVC6学习资料
  • .NET Micro Framework初体验(二)
  • .net mvc 获取url中controller和action
  • .Net Winform开发笔记(一)
  • .NET 读取 JSON格式的数据
  • .NET/C# 将一个命令行参数字符串转换为命令行参数数组 args
  • .net6 core Worker Service项目,使用Exchange Web Services (EWS) 分页获取电子邮件收件箱列表,邮件信息字段
  • .Net6支持的操作系统版本(.net8已来,你还在用.netframework4.5吗)
  • .NET和.COM和.CN域名区别