编辑推荐: |
本文主要介绍了FPGA:用状态机点亮一个LED灯相关内容。 希望对你的学习有帮助。
本文来自于博客园,由火龙果软件Linda编辑、推荐。 |
|
实现功能:按键控制LED,按一下亮,按一下灭,就这么简单;
其实有很多方法都能实现这个功能,最简单的
如下:
module led_ctrl(
input wire sys_clk,
input wire sys_rstn,
input wire key,
output reg led
);
always@(posedge sys_clk or negedge sys_rstn)
if(sys_rstn==1'b0)
led <= 1'b1;
else if(key==1'b0)
led <= ~led;
else
led <= led;
endmodule
|
下面这个图是上面代码综合出的RTL视图,
但是实际上这里我在做的时候出现了一个问题,就是有时候按一下就灭了,再按下的时候不亮了,我再按久一点,它又亮了,这是为什么呢,这里存在一个硬件上的问题,就是机械按键在按下去的时候信号会有抖动,什么意思呢,见下图:
我本来是按照这样一个波形图去设计的,但是实际上的输入是下面这样的波形:
这就是机械按键自带的抖动,按下之后会有,弹回之前也会有,也就是说,我无法保证中间状态抖了多少次,所以最后的led输出状态也不确定。
怎么去除这两个状态呢,有两个方法:硬件和软件,硬件的话就是加一个RS触发器如下图,这样在跳变期间会保持状态而不至于抖动。
但是这样会增加电路元件,在寸土寸金的电路板上,我们大概率不会考虑这样的方法,所以软件消抖比较实用也比较好实现,软件消抖的原理是什么呢,就是延时检测,抖动时间一般小于10ms,把那段跳动避过去,就OK了,具体怎么实现呢,通过一个计数器计时,按键按下为低电平,一般一次按下那段稳定的低电平信号会持续20ms左右,当电平为低时,开始计数,当电平为高,计数归0,只要计时到20ms了,那都稳定这么久了,那信号肯定稳了,给个信号输出来就行。就像这样:
你们可能会奇怪为什么要计数要到999_999,是因为我的开发板晶振频率f是50MHz,1/f换成时间就是20ns一周期,要计时20ms就是要10_000_000*20ns,所以从0计到999_999,所以计数多少看你们自己情况,计时时长15ms~20ms,写成代码:
module key_filter
#(
parameter CNT_MAX = 20'd999_999 //计数器计数最大值
)
(
input wire sys_clk , //系统时钟50Mhz
input wire sys_rst_n , //全局复位
input wire key_in , //按键输入信号
output reg key_flag //key_flag为1时表示消抖后检测到按键被按下
//key_flag为0时表示没有检测到按键被按下
);
//reg define
reg [19:0] cnt_20ms ; //计数器
//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 == 1'b1)
cnt_20ms <= 20'b0;
else if(cnt_20ms == CNT_MAX && key_in == 1'b0)
cnt_20ms <= cnt_20ms;
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)
key_flag <= 1'b0;
else if(cnt_20ms == CNT_MAX - 1'b1)
key_flag <= 1'b1;
else
key_flag <= 1'b0;
endmodule
|
把这个结合到刚刚那个里面去再来一次:代码如下:
1 module led_ctrl(
2 input wire sys_clk,
3 input wire sys_rstn,
4 input wire key,
5
6 output reg led
7 );
8
9 wire key_flag;
10
11 always@(posedge sys_clk or negedge sys_rstn)
12 if(sys_rstn==1'b0)
13 led <= 1'b1;
14 else if(key_flag==1'b1)
15 led <= ~led;
16 else
17 led <= led;
18
19 key_filter
20 #(
21 .CNT_MAX(20'd999_999) //计数器计数最大值
22 )
23 key_filter_inst
24 (
25 .sys_clk (sys_clk ), //系统时钟50Mhz
26 .sys_rst_n (sys_rstn ), //全局复位
27 .key_in (key ), //按键输入信号
28
29 .key_flag (key_flag ) //key_flag为1时表示消抖后检测到按键被按下
30 //key_flag为0时表示没有检测到按键被按下
31 );
32
33 endmodule
|
这是综合出来的RTL视图,相比于之前那个,前面多了一个按键消抖模块。
OK,讲到这里,才把按键消抖模块讲清楚,今天的主角还没上呢,好了,主角登场:状态机,一般说状态机指有限状态机啊,就是FSM(Finite
State Machine)。
状态机的按照类型分呢,主要分类两种:Moore型状态机和Mealy型状态机,Moore型状态机的输出只与当前状态有关,Mealy型状态机的输出既与当前状态还和输入有关系;由于今天讲的东西呢,只有两个状态,所以我们以Moore型状态机来举例哈,以为讲完了吗?还没有,
另外一种分类方式呢,就是代码风格分类,分为一段式、二段式和三段式,
一段式就是把输入,状态变化条件和状态变化,输出全写在一起,比较精简吧,但是后期难以维护;
二段式就是把时序逻辑和组合逻辑分开,时序逻辑内进行当前状态和下一状态的切换,组合逻辑内实现各个输入、输出以及状态判断,二段式相较于一段式好维护,但是组合逻辑输出易出现毛刺等常见问题;
三段式就是一段时序逻辑内进行当前状态和下一状态的切换,一段组合逻辑实现状态判断,再来一段时序逻辑输出;解决了毛刺问题,但是耗费的资源比二段式要多。
好了,状态机概念先讲到这,下面实现目的,点灯!!!
状态机,首先要明白状态怎么变,所以要画状态转换图,如下;
看图写代码,如下(加上消抖模块!!!不然会出现和之前一样的情况)
module led_ctrl(
input wire sys_clk,
input wire sys_rstn,
input wire key,
output wire led
);
parameter OFF=1'b0,ON=1'b1; //定义状态
reg state,nstate ; //定义状态变量
wire key_flag ;
always@(posedge sys_clk or negedge sys_rstn)
begin
if(sys_rstn==1'b0)
state <= OFF;
else
state <= nstate;
end
always@(*)
begin
case(state)
OFF: nstate = key_flag ? ON:OFF;
ON : nstate = key_flag ? OFF:ON;
default:nstate = OFF;
endcase
end
assign led=(state==OFF);
key_filter
#(
.CNT_MAX(20'd999_999) //计数器计数最大值
)
key_filter_inst
(
.sys_clk (sys_clk ), //系统时钟50Mhz
.sys_rst_n (sys_rstn ), //全局复位
.key_in (key ), //按键输入信号
.key_flag (key_flag ) //key_flag为1时表示消抖后检测到按键被按下
//key_flag为0时表示没有检测到按键被按下
);
endmodule
|
比之前要复杂,同样实现功能,但是用的是不一样的方法,还有个方法,叫边缘检测,大家可以试一下;
点个灯可能很简单,但是要用学到的新东西去实现功能,活学活用才是硬道理;
注:用的硬件平台是野火FPGA征途系列开发板,软件平台是quartus II + notepad++
|