原文: https://blog.csdn.net/qq_40102829/article/details/108927767
按鍵處理
測試平台:STM32F103C8T6
庫版本:官方標准庫3.5.0版本
按鍵:機械按鍵(需消除抖動影響)或觸摸按鍵,
單片機硬件需求:定時器1個,IO口外部中斷功能
按鍵處理是單片機底層驅動的一個基礎應用,本文說的按鍵處理為獨立按鍵的單擊,連擊和長按的識別(不是按鍵矩陣的實現)。在51單片機入門時,通常是通過主循環查詢I/O口狀態來進行按鍵識別的,但是占用資源較多,而且實時性較差;進階的會使用定時器進行識別,關於定時器實現按鍵檢測的例程,網上有很多,但是觀看下來發現能詳細描述獨立按鍵的不同動作識別的例程不多,故寫此文
注: 文中首次出現的代碼塊會標注[xxx.c]或[xxx.h],表明該代碼是屬於對應的文件,未標注的即為重復出現的
目錄
按鍵處理
1、電路設計
2、程序設計
2.1、原理
2.2、基礎配置
2.3、按鍵處理結構體
2.4、外部中斷處理函數
2.5、按鍵處理函數
2.5.1、按鍵按下處理
2.5.2、按鍵松開處理
2.6、按鍵事件類型識別
3、測試結果
4、總結
1、電路設計
獨立按鍵的電路設計很簡單,這是使用的是開發板上的按鍵,IO到按鍵后直接下拉到地,那么按鍵按下時為低電平,常態為高電平,也就是說IO口需要配置成內部上拉。
需要注意的是,這種設計是不夠完善的,對於機械按鍵最好可以加上一個旁路電容來消除抖動,或者在程序里需要加上消抖延遲,本文是基於觸摸按鍵進行測試的,因此解決了抖動問題。
2、程序設計
2.1、原理
獨立按鍵處理無非是對於IO口電平的識別,但是要實現比較不同動作的識別,還需要對按鍵的時間進行判斷,理清按鍵時間和按鍵狀態的關系有助於接下來的程序編寫
/*
常態為高電平
按鍵動作 兩次動作間隔 按鍵動作
| continus | idle |
_____________ _____________ _____________
| | | |
|_____________| |_____________|
*/
1
2
3
4
5
6
7
8
9
按鍵時間分為兩種【按下后的持續時間:continus】【按鍵松開后的空閑時間:idle】,這兩個時間可以根據實際測試確定
這里作兩個設定:
1、當【continus】在500ms內時,為短按,否則為長按
2、當【idle】小於400ms,為連擊動作,否則為可認為一次完整的按鍵事件結束
對這兩個時間進行組合判斷,就可以識別出按鍵的事件類型【單擊、連擊、長按】
短按和長按的通過【continus】來區分
單擊是連擊的一種特殊狀態,都划分為短按,可以通過【idle】的時間來區分,可設置一個變量來記錄連擊的次數
2.2、基礎配置
主要包括IO口配置,外部中斷配置、定時器配置
[key_process.c]
/*******************************************************************************
* @brief 以外部中斷形式配置按鍵GPIO
* @param 無
* @retval 無
******************************************************************************/
void EXTI_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
/* GPIO config*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //內部上拉輸入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* EXTI line mode config */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource8);
EXTI_InitStructure.EXTI_Line = EXTI_Line8;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; //上升下降沿中斷
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
▲這里使用的是PA8管腳,內部上拉,外部中斷選擇上下沿都觸發,也就是說按鍵按下和松開都會進行一次觸發
[key_process.c]
/*******************************************************************************
* @brief 計數定時器配置函數
* @param TIMx 使用的定時器
* @retval 無
******************************************************************************/
void TIMx_Config(TIM_TypeDef* TIMx)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 自動重裝載寄存器周的值(計數值)
TIM_TimeBaseStructure.TIM_Prescaler= 71; // 時鍾預分頻數為 71,則驅動計數器的時鍾 CK_CNT = CK_INT / (71+1)=1M
TIM_TimeBaseStructure.TIM_Period=1000; // 累計 TIM_Period 個頻率后產生一個更新或者中斷
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; // 時鍾分頻因子 ,基本定時器沒有,不用管
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; // 計數器計數模式,基本定時器只能向上計數,沒有計數模式的設置
// 初始化定時器
TIM_TimeBaseInit(TIMx, &TIM_TimeBaseStructure);
TIM_ARRPreloadConfig(TIMx, ENABLE); //使能TIM重載寄存器ARR
TIM_ClearFlag(TIMx, TIM_FLAG_Update); // 清除計數器中斷標志位
TIM_ITConfig(TIMx,TIM_IT_Update,ENABLE); // 開啟計數器中斷
TIM_Cmd(TIMx, DISABLE); // 關閉定時器的時鍾,等待使用
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
▲為了后面方便計算,這里定時器配置為1ms觸發一次,當然也可以設定為其他觸發時間,只要后面的時間數據計算好就行
[key_process.c]
/*******************************************************************************
* @brief 按鍵外部中斷NVIC配置,與使用的GPIO保持一致
* @param 無
* @retval 無
******************************************************************************/
static void NVIC_EXTI_GPIO_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Configure one bit for preemption priority */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* 配置中斷源 */
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/*******************************************************************************
* @brief TIM2中斷NVIC配置,與計數使用的定時器保持一致
* @param 無
* @retval 無
******************************************************************************/
static void NVIC_TIM2_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// 設置中斷組為 0
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
// 設置中斷來源
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
▲對於NVIC的配置就隨各位喜歡了
2.3、按鍵處理結構體
以下是完整的頭文件(除去函數聲明等)
[key_process.h]
#define KEY_PIN GPIO_Pin_8 //注意查看中斷服務函數是否與該管腳對應
//定時器為1ms定時
#define KEY_TIME_IDLE 400 //按鍵動作空閑時間
#define KEY_TIME_CONTINUS 500 //按鍵動作持續時間
#define KEY_TIME_OUT 2000 //按鍵超時
//事件類型
#define EVENT_NONE_CLICK 0x00 //無動作
#define EVENT_SHORT_CLICK 0x01 //短按
#define EVENT_DOUBLE_CLICK 0x02 //連擊
#define EVENT_LONG_CLICK 0x03 //長按
//按鍵狀態
#define KEY_STATE_IDLE 0x00 //空閑
#define KEY_STATE_PRESS 0x01 //按下
#define KEY_STATE_RELEASE 0x02 //松開
typedef struct{
struct{
u8 check :1; //查詢標志
u8 key_state :2; //單次按鍵動作【0:無動作/1:按下/2:松開】
u8 once_event :1; //一次完整按鍵事件,置1時可以進行事件識別,必須手動清零
u8 press_time :1; //單次按鍵動作持續時間【0:短按/1:長按】
}flag;
u8 event_current_type :4; //當前按鍵事件類型【00:無動作】【01:短按】【10:連擊】【11:長按】
u8 event_previous_type :4; //之前按鍵事件類型,需手動更新
u8 press_cnt; //按下次數,復位值為1,最大連擊次數為256次
u16 time_idle; //按鍵空閑時間計數器
u16 time_continus; //按鍵動作持續時間計數器
}KEY_PROCESS_TypeDef;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
▲每個變量的作用在注釋里已經標明,這里對單擊連擊長按表述為一次完整的【事件類型】
2.4、外部中斷處理函數
[key_process.c]
/*******************************************************************************
* @brief This function handles EXTI9_5 interrupt request.與使用的GPIO保持一致
* @param 無
* @retval 無
******************************************************************************/
void EXTI9_5_IRQHandler(void)
{
// delay_ms(50); //消抖,機械按鍵需要消抖
EXTI->PR = EXTI_Line8; //清除中斷標志位
if((GPIOA->IDR & KEY_PIN) == 0){
key.flag.key_state = KEY_STATE_PRESS; //按下
key.flag.check = 1;
key.time_continus = 0; //按鍵持續時間置零,准備開始計時
}
else{
key.flag.key_state = KEY_STATE_RELEASE; //松開
key.flag.check = 1;
key.time_idle = 0; //按鍵空閑時間置零,准備開始計時
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
▲如果是機械按鍵,那么外部中斷函數里的消抖是必須的
在配置外部中斷功能時,是使用上下沿中斷觸發的,因此在每次觸發時都需要讀取IO口的狀態,以判斷此次觸發是上升沿還是下降沿觸發,對【key.flag.key_state】賦值
【key.flag.check】是內部查詢標志,按下或松開都觸發
【key.time_continus】和【key.time_idle】分別在按下和松開時都清零,其計數是在定時器里實現的
2.5、按鍵處理函數
包含定時器中斷函數、按鍵處理函數、按鍵事件識別函數(該函數也可放在主循環)
[key_process.c]
/*******************************************************************************
* @brief This function handles TIM2 interrupt request.與計數使用的定時器保持一致
* @param 無
* @retval 無
******************************************************************************/
void TIM2_IRQHandler(void)
{
// TIM_ClearITPendingBit(TIM2 , TIM_FLAG_Update);
TIM2->SR = 0x0000; //清除中斷標志位
KEY_Process();
KEY_Scan();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
▲使用定時器TIM2,這里【KEY_Process】是核心的判斷函數,定時器設定為1ms觸發一次,也就是【KEY_Process】每1ms調用一次,這點很關鍵
【KEY_Scan】是按鍵事件類型識別函數
[key_process.c]
/*******************************************************************************
* @brief 按鍵處理函數
* @param 無
* @retval 無
******************************************************************************/
void KEY_Process(void)
{
switch(key.flag.key_state){
//【按鍵按下】
case KEY_STATE_PRESS :
//在按鍵按下時從0開始計時,直到超時
if(key.time_continus < KEY_TIME_OUT){key.time_continus++;}
//發生長按事件
if(key.time_continus > KEY_TIME_CONTINUS){
if(key.event_current_type != EVENT_NONE_CLICK){ //識別長按前的按鍵事件
if(key.press_cnt > 1){key.press_cnt--;}
key.flag.once_event = 1;
}
else{
key.flag.press_time = 1; //【0:短按/1:長按】識別此次為長按
key.flag.key_state = KEY_STATE_IDLE; //主動結束按下動作,進入無動作狀態,保證在長按的按下過程中就識別出長按事件
key.event_current_type = EVENT_LONG_CLICK; //分配當前按鍵事件類型
key.flag.once_event = 1; //產生按鍵事件
key.press_cnt = 1;
key.time_idle = KEY_TIME_OUT; //按鍵空閑時間超時
}
}
//按下時進行一次判斷
if(key.flag.check){
key.flag.check = 0;
if(!key.flag.press_time){ //判斷上一次按鍵類型
//判斷上一次按鍵動作空閑時間
if(key.time_idle < KEY_TIME_IDLE){ //若上一次按鍵動作后的空閑時間在規定時間內,說明發生了連擊事件,一次完整的按鍵事件還未結束
key.press_cnt++;
}else{key.press_cnt = 1;}
}
key.flag.press_time = 0; //【0:短按/1:長按】此次為短按
}
break;
//【按鍵松開】,若是長按,不會進入該判斷
case KEY_STATE_RELEASE:
//在按鍵按下時從0開始計時,直到超時
if(key.time_idle < KEY_TIME_OUT){key.time_idle++;}
//松開時進行一次判斷
if(key.flag.check){
key.flag.check = 0;
//判斷此次按鍵動作
if(!key.flag.press_time){ ///長按會屏蔽短按
if(key.press_cnt > 1){ //連擊事件
key.event_current_type = EVENT_DOUBLE_CLICK; //分配按鍵事件類型
}
else{ //單擊事件
key.event_current_type = EVENT_SHORT_CLICK; //分配按鍵事件類型
key.press_cnt = 1; //連擊次數置1
}
}
}
//按鍵松開后判斷此次按鍵動作后的空閑時間,從而判斷此次動作是否結束
if(key.time_idle > KEY_TIME_IDLE){ //空閑時間超時,認為一次完整的按鍵事件結束
if(!key.flag.press_time){ //松開前是短按標志,則產生按鍵事件,這里是為了屏蔽長按后的松手動作
key.flag.once_event = 1; //產生按鍵事件
key.flag.key_state = KEY_STATE_IDLE;//進入無動作狀態
}
}
break;
//【按鍵無動作】
default :
break;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
▲按鍵處理函數是一個switch判斷,分為三部分:
1、按鍵按下
2、按鍵松開
3、按鍵無動作(不做任何處理)
switch的判斷值【key.flag.key_state】是在外部中斷函數中進行賦值的,是人為控制的
2.5.1、按鍵按下處理
按下時分為三部分處理
一、【key.time_continus】計時,在2000ms超時前進行自加
if(key.time_continus < KEY_TIME_OUT){key.time_continus++;}
1
這里就涉及到其前邊提到的【KEY_Process()】每1ms調用一次
當按鍵按下且未松開時:
【key.flag.key_state】在外部中斷函數中賦值為【KEY_STATE_PRESS】,【key.time_continus】清零
退出外部中斷函數后,在定時器中斷內,switch判斷會進入【KEY_STATE_PRESS】
【key.time_continus】從零開始計數,每1ms加一
二、【key.time_continus】超時判斷(長按 / 短按)
if(key.time_continus > KEY_TIME_CONTINUS){
if(key.event_current_type != EVENT_NONE_CLICK){
if(key.press_cnt > 1){key.press_cnt--;}
key.flag.once_event = 1;
}
else{
key.flag.press_time = 1; //【0:短按/1:長按】識別此次為長按
key.flag.key_state = KEY_STATE_IDLE; //主動結束按下動作,進入無動作狀態,保證在長按的按下過程中就識別出長按事件
key.event_current_type = EVENT_LONG_CLICK; //分配當前按鍵事件類型
key.flag.once_event = 1; //產生按鍵事件
key.press_cnt = 1; //連擊次數置1
key.time_idle = KEY_TIME_OUT; //按鍵空閑時間超時
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
當【key.time_continus】 >【 KEY_TIME_CONTINUS】時(這里設定為500ms見頭文件定義,時長可自由定義),判斷為長按
進入長按判斷后先判斷當前按鍵是否有短按事件(單擊或連擊),如果有,產生事件標志,給到【void KEY_Scan(void)】進行處理,在下一個中斷內再進行長按處理(該判斷是為了解決短按后快速接一個長按,只能識別出長按事件的BUG)
長按處理會分配當前事件類型,主動進入無動作狀態(不進入按鍵松開判斷)
【key.flag.press_time】是長短按標志,用來區分長按和短按(短按也包括連擊),長按會屏蔽短按
【key.flag.once_event = 1】產生按鍵事件,則該說明可以進行按鍵事件類型的判斷了。
三、按下時的單次判斷
//按下時進行一次判斷
if(key.flag.check){
key.flag.check = 0;
if(!key.flag.press_time){ //判斷上一次按鍵類型
//判斷上一次按鍵動作空閑時間
if(key.time_idle < KEY_TIME_IDLE){ //若上一次按鍵動作后的空閑時間在規定時間內,說明發生了連擊事件,一次完整的按鍵事件還未結束
key.press_cnt++;
}else{key.press_cnt = 1;}
}
key.flag.press_time = 0; //【0:短按/1:長按】此次為短按
}
1
2
3
4
5
6
7
8
9
10
11
該判斷只會在按下時執行一次,而不是1ms執行一次
【key.flag.press_time】在按下時一定會識別為短按,只有進入長按時才會置1
在按下時對上一次的按鍵動作空閑時間進行判斷【key.time_idle < KEY_TIME_IDLE】,可識別是否為連擊,若是連擊則【key.press_cnt++】,記錄連擊次數;否則置1
▼對於長按的判斷,是為了屏蔽長按后快速單擊的錯誤識別,若沒有該判斷,長按按后快速單擊,會將長按和單擊識別為一個事件,即2連擊
if(!key.flag.press_time){ }
1
2.5.2、按鍵松開處理
松開時分為三部分處理
一、【key.time_idle】計時,在2000ms超時前進行自加
if(key.time_idle < KEY_TIME_OUT){key.time_idle++;}
1
當按鍵松開時:
【key.flag.key_state】在外部中斷函數中賦值為【KEY_STATE_RELEASE】,【key.time_idle】清零
退出外部中斷函數后,在定時器中斷內,switch判斷會進入【KEY_STATE_RELEASE】
【key.time_idle】從零開始計數,每1ms加一
二、松開時的單次判斷
//松開時進行一次判斷
if(key.flag.check){
key.flag.check = 0;
//判斷此次按鍵動作
if(!key.flag.press_time){ //長按會屏蔽短按
if(key.press_cnt > 1){ //連擊事件
key.event_current_type = EVENT_DOUBLE_CLICK; //分配按鍵事件類型
}
else{ //單擊事件
key.event_current_type = EVENT_SHORT_CLICK; //分配按鍵事件類型
key.press_cnt = 1; //連擊次數置1
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
當按鍵松開時執行一次,對上一次按鍵按下的動作進行判斷,注意的是這里是通過【key.flag.press_time】排除了長按的松手事件判斷,並且根據【key.press_cnt】分配按鍵事件類型
三、此次松開時間判斷
//按鍵松開后判斷此次按鍵動作后的空閑時間,從而判斷此次動作是否結束
if(key.time_idle > KEY_TIME_IDLE){ //空閑時間超時,認為一次完整的按鍵事件結束
if(!key.flag.press_time){ //松開前是短按標志,則產生按鍵事件,這里是為了屏蔽長按后的松手動作
key.flag.once_event = 1; //產生按鍵事件
key.flag.key_state = KEY_STATE_IDLE;//進入無動作狀態
}
}
1
2
3
4
5
6
7
在第二步分配好按鍵事件類型后,還需要判斷此次松開后的時間,用來判斷是否為完整的一次事件
當【key.time_idle > KEY_TIME_IDLE】時,識別為完整的事件結束,產生按鍵事件標志,進入無動作狀態,通知主程序進行按鍵類型判斷
if(!key.flag.press_time){ }
1
▲因為長按的松手也會產生中斷,這是用來屏蔽長按的松手動作
2.6、按鍵事件類型識別
對於事件類型的識別是靠三個變量
【key.flag.once_event】一次完整按鍵事件,置1時可以進行事件識別,必須手動清零
【key.event_current_type】當前事件類型
【key.press_cnt】連擊次數,在連擊事件時有效
[key_process.c]
/*******************************************************************************
* @brief 使用模板,按鍵事件判斷例子,在主函數循環調用該函數 或 通過定時器定時查詢
將printf替換成需要處理的函數即可使用單擊、連擊、長按等按鍵功能
* @param 無
* @retval 無
******************************************************************************/
void KEY_Scan(void)
{
if(key.flag.once_event){
key.flag.once_event = 0;
switch(key.event_current_type){
case EVENT_SHORT_CLICK : printf("單擊\r\n");break;
case EVENT_DOUBLE_CLICK : printf("%d連擊\r\n",key.press_cnt);break;
case EVENT_LONG_CLICK : printf("長按\r\n");break;
default: printf("none\r\n");break;
}
//事件處理完需更新前態和現態
key.event_previous_type = key.event_current_type;
key.event_current_type = EVENT_NONE_CLICK;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
該函數可以在主循環調用或者在定時器中斷中調用,這里是選擇放在定時器中斷內處理,用戶處理內容直接替換到【printf函數】的位置就行了,或者通過外部標志進行鏈接
▼對於按鍵處理結構體的初始化值如下
[key_process.c]
/*******************************************************************************
* @brief 按鍵配置初始化函數,在主函數調用
* @param 無
* @retval 無
******************************************************************************/
void KEY_Config(void)
{
NVIC_EXTI_GPIO_Config();
NVIC_TIM2_Config();
EXTI_GPIO_Config();
TIMx_Config(TIM2);
//初始化
key.flag.check = 0;
key.flag.key_state = KEY_STATE_IDLE;
key.flag.once_event = 0;
key.flag.press_time = 0;
key.event_current_type = EVENT_NONE_CLICK;
key.event_previous_type = EVENT_NONE_CLICK;
key.press_cnt = 1;
key.time_continus = 0;
key.time_idle = KEY_TIME_OUT;
//使能定時器
TIM2->CR1 |= 0x0001;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
3、測試結果
測試包括普通的按鍵事件,即每個按鍵事件的空閑時間間隔均超過【KEY_TIME_IDLE】
特殊測試條件:
1、短按(單擊或連擊)松開后在【KEY_TIME_IDLE】時間內接一個長按,可識別出短按的事件和長按事件
2、長按松開后在【KEY_TIME_IDLE】內接短按(單擊或連擊),可識別出長按事件和短按事件
4、總結
除去基礎的IO配置和定時器配置函數外,主要的函數有:
【void EXTI9_5_IRQHandler(void)】外部中斷處理函數
【void TIM2_IRQHandler(void)】定時器中斷處理函數
【void KEY_Process(void)】按鍵處理函數
【void KEY_Scan(void)】按鍵事件判斷函數
【void KEY_Config(void)】初始化函數
該方案使用的是基於外部中斷的上下沿中斷進行判斷的,但考慮到有些單片機只能配置成上升沿或者下降沿檢測,無法對上下沿都進行判斷,移值上有一定的缺陷,這里給出一些修改思路,若是只使用上升沿或者下降沿觸發,可在外部中斷觸發后(即按鍵按下觸發),通過定時器循環讀取IO口狀態來判斷按鍵是否松開,按鍵的松開處理按照【2.4章節】進行處理就行,只不過該處理需要放進定時器中斷內的【void KEY_Process(void)】函數之前,其他的無需修改
▼頭文件中的定義可以改變按鍵識別的靈敏度
//定時器為1ms定時
#define KEY_TIME_IDLE 400 //按鍵動作空閑時間
#define KEY_TIME_CONTINUS 500 //按鍵動作持續時間
#define KEY_TIME_OUT 2000 //按鍵超時