1. 程式人生 > >stm32按鍵FIFO的實現

stm32按鍵FIFO的實現

學習目標:

  1、理解FIFO的基本概念和設計按鍵FIFO的意義

    2、寫出實現按鍵FIFO的程式碼


 1、設計按鍵FIFO的優點

  要介紹實現按鍵FIFO的優點,首先要了解FIFO的一些基本概念。FIFO即First In First Out,是一種先進先出的資料快取方式,例如在超市購物之後我們會提著滿滿的購物車來到收銀臺排在結賬隊伍的最後等待付款,先排隊的客戶先付款離開,後面排隊的只有等待前面付款離開才能進行付款。說白了FIFO就是這樣一種先進先出機制,先存入的資料在讀取時最先被讀取到。

  設計按鍵FIFO注意有三個方面的優點(來自於安富萊電子Eric2013大佬總結):

  1、可以有效記錄按鍵事件的發生,特別是系統要實現記錄按鍵按下、鬆開、長按時,使用FIFO來實現是一種不錯的選擇方式。

  2、系統是阻塞的,這樣系統在檢測到按鍵按下的情況,由於機械按鍵抖動的原因不需要在這裡等待一段時間,然後在確定按鍵是否正常按下。

  3、按鍵FIFO程式在系統定時器中定時檢測按鍵狀態,確認按鍵按下後將狀態寫入FIFO中,不一定在主程式中一直做檢測,這樣可以有效降低系統資源的消耗。

2、按鍵的硬體設計

 

  按鍵的原理圖如上圖所示,對於KEY0~KEY2這三個按鍵,一端接地,另一端連線stm32的GPIO埠。當按鍵按下時相應的IO口被拉低,如果把GPIO口配置為輸入模式,此時讀取相應的IO口電平,就可以檢測到按鍵是否被按下。對於KEY_UP按鍵則是與前面三個按鍵相反,IO口配置為輸入模式時,讀取到高電平時表示按鍵按下。因為機械固有的物理特性,按鍵按下內部彈簧片在瞬間接觸的時候會有力學的回彈,造成2-8毫秒內訊號不穩定,所以在設計檢測機械按鍵是否按下的程式時,應考慮到按鍵消抖問題。

3、按鍵FIFO程式碼的設計

3.1 按鍵FIFO程式碼主要框圖

 

bsp_KeyScan()檢測到按鍵狀態時以3*x+1關係計算出(x代表按鍵編號)寫入FIFO值,例如:

FIFO中讀取值1---------------->按鍵1按下

FIFO中讀取值2---------------->按鍵1彈開

FIFO中讀取值3---------------->按鍵1長按

 3.2 按鍵FIFO程式碼實現

 bsp_key.c實現

#include "bsp_key.h"

/*
    開發板 按鍵口線分配:
        K0 鍵      : PH3   (低電平表示按下)
        K1 鍵      : PH2   (低電平表示按下)
        K2 鍵      : PC14  (低電平表示按下)
        WAKE_UP鍵  : PA0   (高電平表示按下)
*/ /* 按鍵連線GPIO對應RCC時鐘 */ #define RCC_ALL_KEY (RCC_AHB1Periph_GPIOH|RCC_AHB1Periph_GPIOC|RCC_AHB1Periph_GPIOA) /* 按鍵0的GPIO----------->GPIOH3 */ #define KEY0_GPIO_PORT GPIOH #define KEY0_GPIO_PIN GPIO_Pin_3 /* 按鍵1的GPIO----------->GPIOH2 */ #define KEY1_GPIO_PORT GPIOH #define KEY1_GPIO_PIN GPIO_Pin_2 /* 按鍵2的GPIO----------->GPIOC13 */ #define KEY2_GPIO_PORT GPIOC #define KEY2_GPIO_PIN GPIO_Pin_13 /* 按鍵WK_UP的GPIO----------->GPIOA0 */ #define KEY_WKUP_GPIO_PORT GPIOA #define KEY_WKUP_GPIO_PIN GPIO_Pin_0 static KEY_T s_tBtn[KEY_COUNT]; static KEY_FIFO_T s_tKey; /* 按鍵FIFO變數,結構體 */ static void bsp_InitKeyVar(void); static void bsp_InitKeyHard(void); static void bsp_DetectKey(uint8_t i); /* ********************************************************************************************************* * 函 數 名: IsKeyDownX * 功能說明: 判斷按鍵是否按下 * 形 參: 無 * 返 回 值: 返回值1 表示按下,0表示未按下 ********************************************************************************************************* */ static uint8_t IsKeyDown0(void) {if((KEY0_GPIO_PORT->IDR & KEY0_GPIO_PIN) == 0) return 1; else return 0;}; static uint8_t IsKeyDown1(void) {if((KEY1_GPIO_PORT->IDR & KEY1_GPIO_PIN) == 0) return 1; else return 0;}; static uint8_t IsKeyDown2(void) {if((KEY2_GPIO_PORT->IDR & KEY2_GPIO_PIN) == 0) return 1; else return 0;}; static uint8_t IsKeyDown3(void) {if((KEY_WKUP_GPIO_PORT->IDR & KEY_WKUP_GPIO_PIN) == KEY_WKUP_GPIO_PIN) return 1; else return 0;}; /* ********************************************************************************************************* * 函 數 名: bsp_InitKey * 功能說明: 配置按鍵相關的GPIO,該函式被 bsp_Init()呼叫。 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_InitKey(void) { bsp_InitKeyVar(); /* 初始化按鍵變數 */ bsp_InitKeyHard(); /* 初始化按鍵硬體 */ } /* ********************************************************************************************************* * 函 數 名: bsp_InitKeyHard * 功能說明: 配置按鍵相關的GPIO,該函式被 bsp_InitKey()呼叫。 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void bsp_InitKeyHard(void) { GPIO_InitTypeDef GPIO_InitStructure; /* 開啟按鍵連線GPIO的RCC時鐘 */ RCC_AHB1PeriphClockCmd(RCC_ALL_KEY, ENABLE); /* 設定按鍵連線GPIO為浮空輸入模式 */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; /* 設為輸入口 */ GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; /* 上拉電阻 */ GPIO_InitStructure.GPIO_Pin = KEY0_GPIO_PIN; GPIO_Init(KEY0_GPIO_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN; GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = KEY2_GPIO_PIN; GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = KEY_WKUP_GPIO_PIN; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; /* 下拉電阻 */ GPIO_Init(KEY_WKUP_GPIO_PORT, &GPIO_InitStructure); } /* ********************************************************************************************************* * 函 數 名: bsp_InitKeyVar * 功能說明: 初始化按鍵變數 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void bsp_InitKeyVar(void) { uint8_t i; /* 對按鍵FIFO讀寫指標清零 */ s_tKey.Read = 0; s_tKey.Write = 0; s_tKey.Read2 = 0; /* 給每個按鍵結構體成員變數賦一組預設值 */ for (i = 0; i < KEY_COUNT; i++) { s_tBtn[i].LongTime = KEY_LONG_TIME; /* 長按時間 0 表示不檢測長按鍵事件 */ s_tBtn[i].Count = KEY_FILTER_TIME / 2; /* 計數器設定為濾波時間的一半 */ s_tBtn[i].State = 0; /* 按鍵預設狀態,0為未按下 */ //s_tBtn[i].KeyCodeDown = 3 * i + 1; /* 按鍵按下的鍵值程式碼 */ //s_tBtn[i].KeyCodeUp = 3 * i + 2; /* 按鍵彈起的鍵值程式碼 */ //s_tBtn[i].KeyCodeLong = 3 * i + 3; /* 按鍵被持續按下的鍵值程式碼 */ s_tBtn[i].RepeatSpeed = 0; /* 按鍵連發的速度,0表示不支援連發 */ s_tBtn[i].RepeatCount = 0; /* 連發計數器 */ } /* 如果需要單獨更改某個按鍵的引數,可以在此單獨重新賦值 */ /* 比如,我們希望按鍵1按下超過1秒後,自動重發相同鍵值 */ // s_tBtn[KID_JOY_U].LongTime = 100; // s_tBtn[KID_JOY_U].RepeatSpeed = 5; /* 每隔50ms自動傳送鍵值 */ /* 判斷按鍵按下的函式 */ s_tBtn[0].IsKeyDownFunc = IsKeyDown0; s_tBtn[1].IsKeyDownFunc = IsKeyDown1; s_tBtn[2].IsKeyDownFunc = IsKeyDown2; s_tBtn[3].IsKeyDownFunc = IsKeyDown3; } /* ********************************************************************************************************* * 函 數 名: bsp_PutKey * 功能說明: 將1個鍵值壓入按鍵FIFO緩衝區。可用於模擬一個按鍵。 * 形 參: _KeyCode : 按鍵程式碼 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_PutKey(uint8_t _KeyCode) { s_tKey.Buf[s_tKey.Write] = _KeyCode; if(++s_tKey.Write >= KEY_FIFO_SIZE) { s_tKey.Write = 0; } } /* ********************************************************************************************************* * 函 數 名: bsp_GetKey * 功能說明: 從按鍵FIFO緩衝區讀取一個鍵值。 * 形 參: 無 * 返 回 值: 按鍵程式碼 ********************************************************************************************************* */ uint8_t bsp_GetKey(void) { uint8_t ret; if (s_tKey.Read == s_tKey.Write) { return KEY_NONE; } else { ret = s_tKey.Buf[s_tKey.Read]; if(++s_tKey.Read >= KEY_FIFO_SIZE) { s_tKey.Read = 0; } return ret; } }/* ********************************************************************************************************* * 函 數 名: bsp_GetKeyState * 功能說明: 讀取按鍵的狀態 * 形 參: _ucKeyID : 按鍵ID,從0開始 * 返 回 值: 1 表示按下, 0 表示未按下 ********************************************************************************************************* */ uint8_t bsp_GetKeyState(KEY_ID_E _ucKeyID) { return s_tBtn[_ucKeyID].State; } /* ********************************************************************************************************* * 函 數 名: bsp_SetKeyParam * 功能說明: 設定按鍵引數 * 形 參:_ucKeyID : 按鍵ID,從0開始 * _LongTime : 長按事件時間 * _RepeatSpeed : 連發速度 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_SetKeyParam(uint8_t _ucKeyID, uint16_t _LongTime, uint8_t _RepeatSpeed) { s_tBtn[_ucKeyID].LongTime = _LongTime; /* 長按時間 0 表示不檢測長按鍵事件 */ s_tBtn[_ucKeyID].RepeatSpeed = _RepeatSpeed; /* 按鍵連發的速度,0表示不支援連發 */ s_tBtn[_ucKeyID].RepeatCount = 0; /* 連發計數器 */ } /* ********************************************************************************************************* * 函 數 名: bsp_ClearKey * 功能說明: 清空按鍵FIFO緩衝區 * 形 參:無 * 返 回 值: 按鍵程式碼 ********************************************************************************************************* */ void bsp_ClearKey(void) { s_tKey.Read = s_tKey.Write; } /* ********************************************************************************************************* * 函 數 名: bsp_DetectKey * 功能說明: 檢測一個按鍵。非阻塞狀態,必須被週期性的呼叫。 * 形 參: 按鍵結構變數指標 * 返 回 值: 無 ********************************************************************************************************* */ static void bsp_DetectKey(uint8_t i) { KEY_T *pBtn; /* 如果沒有初始化按鍵函式,則報錯 if (s_tBtn[i].IsKeyDownFunc == 0) { printf("Fault : DetectButton(), s_tBtn[i].IsKeyDownFunc undefine"); } */ pBtn = &s_tBtn[i]; if (pBtn->IsKeyDownFunc()) { if (pBtn->Count < KEY_FILTER_TIME) { pBtn->Count = KEY_FILTER_TIME; } else if(pBtn->Count < 2 * KEY_FILTER_TIME) { pBtn->Count++; } else { if (pBtn->State == 0) { pBtn->State = 1; /* 傳送按鈕按下的訊息 */ bsp_PutKey((uint8_t)(3 * i + 1)); } if (pBtn->LongTime > 0) { if (pBtn->LongCount < pBtn->LongTime) { /* 傳送按鈕持續按下的訊息 */ if (++pBtn->LongCount == pBtn->LongTime) { /* 鍵值放入按鍵FIFO */ bsp_PutKey((uint8_t)(3 * i + 3)); } } else { if (pBtn->RepeatSpeed > 0) { if (++pBtn->RepeatCount >= pBtn->RepeatSpeed) { pBtn->RepeatCount = 0; /* 常按鍵後,每隔10ms傳送1個按鍵 */ bsp_PutKey((uint8_t)(3 * i + 1)); } } } } } } else { if(pBtn->Count > KEY_FILTER_TIME) { pBtn->Count = KEY_FILTER_TIME; } else if(pBtn->Count != 0) { pBtn->Count--; } else { if (pBtn->State == 1) { pBtn->State = 0; /* 傳送按鈕彈起的訊息 */ bsp_PutKey((uint8_t)(3 * i + 2)); } } pBtn->LongCount = 0; pBtn->RepeatCount = 0; } } /* ********************************************************************************************************* * 函 數 名: bsp_KeyScan * 功能說明: 掃描所有按鍵。非阻塞,被systick中斷週期性的呼叫 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_KeyScan(void) { uint8_t i; for (i = 0; i < KEY_COUNT; i++) { bsp_DetectKey(i); } }

 bsp_key.h實現

#ifndef __BSP_KEY_H
#define __BSP_KEY_H

#define KEY_COUNT    4                           /* 按鍵個數, 4個獨立按鍵 */

/* 根據應用程式的功能重新命名按鍵巨集 */
#define KEY_DOWN_K0      KEY_0_DOWN
#define KEY_UP_K0        KEY_0_UP
#define KEY_LONG_K0      KEY_0_LONG

#define KEY_DOWN_K1      KEY_1_DOWN
#define KEY_UP_K1        KEY_1_UP
#define KEY_LONG_K1      KEY_1_LONG

#define KEY_DOWN_K2      KEY_2_DOWN
#define KEY_UP_K2        KEY_2_UP
#define KEY_LONG_K2      KEY_2_LONG

#define KEY_DOWN_WP      KEY_4_DOWN        /* 上 */
#define KEY_UP_WP        KEY_4_UP
#define KEY_LONG_WP      KEY_4_LONG


/* 按鍵ID, 主要用於bsp_KeyState()函式的入口引數 */
typedef enum
{
    KID_K1 = 0,
    KID_K2,
    KID_K3,
    KID_WAKE_UP,
}KEY_ID_E;

/*
    按鍵濾波時間50ms, 單位10ms。
    只有連續檢測到50ms狀態不變才認為有效,包括彈起和按下兩種事件
    即使按鍵電路不做硬體濾波,該濾波機制也可以保證可靠地檢測到按鍵事件
*/
#define KEY_FILTER_TIME   5
#define KEY_LONG_TIME     100            /* 單位10ms, 持續1秒,認為長按事件 */

/*
    每個按鍵對應1個全域性的結構體變數。
*/
typedef struct
{
    /* 下面是一個函式指標,指向判斷按鍵手否按下的函式 */
    uint8_t (*IsKeyDownFunc)(void); /* 按鍵按下的判斷函式,1表示按下 */

    uint8_t  Count;          /* 濾波器計數器 */
    uint16_t LongCount;      /* 長按計數器 */
    uint16_t LongTime;       /* 按鍵按下持續時間, 0表示不檢測長按 */
    uint8_t  State;          /* 按鍵當前狀態(按下還是彈起) */
    uint8_t  RepeatSpeed;    /* 連續按鍵週期 */
    uint8_t  RepeatCount;    /* 連續按鍵計數器 */
}KEY_T;

/*
    定義鍵值程式碼, 必須按如下次序定時每個鍵的按下、彈起和長按事件,檢測按鍵時使用
*/
typedef enum
{
    KEY_NONE = 0,            /* 0 表示按鍵事件 */

    KEY_0_DOWN,                /* 1鍵按下 */
    KEY_0_UP,                /* 1鍵彈起 */
    KEY_0_LONG,                /* 1鍵長按 */

    KEY_1_DOWN,                /* 2鍵按下 */
    KEY_1_UP,                /* 2鍵彈起 */
    KEY_1_LONG,                /* 2鍵長按 */

    KEY_2_DOWN,                /* 3鍵按下 */
    KEY_2_UP,                /* 3鍵彈起 */
    KEY_2_LONG,                /* 3鍵長按 */

    KEY_3_DOWN,                /* 4鍵按下 */
    KEY_3_UP,                /* 4鍵彈起 */
    KEY_3_LONG,                /* 4鍵長按 */
}KEY_ENUM;

/* 按鍵FIFO用到變數 */
#define KEY_FIFO_SIZE    10
typedef struct
{
    uint8_t Buf[KEY_FIFO_SIZE];        /* 鍵值緩衝區 */
    uint8_t Read;                    /* 緩衝區讀指標1 */
    uint8_t Write;                    /* 緩衝區寫指標 */
}KEY_FIFO_T;

/* 供外部呼叫的函式宣告 */
void bsp_InitKey(void);
void bsp_KeyScan(void);
void bsp_PutKey(uint8_t _KeyCode);
uint8_t bsp_GetKey(void);
uint8_t bsp_GetKeyState(KEY_ID_E _ucKeyID);
void bsp_SetKeyParam(uint8_t _ucKeyID, uint16_t _LongTime, uint8_t  _RepeatSpeed);
void bsp_ClearKey(void);

#endif

 注意:在配置按鍵GPIO相應模式時,如果按鍵硬體設計沒有電阻上拉,那麼在配置GPIO口時必須將GPIO口內部配置成上拉狀態,否則對讀取按鍵結構有影響!