设为首页 加入收藏

TOP

状态机设计——从简单的按键消抖开始(一)
2017-10-09 14:07:29 】 浏览:836
Tags:状态 设计 简单 按键 开始

  目前笔者正在接受明德扬FPGA网上培训班的培训,讲的内容非常适合新手,且以练习和互动答疑的教学模式让我学到了很多东西。由于是根据自身时间安排进度的,所以战线拉的比较长,发现做些设计总结非常重要,可以帮助自己理清思路,同时也能得到很好的复习。

  之前一直在做altera FPGA的相关学习,对xilinx还不是很熟悉,借着这个契机,将比较基础常用的设计在VIVADO开发环境中过一遍,对我来说是个不错的选择。进入今天的正题,本篇博文旨在通过一个小例子掌握状态机的设计方法。由于设计非常简单,采用常见的三段式状态机来规范设计。后续复杂的例子中,将采用明德扬提出的四段式状态机,个人理解虽然与三段式基本思想相同,但有助于简化设计,理清思路。

  众所周知,硬件按键都存在机械抖动。所以一次人为按下的动作会触发数次按键按下的行为。所谓“按键消抖”模块的功能就是将抖动滤除掉,保证对按键状态的有效识别。单片机的设计思想比较通用,即检测到按键连接端口为低电平(低电平有效)后,延迟一段时间再次确认是否为低。若是则说明此次低电平确实为一次按键行为,否则视为抖动。按键松手检测同理。其大体设计流程如下:

 

   这是典型的顺序设计思想,但FPGA是并行的。所以这种时间有先后,且操作差异较大的处理过程要用到状态机进行设计。简化后可将上述过程分为四个状态:初始空闲状态、延迟并检测低电平状态、检测释放状态和延迟并检测高电平状态。以下是状态转移图:

  空闲状态下如检测到按键接口低电平进入延迟并确认低电平状态,延迟计数时间设定为10ms。若计数完成且依然为低电平则按下有效进入检测释放状态,若计数期间按键出现高电平说明为抖动回到初始状态。在检测释放状态中若出现高电平进入延迟确认状态,否则持续检测高电平。在延迟确认高电平状态若计数完成且为高电平视为有效松手行为,此时置位有效标志位,按键完成了一次按下到松手的完整有效过程回到IDLE状态再检测下一次按下。如果计数期间出现低电平同样为抖动回到检测释放状态重新检测。

  1 `timescale 1ns / 1ps
  2 
  3 module key_jitter#
  4 (
  5 parameter DELAY_TIME = 2000_000  //延迟10ms
  6 )
  7 (
  8     input clk,
  9     input rst_n,
 10     
 11     input key_i,
 12     output reg led_o
 13     );
 14     
 15     localparam IDLE          = 4'b0001,
 16                DELAY_LOW     = 4'b0010,
 17                CHECK_RELEASE = 4'b0100,
 18                DELAY_HIGH    = 4'b1000;
 19                
 20     reg [20:0] div_cnt;
 21     reg [3:0] state_c,state_n;
 22     reg key_tmp0,key_tmp1;
 23     
 24     wire add_cnt,end_cnt;
 25     wire vld_flag;
 26     wire cnt_during;
 27 
 28     //消除亚稳态
 29     always@(posedge clk or negedge rst_n)begin
 30         if(!rst_n)begin
 31             key_tmp0 <= 0;
 32             key_tmp1 <= 0;
 33         end
 34         else begin
 35             key_tmp0 <= key_i;
 36             key_tmp1 <= key_tmp0;
 37         end
 38     end
 39     
 40     //状态机
 41     always@(posedge clk or negedge rst_n)begin
 42         if(!rst_n)
 43             state_c <= IDLE;
 44         else 
 45             state_c <= state_n;
 46     end
 47     
 48     always@(*)begin
 49         case(state_c)
 50             IDLE:begin  //初始状态检测是否有按键按下 //4'b0001
 51                 if(key_tmp1 == 0)//有按键按下进入延时后再次确认低电平状态
 52                     state_n <= DELAY_LOW;
 53                 else 
 54                     state_n <= state_c;
 55             end
 56             
 57             DELAY_LOW:begin  //延时并再次确认低电平状态  //4'b0010
 58                 if(end_cnt && key_tmp1 == 0)//10ms后依然是低电平则有按键按下,此时检测是否松手
 59                     state_n <= CHECK_RELEASE;
 60                 else if(cnt_during && key_tmp1 == 1)
 61                     state_n <= IDLE;//若未计数完成出现高电平则视为抖动,重新检测按下
 62                 else 
 63                     state_n <= state_c;//计数未完成继续
 64             end
 65             
 66             CHECK_RELEASE:begin  //4'b0100
 67                 if(key_tmp1 == 1)//为高电平则等待并再次确认
 68                     state_n <= DELAY_HIGH;
 69                 else 
 70                     state_n <= state_c;//若没有高电平则持续检测
 71             end
 72             
 73             DELAY_HIGH:begin  //4'b1000
 74                 if(vld_flag)//10ms后依然高电平则按键释放
 75                     state_n <= IDLE;//释放后回到初始状态再次检测下一次的按下
 76                 else if(cnt_during && key_tmp1 == 0)//若延时后为0则松手过程视为抖动
 77                     state_n <= CHECK_RELEASE;
 78                 else 
 79                     state_n <= state_c;//继续计数
 80             end
 81             
 82             default:
 83                 state_n <= IDLE;
 84         endcase
 85     end
 86     
 87     assign cnt_during = add_cnt && div_cnt < DELAY_TIME;
 88     
 89     //延迟计数器
 90     always@(posedge clk or negedge rst_n)begin
 91         if(!rst_n)
 92             div_cnt <= 0;
 93         else if(add_cnt)begin
 94             if(end_cnt)
 95                 div_cnt <= 0;
 96             else 
 97                 div_cnt <= div_cnt + 1'b1;
 98         end
 99         else 
100             div_cnt <= 0;
101     end
102     
103     assign add_cnt = state_c == DELAY_HIGH || state_c == DELAY_LOW;
104     assign end_cnt = add_cnt && div_cnt == DELAY_TIME - 1;
105     //按下一次并释放后表示一次有效的操作,此时led翻转
106     always@(posedge clk or negedge rst_n)begin
107         if(!rst_n)
108             led_o <= 0;//上电复位点亮
109         else if(state_c == DELAY_HIGH && vld_flag)//可将()内条件作为按键有效输出
110             led_o <= ~led_o;
1
首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇FPGA基础知识(四)锁存器、触发.. 下一篇全网首创ISE入门级教程

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目