1. 程式人生 > >STM32F103按鍵操作的另一種實現——狀態機

STM32F103按鍵操作的另一種實現——狀態機

#ifndef _KEY_H_
#define _KEY_H_

#include "HAL_gpio.h" // 換成STM32F103對應的GPIO庫
#include "type.h"     // type.h主要是一些型別的重新命名

#define KEY_UP_GRP         GPIOA
#define KEY_UP_IDX         GPIO_Pin_9
#define KEY_UP_IS_DOWN()   GPIO_ReadInputDataBit(KEY_UP_GRP, KEY_UP_IDX)
#define KEY_UP_CONFIG()    GPIOConfig(KEY_UP_GRP, KEY_UP_IDX, GPIO_Mode_IPU) // 這個函式我在之前帖子裡面寫過

#define KEY_DOWN_GRP       GPIOA
#define KEY_DOWN_IDX       GPIO_Pin_10
#define KEY_DOWN_IS_DOWN() GPIO_ReadInputDataBit(KEY_DOWN_GRP, KEY_DOWN_IDX)
#define KEY_DOWN_CONFIG()  GPIOConfig(KEY_DOWN_GRP, KEY_DOWN_IDX, GPIO_Mode_IPU)

#define KEY_FUNC_GRP       GPIOA
#define KEY_FUNC_IDX       GPIO_Pin_11
#define KEY_FUNC_IS_DOWN() GPIO_ReadInputDataBit(KEY_FUNC_GRP, KEY_FUNC_IDX)
#define KEY_FUNC_CONFIG()  GPIOConfig(KEY_FUNC_GRP, KEY_FUNC_IDX, GPIO_Mode_IPU) 

#define KEY_TURN_GRP       GPIOA
#define KEY_TURN_IDX       GPIO_Pin_12 | GPIO_Pin_13
#define KEY_TURN_IS_DOWN() GPIO_ReadInputDataBit(KEY_TURN_GRP, KEY_TURN_IDX)
#define KEY_TURN_CONFIG()  GPIOConfig(KEY_TURN_GRP, KEY_TURN_IDX, GPIO_Mode_IPU)

//====================================================================================
typedef enum
{
  CONFIRM_KEY = 1,
  FUNC_KEY,
  UP_KEY,
  DOWN_KEY
} key_event_t;

#define state_keyUp         0       //初始狀態,未按鍵
#define state_keyDown       1       //鍵被按下
#define state_keyLong       2       //長按
#define state_keyTime       3       //按鍵計時態

#define return_keyUp        0x00    //初始狀態
#define return_keyPressed   0x01    //鍵被按過,普通按鍵
#define return_keyLong      0x02    //長按
#define return_keyAuto      0x04    //自動連發

#define key_down            0       //按下
#define key_up              0xf0    //未按時的key有效位鍵值
#define key_longTimes       100     //10ms一次,200次即2秒,定義長按的判定時間
#define key_autoTimes       20      //連發時間定義,20*10=200,200毫秒發一次


#define KEYS1_VALUE         0xe0    //keyS1 按下
#define KEYS2_VALUE         0xd0    //keyS2 按下
#define KEYS3_VALUE         0xb0    //keyS3 按下
#define KEYS4_5_VALUE       0x70    //keyS4_5 按下

//====================================================================================
void KeyProcess(void);  //T0定時器呼叫的工作函式
void KeyTimerInit(void);

#endif /* _KEY_H_ */
#include <stdio.h>
#include "key.h"
#include "timer.h" // STM32F103定時器的配置

static uint8_t
key_get(void)       //獲取P3口值
{
  if(KEY_UP_IS_DOWN() == key_down)
  {
    return KEYS1_VALUE;
  }
  if(KEY_DOWN_IS_DOWN() == key_down)
  {
    return KEYS2_VALUE;
  }
  if(KEY_FUNC_IS_DOWN() == key_down)
  {
    return KEYS3_VALUE;
  }
  if(KEY_TURN_IS_DOWN() == key_down)
  {
    return KEYS4_5_VALUE;
  }

  return key_up ;    //0xf0  沒有任何按鍵
}

//函式每20ms被呼叫一次,而我們彈性按鍵過程時一般都20ms以上
//所以每次按鍵至少呼叫本函式2次
static uint8_t
key_read(uint8_t* pKeyValue)
{
  static uint8_t  s_u8keyState = 0;        //未按,普通短按,長按,連發等狀態
  static uint16_t s_u16keyTimeCounts = 0;  //在計時狀態的計數器
  static uint8_t  s_u8LastKey = key_up ; //儲存按鍵釋放時的P3口資料

  uint8_t keyTemp = 0;              //鍵對應io口的電平
  int8_t key_return = 0;          //函式返回值
  keyTemp = key_up & key_get();  //提取所有的key對應的io口

  switch(s_u8keyState)           //這裡檢測到的是先前的狀態
  {
  case state_keyUp:   //如果先前是初始態,即無動作
  {
    if(key_up != keyTemp) //如果鍵被按下
    {
      s_u8keyState = state_keyDown; //更新鍵的狀態,普通被按下
    }
  }
  break;

  case state_keyDown: //如果先前是被按著的
  {
    if(key_up != keyTemp) //如果現在還被按著
    {
      s_u8keyState = state_keyTime; //轉換到計時態
      s_u16keyTimeCounts = 0;
      s_u8LastKey = keyTemp;     //儲存鍵值
    }
    else
    {
      s_u8keyState = state_keyUp; //鍵沒被按著,回初始態,說明是干擾
    }
  }
  break;

  case state_keyTime:  //如果先前已經轉換到計時態(值為3)
  {
    //如果真的是手動按鍵,必然進入本程式碼塊,並且會多次進入
    if(key_up == keyTemp) //如果未按鍵
    {
      s_u8keyState = state_keyUp;
      key_return = return_keyPressed;    //返回1,一次完整的普通按鍵
      //程式進入這個語句塊,說明已經有2次以上10ms的中斷,等於已經消抖
      //那麼此時檢測到按鍵被釋放,說明是一次普通短按
    }
    else  //在計時態,檢測到鍵還被按著
    {
      if(++s_u16keyTimeCounts > key_longTimes) //時間達到2秒
      {
        s_u8keyState = state_keyLong;  //進入長按狀態
        s_u16keyTimeCounts = 0;      //計數器清空,便於進入連發重新計數
        key_return = return_keyLong;   //返回state_keyLong
      }
      //程式碼中,在2秒內如果我們一直按著key的話,返回值只會是0,不會識別為短按或長按的
    }
  }
  break;

  case state_keyLong:  //在長按狀態檢測連發  ,每0.2秒發一次
  {
    if(key_up == keyTemp)
    {
      s_u8keyState = state_keyUp;
    }
    else //按鍵時間超過2秒時
    {
      if(++s_u16keyTimeCounts > key_autoTimes)//10*20=200ms
      {
        s_u16keyTimeCounts = 0;
        key_return = return_keyAuto;  //每0.2秒返回值的第2位置位(1<<2)
      }//連發的時候,肯定也伴隨著長按
    }
    key_return |= return_keyLong;  //0x02是肯定的,0x04|0x02是可能的
  }
  break;

  default:
    break;
  }
  *pKeyValue = s_u8LastKey ; //返回鍵值
  return key_return;
}

// 這個函式就是要在中斷中呼叫的。主要是使用事件佇列的方式。
void
KeyProcess(void)
{
  uint8_t key_stateValue;
  uint8_t keyValue = 0;
  uint8_t* pKeyValue = &keyValue;

  key_stateValue = key_read(pKeyValue);

  if ((return_keyPressed == key_stateValue) && (*pKeyValue == KEYS1_VALUE))
  {
    //短按keyS1時改變對時狀態,將其加入佇列,佇列的基本操作在將佇列的時候寫過。
    if(QueueEventIsEmpty(g_state_manager.process->key_event) ||
        (!QueueEventIsEmpty(g_state_manager.process->key_event) &&
         !QUEUE_EVENT_IS_EQUEL(g_state_manager.process->key_event, UP_KEY)))
    {
      QueueEventPush(g_state_manager.process->key_event, UP_KEY);
    }
  }
}

//======================================================================================
void
KeyTimerInit(void)
{
  KEY_UP_CONFIG();
  KEY_DOWN_CONFIG();
  KEY_FUNC_CONFIG();
  KEY_TURN_CONFIG();

  // 20ms
  TimerConfig(KEY_TIMER, KEY_TIMER_DIV, KEY_TIMER_PERIOD);
  TimerDisable(KEY_TIMER);
  TimerEnable(KEY_TIMER);
}
主要是描述一下按鍵狀態機的思維,使用定時器中斷的方法,按鍵按下將其加入佇列中,在主函式的迴圈中實現出隊。親測可用。


相關推薦

STM32F103按鍵操作實現——狀態

#ifndef _KEY_H_ #define _KEY_H_ #include "HAL_gpio.h" // 換成STM32F103對應的GPIO庫 #include "type.h" // type.h主要是一些型別的重新命名 #define KEY_U

Java模版方法的實現

pan strategy 全部 相關 必須 rod () 抽象方法 rate   面試荔枝FM杯具,遂死磕AQS途中發現一個有趣的模版用法,記下來。   模版方法是很重要的設計模式,在數據訪問層、眾多的插件接口都可見其影子,一般的實現都是在模版中定義抽象方法並使用其方法進行

吸頂效果的實現

  前面介紹過一篇文章,是使用ItemDecoration來實現吸頂效果,使用起來很解耦,簡單,方便,但是優缺點是拓展性比較差,今天就通過另一種方式來實現吸頂效果,並且吸頂欄可以高度制定佈局和互動,步入正題,下面來實現它,先看看效果圖: 一、實現原理 頭部的內容位於R

101889I (LCA的實現

題意:   Q次詢問,每次詢問必須包含特定邊的最小生成樹。 思路: 考慮 最淳樸的最小生成樹,如果加了一條特定邊,肯定是構成了一個環,那麼環外的邊肯定是不變的,要不然,根本就不可能選外面的那些邊了, 所以我們現在就是求這個環上的最小 生成樹,肯定是找樹上之前的 兩個點之

利用IAT匯出OpenGL函式:OpenGL Loader的實現辦法

利用這種辦法可以用50KB的DLL匯出OpenGL 3.3版本所有Core Profile函式, DLL比GLEW小很多, 根據glcorearb.h自動生成的程式碼與glLoadGen生成的程式碼差不多, 這部分程式碼包含的函式都是空實現,結合__declspec(dllexport,

載入一個類時,其內部類是否同時被載入?引申出單例模式的實現方式...

載入一個類時,其內部類是否同時被載入?下面我們做一個實驗來看一下。public class Outer { static { System.out.println("load outer class..."); } //靜態內部類 sta

載入一個類時,其內部類是否同時被載入?引申出單例模式的實現方式

 載入一個類時,其內部類是否同時被載入?下面我們做一個實驗來看一下。  Java程式碼   1.    public class Outer {   2.        static {   3.            System.out.println("load o

分頁的實現-不用額外請求

情景:千里碼有些最優化題目的旁邊會有一個排行榜,用來展示不同的答案。比如[Uber打車匹配](http://www.qlcoder.com/task/7596) 這裡的答題人數並不多,但是[老王

java中NIO程式設計實現超實用

  除了普通的Socket與ServerSocket實現的阻塞式通訊外,Java提供了非阻塞式通訊的NIO API。先看一下NIO的實現原理。        從圖中可以看出,伺服器上所有Channel(包括ServerSocketChannel和SocketCha

實現非阻塞網路通訊的方法———使用libev

背景:最近終於開始了我的實習生之路,本來在進公司之前還比較緊張,儘管拿到了offer,因為畢竟這是一個新的起點,一開始從學生到員工這個身份的轉變讓我有些不太適應,但是還好在公司裡遇到了人超級好的軟體經理Alex以及其他精明能幹的小夥伴們,所以這個過渡時間也很快。 一開始Al

嘗試新思路——CError的實現方式

程式碼如下: #ifndef __MYERROR_H__ #define __MYERROR_H__ #include "Error.h" #include <map> #include

Sticky Header的實現方法

使用Sticky Header的list單個item一般情況下使用的資料結構是 {data:"what's inside", category:"section name"} 這樣儲存其實是浪費了很多的空間,因為category的名字被儲存的多次。在移動環境

縮放到選中的實現

Dim pDoc As IMxDocumentSet pDoc = ThisDocumentDim pMap As IMapSet pMap = pDoc.FocusMapDim pLayer As IFeatureLayerDim pFSel As IFeatureSele

代理的實現方式

代理相信大家都很熟悉了。不過還是說下吧。 舉個例子: // // A.h // Created by XX // @protocol SomeDelegate <NSObject> - (void)someMethod:(UIVi

頭部底部固定的實現方式

需求:點選按鈕 頁面左側或右側滑出一個小視窗,頭部,底部固定,中間內容區域可以滑動滾動條檢視資料,這種視窗 我一般的做法是視窗fixed定位,頭部底部也fixed定位或者絕對定位都可以實現,今天看到一種新的實現方式,自己研究了一下,寫了一個demo記錄一下,也不知這樣寫好不

JPA複合主鍵實現--聯合約束

前言 關於複合主鍵一般是三種方式,但必須建立複合主鍵類,然後通過註解的方式完成,這三種方式網上很容易找到,這裡主要記錄自己使用時的一些坑和專案中的特殊需求。 結合JPA使用時,關於Repository類中第二個引數不再是Long(主鍵id型別),而是複合主

實現“飢漢”與“懶漢”

一般實現飢漢用的是靜態關鍵字。在類載入時,提前載入靜態區的內容。而懶漢則是實現單例模式。靜態內容,在呼叫時才載入。 關於類載入,還可以用class.getInstance(); 和 類.class; 此兩者區別就是靜態載入的時間機。看下程式碼: public class

Linux避免多次sudo時重複輸入密碼的實現

之所以稱為”另一種實現”是因為在網路上搜索類似的標題時,幾乎所有的建議都是去修改/etc/sudoers這個檔案,加上NOPASSWORD這個引數來搞定。 在我們的系統中,因為某種安全原因,只允許使用者在某些情況下輸入密碼來獲取root許可權,但不能總有超級使用者的許可權

利用Guzzle實現PHP異步發送郵件(laravel5.4)

dot 博文 接下來 lar 時間 重點 5.4 targe 占用 前言:第二種實現方法 方法的思路: 此方法的實現需要借助Guzzle這個PHP的HTTP客戶端,用來輕而易舉地發送請求,並集成到我們的WEB服務上(laravel中如何引入guzzle不多說) 使用該方

字符設備驅動寫法—mmap方法操作LED

一個 控制寄存器 abs 提交數據 函數參數 功能 控制 讀取 調用方法 最近在看韋老師的視頻,講解了很多種字符設備的驅動寫法。經過自己的研究之後,我發現還有另外一種寫法,直接在應用層操作,省去了內核中的地址映射部分,使得用戶可以在應用層直接操作LED。 mm