1. 程式人生 > 其它 >STM32之按鍵輸入實驗

STM32之按鍵輸入實驗

技術標籤:STM32嵌入式微控制器程式語言c#

**

STM32之如何通過按鍵控制LED燈和蜂鳴器

**
用到的板子:STM32F103開發板,一共有三個按鍵:WK_UP、KEY0和 KEY1。
目標:編寫通過這三個按鍵來控制LED燈和蜂鳴器,WK_UP控制蜂鳴器,按下響,在按一次停。KEY1 控制 LED1, 按一次亮, 再按一次滅;KEY0 則同時控制 LED0 和LED1,按下一次,他們的狀態就翻轉一次。
分析:

既然要通過按鍵控制,那麼先開啟原理圖,檢視三個按鍵WK_UP、KEY0和 KEY1對應的引腳。如下圖:
在這裡插入圖片描述
在這裡插入圖片描述

於是可以得知WK_UP接GPIOA引腳,KEY0和 KEY1接GPIOE引腳。查參考手冊,如下

在這裡插入圖片描述

GPIOA、GPIOE掛在RCC暫存器下的APB2上。
那麼步驟的第一步就有了,需要呼叫RCC_APB2PeriphClockCmd()這個函式,來初始化I/O口時鐘,將GPIOA和GPIOE對應的時鐘使能。第二步則是呼叫GPIO_Init()這個函式,來配置I/O口,包括配置pin腳,輸入方式(上拉還是下拉)。到此關於按鍵的初始化函式就完成了。
然後題目中,對蜂鳴器、LED燈都是按一次響,在按一次滅,不用連續按,那麼就需要寫一個函式,即按鍵掃描函式,來實現這一功能。
按鍵輸入有兩種模式,一種為按下按鍵不鬆開,LED燈長亮,鬆開之後,在按一次按鍵,LED燈熄滅。另一種為按下按鍵不鬆開,LED燈一直閃爍(閃爍有可能觀察不到,因為程式掃描太快,眼睛跟不上燈變化的速度),鬆開之後,LED燈可能熄滅,也可能被點亮。這在程式中通過設定mode ,若mode=0,則符合第一種,若mode=1,則符合第二種。

另外根據如下電路原理圖,可以發現,KEY0 和 KEY1 是低電平有效的,而 WK_UP 是高電平有效。同時還發現這些按鍵都沒有上\下拉電阻,這樣容易導致電平不穩定,故還需要設定上\下拉電阻。根據此電路圖WK_UP左端接低電平,故需要接下拉電阻。KEY0 和 KEY1左端接高電平,故需要接上拉電阻。那什麼叫上\下拉電阻呢?(見文末)
在這裡插入圖片描述

下面便是編寫程式。
1、新建一個工程檔案,將所需要的各個標頭檔案包含進去。
2、在工程資料夾中新建一個資料夾—HARDWARE(名字可任取),然後在其中新建key資料夾,當然由於這個實驗用到了LED燈和BEEP蜂鳴器,故需要將以前編寫的LED燈和BEEP蜂鳴器的資料夾複製過來,最終需要在主程式中用到它們的原始檔。

3、開啟 xxx.uvprojx 工程檔案,新建 key.c 和 key.h,儲存至 key 資料夾中。key.c檔案目的是建立一個初始化函式(即上文提到的第一、二步)和建立一個按鍵掃描函式。而 key.h 檔案則是 key.c 檔案的標頭檔案,宣告原始檔中的兩個函式,並且進行一些巨集定義(見程式)。
4、將key.c檔案新增到HARDWARE- key 資料夾中,將key.h 檔案的路徑新增到 HARDWARE-BEEP 資料夾中。
5、編寫key.h程式碼

#ifndef __KEY_H
#define __KEY_H
#include "sys.h"

#define KEY0  GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)
//(巨集定義:讀取按鍵0的埠和引腳的電平,並用KEY0表示)
#define KEY1  GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)
//(讀取按鍵1的埠和引腳的電平,並用KEY1表示) 
#define WK_UP   GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)
//(讀取按鍵up的埠和引腳的電平,並用WK_UP表示) 
 
#define KEY0_PRES  1  
//巨集定義:用KEY0_PRES表示1,目的是增加程式可讀性,在key.c程式中代表KEY0 按下 
#define KEY1_PRES  2  //KEY1 按下 (解釋同上)
#define WKUP_PRES  3 //WK_UP 按下(解釋同上)

void KEY_Init(void);//定義一個按鍵初始化配置函式
u8 KEY_Scan(u8);//定義一個按鍵掃描函式

#endif

6、編寫key.c程式碼

#include "key.h"
#include "sys.h"
#include "delay.h"

void KEY_Init (void)
{
	GPIO_InitTypeDef GPIO_InitStruct;//定義一個結構體變數
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE);
	//使能GPIOA和GPIOE的時鐘
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU; //設定成上拉輸入
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_3|GPIO_Pin_4;//配置pin_3和pin_4引腳
	GPIO_Init(GPIOE,&GPIO_InitStruct);
	//配置GPIO口的初始化函式,這裡是針對GPIOE的pin_3和pin_4
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPD;//設定成下拉輸入
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0;//配置pin_0引腳
	GPIO_Init(GPIOA,&GPIO_InitStruct);
//配置GPIO口的初始化函式,這裡是針對GPIOA的pin_0
}

u8 KEY_Scan(u8 mode)//定義一個按鍵掃描函式,資料型別為8位的無符號數,引數為mode
{
	static u8 key_up=1;//定義一個關鍵字static ,給key_up賦值為1
	if(mode)key_up=1; 
	if(key_up&&(KEY0==0||KEY1==0||WK_UP==1)) 
	{
	   delay_ms(10);//去抖動  
       key_up=0; 
       if(KEY0==0)return KEY0_PRES; 
       else if(KEY1==0)return KEY1_PRES; 
       else if(WK_UP==1)return WKUP_PRES; 
	}
	   else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1;       
   return 0;// 無按鍵按下 
}

在主函式中 執行到這條 key=KEY_Scan(0)命令 ,也就是mode=0,那麼開始執行該函式。定義一個關鍵字static 變數 key_up,並給其賦值為1。if(mode)key_up=1; 不執行。執行if(key_up&&(KEY0==0||KEY1==0||WK_UP==1)) ,假設此時按下KEY1且不鬆開,則執行該if後面的語句。delay_ms(10);目的是去抖動。將key_up賦值為0,然後函式返回值為1(KEY0_PRES在巨集定義中表示為1),跳出函式,繼續執行主函式,使得LED1燈亮。程式繼續走,再一次走到key=KEY_Scan(0),呼叫KEY_Scan函式,這時候由於key_up在上一次被賦值為0了,故此時if(key_up&&(KEY0==0||KEY1==0||WK_UP==1)) 也不執行,直接執行elseif(KEY0==1&&KEY1==1&&KEY2==1&&KEY3==0)key_up=1;此時key_up才重新變為1,然後return 0;那麼主函式中key=0,則不執行if(key);故此時LED1燈狀態不變,依然亮。這樣就實現了第一種模式,即按鍵連續按的時候,燈/蜂鳴器的狀態不改變。(其他的形式類似)

下面貼一個正點原子論壇中一位老哥的解釋:
key_up只是作為一個標誌,
if(key_up&&(KEY0==0||KEY1==0||KEY2==0||KEY3==1))這裡邊判斷按鍵按下是要跟key_up相與的,也就是當你有鍵按下時還需要key_up為1才能讓程式讀取到按鍵值。key_up作為靜態變數只會被初始化一次(也就是static u8 key_up=1只會執行一次),每一次讀取到一個按鍵值後key_up就會被置0,讓它重新變為1只有兩種情況,
一:else if(KEY0==1&&KEY1==1&&KEY2==1&&KEY3==0)key_up=1;這裡就並不是連續按了,只要你按鍵鬆開了,下次繼續讀取沒問題。二:你如果是想連續按著不動又希望程式能多次讀取到的話,必須通過if(mode)key_up=1; 這一句就能讓key_up恢復1,這樣就實現了不需要鬆開按鍵就讓它恢復1,即可實現連續按鍵並且程式連續讀取。

7、編寫main.c程式碼

#include "LED.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "beep.h"

int main(void)
 {
 	u8 key=0;	
	delay_init();	    	 //延時函式初始化	  
	led_init();		  		//初始化與LED連線的硬體介面
	Beep_init();         	//初始化蜂鳴器埠
	KEY_Init();         	//初始化與按鍵連線的硬體介面
	LED0=0;				//先點亮紅燈
	while(1)
	{
 		key=KEY_Scan(0);	
		//呼叫KEY_Scan這個函式,mode=0,將函式的返回值賦值給 key。也就是得到按鍵的值
	   	if(key)
		{						   
			switch(key)
			{				 
				case WKUP_PRES:	//控制蜂鳴器
					BEEP=!BEEP;//狀態翻轉
					break; 
				case KEY1_PRES:	//控制LED1翻轉	 
					LED1=!LED1;
					break;
				case KEY0_PRES:	//同時控制LED0,LED1翻轉 
					LED0=!LED0;
					LED1=!LED1;
					break;
			}
		}else delay_ms(10); 
	}	 
}

8、編譯、執行

**

知識補充:

**
什麼叫上\下拉電阻呢?
上拉是將不確定訊號通過一個電阻鉗位在高電平,電阻同時限流作用;
下拉是將不確定訊號通過一個電阻鉗位在低電平。
上面兩句話是什麼原理呢?以上拉電阻為例,下面引用網上搜到的答案:
一根既不與固定電位連線、也不與實際訊號源連線的引線,其電平是不確定的。引線的這種狀態可以叫做浮空狀態。電子線路中浮空的引線可能帶來危害。例如,CMOS積體電路的輸入引腳浮空會導致邏輯混亂、晶片功耗增大、甚至引起晶片損壞,所以必須使引線的電平固定。
浮空的引線通過一隻電阻接到高電平,可以使其被拉高到高電平狀態;若接到地,則被拉低到0電平。如果連線到高低電平之間的2只串聯電阻的中點,則引線電平可以隨2只電阻阻值的不同而被鉗制在0~高電平之間任一點上。
通過電阻將電位不確定的引線鉗位到固定電平,是利用了電阻的分壓原理。
理論上,不通過上拉電阻,直接將引線接電源,也可以拉到高電平,但是可能存在風險,如COMS積體電路如果出現異常狀態,一些引腳對地呈現很小的電阻,該引腳的電流會很大,導致晶片發熱或燒壞。通過電阻接VDD沒有此隱患。

C語言中 Static 關鍵字用法:
Static的用途主要有兩個:
一是用於修飾儲存型別使之成為靜態儲存型別
二是用於修飾連結屬性使之成為內部連結屬性。
1)靜態儲存型別:
在函式內定義的靜態區域性變數,該變數存在記憶體的靜態區,所以即使該函式執行結束,靜態變數的值不會被銷燬,函式下次執行時能仍用到這個值。
在函式外定義的靜態變數——靜態全域性變數,該變數的作用域只能在定義該變數的檔案中,不能被其他檔案通過extern引用。
2) 內部連結屬性
靜態函式只能在宣告它的原始檔中使用。

此處再貼一個別人寫的部落格: https://blog.csdn.net/guotianqing/article/details/79828100