當我們按下一個按鍵,LED燈做出反轉,再按另一個,蜂鳴器隨之響起,如何做到這些,這一章就帶你領略——按鍵輸入,
對于按鍵輸入有兩種方式:
1、查詢式(不斷檢測GPIO口電平變化)
2、中斷式(觸發中斷進入中斷服務程式)
這一章先講查詢式,學過中斷之后再講中斷式,
基本思路如下:
1、開啟GPIO口時鐘;
2、配置按鍵輸入方式;
3、掃描按鍵是否輸入;
4、根據按鍵做出動作,
目錄
- 一、電路連接
- 二、按鍵配置
- 1、時鐘的開啟
- 2、上拉下拉配置
- 3、按鍵檢測
- 4、根據按鍵做動作
- 三、總程式
一、電路連接


四個按鍵:
WK_UP---->PA0
KEY2------->PE2
KEY1------->PE3
KEY0------->PE4
注意看自己開發板按鍵電路圖
二、按鍵配置
1、時鐘的開啟
由電路圖可得,按鍵涉及的IO口有PA,PE,那我們得開啟GPIOA和GPIOE的時鐘,關于開啟外設時鐘方法之前有講到,故這里不再贅述,它需用到RCC_APB2ENR(外設時鐘使能暫存器),
2、上拉下拉配置
上拉是上拉至電源(高電平),下拉是拉至地(低電平),那為什么要設定上拉下拉呢?我們的按鍵還沒按下的時候,GPIO口輸入的電平是不確定的,有可能高,有可能低,這時候外界對IO口可能會造成干擾,從而影響設備的動作,所以通過上拉下拉,把GPIO口電位鉗置在高電平或低電平,來增強它的抗干擾能力,

根據電路圖可以得知,WK_UP要設定為下拉,如果設定為上拉就是高電平,按鍵按下和松開都是高電平,沒有變化,同理,KEY0、KEY1、KEY2要設定為上拉,

如圖,設定上拉下拉我們還是用GPIOx_CRL(埠配置低暫存器),還有,就是最重要的,設定上拉,還需設定GPIOx_ODR(資料輸出暫存器),
3、按鍵檢測

一個IO口需要輸出高電平還是低電平,我們通過ODR來控制,IDR正好相反,這個暫存器就是用來檢測該IO口是高電平還是低電平,我們具體來看使用方法:
- 現在假設我們用按鍵KEY2(PE2),KEY2是上拉模式,不按下時,PE2引腳是高電平,GPIOE_IDR第2位值讀出為1,按鍵按下時,接地,PE2引腳是低電平,GPIOE_IDR第2位值讀出為0,
這樣分析,我們便有了按鍵檢測的依據,可以如下定義KEY2
#define KEY2 ((GPIOE_IDR&(1<<2))?1:0) //KEY2->PE2
// (?:)這是一個三目運算子
// KEY2 ((GPIOE_IDR&(1<<2))?1:0)
//如果GPIOE_IDR&(1<<2)成立,那KEY2值為1,不成立則為0
四個按鍵都如此定義:
//---------------------按鍵配置---------------------------
#define WK_UP ((GPIOA_IDR&(1<<0))?1:0) //WK_UP->PA0
#define KEY0 ((GPIOE_IDR&(1<<4))?1:0) //KEY0->PE4
#define KEY1 ((GPIOE_IDR&(1<<3))?1:0) //KEY1->PE3
#define KEY2 ((GPIOE_IDR&(1<<2))?1:0) //KEY2->PE2
同樣,所有IO口都可以檢測,和上邊方式一樣
//--------------------------------------------------------
#define LED0 ((GPIOB_IDR&(1<<5))?1:0) //判斷LED0狀態
#define LED1 ((GPIOE_IDR&(1<<5))?1:0) //判斷LED1狀態
#define BEEP ((GPIOB_IDR&(1<<8))?1:0) //判斷蜂鳴器狀態
4、根據按鍵做動作
我們上邊定義了按鍵,就可以根據回傳的值是0還是1來判斷按鍵有沒有按下,這里強調一下,WK_UP是設定下拉,所以沒按下時是0,按下了是1,
unsigned int KEY_SCANF()
{
static unsigned int key_up=1;
if(key_up&&((KEY2==0)||(KEY1==0)||(KEY0==0)||(WK_UP==1)))
{
SysTick_ms(10); //消抖
key_up=0;
if(KEY2==0)return 1; //控制LED0
else if(KEY1==0)return 2; //控制LED1
else if(KEY0==0)return 3; //控制蜂鳴器
else if(WK_UP==1)return 4; //LED反轉
}
else if((KEY2==1)&&(KEY1==1)&&(KEY0==1)&&(WK_UP==0))
key_up=1;
return 0;
}
/* static是靜態變數,C語言中學到自定義函式,呼叫完后,自定義函式里定義的
變數就會釋放存盤空間,而靜態變數就可以讓變數持久存盤
*/
這里有個消抖,要重視一下,對于我們這種按鍵,在按下的時候,電信號會有一個抖動,如下圖:

消抖有兩種方式,硬體消抖和軟體消抖,我們用的就是軟體消抖,利用延時的方法,去除抖動,
根據回傳值,我們就可以做相應的動作:
int main(void)
{
unsigned int key; //定義一個變數接識訓傳值
System_clock(9); //打開HSE高速時鐘
LED(); //LED燈初始化
KEY(); //按鍵初始化
GPIOB_ODR&=~(1<<5); //打開LED0
while(1)
{
key=KEY_SCANF(); //獲取鍵值
if(key)
{
switch(key)
{
case 1:
PB5=~(PB5); //LED燈取反,若亮就滅,滅就亮
break;
case 2:
PE5=~(PE5); //LED燈取反,若亮就滅,滅就亮
break;
case 3:
PB8=~(PB8); //蜂鳴器取反
break;
case 4:
PB5=~(PB5);
PE5=~(PE5);
break;
}
}
SysTick_ms(10);
}
}
三、總程式
//--------------APB2使能時鐘暫存器------------------------
#define RCC_APB2ENR *((unsigned volatile int*)0x40021018)
//----------------GPIOA配置暫存器-------------------------
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)
#define GPIOA_IDR *((unsigned volatile int*)0x40010808)
//----------------GPIOB配置暫存器-------------------------
#define GPIOB_CRL *((unsigned volatile int*)0x40010C00)
#define GPIOB_CRH *((unsigned volatile int*)0x40010C04)
#define GPIOB_ODR *((unsigned volatile int*)0x40010C0C)
//----------------GPIOE配置暫存器 ------------------------
#define GPIOE_CRL *((unsigned volatile int*)0x40011800)
#define GPIOE_ODR *((unsigned volatile int*)0x4001180C)
#define GPIOE_IDR *((unsigned volatile int*)0x40011808)
//--------------------位帶定義--------------------------------
#define PB5 *((unsigned volatile int*)0x42218194)
#define PE5 *((unsigned volatile int*)0x42230194)
#define PB8 *((unsigned volatile int*)0x422181A0)
//------------------RCC時鐘暫存器-------------------------
#define RCC_CR *((unsigned volatile int*)0x40021000)
#define RCC_CFGR *((unsigned volatile int*)0x40021004)
//--------------FLASH閃存存盤器介面-----------------------
#define FLASH_ACR *((unsigned volatile int*)0x40022000)
//---------------------按鍵配置---------------------------
#define WK_UP ((GPIOA_IDR&(1<<0))?1:0) //WK_UP->PA0
#define KEY0 ((GPIOE_IDR&(1<<4))?1:0) //KEY0->PE4
#define KEY1 ((GPIOE_IDR&(1<<3))?1:0) //KEY1->PE3
#define KEY2 ((GPIOE_IDR&(1<<2))?1:0) //KEY2->PE2
//-----------------SysTick暫存器地址----------------------
#define SysTick_Base 0xE000E010
#define SysTick ((SysTick_Typedef*)SysTick_Base)
//-----------------SysTick暫存器定義----------------------
typedef struct
{
volatile unsigned long CTRL; //控制和狀態暫存器
volatile unsigned long RELOAD; //重裝載暫存器
volatile unsigned long VAL; //當前值暫存器
volatile unsigned long CALIB; //校準暫存器
}SysTick_Typedef;
//------------------系統時鐘配置---------------------------
void System_clock(unsigned char PLL)
{
unsigned int Clock_OK;
RCC_CR|=1<<16; //開啟HSE高速外部時鐘
while(!(RCC_CR&(1<<17))); //等待HSE開啟成功
RCC_CFGR|=4<<8; //0x00000400 AHB不分頻;APB2不分頻;APB1二分頻
FLASH_ACR|=0x2; //FLASH緩沖
RCC_CFGR|=1<<16; //HSE輸出作為PLL輸入時鐘
PLL=PLL-2; //選擇PLL倍頻2--9
RCC_CFGR|=PLL<<18; //PLL九倍頻輸出
RCC_CR|=1<<24; //PLL使能
while(!(RCC_CR&(1<<25))); //等待PLL使能成功
RCC_CFGR|=2<<0; //選擇PLL為系統時鐘
do //等待系統時鐘設定成功
{
Clock_OK=RCC_CFGR&0x0c;
}
while(Clock_OK!=0x08);
}
//設定完成后系統時鐘:SYSCLK=72MHZ;AHB:HCLK=72MHZ;APB2:PCLK=72MHZ;APB1:PCLK1=36MHZ
//----------------------滴答定時器---------------------------
void SysTick_ms(unsigned int time)
{
unsigned long num;
SysTick->VAL=0; //計數器清零
SysTick->RELOAD=9000*time; //重裝載計數值
SysTick->CTRL|=1<<0; //定時器使能,打開定時器
do
{
num=SysTick->CTRL;
}
while((num&0x01)&&!(num&(1<<16))); //等待計數器到0
SysTick->CTRL&=~(1<<0); //關閉計數器
SysTick->VAL=0; //計數器清零
}
//-----------------------按鍵檢測---------------------------
unsigned int KEY_SCANF()
{
static unsigned int key_up=1;
if(key_up&&((KEY2==0)||(KEY1==0)||(KEY0==0)||(WK_UP==1)))
{
SysTick_ms(10); //消抖
key_up=0;
if(KEY2==0)return 1; //控制LED0
else if(KEY1==0)return 2; //控制LED1
else if(KEY0==0)return 3; //控制蜂鳴器
else if(WK_UP==1)return 4; //LED反轉
}
else if((KEY2==1)&&(KEY1==1)&&(KEY0==1)&&(WK_UP==0))
key_up=1;
return 0;
}
//-----------------------按鍵初始化---------------------------
void KEY(void)
{
RCC_APB2ENR|=1<<2; //GPIOA時鐘
RCC_APB2ENR|=1<<6; //GPIOE時鐘
GPIOA_CRL&=0xFFFFFFF0; //引腳初始化
GPIOA_CRL|=0x00000008; //PA0下拉
GPIOE_CRL&=0xFFF000FF; //引腳初始化
GPIOE_CRL|=0x00088800; //PE2、PE3、PE4上拉
GPIOE_ODR|=7<<2; //PE2上拉
}
//------------------------LED初始化---------------------------
void LED(void)
{
RCC_APB2ENR|=1<<3; //APB2-GPIOB外設時鐘使能
RCC_APB2ENR|=1<<6; //APB2-GPIOE外設時鐘使能
GPIOB_CRL&=0xFF0FFFFF; //設定位清零
GPIOB_CRL|=0x00200000; //PB5推挽輸出
GPIOB_ODR|=1<<5; //設定初始燈為滅
GPIOE_CRL&=0xFF0FFFFF; //設定位清零
GPIOE_CRL|=0x00200000; //PE5推挽輸出
GPIOE_ODR|=1<<5; //設定初始燈為滅
GPIOB_CRH&=0xFFFFFFF0; //引腳初始化
GPIOB_CRH|=0x00000002; //PB8推挽
GPIOE_ODR&=~(1<<8); //蜂鳴器不作業
}
//-------------------------主函式-----------------------------
int main(void)
{
unsigned int key; //定義一個變數接識訓傳值
System_clock(9); //打開HSE高速時鐘
LED(); //LED燈初始化
KEY(); //按鍵初始化
GPIOB_ODR&=~(1<<5); //打開LED0
while(1)
{
key=KEY_SCANF(); //獲取鍵值
if(key)
{
switch(key)
{
case 1:
PB5=~(PB5);
break;
case 2:
PE5=~(PE5);
break;
case 3:
PB8=~(PB8);
break;
case 4:
PB5=~(PB5);
PE5=~(PE5);
break;
}
}
SysTick_ms(10);
}
}
建議大家寫程式的時候,養成一個好習慣,多用自定義函式,自定義函式就是把一個一個功能模塊化了,在主函式只需要呼叫一下,代碼看起來簡潔,不雜亂,后期除錯的時候也不影響別的模塊,如果代碼全寫在主函式,在后期維護時,會帶來很大的不便,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/276682.html
標籤:其他
