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長按

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

 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.c文件中核心函數是bsp_DetectKey(),這個函數先通過函數指針調用檢測按鍵GPIO口狀態的函數,判斷是否有按鍵按下。

       檢測到按鍵按下時先進行相應的消抖操作,消抖持續時間通過bsp_key.h中定義的宏 KEY_FILTER_TIME設置。確定按鍵按下后,如果在此之前按鍵是松開的就把相應標志位置位,把按鍵按下的狀態值寫入到FIFO

中。如果使能了檢測按鍵長按功能(LongCount>0),程序會繼續檢測按鍵是否達到長按時間,如果到達長按時間,將長按狀態值寫入到FIFO中,在繼續檢測是否支持長按時狀態連續發送功能,如果使能了長按時連

續發送功能(RepeatSpeed>0),達到周期發送時間時,會繼續寫入FIFO按鍵按下狀態值。

  檢測到按鍵釋放時,首先也會進行相應濾波處理。確認按鍵松開后,若之前按鍵處於按下狀態,會將檢查到的按鍵松開狀況寫入到FIFO中,然后清除長按、重復發生的計數值,為下次查詢做准備。

 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM