/*
*********************************************************************************************************
*
* 模塊名稱 : 獨立按鍵驅動模塊 (外部輸入IO)
* 文件名稱 : bsp_key.c
* 版 本 : V1.3
* 說 明 : 掃描獨立按鍵,具有軟件濾波機制,具有按鍵FIFO。可以檢測如下事件:
* (1) 按鍵按下
* (2) 按鍵彈起
* (3) 長按鍵
* (4) 長按時自動連發
*
* 修改記錄 :
* 版本號 日期 作者 說明
* V1.0 2020.02.27 armfly KEY0、KEY1 和 KEY2 是低電平有效的,
* 而 KEY_UP 是高電平有效的,並且外部
* 都沒有上下拉電阻,所以,需要在 STM32F4 內部設置上下拉。
*
*
*
*********************************************************************************************************
*/
#include "bsp.h"
/*
正點原子探索者 按鍵口線分配:
KEY0 鍵 : PE4 (低電平表示按下)
KEY1 鍵 : PE3 (低電平表示按下)
KEY2 鍵 : PE2 (低電平表示按下)
KEY_UP 鍵 : PA0 (高電平表示按下)
*/
#define HARD_KEY_NUM 4 /* 實體按鍵個數 */
#define KEY_COUNT (HARD_KEY_NUM + 2) /* 4個獨立建 + 2個組合按鍵 */
/* 使能GPIO時鐘 */
#define ALL_KEY_GPIO_CLK_ENABLE() { \
__HAL_RCC_GPIOA_CLK_ENABLE(); \
__HAL_RCC_GPIOE_CLK_ENABLE(); \
};
/* 依次定義GPIO */
typedef struct
{
GPIO_TypeDef* gpio;
uint16_t pin;
uint8_t ActiveLevel; /* 激活電平 */
}X_GPIO_T;
/* GPIO和PIN定義 */
static const X_GPIO_T s_gpio_list[HARD_KEY_NUM] = {
{GPIOE, GPIO_PIN_4, 0}, /* KEY0 */
{GPIOE, GPIO_PIN_3, 0}, /* KEY1 */
{GPIOE, GPIO_PIN_2, 0}, /* KEY2 */
{GPIOA, GPIO_PIN_0, 1}, /* KEY_UP */
};
/* 定義一個宏函數簡化後續代碼
判斷GPIO引腳是否有效按下
*/
static KEY_T s_tBtn[KEY_COUNT] = {0}; /* 按鍵對應的全局變量結構體 */
static KEY_FIFO_T s_tKey; /* 按鍵FIFO變量,結構體 */
static void bsp_InitKeyVar(void);
static void bsp_InitKeyHard(void);
static void bsp_DetectKey(uint8_t i);
#define KEY_PIN_ACTIVE(id)
/*
*********************************************************************************************************
* 函 數 名: KeyPinActive
* 功能說明: 判斷按鍵是否按下
* 形 參: 無
* 返 回 值: 返回值1 表示按下(導通),0表示未按下(釋放)
*********************************************************************************************************
*/
static uint8_t KeyPinActive(uint8_t _id)
{
uint8_t level;
if ((s_gpio_list[_id].gpio->IDR & s_gpio_list[_id].pin) == 0)
{
level = 0;
}
else
{
level = 1;
}
if (level == s_gpio_list[_id].ActiveLevel)
{
return 1;
}
else
{
return 0;
}
}
/*
*********************************************************************************************************
* 函 數 名: IsKeyDownFunc
* 功能說明: 判斷按鍵是否按下。單鍵和組合鍵區分。單鍵事件不允許有其他鍵按下。
* 形 參: 無
* 返 回 值: 返回值1 表示按下(導通),0表示未按下(釋放)
*********************************************************************************************************
*/
static uint8_t IsKeyDownFunc(uint8_t _id)
{
/* 實體單鍵 */
if (_id < HARD_KEY_NUM)
{
uint8_t i;
uint8_t count = 0;
uint8_t save = 255;
/* 判斷有幾個鍵按下 */
for (i = 0; i < HARD_KEY_NUM; i++)
{
if (KeyPinActive(i))
{
count++;
save = i;
}
}
if (count == 1 && save == _id)
{
return 1; /* 只有1個鍵按下時纔有效 */
}
return 0;
}
/* 組合鍵 K0K1 */
if (_id == HARD_KEY_NUM + 0)
{
if (KeyPinActive(KID_K0) && KeyPinActive(KID_K1))
{
return 1;
}
else
{
return 0;
}
}
/* 組合鍵 K1KU */
if (_id == HARD_KEY_NUM + 1)
{
if (KeyPinActive(KID_K1) && KeyPinActive(KID_KU))
{
return 1;
}
else
{
return 0;
}
}
return 0;
}
/*
*********************************************************************************************************
* 函 數 名: bsp_InitKey
* 功能說明: 初始化按鍵. 該函數被 bsp_Init() 調用。
* 形 參: 無
* 返 回 值: 無
*********************************************************************************************************
*/
void bsp_InitKey(void)
{
bsp_InitKeyVar(); /* 初始化按鍵變量 */
bsp_InitKeyHard(); /* 初始化按鍵硬件 */
}
/*
*********************************************************************************************************
* 函 數 名: bsp_InitKeyHard
* 功能說明: 配置按鍵對應的GPIO
* 形 參: 無
* 返 回 值: 無
*********************************************************************************************************
*/
static void bsp_InitKeyHard(void)
{
GPIO_InitTypeDef gpio_init;
uint8_t i;
/* 第1步:打開GPIO時鐘 */
ALL_KEY_GPIO_CLK_ENABLE();
/* 第2步:配置所有的按鍵GPIO爲浮動輸入模式(實際上CPU復位後就是輸入狀態) */
gpio_init.Mode = GPIO_MODE_INPUT; /* 設置輸入 */
gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* GPIO速度等級 */
for (i = 0; i < HARD_KEY_NUM; i++)
{
if(i<3) /* KEY0 KEY1 KEY2 */
{
gpio_init.Pin = s_gpio_list[i].pin;
gpio_init.Pull = GPIO_PULLUP; /* 上拉電阻 */
HAL_GPIO_Init(s_gpio_list[i].gpio, &gpio_init);
}
else /* KEY_UP */
{
gpio_init.Pin = s_gpio_list[i].pin;
gpio_init.Pull = GPIO_PULLDOWN; /* 下拉電阻 */
HAL_GPIO_Init(s_gpio_list[i].gpio, &gpio_init);
}
}
}
/*
*********************************************************************************************************
* 函 數 名: 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].RepeatSpeed = 0; /* 按鍵連發的速度,0表示不支持連發 */
s_tBtn[i].RepeatCount = 0; /* 連發計數器 */
}
/* 如果需要單獨更改某個按鍵的參數,可以在此單獨重新賦值 */
}
/*
*********************************************************************************************************
* 函 數 名: 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_GetKey2
* 功能說明: 從按鍵FIFO緩衝區讀取一個鍵值。獨立的讀指針。
* 形 參: 無
* 返 回 值: 按鍵代碼
*********************************************************************************************************
*/
uint8_t bsp_GetKey2(void)
{
uint8_t ret;
if (s_tKey.Read2 == s_tKey.Write)
{
return KEY_NONE;
}
else
{
ret = s_tKey.Buf[s_tKey.Read2];
if (++s_tKey.Read2 >= KEY_FIFO_SIZE)
{
s_tKey.Read2 = 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
* 功能說明: 檢測一個按鍵。非阻塞狀態,必須被週期性的調用。
* 形 參: IO的id, 從0開始編碼
* 返 回 值: 無
*********************************************************************************************************
*/
static void bsp_DetectKey(uint8_t i)
{
KEY_T *pBtn;
pBtn = &s_tBtn[i];
if (IsKeyDownFunc(i)) /* 是否有按鍵i按下 */
{
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_DetectFastIO
* 功能說明: 檢測高速的輸入IO. 1ms刷新一次
* 形 參: IO的id, 從0開始編碼
* 返 回 值: 無
*********************************************************************************************************
*/
static void bsp_DetectFastIO(uint8_t i)
{
KEY_T *pBtn;
pBtn = &s_tBtn[i];
if (IsKeyDownFunc(i))
{
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->State == 1)
{
pBtn->State = 0;
/* 發送按鈕彈起的消息 */
bsp_PutKey((uint8_t)(3 * i + 2));
}
pBtn->LongCount = 0;
pBtn->RepeatCount = 0;
}
}
/*
*********************************************************************************************************
* 函 數 名: bsp_KeyScan10ms
* 功能說明: 掃描所有按鍵。非阻塞,被systick中斷週期性的調用,10ms一次
* 形 參: 無
* 返 回 值: 無
*********************************************************************************************************
*/
void bsp_KeyScan10ms(void)
{
uint8_t i;
for (i = 0; i < KEY_COUNT; i++)
{
bsp_DetectKey(i);
}
}
/*
*********************************************************************************************************
* 函 數 名: bsp_KeyScan1ms
* 功能說明: 掃描所有按鍵。非阻塞,被systick中斷週期性的調用,1ms一次.
* 形 參: 無
* 返 回 值: 無
*********************************************************************************************************
*/
void bsp_KeyScan1ms(void)
{
uint8_t i;
for (i = 0; i < KEY_COUNT; i++)
{
bsp_DetectFastIO(i);
}
}
/***************************** 安富萊電子 www.armfly.com (END OF FILE) *********************************/
/* 例程 */
/* 按鍵濾波和檢測由後臺systick中斷服務程序實現,我們只需要調用bsp_GetKey讀取鍵值即可。 */
// uint8_t ucKeyCode; /* 按鍵代碼 */
// while(1)
// {
// ucKeyCode = bsp_GetKey(); /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */
// if (ucKeyCode != KEY_NONE)
// {
// switch (ucKeyCode)
// {
// case KEY_DOWN_K0: /* K0鍵按下 */
// bsp_LedToggle(0);
// break;
// case KEY_DOWN_K1: /* K0鍵按下 */
// bsp_LedToggle(1);
// break;
// case SYS_DOWN_K1KU: /* K1KU鍵按下 */
// bsp_LedOff(1);
// bsp_LedOff(0);
// break;
// default:
// /* 其它的鍵值不處理 */
// break;
// }
//
// }
// }
/*
*********************************************************************************************************
*
* 模塊名稱 : 按鍵驅動模塊
* 文件名稱 : bsp_key.h
* 版 本 : V1.0
* 說 明 : 頭文件
*
* Copyright (C), 2013-2014, 安富萊電子 www.armfly.com
*
*********************************************************************************************************
*/
#ifndef __BSP_KEY_H
#define __BSP_KEY_H
/* 根據應用程序的功能重命名按鍵宏 */
#define KEY_DOWN_K0 KEY_1_DOWN /* KEY0 鍵 */
#define KEY_UP_K0 KEY_1_UP
#define KEY_LONG_K0 KEY_1_LONG
#define KEY_DOWN_K1 KEY_2_DOWN /* KEY1 鍵 */
#define KEY_UP_K1 KEY_2_UP
#define KEY_LONG_K1 KEY_2_LONG
#define KEY_DOWN_K2 KEY_3_DOWN /* KEY2 鍵 */
#define KEY_UP_K2 KEY_3_UP
#define KEY_LONG_K2 KEY_3_LONG
#define KEY_DOWN_KU KEY_4_DOWN /* KEY_UP 鍵 */
#define KEY_UP_KU KEY_4_UP
#define KEY_LONG_KU KEY_4_LONG
#define SYS_DOWN_K0K1 KEY_5_DOWN /* K0 K1 組合鍵 */
#define SYS_UP_K0K1 KEY_5_UP
#define SYS_LONG_K0K1 KEY_5_LONG
#define SYS_DOWN_K1KU KEY_6_DOWN /* K1 KU 組合鍵 */
#define SYS_UP_K1KU KEY_6_UP
#define SYS_LONG_K1KU KEY_6_LONG
/* 按鍵ID, 主要用於bsp_KeyState()函數的入口參數 */
typedef enum
{
KID_K0 = 0,
KID_K1,
KID_K2,
KID_KU,
}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;
/*
定義鍵值代碼, 必須按如下次序定時每個鍵的按下、彈起和長按事件
推薦使用enum, 不用#define,原因:
(1) 便於新增鍵值,方便調整順序,使代碼看起來舒服點
(2) 編譯器可幫我們避免鍵值重複。
*/
typedef enum
{
KEY_NONE = 0, /* 0 表示按鍵事件 */
KEY_1_DOWN, /* 1鍵按下 */
KEY_1_UP, /* 1鍵彈起 */
KEY_1_LONG, /* 1鍵長按 */
KEY_2_DOWN, /* 2鍵按下 */
KEY_2_UP, /* 2鍵彈起 */
KEY_2_LONG, /* 2鍵長按 */
KEY_3_DOWN, /* 3鍵按下 */
KEY_3_UP, /* 3鍵彈起 */
KEY_3_LONG, /* 3鍵長按 */
KEY_4_DOWN, /* 4鍵按下 */
KEY_4_UP, /* 4鍵彈起 */
KEY_4_LONG, /* 4鍵長按 */
/* 組合鍵 */
KEY_5_DOWN, /* 5鍵按下 */
KEY_5_UP, /* 5鍵彈起 */
KEY_5_LONG, /* 5鍵長按 */
KEY_6_DOWN, /* 6鍵按下 */
KEY_6_UP, /* 6鍵彈起 */
KEY_6_LONG, /* 6鍵長按 */
}KEY_ENUM;
/* 按鍵FIFO用到變量 */
#define KEY_FIFO_SIZE 10
typedef struct
{
uint8_t Buf[KEY_FIFO_SIZE]; /* 鍵值緩衝區 */
uint8_t Read; /* 緩衝區讀指針1 */
uint8_t Write; /* 緩衝區寫指針 */
uint8_t Read2; /* 緩衝區讀指針2 */
}KEY_FIFO_T;
/* 供外部調用的函數聲明 */
void bsp_InitKey(void);
void bsp_KeyScan10ms(void);
void bsp_PutKey(uint8_t _KeyCode);
uint8_t bsp_GetKey(void);
uint8_t bsp_GetKey2(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
/***************************** 安富萊電子 www.armfly.com (END OF FILE) *********************************/