????????大家好,我是老耿,高職青椒一枚,一直從事單片機、嵌入式、物聯網等課程的教學。對于高職的學生層次,同行應該都懂的,老師在課堂上教學幾乎是沒什么成就感的。正因如此,才有了借助 CSDN 平臺尋求認同感和成就感的想法。在這里,我準備陸續把自己花了很多心思的教學設計分享出來,主要面向廣大師生朋友,單片機老鳥就略過吧。歡迎點贊+關注,各位的支持是本人持續輸出的動力,多謝多謝!
????????前面,我們介紹了STM32的IO口作為輸出的使用,這一章,我們將向大家介紹如何使用IO口作為輸入。在本章中,我們將利用開發板上的按鍵來控制LED的亮滅。通過本章的學習,我們將明白按鍵的電路原理,了解按鍵消抖是怎么回事,鞏固GPIO的初始化配置,學習GPIO端口輸入函數等知識。
【學習目標】
- 了解按鍵防抖、鎖存的方法
- 鞏固GPIO初始化的過程,獨立完成代碼編寫
- 理解按鍵單擊、雙擊、長按的程序算法
????????按鍵是初學嵌入式的第一類輸入器件,入門不難,但是一旦按法多樣化(單擊/雙擊/長按),或是結合其他被控器件,就需要用上中斷、定時器、狀態機等知識,難度也就上來了。本章還是基于GPIO輸入電平的傳統方法來按鍵,計劃分兩個部分,本文是第一部分。
一、認識按鍵開關
1.1 按鍵開關的形態和結構
????????按鍵開關主要是指輕觸式按鍵開關,廣泛用于各種電器和電子消費品中,有各種各樣不同的形態,如圖1所示,用圈標注的是我們開發板使用的類型。不管何種形態,它都是一種電子開關,使用時以滿足操作力的條件向開關操作方向施壓,開關可以閉合接通,當撤銷壓力時開關即斷開,其內部結構是靠金屬彈片受力變化來實現通斷的。

????????如圖2所示,我們的開發板上一共有4個按鍵開關,用KEYx來表示。這種開關雖然有四個引腳,但我們來看圖3,內部①腳和②腳是常閉的,③腳和④腳也是常閉的,這樣其實相當于只有兩個引腳了。


1.2 按鍵的抖動和消抖
????????按鍵機械觸點斷開或閉合時,由于觸點的彈性作用,按鍵開關不會馬上穩定接通或一下子斷開,使用按鍵時會產生帶波紋信號,如圖4所示,所以需要對波紋信號進行消抖處理,否則可能會引起誤判。

?
????????顯然上圖中的紋波是我們不希望的,因此就需要想辦法消抖。消抖的辦法有兩種:硬件消抖和軟件消抖,硬件消抖是在按鍵兩端并聯一個0.1uF的電容,利用電容充放電的延時,消除波紋。硬件消抖很少采用,更普遍的做法是通過軟件消抖,通過延時10~20ms的方式避開抖動,也可以采用連續多次檢測電平的方式避開抖動。
二、按鍵控制LED編程實踐
2.1 任務描述
????????本實驗的任務是用一個按鍵實現對一個LED的控制,每按一次,LED的狀態就改變一次。在控制方式上,使用了無鎖存和有鎖存兩種方法,分別實現了按下有效和松開有效。
2.2 硬件電路
????????圖5是開發板上四個按鍵與STM32連接的電路原理圖,連接方式都是一樣的,這里就以SW1(KEY1)為例進行介紹。當SW1按下時,KEY1端(PC13)與GND接通,為低電平;當SW1松開時,KEY1端電平被上拉電阻R48拉高。這樣,我們通過檢測PC13的輸入電平就知道KEY1是按下還是松開了。同理,KEY2、KEY3、KEY4的狀態需要分別檢測PC11、PC12、PD2的輸入電平。

2.3 工程文件清單
????????本實驗的工程文件清單如圖6所示,在HARDWARE目錄中添加了一對按鍵的驅動文件 key.c 和 key.h。由于用到了LED,因此之前的 led.c 和 led.h 需要保留。

2.4 工程代碼剖析
1. key.h文件源碼
????????頭文件里依然是與IO有關的宏和函數聲明,如代碼清單1所示。
?
//-------------------------------------------------------
// 代碼清單1:key.h
//-------------------------------------------------------#ifndef _KEY_H_
#define _KEY_H_#include "stm32f10x.h"//-------------------------------------------------------
// 必要的宏定義
//-------------------------------------------------------
#define KEY1_PIN GPIO_Pin_13
#define KEY2_PIN GPIO_Pin_11
#define KEY3_PIN GPIO_Pin_12
#define KEY4_PIN GPIO_Pin_2//-------------------------------------------------------
// 庫函數操作宏定義
//-------------------------------------------------------
#define READ_KEY1 GPIO_ReadInputDataBit(GPIOC, KEY1_PIN)
#define READ_KEY2 GPIO_ReadInputDataBit(GPIOC, KEY2_PIN)
#define READ_KEY3 GPIO_ReadInputDataBit(GPIOC, KEY3_PIN)
#define READ_KEY4 GPIO_ReadInputDataBit(GPIOD, KEY4_PIN)//--------------------------------------------------------
// 函數聲明
//--------------------------------------------------------
void Key_Init(void); //按鍵初始化函數#endif
????????這里,我們用 GPIO_ReadInputDataBit() 這個庫函數來讀取一個IO口的電平,函數名雖然長了點,但確實見名就能知意。它有兩個參數:GPIOx和GPIO_Pin_x,返回值就是讀到的電平(1或0),確實也很直觀。
2. key.c文件源碼
????????該文件里就一個函數 Key_Init(),用來初始化按鍵的IO口,如代碼清單2所示。
/************************************************************************** 代碼清單2:key.c* 描 述:按鍵的初始化、驅動* 平 臺:麒麟座V3.2* 作 者:老耿* 日 期:yyyy-mm-dd* 固 件 庫:ST3.5.0* 版 本:V1.0* 修改記錄:無************************************************************************
*///必要的頭文件
#include "key.h"
#include "delay.h"/*************************************************************************** 函 數 名:Key_Init* 功 能:按鍵端口初始化* 入口參數:無* 出口參數:無* 說 明:將按鍵端口設置成輸入模式************************************************************************
**/
void Key_Init(void)
{//定義一個GPIO初始化對象(結構體)GPIO_InitTypeDef gpio_initstruct;//打開必要的外設時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);//填充初始化結構體,上拉輸入模式,并執行生效gpio_initstruct.GPIO_Pin = KEY1_PIN | KEY2_PIN | KEY3_PIN;gpio_initstruct.GPIO_Mode = GPIO_Mode_IPU;//輸入模式不用配置Speed參數//gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOC, &gpio_initstruct);//KEY4與KEY1/2/3不是一組端口,單獨再初始化gpio_initstruct.GPIO_Pin = KEY4_PIN;GPIO_Init(GPIOD, &gpio_initstruct);
}
????????雖然本實驗只使用到了KEY1,但在代碼中,我們將KEY2、KEY3、KEY4的IO口也一并進行了初始化。需要注意的是,GPIO要配置為輸入模式。由于按鍵松開時對應的引腳為高電平,所以我們將其配置為上拉輸入模式。
????????那為什么沒有配置為下拉輸入模式呢?那是因為下拉模式,引腳默認為低電平,這相當于按鍵被按下的狀態。這樣的話,程序將無法檢測按鍵是否被按下,因為無論按鍵是否被按下,引腳電平都為低電平了。當然,配置成浮空輸入模式當然也是可以的。
????????還有一點大家是否發現,上面代碼中,沒有配置引腳的速度,那是因為引腳處于輸入模式時,并不需要配置引腳速度。引腳速度僅是指單片機向引腳刷新電平的頻率,所以只有在引腳處于輸出模式時才有效。
3. main.c文件源碼
????????主程序里編寫了無鎖存和有鎖存兩種按鍵控燈的方法,如代碼清單3所示,大家測試的時候請保留一種方式的代碼,注釋掉另一種,來體會這兩種方式的差異。
/********************************************************* 代碼清單3:main.c* 項 目:按鍵控制LED* 任務描述:按一次KEY1,變一次LED* 實驗平臺:OneNET STM32開發板V3.2* 作 者:老耿* 日 期:yyyy/mm/dd******************************************************
**///-----------------------------------------------------
// 必要的頭文件
//-----------------------------------------------------
#include "delay.h"
#include "led.h"
#include "key.h"//-----------------------------------------------------
// 主函數
//-----------------------------------------------------
int main()
{delay_init(); //延時初始化Led_Init(); //LED初始化Key_Init(); //按鍵初始化while(1) //以下兩種方式保留一種,注釋掉另一種{/*-------------- 無鎖存方式 -------------*/
// if(!READ_KEY1) //按住紅燈亮
// RED_ON;
// else
// RED_OFF; //松開紅燈滅/*-------------- 有鎖存方式 -------------*/if(!READ_KEY1) //按下KEY1{delay_ms(10); //延時消抖if(!READ_KEY1) //確認按下{while(!READ_KEY1); //等待松開RED_TOG; //紅燈變化}}}
}
2.5 驗證與測試
????????我們對兩種方式分別下載和測試后,大家應該可以發現,無鎖存方式下需要按住不放,LED才亮著,一旦松開,LED就滅了。而有鎖存的方式下,需要完成按下和松開這一組動作(即單擊)才能改變LED的狀態,實現這個效果的就是代碼清單3中的第36行,當按鍵被按下時,while的條件將永遠成立,這樣將導致程序一直停留在此處,只有按鍵松開時,后續代碼才可以得到執行,也就達到了通過按鍵鎖存程序狀態的目的。
????????此外,在有鎖存方式中,當按鍵被按下后,并不是馬上進行后續動作,而是延時了10ms,再次判斷按鍵是否被按下,這樣就避免了按鍵因電平抖動造成被誤判的可能。大家也可以嘗試去掉消抖這條語句,看每次按鍵按下的操作是否都能有效。
(第一部分完,共兩部分)