stm32f407按鍵檢測庫函式版

叫啥好呢嗚嗚嗚發表於2020-12-26

  開始之前呢先祝大家聖誕節快樂,同時參加明天的研究生考試的同學們一戰成"碩",接下來我就直奔主題了。
  今天我要通過庫函式操作stm32f407上的按鍵實現控制LED小燈以及蜂鳴器,實現的功能如下:

  • KEY0鍵控制LED0的亮滅
  • KEY1鍵控制LED1的亮滅
  • KEY2鍵同時控制控制LED0和LED1的亮滅轉換
  • WK_UP鍵控制蜂鳴器

  這篇文章同時會涉及到LED和蜂鳴器,相當於是對前兩次的學習進行一個複習,可能在前兩篇文章中沒有考慮到的細節今天都會盡量考慮進去,希望這篇文章可以更好的幫助到大家對GPIO的理解,廢話不多說,下面正式開始。

LED的初始化配置

  想要點亮LED,首先我們需要確定LED在stm32f407開發板上的硬體電路連線,如下圖所示:
在這裡插入圖片描述
從圖上可以看出,兩個LED屬於共陽極連線,也就是說,當GPIO口輸出高電平時,LED熄滅,當GPIO口輸出低電平時,LED點亮,那麼只需要配置好GPIO的輸出,即可實現LED的亮滅,所以,我們還得知道控制LED的GPIO口是哪一個,畢竟stm32f4有70個用於控制LED的GPIO口;
在這裡插入圖片描述
我們可以從從上圖中看到,控制LED0的是GPIOF_9,控制LED1的是GPIOF_10,同時我們還可以看到GPIOF_8是用來控制蜂鳴器(BEEP)的。知道了LED的硬體連線後,接下來就可以開始通過庫函式配置我們的GPIO了。
  通過庫函式來配置GPIO,我們需要用到的庫函式是GPIO_Init,對於這個函式,有如下的說明:

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);

從GPIO_Init的函式原型可以看出,需要用到兩個引數,這兩個引數分別是:

  • GPIO_TypeDef* GPIOx:用來指明配置的GPIO是7組GPIO中的哪一組,這裡控制LED用到的是GPIOF,所以該引數只需填入巨集定義GPIOF即可
  • GPIO_InitTypeDef* GPIO_InitStruct:從引數名字就可以知道這個引數是一個結構體的地址,所以在這裡為了更好的說明,我們自定義一個結構體:
GPIO_InitTypeDef led_gpio;

該結構體的成員呢有以下幾個:

  uint32_t GPIO_Pin;  //該引數對應的是某組GPIO中的某一個,畢竟一組GPIO有16個GPIO引腳
  					  //就如前面說到控制LED0的GPIO口是GPIOF_9;控制LED1的GPIO口是GPIOF_10;
  /*下面這幾個引數我就放到正文裡細說吧*/
  GPIOMode_TypeDef GPIO_Mode;  
  GPIOSpeed_TypeDef GPIO_Speed;
  GPIOOType_TypeDef GPIO_OType;  
  GPIOPuPd_TypeDef GPIO_PuPd;

GPIO_InitStruct的後面4個引數就是用於配置GPIO模式暫存器的,我在第一篇文章裡說到,配置GPIO的模式需要用到4個暫存器,如果你沒有看過我的第一篇文章的話,沒有關係,我再來詳細的說明一下:這裡說到的暫存器分別是:

  • GPIOx_MODER:GPIO埠模式暫存器
  • GPIOx_OSPEEDR:GPIO埠輸出速度暫存器
  • GPIOx_OTYPER:GPIO埠輸出型別暫存器
  • GPIOx_PUPDR:GPIO埠上拉/下拉暫存器

分別對應著GPIO_InitStruct的後四個成員
下面我就來具體說說要控制LED,這4個GPIOF分別需要怎麼配置:
>GPIOF_MODER
  GPIOF_MODER暫存器可選的配置分別有:輸入模式(復位狀態)通用輸出模式複用功能模式模擬模式,這裡很容易想到,我們要向控制LED0,需要通過GPIOF_9輸出高電平或低電平,所以毫無疑問我們需要將GPIOF_MODER暫存器配置成通用輸出模式,對於該暫存器的四種模式在庫函式中有如下的說明:

 GPIO_Mode_IN   = 0x00, /*輸入模式*/
 GPIO_Mode_OUT  = 0x01, /*通用輸出模式*/
 GPIO_Mode_AF   = 0x02, /*複用功能模式*/
 GPIO_Mode_AN   = 0x03  /*模擬模式*/

在此我們只需選擇GPIO_Mode_OUT即可,一句話程式碼如下:

 led_gpio.GPIO_Mode = GPIO_Mode_OUT;

>GPIOF_OSPEEDR
  GPIOF_OSPEEDR暫存器可選的配置分別有:2MHz25MHz50MHz100MHz,在此我們選擇100MHz的埠輸出速度(對於速度的選擇我也不太明白會有啥影響,或者說選擇的標準是啥,希望明白的大佬可以在評論區裡解答下,非常感謝),對於這4中選擇在庫函式中有如下定義:

 #define  GPIO_Speed_2MHz    GPIO_Low_Speed    
 #define  GPIO_Speed_25MHz   GPIO_Medium_Speed 
 #define  GPIO_Speed_50MHz   GPIO_Fast_Speed 
 #define  GPIO_Speed_100MHz  GPIO_High_Speed 

所以一句話程式碼如下:

 led_gpio.GPIO_Speed = GPIO_Speed_100MHz;

>GPIOx_OTYPER
  GPIOF_OSPEEDR暫存器可選的配置分別有:推輓輸出開漏輸出,在此我們選擇推輓輸出模式(同樣不明白這兩種模式的區別以及選擇標準,希望有大佬可以在評論區解答下,感謝),對於這兩種輸出模式在庫函式中有如下的定義:

typedef enum
{ 
  GPIO_OType_PP = 0x00,	//推輓輸出
  GPIO_OType_OD = 0x01	//開漏輸出
}GPIOOType_TypeDef;

所以一句話程式碼如下:

 led_gpio.GPIO_OType = GPIO_OType_PP;

>GPIOx_PUPDR
  GPIOF_OSPEEDR暫存器可選的配置分別有:浮空上拉下拉,GPIOF_OSPEEDR的配置就比較自由,在此我選擇上拉,以確保開發板上電時是熄滅的,對於這3種模式在庫函式中有如下定義:

 GPIO_PuPd_NOPULL = 0x00,
 GPIO_PuPd_UP     = 0x01,
 GPIO_PuPd_DOWN   = 0x02

所以一句話程式碼如下

 led_gpio.GPIO_PuPd = GPIO_PuPd_UP;

到此,LED的GPIO配置就完成了,注意LED0和LED1配置的GPIO_pin是有區別的,同時不要忘了在呼叫GPIO_Init之前,一定要先使能GPIOF的外設時鐘,這裡需要用到的函式為:

 void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState)

對於該函式的第1個引數用來指明初始化的是哪一個外設時鐘,有如下定義(只擷取了一段):

 #define RCC_AHB1Periph_GPIOA             ((uint32_t)0x00000001)
 #define RCC_AHB1Periph_GPIOB             ((uint32_t)0x00000002)
 #define RCC_AHB1Periph_GPIOC             ((uint32_t)0x00000004)
 #define RCC_AHB1Periph_GPIOD             ((uint32_t)0x00000008)
 #define RCC_AHB1Periph_GPIOE             ((uint32_t)0x00000010)
 #define RCC_AHB1Periph_GPIOF             ((uint32_t)0x00000020)
 #define RCC_AHB1Periph_GPIOG             ((uint32_t)0x00000040)

在此我們選擇RCC_AHB1Periph_GPIOF即可,對於第2個引數是用於說明是否要初始化的,具體的定義如下:

 #define IS_FUNCTIONAL_STATE(STATE) (((STATE) == DISABLE) || ((STATE) == ENABLE))

很明顯我們要選擇ENABLE
那麼到這裡第一個階段的工作就完成了,我們可以將以上的程式碼封裝成一個LED初始化的.c檔案,只需要在main函式中呼叫即可,led_init.c程式碼如下:

void led_init(void){
	GPIO_InitTypeDef led_gpio;	//定義的GPIO模式初始化結構體
	/*使能GPIOF外設時鐘*/
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);
	/*配置控制LED的GPIOF_9和GPIOF_10*/
	led_gpio.GPIO_Pin = GPIO_Pin_9||GPIO_Pin_10;//採用或運算可同時對LED1和LED0進行初始化
	led_gpio.GPIO_Mode = GPIO_Mode_OUT;
	led_gpio.GPIO_Speed = GPIO_Speed_100MHz;
	led_gpio.GPIO_OType = GPIO_OType_PP;
	led_gpio.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Init(GPIOF,&led_gpio);
}

BEEP(蜂鳴器)的初始化配置

  在瞭解了GPIO的配置過程後,後續的共組就容易的多了。同樣的,想要配置好BEEP,首先我們需要先知道BEEP的硬體是怎樣連線的,從而知道應該對哪一個GPIO口進行配置,從前面的LED的硬體連線圖上可以找的BEEP連線的是GPIOF_8,那麼下面我直接上程式碼,詳細的內容見程式碼的註釋,畢竟今天的重點是按鍵:

void beep_init(){
	GPIO_InitTypeDef beep_gpio;	//定義的GPIO模式初始化結構體
	/*使能GPIOF外設時鐘*/
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);
	/*配置控制BEEP的GPIOF_8*/
	beep_gpio.GPIO_Mode = GPIO_Mode_OUT;	//配置GPIO模式為通用輸出模式
	beep_gpio.GPIO_OType = GPIO_OType_PP;	//配置GPIO輸出模式為推輓輸出
	beep_gpio.GPIO_Pin = GPIO_Pin_8;		//選擇配置8號引腳
	beep_gpio.GPIO_PuPd = GPIO_PuPd_UP;		//配置GPIO埠為上拉模式
	beep_gpio.GPIO_Speed = GPIO_Speed_100MHz;	//配置GPIO輸出速度為100MHz
	GPIO_Init(GPIOF,&beep_gpio);	//呼叫GPIO初始化函式
}

KEY的初始化配置與掃描

KEY的初始化配置

  首先逃不掉的還是硬體連線圖:
在這裡插入圖片描述
  從圖中可以看出,對於按鍵KEY0,KEY1,KEY2,當它們連線到的GPIO埠為上拉模式時,如按鍵按下,此時GPIO口讀到低電平,若按鍵沒有按下,則GPIO口讀到高電平,二對於KEY_UP來說就正好相反,當它對應的GPIO口為下拉模式時,沒有按鍵按下,GPIO口讀到第電平,按鍵按下則讀到高點配,也就意味著我們對KEY_num和KEY_UP的配置是不一樣的,同時,我們現在需要的不再是GPIO的通用輸出模式,而是輸入模式了,而且此處我們不需要再配置GPIO的輸出模式(即推輓輸出或者開漏輸出),有了這樣的認識後,我麼們再來確定需要配置哪寫GPIO口吧:
在這裡插入圖片描述
在這裡插入圖片描述
由上圖可以知道,對於KEY0,KEY1,KEY2需要配置的GPIO分別是GPIOE_4GPIOE_3GPIOE_2,而KEY_UP配置的GPIO是GPIOA_0。在此,模仿LED與BEEP的配置過程可以有一下程式碼:

void key_init(void){
	GPIO_InitTypeDef key_gpio;	//定義GPIO初始化的結構體
	/*使能GPIOE和GPIOA的外設時鐘*/
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE|RCC_AHB1Periph_GPIOA,ENABLE);
	/*配置GPIOE_4 & GPIOE_3 & GPIOE_2*/
	key_gpio.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_3|GPIO_Pin_2;//採用或運算可同時對KEY0,KEY1和KEY2進行初始化
	key_gpio.GPIO_Mode = GPIO_Mode_IN;		//配置GPIO模式為輸入模式
	key_gpio.GPIO_Speed = GPIO_Speed_100MHz;//配置GPIO輸出速度為100MHz
	key_gpio.GPIO_PuPd = GPIO_PuPd_UP;		//配置GPIO埠為上拉模式
	GPIO_Init(GPIOE,&key_gpio);				//呼叫GPIO初始化函式
	/*ÅäÖÃGPIOA_0*/
	key_gpio.GPIO_Pin = GPIO_Pin_0;			//對KEY_UP進行初始化
	key_gpio.GPIO_PuPd = GPIO_PuPd_DOWN;	//配置GPIO埠為下拉模式
	GPIO_Init(GPIOA,&key_gpio);				//呼叫GPIO初始化函式	
}

KEY的掃描

  想要按下不同的按鍵實現不同的功能,就需要知道按下的鍵是哪一個,然後通過按下的按鍵控制相應的裝置,就需要迴圈掃描按鍵以確定鍵值,下面使用虛擬碼說明一下按鍵掃描的過程:

//key_scan.c
void key_scan{
	if(有按鍵按下){
 		延時10ms消抖;
 		if(確實有按鍵按下)	
 			return 鍵值
 		else return 無效值;
 	}
}
//main.c
int main(){
	while(1){
		int key = key_scan();
		switch(key){
			case 1 : 點亮LED;break;
			case 2 : 熄滅LED;break;
			...
		}
		延時10ms
	}
}	

  這樣便實現了通過按鍵控制不同裝置了,但是存在一個問題,當我按下某一個按鍵不放時,key_scan函式永遠檢測到有按鍵按下並且返回該鍵鍵值,再main函式中就會一直點亮或熄滅LED,如何避免呢,在此可以很巧妙的運用static定義一個靜態的變數作為按鍵按下與否的標誌位(簡單來說就是儲存一下上一次按鍵的結果),直接上虛擬碼解釋(只需再原來的按鍵掃描的基礎上修改即可)

//key_scan.c
void key_scan{
	static KEY_STATE = 1;
	if(KEY_STATE = 1 && 有按鍵按下){
 		延時10ms消抖;
 		KEY_STATE = 0	//按鍵有效從而更新狀態
 		if(確實有按鍵按下)	
 			return 鍵值
 		else if(沒有按鍵按下)	KEY_STATE= 1;//重新更新狀態	
 	}
 	return 0}

可能不好理解,可以試著多迴圈幾次應該就會明朗許多,那麼具體的按鍵掃描函式如下所示:

u8 key_scan(u8 mode){
	static u8 key_state = 1;
	if(mode) key_state = 1;
	if(key_state && (KEY0 == 0 || KEY1 == 0 || KEY2 == 0 || WKUP == 1)){
		delay_ms(10);
		key_state = 0;
		if(KEY0 == 0)	return 1;
		else if(KEY1 == 0)	return 2;
		else if(KEY2 == 0)	return 3;
		else if(WKUP == 1)	return 4;
	}else if(KEY0 == 1 && KEY1 == 1 && KEY2 == 1 && WKUP == 0)
		key_state = 1;
	return 0;	
}

在此需要說明的是:判斷是否有按鍵按下,我們需要呼叫函式實現

GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)

在以上的按鍵掃描函式中KEY0 == 0這樣的表示即為GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4),是在標頭檔案中進行了巨集定義而已,具體如下:

//key.h
#define KEY0 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)
#define KEY1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)
#define KEY2 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)
#define WKUP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)

分別對應判讀KEY0,KEY1,KEY2以及KEY_UP是否按下。
至此所有的流程都已搞定,接下啦就是main函式的類容了:

int main(void){
	u8 key;
	delay_init(168);
	led_init();
	beep_init();
	key_init();
	LED0 = 0;
	LED1 = 0;
	while(1){
		key = key_scan(0);
		if(key){
			switch(key){
				case LED0_PRES:	LED0 = !LED0; break;
				case LED1_PRES:	LED1 = !LED1; break;
				case BEEP_PRES:	BEEP = !BEEP; break;
				case CHAG_PRES:	{
					LED0 = !LED0;
					LED1 = !LED1;
				}
				break;
			}
		}else delay_ms(10);
	}
}

同樣這裡需要注意的是對於LED和蜂鳴器的操作我採用了位操作的方式,位操作的相關內容可以參考我上一篇的文章,這裡也是採用的巨集定義方式,具體如下:

//led.h
#define LED0 PFout(9)
#define LED1 PFout(10)
//beep.h
#define BEEP PFout(8)

總結一下:經常會在CSDN上查詢大佬的文章來學習,但沒想過寫一篇文章這麼惱火,花了我兩個半小時才搞定,太難了,不過一定要堅持呀!!!

相關文章