??開始之前呢先祝大家圣誕節快樂,同時參加明天的研究生考試的同學們一戰成"碩",接下來我就直奔主題了,
??今天我要通過庫函式操作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暫存器可選的配置分別有:2MHz,25MHz,50MHz,100MHz,在此我們選擇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_4,GPIOE_3,GPIOE_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上查找大佬的文章來學習,但沒想過寫一篇文章這么畝訓,花了我兩個半小時才搞定,太難了,不過一定要堅持呀!!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/240893.html
標籤:其他
