零、按鍵基本認識
1、防抖
按鍵機械觸點斷開、閉合的時候,由於觸點的彈性作用,按鍵開關不會馬上穩定接通或一下子斷開,而是會產生一些波紋信號,這些波紋信號會干擾高低電平的判斷。如下圖所示,在按鍵按下的前后均有信號抖動:
為了解決這個問題,有一些電路自帶消抖功能,利用電容充放電的延時,消除了干擾波紋,從而簡化了軟件的處理,軟件只需檢測引腳的高低電平即可。這叫硬件消抖。
然而,我們的實驗板卻沒有硬件消抖功能,因此,當用戶按下按鍵時,軟件需要延時一會兒(一般為10ms左右),待引腳的輸入電平穩定后再判斷高低電平。這叫軟件消抖。
大致的思路如下:
uint8_t KEY_Scan(void)
{
if(KEY按下)
{
delay_ms(10);//延時10-20ms,防抖。
if(KEY確實按下)
{
return KEY_Value;
}
return 無效值;
}
}
返回的結果只有兩種:要么按鍵確實被按下了,要么按鍵確實沒被按下。
2、支持連續按
支持連續按的意思是,用戶一直按着按鍵,相應的外設就會一直工作,直到用戶松手,相應的外設才不工作。簡單來說就是用戶按一次不松手算很多次按下。其實,上面的程序就實現了這個功能,思路跟上面是一樣的。
我們重復寫一遍。大致的思路如下:
uint8_t KEY_Scan(void)
{
if(KEY按下)
{
delay_ms(10);//延時,防抖。
if(KEY確實按下)
{
return KEY_Value;
}
return 無效值;
}
}
3、不支持連續按
不支持連續按的意思是,即使用戶一直按着按鍵,但相應的外設不會一直工作,響一下就不響了。簡單來說就是用戶按一次不松手只能算一次按下。
大致的思路如下:
uint8_t KEY_Scan(void)
{
static uint8_t iskey = 0; //記錄之前按鍵有無被按過
//初始化iskey為0,表明按鍵之前沒被按過
if(!iskey && KEY按下) //如果按鍵之前沒被按過且現在按鍵被按下了
{
delay_ms(10);//延時,防抖
iskey = 1; //標記按鍵已經被按下
if(KEY確實按下)
{
return KEY_VALUE;
}
}else if(KEY沒有按下)
iskey = 0; //如果按鍵沒被按下(即松開),則標記按鍵沒被按過
return 沒有按下
}
4、STM32F103精英上按鍵的電路圖
電路圖如下如所示:
從電路圖我們可以看到,精英版為我們提供了兩種按鍵,WK_UP接VCC電源,而KEY0和KEY1接地。因此,WK_UP連接的端口為下拉輸入模式,KEY0和KEY1連接的端口為上拉輸入模式。
當單片機讀到的電平為低電平時,說明KEY0和KEY1被按下,而WK_UP沒有被按下;
當單片機讀到的電平為高電平時,說明KEY0和KEY1沒有被按下,而WK_UP被按下。
最后,WK_UP接的是PA0端口,KEY0接的是PE4端口,KEY1接的是PE3端口。
一、按鍵實驗初體驗
1.支持連續按
本程序實現功能:按下按鍵,相應外設會工作,若按鍵不松手則外設一直工作。換言之,外設工作與否取決於按鍵是否被按下。
程序如下:
/* =====key.h=====*/
#ifndef __KEY_H
#define __KEY_H
#include "sys.h"
/* 檢測按鍵電平情況的函數 */
#define KEY0 GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_4)
#define KEY1 GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_3)
#define KEY_UP GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)
/* 由上到下分別是:上拉輸入式按下、下拉輸入式按下 */
#define IPU_ON 0
#define IPD_ON 1
/* 由上到下均是不同按鍵對應的編號:全部未按下為0、key0為1、key1為2、wk_up為3 */
#define ALL_KEY_UNPRS 0
#define KEY0_PRS 1
#define KEY1_PRS 2
#define KEY_UP_PRS 3
void KEY_Init(void);
u8 KEY_Scan(void);
#endif
/* =====key.c===== */
#include "stm32f10x.h"
#include "key.h"
#include "delay.h"
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOE, ENABLE);
/* KEY0 & KEY1 -- Ground */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4; /* PinE3 -- KEY1, PinE4 -- KEY0 */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; /* 上拉輸入 */
GPIO_Init(GPIOE, &GPIO_InitStructure);
/* KEY_UP(WK_UP) -- VCC */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; /* PinA0 -- KEY_UP */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; /* 下拉輸入 */
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
u8 KEY_Scan(void)
{
if(KEY0 == IPU_ON || KEY1 == IPU_ON || KEY_UP == IPD_ON)
{
delay_ms(10);
if(KEY0 == IPU_ON)
return KEY0_PRS;
else if(KEY1 == IPU_ON)
return KEY1_PRS;
else if(KEY_UP == IPD_ON)
return KEY_UP_PRS;
}
return ALL_KEY_UNPRS;
}
/* =====main.c===== */
#include "stm32f10x.h"
#include "sys.h"
#include "beep.h"
#include "key.h"
#include "led.h"
#include "delay.h"
int main()
{
u8 key = 0;
delay_init();
KEY_Init();
LED_Init();
Beep_Init();
while(1)
{
key = KEY_Scan();
switch(key)
{
case KEY0_PRS:
LED0_ON;
while(KEY_Scan() != ALL_KEY_UNPRS); /* 松手檢測,這里的作用是:
/* 如果用戶不松手,程序將會卡在這個地方,相應的外設也會持續工作 */
break;
case KEY1_PRS:
LED1_ON;
while(KEY_Scan() != ALL_KEY_UNPRS);
break;
case KEY_UP_PRS:
BEEP_ON;
while(KEY_Scan() != ALL_KEY_UNPRS);
break;
default:
LED0_OFF;
LED1_OFF;
BEEP_OFF;
break;
}
}
}
2.不支持連續按
本程序實現功能:按下按鍵,相應外設會工作,且經過300ms后不工作。這就是說若按鍵不松手,外設不會一直工作。
將以上部分程序修改如下:
/* =====key.c===== */
/* key_Scan函數修改如下,其他部分不變 */
u8 KEY_Scan(void)
{
static int isPrsBefore = 0;
if((isPrsBefore == 0) && (KEY0 == IPU_ON || KEY1 == IPU_ON || KEY_UP == IPD_ON))
{
delay_ms(10);
isPrsBefore = 1;
if(KEY0 == IPU_ON)
return KEY0_PRS;
else if(KEY1 == IPU_ON)
return KEY1_PRS;
else if(KEY_UP == IPD_ON)
return KEY_UP_PRS;
}else if (KEY0 == !IPU_ON && KEY1 == !IPU_ON && KEY_UP == !IPD_ON)
isPrsBefore = 0;
return ALL_KEY_UNPRS;
}
/* =====main.c===== */
#include "stm32f10x.h"
#include "sys.h"
#include "beep.h"
#include "key.h"
#include "led.h"
#include "delay.h"
int main()
{
u8 key = 0;
delay_init();
KEY_Init();
LED_Init();
Beep_Init();
while(1)
{
key = KEY_Scan();
switch(key)
{
case KEY0_PRS:
LED0_ON;
delay_ms(300);
break;
case KEY1_PRS:
LED1_ON;
delay_ms(300);
break;
case KEY_UP_PRS:
BEEP_ON;
delay_ms(300);
break;
default:
LED0_OFF;
LED1_OFF;
BEEP_OFF;
break;
}
}
}
二、綜合實驗
以下我們實現一個功能:按下按鍵,相應外設工作狀態將反轉。按鍵不松手,工作狀態不會改變(即不支持連續按)。
為實現工作狀態反轉功能,本程序使用位帶操作讀取IO口輸入電平和輸出電平。這種辦法實現了與51類似的IO口控制功能,比如51里是這樣用的:sbit LED0 = P2^6;
在STM32里是這樣用的:#define LED0 PEin(5)
,意思是讀取GPIOE.5口的輸入電平;#define LED0 PEout(5)
,意思是讀取GPIOE.5口的輸出電平。
完整代碼如下(頭文件sys.h為正點原子資料盤自帶文件):
/* =====led.h===== */
#ifndef __LED_H
#define __LED_H
#define LED0_OFF GPIO_SetBits(GPIOB, GPIO_Pin_5)
#define LED0_ON GPIO_ResetBits(GPIOB, GPIO_Pin_5)
#define LED1_OFF GPIO_SetBits(GPIOE, GPIO_Pin_5)
#define LED1_ON GPIO_ResetBits(GPIOE, GPIO_Pin_5)
#define LED0 PBout(5) //位帶操作
#define LED1 PEout(5)
void LED_Init(void);
#endif
/* =====led.c===== */
#include "stm32f10x.h"
#include "led.h"
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOE, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; /* 推挽輸出 */
/* Definition: void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct) */
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_Init(GPIOE, &GPIO_InitStructure);
LED0_OFF;
LED1_OFF;
}
/* =====beep.h===== */
#ifndef __BEEP_H
#define __BEEP_H
#define BEEP_ON GPIO_SetBits(GPIOB, GPIO_Pin_8)
#define BEEP_OFF GPIO_ResetBits(GPIOB, GPIO_Pin_8)
#define BEEP PBout(8)
void Beep_Init(void);
#endif
/* =====beep.c===== */
#include "stm32f10x.h"
#include "beep.h"
void Beep_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
BEEP_OFF;
}
/* =====key.h===== */
#ifndef __KEY_H
#define __KEY_H
#include "sys.h"
/* Definition: uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) */
#define KEY0 GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_4)
#define KEY1 GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_3)
#define KEY_UP GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)
#define IPU_ON 0
#define IPD_ON 1
#define ALL_KEY_UNPRS 0
#define KEY0_PRS 1
#define KEY1_PRS 2
#define KEY_UP_PRS 3
void KEY_Init(void);
u8 KEY_Scan(void);
#endif
/* =====key.c===== */
#include "stm32f10x.h"
#include "key.h"
#include "delay.h"
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOE, ENABLE);
/* KEY0 & KEY1 -- Ground */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4; /* PinE3 -- KEY1, PinE4 -- KEY0 */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; /* 上拉輸入 */
GPIO_Init(GPIOE, &GPIO_InitStructure);
/* KEY_UP(WK_UP) -- VCC */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; /* PinA0 -- KEY_UP */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; /* 下拉輸入 */
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
u8 KEY_Scan(void)
{
static int isPrsBefore = 0;
if((isPrsBefore == 0) && (KEY0 == IPU_ON || KEY1 == IPU_ON || KEY_UP == IPD_ON))
{
delay_ms(10);
isPrsBefore = 1;
if(KEY0 == IPU_ON)
return KEY0_PRS;
else if(KEY1 == IPU_ON)
return KEY1_PRS;
else if(KEY_UP == IPD_ON)
return KEY_UP_PRS;
}else if (KEY0 == !IPU_ON && KEY1 == !IPU_ON && KEY_UP == !IPD_ON)
isPrsBefore = 0;
return ALL_KEY_UNPRS;
}
/* =====main.c===== */
#include "stm32f10x.h"
#include "sys.h"
#include "beep.h"
#include "key.h"
#include "led.h"
#include "delay.h"
int main()
{
u8 key = 0;
delay_init();
KEY_Init();
LED_Init();
Beep_Init();
while(1)
{
key = KEY_Scan();
switch(key)
{
case KEY0_PRS:
LED0 = !LED0; //工作狀態實現反轉
break;
case KEY1_PRS:
LED1 = !LED1;
break;
case KEY_UP_PRS:
BEEP = !BEEP;
break;
default:
break;
}
}
}