接上文:普中STM32F103ZET6開發攻略(一)-CSDN博客
各位看官老爺們,點擊關注不迷路喲。你的點贊、收藏,一鍵三連,是我持續更新的動力喲!!!
目錄
接上文:普中STM32F103ZET6開發攻略(一)-CSDN博客
2.GPIO端口實驗_按鍵控制:
2.1 實驗目的:
2.2 實驗環境:
2.3 實驗原理
2.4 實驗思路
2.4.1 硬件電路
2.5 實驗代碼
2.5.1 LED頭、源
2.5.2 key頭、源
2.5.3 beep頭、源
2.6 實驗思考和拓展
2.6.1 如何改進按鍵消抖方法,使其更加可靠?
2.6.2 如何修改程序,實現不同按鍵組合的功能(如同時按下兩個按鍵)?
2.6.3 如何實現按鍵長按與短按的區分,并執行不同功能?
2.6.4 按鍵掃描可以用中斷方式替代輪詢方式嗎?兩種方式各有什么優缺點?
2.7 注意事項
2.GPIO端口實驗_按鍵控制:
2.1 實驗目的:
-
熟悉STM32F10x微控制器的GPIO口結構和基本操作
-
掌握STM32標準庫函數對GPIO輸入和輸出的配置方法
-
學會使用按鍵輸入控制LED和蜂鳴器的工作狀態
-
理解按鍵消抖原理并實現穩定的按鍵檢測
2.2 實驗環境:
-
開發板:STM32F103ZET6
-
IDE:Keil MDK 5 /Visual Studio
-
調試工具:CMSIS-DAP
-
電路連接:
-
LED0 接 PB5 引腳
-
LED1 接 PE5 引腳
-
KEY0 接 PE4 引腳
-
KEY1 接 PE3 引腳
-
KEY2 接 PE2 引腳
-
WK_UP 接 PA0 引腳
-
2.3 實驗原理
1. 按鍵輸入原理
STM32的GPIO可配置為輸入模式,用于檢測外部按鍵狀態。按鍵輸入模式包括: (1) 上拉輸入:默認高電平,按下按鍵后為低電平 (2) 下拉輸入:默認低電平,按下按鍵后為高電平 本實驗中KEY0、KEY1、KEY2采用上拉輸入方式,KEY_UP采用下拉輸入方式。
模式分類 | 具體模式 | 核心特點 | 典型應用 |
---|---|---|---|
輸入模式 | 浮空輸入(GPIO_Mode_IN_FLOATING) | 無內部上下拉,電平由外部決定 | 外部信號采集(需外部上下拉) |
上拉輸入(GPIO_Mode_IPU) | 內部上拉,默認高電平 | 按鍵輸入(低電平有效) | |
下拉輸入(GPIO_Mode_IPD) | 內部下拉,默認低電平 | 按鍵輸入(高電平有效) | |
模擬輸入(GPIO_Mode_AIN) | 連接 ADC,禁用數字輸入功能 | ADC 模數轉換 | |
輸出模式 | 開漏輸出(GPIO_Mode_Out_OD) | 需外部上拉,支持線與邏輯 | I2C 總線、電平轉換 |
推挽輸出(GPIO_Mode_Out_PP) | 直接輸出高低電平,驅動能力強 | LED 控制、普通數字信號 | |
開漏復用功能()(GPIO_Mode_AF_OD) | 外設驅動,需外部上拉 | SPI/I2C 外設輸出 | |
推挽復用功能(GPIO_Mode_AF_PP) | 外設驅動,直接輸出高低電平 | USART/CAN 外設輸出 |
2. 按鍵消抖技術 機械按鍵在按下或釋放時會產生抖動,導致一次按鍵操作被多次檢測。消抖的常用方法有: (1) 延時消抖:檢測到按鍵狀態變化后,延時一段時間再次檢測確認 (2) 連續采樣消抖:連續多次采樣按鍵狀態,確認狀態穩定才視為有效
3. LED和蜂鳴器控制原理
本實驗板上的LED采用共陽極連接方式,GPIO輸出低電平時LED點亮;蜂鳴器則是GPIO輸出高電平時發聲。
2.4 實驗思路
2.4.1 硬件電路
本實驗使用STM32F10x開發板上的按鍵、LED燈和蜂鳴器進行測試。LED1連接到PB5,LED2連接到PE5,KEY_UP連接到PA0(下拉輸入),KEY0連接到PE4,KEY1連接到PE3,KEY2連接到PE2(上拉輸入),蜂鳴器連接到PB8,接線圖如下:
由上圖可知:KEY0、KEY1、KEY2采用上拉輸入方式,KEY_UP采用下拉輸入方式。
為了完成實驗目的:
我們需要在項目工程中,在實驗一的基礎上新建兩個“庫函數”:key.c+beep.c
由于實驗二的實驗目的與實驗一的不一樣所以Led頭、源文件需要重新寫。
2.5 實驗代碼
2.5.1 LED頭、源
led.h
#ifndef __LED_H #define __LED_H ? #include "stm32f10x.h" ? #define LED0_GPIO_PORT GPIOB #define LED0_GPIO_PIN GPIO_Pin_5 //LED0 ? #define LED1_GPIO_PORT GPIOE #define LED1_GPIO_PIN GPIO_Pin_5 //LED1 ? void LED_Init(void); void LED0_TOGGLE(void); void LED1_ON(void); void LED1_OFF(void); ? #endif ?
led.c
#include "led.h" ? void LED_Init(void) {GPIO_InitTypeDef GPIO_InitStructure;// 開啟 GPIOB 和 GPIOE 的時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOE, ENABLE); ?//配置PB5(LED0)GPIO_InitStructure.GPIO_Pin = LED0_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(LED0_GPIO_PORT, &GPIO_InitStructure); ?//配置PE5(LED1)GPIO_InitStructure.GPIO_Pin = LED1_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure); ?//低電平輸出GPIO_ResetBits(LED0_GPIO_PORT, LED0_GPIO_PIN);GPIO_ResetBits(LED1_GPIO_PORT, LED1_GPIO_PIN); } ? void LED0_TOGGLE(void) {LED0_GPIO_PORT->ODR ^= LED0_GPIO_PIN; } ? void LED1_ON(void) {GPIO_ResetBits(LED1_GPIO_PORT, LED1_GPIO_PIN); } ? void LED1_OFF(void) {GPIO_SetBits(LED1_GPIO_PORT, LED1_GPIO_PIN); } ?
2.5.2 key頭、源
key.h
#ifndef __KEY_H #define __KEY_H ? #include "stm32f10x.h" ? // GPIO 輸入宏 #define PAin(n) (GPIO_ReadInputDataBit(GPIOA, (1 << (n)))) #define PEin(n) (GPIO_ReadInputDataBit(GPIOE, (1 << (n)))) ? // 按鍵 GPIO 宏定義 #define KEY0_PIN ? ? GPIO_Pin_4 #define KEY1_PIN ? ? GPIO_Pin_3 #define KEY2_PIN ? ? GPIO_Pin_2 #define KEY_UP_PIN ? GPIO_Pin_0 ? #define KEY_PORT ? ? GPIOE #define KEY_UP_PORT ? GPIOA ? #define KEY_UP ? ? ? PAin(0) #define KEY0 ? ? ? ? PEin(4) #define KEY1 ? ? ? ? PEin(3) #define KEY2 ? ? ? ? PEin(2) ? // 返回值宏定義 #define KEY_UP_PRESS 1 #define KEY0_PRESS ? 2 #define KEY1_PRESS ? 3 #define KEY2_PRESS ? 4 ? void KEY_Init(void); u8 KEY_Scan(u8 mode); ? #endif ?
key.c
#include "key.h" #include "delay.h" ? void KEY_Init(void) {GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOE, ENABLE); ?// KEY_UP: 下拉輸入GPIO_InitStructure.GPIO_Pin = KEY_UP_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;GPIO_Init(KEY_UP_PORT, &GPIO_InitStructure); ?// KEY0, KEY1, KEY2: 上拉輸入GPIO_InitStructure.GPIO_Pin = KEY0_PIN | KEY1_PIN | KEY2_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_Init(KEY_PORT, &GPIO_InitStructure); } ? u8 KEY_Scan(u8 mode) {static u8 key_up = 1; ?if (mode) key_up = 1; ?if (key_up && (KEY_UP || !KEY0 || !KEY1 || !KEY2)){Delay_ms(10); // 消抖key_up = 0;if (KEY_UP) ? return KEY_UP_PRESS;if (!KEY0) ? return KEY0_PRESS;if (!KEY1) ? return KEY1_PRESS;if (!KEY2) ? return KEY2_PRESS;}else if (!KEY_UP && KEY0 && KEY1 && KEY2){key_up = 1;} ?return 0; } ?
2.5.3 beep頭、源
bepp.h
#ifndef __BEEP_H #define __BEEP_H ? #include "stm32f10x.h" ? #define BEEP_GPIO_PORT GPIOB #define BEEP_GPIO_PIN GPIO_Pin_8 ? void BEEP_Init(void); void BEEP_ON(void); void BEEP_OFF(void); ? #endif ?
beep.c
#include "beep.h" ? void BEEP_Init(void) {GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //初始化各項參數:GPIO_InitStructure.GPIO_Pin = BEEP_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(BEEP_GPIO_PORT, &GPIO_InitStructure); ?GPIO_ResetBits(BEEP_GPIO_PORT, BEEP_GPIO_PIN); // 默認關閉蜂鳴器 } ? void BEEP_OFF(void) {GPIO_ResetBits(BEEP_GPIO_PORT, BEEP_GPIO_PIN); } ? void BEEP_ON(void) {GPIO_SetBits(BEEP_GPIO_PORT, BEEP_GPIO_PIN); } ?
實驗現象:
DS0指示燈會不斷閃爍(200ms翻轉一次)
按下KEY_UP鍵,DS1指示燈點亮
按下KEY2鍵,DS1指示燈熄滅
按下KEY1鍵,蜂鳴器發聲
按下KEY0鍵,蜂鳴器停止發聲
2.6 實驗思考和拓展
2.6.1 如何改進按鍵消抖方法,使其更加可靠?
傳統的延時消抖存在阻塞問題,可通過以下方式優化:
改進方案:
-
雙閾值檢測:同時設置按下閾值和釋放閾值(如按下 > 20ms,釋放 > 15ms)
-
狀態機設計:將按鍵狀態分為 "未按下 - 抖動 - 已按下 - 釋放抖動" 等狀態
-
定時器掃描:使用硬件定時器進行定時檢測,避免阻塞主程序
-
濾波算法:采用一階 RC 濾波或滑動平均濾波處理按鍵信號
2.6.2 如何修改程序,實現不同按鍵組合的功能(如同時按下兩個按鍵)?
實現思路:
-
定義組合鍵映射表:將按鍵組合與功能函數關聯
-
掃描所有按鍵狀態:記錄每個按鍵的獨立狀態
-
狀態比對:檢測是否符合預定義的組合模式
例如: // 定義按鍵掩碼 #define KEY1_MASK (1 << 0) #define KEY2_MASK (1 << 1) #define KEY3_MASK (1 << 2) ? // 組合鍵處理函數 void HandleKeyCombination(uint8_t key_mask) {switch(key_mask) {case KEY1_MASK: ? ? ? ? ?// 單按KEY1Function1(); break;case KEY2_MASK: ? ? ? ? ?// 單按KEY2Function2(); break;case KEY1_MASK|KEY2_MASK: // 同時按KEY1+KEY2Function3(); break;case KEY1_MASK|KEY3_MASK: // 同時按KEY1+KEY3Function4(); break;// 其他組合...} } ? // 主循環中調用 void MainLoop(void) {uint8_t key_mask = 0;if(IS_KEY1_PRESSED) key_mask |= KEY1_MASK;if(IS_KEY2_PRESSED) key_mask |= KEY2_MASK;if(IS_KEY3_PRESSED) key_mask |= KEY3_MASK;HandleKeyCombination(key_mask); }
2.6.3 如何實現按鍵長按與短按的區分,并執行不同功能?
實現方案:
-
定時器計時:按下按鍵時啟動定時器,釋放時停止
-
時間閾值判斷:超過長按閾值(如 1 秒)為長按,否則為短按
-
狀態標志:使用標志位區分長按 / 短按回調
2.6.4 按鍵掃描可以用中斷方式替代輪詢方式嗎?兩種方式各有什么優缺點?
中斷方式:
-
優點:實時響應、不占用 CPU 資源、適合低功耗設計
-
缺點:需處理中斷優先級、可能受噪聲干擾、抖動處理復雜
-
適用場景:需要立即響應的場合(如緊急停止按鍵)
輪詢方式:
-
優點:實現簡單、可靠性高、可批量處理多個按鍵
-
缺點:占用 CPU 資源、響應不及時(取決于掃描周期)
-
適用場景:按鍵數量較多、對響應時間要求不高的場合
推薦方案:
-
使用中斷檢測按鍵動作,進入中斷后啟動定時器消抖
-
消抖完成后在定時器回調函數中處理按鍵事件
-
既保證實時性,又避免抖動干擾
2.7 注意事項
(1) 按鍵檢測時需要注意輸入模式的選擇(上拉或下拉)和實際電平變化方向
(2) 按鍵消抖是保證按鍵檢測可靠性的關鍵
(3) 使用標準庫函數時,需要注意頭文件的包含和依賴關系
(4) GPIO操作前必須先使能對應的外設時鐘
(5) 避免在主循環中設置過長的延時,以免影響按鍵響應速度
文章有寫的不當的地方,歡迎在評論區中指正修改。如果感覺文章實用對你有幫助,歡迎點贊收藏和關注,你的點贊關注就是我動力,大家一起學習進步。
有不懂的可以在評論區里提出來喲,博主看見后會及時回答的。