概述
文章目录
- 前言
- 一、前期准备工作
- 二、软件思路
- 1.设计流程
- 2.代码解读
- 3.仿真波形
- 总结
前言
最近闲来没事儿,想练习一下FPGA,于是衍生出了做一个外部按键输入的想法。做起来不难,但是对于新手而言是一个很好的练习方法。首先能够学会多重实例化嵌套的思路,其次能够通过testbench验证自己电路的逻辑状态,通过modelsim里面反馈出的各种时序图从而判断如何去修改自己的代码使得达到理想的结果。verilog跟c很大的不同在于是描述硬件的一款语言,在写代码的时候一定要忘记c语言过程中的一些思路,去适应verilog的硬件描述。
实现的具体功能为:通过外部按键按下判断当时输入的按键所代表的数字,按下四次以后得到一个四位数的数字并将其显示在数码管上,在本项目程序中先开始输入的是个位,依次到千位。若想改变成千位先输入可通过改变状态机里面的赋值语句即可实现要求。
一、前期准备工作
必不可少的硬件外部环境,如下图所示:
按键就采用最普通的轻触按键,一脚连接地,一脚连接到控制的IO口。这里设置了10个IO口,分别代表数字0-9。记住按键的地和FPGA的地一定要连接至一起。建议每一个按键的两端焊接一个104的电容,避免高频抖动带来的影响。
一款FPGA系列的开发板,此处博主选用的是野火的征途mini板,型号为EP4CE10F17C8。用到的板载资源为key_filter和dynamic_seg_595动态数码管例程。
二、软件思路
1.设计流程
我们将整体分为三个流程
1、检测外部按键输入,配置按键滤波器
由于按键按下的时候,会产生一定的抖动,因此要对抖动在软件中进行处理,此处选择20ms的定时作为抖动信号的产生。具体原理是,当我们检测到有按键按下以后,20ms计时器开始计时,计时到20ms后再次判断按键是否按下,若还有按键按下则代表输出一次按键有效信号,用key_flag表示。key_flag将作为按键状态机的一个判断逻辑在后面的状态机.v文件中进行处理。同时,当出现按键有效信号的时候,检测的哪个按键按下就输出key_flag_status,代表当前采集到的按键所对应的数值。因此这一过程,将输出两个有效信号,一个是key_flag(按键触发信号),一个是key_flag_status(按键数值信号)。
2、状态机切换输入状态,实现个位到千位的赋值
状态机主要是切换赋值状态。在状态机的.v文件中能得到按键传输过来的key_flag信号和key_flag_status信号。我们将状态机分为五个状态,分别是IDLE、ONE、TWO、THREE、FOUR五种状态。其中最开始处于IDLE状态,当检测到key_flag按下后,判断如果当时的信号是5’d10,因为在我们的设置中,5‘d10这个按钮是确定按钮,只有按下这个按键的同时,输出的data数据(也即我们要显示的数据)才能够重新归零然后继续进行赋值。状态机通过判断key_flag作为切换状态,依次循环。在ONE状态切换到TWO状态时进行个位赋值,依次类推,最终FOUR状态切换至IDLE状态时将完成千位的赋值。这样最终得到的data数据就是我们想要显示的四位数据。想要改变的话再按下确定键进行下一次的赋值即可。
3、数码管的显示
此处数码管的显示参考野火单片机开发板的动态数码管例程,只需要在data_gen和最终的顶层文件中添加一个data数据即可。因为在第二步状态机内部我们将输出一个data数据,这个data数据将被赋值在动态数码管的data_gen.v文件中,最终数码管将实时显示data的大小。故当我们从个位输出到千位时,数码管会实时显示数据的大小。
2.代码解读
代码如下:
1、key_filter代码
`timescale 1ns/1ns
// Author : EmbedFire
// Create Date : 2019/03/15
// Module Name : key_filter
// Project Name : key_filter
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description : 按键消抖模块
//
// Revision : V1.0
// Additional Comments:
//
// 实验平台: 野火_征途Pro_FPGA开发板
// 公司 : http://www.embedfire.com
// 论坛 : http://www.firebbs.cn
// 淘宝 : https://fire-stm32.taobao.com
module key_filter
#(
parameter CNT_MAX = 20'd999_999 //计数器计数最大值
)
(
input wire sys_clk , //系统时钟50Mhz
input wire sys_rst_n , //全局复位
input wire [10:0] key_in , //按键输入信号
output reg key_flag , //key_flag为1时表示消抖后检测到按键被按下
output reg [4:0] key_flag_status //key_flag为1时表示消抖后检测到按键被按下
//key_flag为0时表示没有检测到按键被按下
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//reg define
reg [19:0] cnt_20ms ; //计数器
reg key_flag1 ;
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_20ms <= 20'b0;
else if(key_in == 11'b11_111_111_111)
cnt_20ms <= 20'b0;
else if(cnt_20ms == CNT_MAX&&key_in != 11'b11_111_111_111)
begin
case(key_in)
11'b11_111_111_110: key_flag_status <= 5'd0;
11'b11_111_111_101: key_flag_status <= 5'd1;
11'b11_111_111_011: key_flag_status <= 5'd2;
11'b11_111_110_111: key_flag_status <= 5'd3;
11'b11_111_101_111: key_flag_status <= 5'd4;
11'b11_111_011_111: key_flag_status <= 5'd5;
11'b11_110_111_111: key_flag_status <= 5'd6;
11'b11_101_111_111: key_flag_status <= 5'd7;
11'b11_011_111_111: key_flag_status <= 5'd8;
11'b10_111_111_111: key_flag_status <= 5'd9;
11'b01_111_111_111: key_flag_status <= 5'd10;
default:key_flag_status <= 5'd11 ;
endcase
cnt_20ms <= cnt_20ms;
end
else
cnt_20ms <= cnt_20ms + 1'b1;
//key_flag:当计数满20ms后产生按键有效标志位
//且key_flag在999_999时拉高,维持一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
key_flag <= 1'b0;
key_flag1<= 1'b0;
end
else if(cnt_20ms == CNT_MAX - 1'b1)
begin
key_flag1 <= 1'b1;
key_flag <= key_flag1;
end
else
begin
key_flag1 <= 1'b0;
key_flag <= key_flag1;
end
endmodule
2、状态机代码
`timescale 1ns/1ns
// Author : EmbedFire
// Create Date : 2019/05/12
// Module Name : simple_fsm
// Project Name : simple_fsm
// Target Devices: Altera EP4CE10F17C8
// Tool Versions : Quartus 13.0
// Description : 简单状态机
//
// Revision : V1.0
// Additional Comments:
//
// 实验平台: 野火_征途Pro_FPGA开发板
// 公司 : http://www.embedfire.com
// 论坛 : http://www.firebbs.cn
// 淘宝 : https://fire-stm32.taobao.com
module simple_fsm
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //全局复位
input wire [10:0] key_in ,
output reg [19:0] data, //输出最终的数据
output reg [3:0] led
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//parameter define
//只有五种状态,使用独热码
parameter IDLE = 5'b00_001;
parameter ONE = 5'b00_010;
parameter TWO = 5'b00_100;
parameter THREE = 5'b01_000;
parameter FOUR = 5'b10_000;
wire key_flag ; //按键使能位
wire [4:0] key_flag_status;//按键按下状态位
//reg define
reg [4:0] state = IDLE;
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//第一段状态机,描述当前状态state如何根据输入跳转到下一状态
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
state <= IDLE; //任何情况下只要按复位就回到初始状态
data <= 1'b0;
end
else case(state)
IDLE : if(key_flag == 1'b1&& key_flag_status == 5'd0)//判断输入情况
begin
state <= ONE;
data <= 1'b0;
end
else
begin
state <= IDLE;
data <= data;
end
ONE : if(key_flag == 1'b1&& key_flag_status != 5'd11)
begin
state <= TWO;
data <= data+key_flag_status*1;
end
else
begin
state <= ONE;
data <= data;
end
TWO : if(key_flag == 1'b1&& key_flag_status != 5'd11)
begin
state <= THREE;
data <= data+key_flag_status*10;
end
else
begin
state <= TWO;
data <= data;
end
THREE : if(key_flag == 1'b1&& key_flag_status != 5'd11)
begin
state <= FOUR;
data <= data+key_flag_status*100;
end
else
begin
state <= THREE;
data <= data;
end
FOUR : if(key_flag == 1'b1&& key_flag_status != 5'd11)
begin
state <= IDLE;
data <= data+key_flag_status*1000;
end
else
begin
state <= FOUR;
data <= data;
end
//如果状态机跳转到编码的状态之外也回到初始状态
default : state <= IDLE;
endcase
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
led <= 4'b1_111; //任何情况下只要按复位就回到初始状态
else if(state== IDLE)
begin
if(data<20'd1_000)led <= 4'b1_110;
else if(data>=20'd1_000&&data<20'd2_000)led<=4'b1_101;
else if(data>=20'd2_000&&data<20'd3_000)led<=4'b1_011;
else led<=4'b0_111;
end
else
led <=4'b1_111;
//-------------key_filter_inst--------------
key_filter
#(
.CNT_MAX (20'd999_999)
)
key_filter_inst
(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n), //复位信号,低电平有效
.key_in (key_in ),
.key_flag (key_flag ), //数码管要显示的值
.key_flag_status (key_flag_status) //小数点显示,高电平有效
);
endmodule
3、数码管的代码
代码参考的就是野火例程动态数码管,在其中只需要更改date_gen中的date传输方式即可,如下面所示:
`timescale 1ns/1ns
// Author : EmbedFire
// Create Date : 2019/07/10
// Module Name : data_gen
// Project Name : top_seg_595
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description : 生成数码管显示数据
//
// Revision : V1.0
// Additional Comments:
//
// 实验平台: 野火_征途Pro_FPGA开发板
// 公司 : http://www.embedfire.com
// 论坛 : http://www.firebbs.cn
// 淘宝 : https://fire-stm32.taobao.com
module data_gen
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire [10:0] key_in ,
output reg [19:0] data , //数码管要显示的值
output reg [3:0] led ,
output wire [5:0] point , //小数点显示,高电平有效
output reg seg_en , //数码管使能信号,高电平有效
output wire sign //符号位,高电平显示负号
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//reg define
reg [22:0] cnt_100ms ; //100ms计数器
reg cnt_flag ; //100ms标志信号
wire [19:0] data1 ;
wire [3:0] led1 ;
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//不显示小数点以及负数
assign point = 6'b000_000;
assign sign = 1'b0;
//数码管使能信号给高即可
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
seg_en <= 1'b0;
else
begin
seg_en <= 1'b1;
data <= data1;
led <= led1 ;
end
simple_fsm simple_fsm_inst
(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n), //复位信号,低电平有效
.key_in (key_in ),
.data (data1 ), //数码管要显示的值
.led (led1 )
);
endmodule
最终也只需在top的文件内部调用data_gen文件,如下图所示:
`timescale 1ns/1ns
///
// Author : EmbedFire
// Create Date : 2019/07/10
// Module Name : top_seg_595
// Project Name : top_seg_595
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description : 数码管动态显示,顶层模块
//
// Revision : V1.0
// Additional Comments:
//
// 实验平台: 野火_征途系列FPGA开发板
// 公司 : http://www.embedfire.com
// 论坛 : http://www.firebbs.cn
// 淘宝 : https://fire-stm32.taobao.com
module top_seg_595
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire [10:0] key_in , //按键输入信号
output wire stcp , //输出数据存储寄时钟
output wire shcp , //移位寄存器的时钟输入
output wire ds , //串行数据输入
output wire [3:0] led ,
output wire oe //输出使能信号
);
//********************************************************************//
//******************** Parameter And Internal Signal *****************//
//********************************************************************//
//wire define
wire [19:0] data ; //数码管要显示的值
wire [5:0] point ; //小数点显示,高电平有效top_seg_595
wire seg_en ; //数码管使能信号,高电平有效
wire sign ; //符号位,高电平显示负号
//********************************************************************//
//**************************** Main Code *****************************//
//********************************************************************//
//-------------data_gen_inst--------------
data_gen data_gen_inst
(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n), //复位信号,低电平有效
.key_in (key_in ), //按键输入信号
.data (data ), //数码管要显示的值
.led (led ),
.point (point ), //小数点显示,高电平有效
.seg_en (seg_en ), //数码管使能信号,高电平有效
.sign (sign ) //符号位,高电平显示负号
);
//-------------seg7_dynamic_inst--------------
seg_595_dynamic seg_595_dynamic_inst
(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n ), //复位信号,低有效
.data (data ), //数码管要显示的值
.point (point ), //小数点显示,高电平有效
.seg_en (seg_en ), //数码管使能信号,高电平有效
.sign (sign ), //符号位,高电平显示负号
.stcp (stcp ), //输出数据存储寄时钟
.shcp (shcp ), //移位寄存器的时钟输入
.ds (ds ), //串行数据输入
.oe (oe ) //输出使能信号
);
endmodule
至此就实现了代码的实现条件
3.仿真波形
如图所示,依次在进入状态位的时候对输出的data进行了赋值
单单key_filter最终综合出的RTL视图
总结
本项目实现了外部按键输入赋值并最终通过动态数码管实时显示的效果,源码和文件将在不久后上传。
最后
以上就是诚心日记本为你收集整理的基于FPGA的外部数字键盘输入前言一、前期准备工作二、软件思路总结的全部内容,希望文章能够帮你解决基于FPGA的外部数字键盘输入前言一、前期准备工作二、软件思路总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复