目錄
概述
1 原理分析
1.1 技術背景
1.2 系統硬件
1.3 STM32 IO(輸入模式)寄存器分析
1.3.1 輸入IO的功能描述
1.3.2 輸入配置
1.3.3 GPIO 寄存器(輸入模式相關)
1.3.3.1 GPIO 端口模式寄存器
1.3.3.2 GPIO 端口上拉/下拉寄存器
1.3.3.3 GPIO 端口輸入數據寄存器
1.4 外設時鐘使能寄存器
2 軟件實現
2.1 使用STM32CubeMX創建工程
2.2 認識Hal庫中和IO相關的函數
2.3 實現代碼
2.3.1 定義一個Key相關的數據結構
2.3.2 初始化函數
2.3.3 按鍵掃描函數
2.3.4 使用鍵值
3 測試
3.1 編寫測試代碼
3.2 測試
源代碼下載地址:
使用SM32-F4實現非阻塞方式,讀取按鍵值資源-CSDN文庫
概述
? ? ? ? 本文主要介紹如何使用非阻塞方式,實現多個按鍵掃描功能,能準確判斷按鍵的狀態。還詳細介紹STM32 F4系列芯片IO相關的寄存器,已經Hal庫中和IO相關的接口函數。重點講解非阻塞高效鍵盤掃描功能的代碼實現邏輯。
1 原理分析
1.1 技術背景
? ? ? ? 在一個系統程序中,一般希望程序運行盡可能的快,這樣MCU才可能經可能多的執行邏輯,或者處理數據。Windows /Linux系統中引入線程和進程來解決這個問題。在單線程系統(單片機程序:主程序main中一個while循環)中,也可以模擬多線程的方式,把阻塞執行的代碼,使用時間片來輪詢來執行。以提高代碼運行的效率。
掃描鍵盤功能就明顯有這類任務的特征,本文就是采用系統定時器產生時間片,實現一個非阻塞任務方式,掃描鍵盤中的按鍵,并判斷鍵值是否有效。
1.2 系統硬件
電路分析:
系統有8個獨立按鍵,每個獨立按鍵一個端口與一個MCU的一個IO相連,且與MCU IO相連的這個端口,接了一個上拉電阻,其目的,保持IO輸入口電平的穩定性。按鍵的另一個端口與GND連接,當按鍵按下之后,MCU IO會檢測到低電平信號。
1.3 STM32 IO(輸入模式)寄存器分析
要使用MCU IO控制外圍設備,就需要對IO模塊有一個清晰的認識,這樣才能正確的使用它,下面來分析STM32 IO模塊的特性。筆者使用的芯片型號是STM32F407IGT6,所以,本文STM32F4xx用戶手冊為例來介紹其IO的使用方法。由于,STM32 IO的功能比較復雜,這里只介紹將其配置為輸入IO時,該如何使用。
1.3.1 輸入IO的功能描述
根據數據手冊中列出的每個 I/O 端口的特性,可通過軟件將通用 I/O (GPIO) 端口的各個端口位分別配置輸入模式時,有如下3種方式配置:
● 輸入浮空
● 輸入上拉
● 輸入下拉
1.3.2 輸入配置
對 I/O 端口進行編程作為輸入時:
● 輸出緩沖器被關閉
● 施密特觸發器輸入被打開
● 根據 GPIOx_PUPDR 寄存器中的值決定是否打開上拉和下拉電阻
● 輸入數據寄存器每隔 1 個 AHB1 時鐘周期對 I/O 引腳上的數據進行一次采樣
● 對輸入數據寄存器的讀訪問可獲取 I/O 狀
1.3.3 GPIO 寄存器(輸入模式相關)
1.3.3.1 GPIO 端口模式寄存器
每一組GPIO有個 GPIOx_MODER 端口模式寄存器, 該寄存器一共有32個bit, 每兩個bit控制一組IO下的一個pin引腳的模式狀態。
MODERy[1:0]: 端口 x 配置位 (Port x configuration bits) (y = 0..15)。這些位通過軟件寫入,用于配置 I/O 方向模式。
00:輸入(復位狀態)
01:通用輸出模式
10:復用功能模式
11:模擬模式
舉個例子:配置GPIOI_PIN7為輸入引腳,需要寫MODER7[1:0] = 00
1.3.3.2 GPIO 端口上拉/下拉寄存器
每一組GPIO有個 (GPIOx_PUPDR) 上拉/下拉寄存器, 該寄存器一共有32個bit, 每2個bit控制一組IO下的一個pin引腳的上拉/下拉狀態。
PUPDRy[1:0]: 端口 x 配置位 (Port x configuration bits) (y = 0..15),這些位通過軟件寫入,用于配置 I/O 上拉或下拉。
00:無上拉或下拉
01:上拉
10:下拉
11:保留
1.3.3.3 GPIO 端口輸入數據寄存器
每一組GPIO有個 (GPIOx_IDR) 輸入數據寄存器 , 該寄存器一共有32個bit, 每1個bit表示對應端口輸入的值。因為stm32每一組IO有16個端口,所以,使用bit0~bit15,存儲輸入的bit值,bit-16~bit-31保留
IDRy[15:0]: 端口輸入數據 (Port input data) (y = 0..15) 這些位為只讀形式,只能在字模式下訪問。它們包含相應 I/O 端口的輸入值。
1.4 外設時鐘使能寄存器
這個寄存器主要用來打開或者關閉所使用的外設功能,STM32F407有3個外設時鐘使能寄存器 ,本文僅介紹和IO相關的 RCC_AHB1ENR ,其定義如下:
在上圖中可以看見,和IO相關的時鐘使能bit位分布在bit0~bit8,要使用那個端口,只需將對應的位置1,就可以使能該對應位的時鐘。
2 軟件實現
2.1 使用STM32CubeMX創建工程
1)打開STM32CubeMX創建工程,然后在GPIO選項卡中,配置和KEY-IO相關的參數。
2)完成參數配置后,點擊GENERATE CODE,生成工程文件。
2.2 認識Hal庫中和IO相關的函數
1) HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)
功能: 用于初始化GPIO的屬性,在STM32CubeMX中配置IO屬性后,該函數會在IO初始代碼中調用。這部分代碼會由STM32CubeMX自動生成。
2) PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
功能:讀取IO_PIN的值
函數參數:
GPIOx: GPIO組(A,B,C...)
GPIO_Pin: GPIO組下的那個引腳(0~15)
返回值: 讀到IO pin 的值
3) HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
功能:寫IO_PIN的值
函數參數:
GPIOx: GPIO組(A,B,C...)
GPIO_Pin: GPIO組下的那個引腳(0~15)
PinState: 要寫的狀態(0 or 1)
4) HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
功能: 觸發IO_Pin電平變化
函數參數:
GPIOx: GPIO組(A,B,C...)
GPIO_Pin: GPIO組下的那個引腳(0~15)
5)HAL_GPIO_LockPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
功能: 鎖存當前IO_Pin的值,reset時,該IO的電平不會發生變化
函數參數:
GPIOx: GPIO組(A,B,C...)
GPIO_Pin: GPIO組下的那個引腳(0~15)
6)HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
功能: 當前IO_Pin的中斷函數
函數參數:
GPIO_Pin: GPIO組下的那個引腳(0~15)
7)HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
功能: 中斷函數的回調函數,HAL_GPIO_EXTI_IRQHandler中會調用該函數,且這個函數函數為weak類型,用戶可重新寫它。
2.3 實現代碼
源代碼下載地址:?使用SM32-F4實現非阻塞方式,讀取按鍵值資源-CSDN文庫
2.3.1 定義一個Key相關的數據結構
定義一個數據結構,由于操作和key相關的狀態控制。
typedef struct
{uint8 (*KeyActFunc)(void); ?
?uint8 ?Count;uint8 ?State;uint8 ?DownState;uint8 ?ReleaseState;
}KeyAct_Stru;
2.3.2 初始化函數
1)在該段函數中,21~29 行,實現IO狀態讀取函數,當按鍵被按下后,返回值為1, 否則返回值為0
2)44~52行, 注冊按鍵觸發函數
源代碼
static KeyAct_Stru s_tBtn[KEY_TOTAL];static unsigned char IsKey_1_Down(void) {if ((KEY_1_GPIO_Port->IDR & KEY_1_Pin) == 0) return 1;else return 0;}
static unsigned char IsKey_2_Down(void) {if ((KEY_2_GPIO_Port->IDR & KEY_2_Pin) == 0) return 1;else return 0;}
static unsigned char IsKey_3_Down(void) {if ((KEY_3_GPIO_Port->IDR & KEY_3_Pin) == 0) return 1;else return 0;}static unsigned char IsKey_up_Down(void) {if ((KEY_UP_GPIO_Port->IDR & KEY_UP_Pin) == 0) return 1;else return 0;}
static unsigned char IsKey_down_Down(void) {if ((KEY_DOWN_GPIO_Port->IDR & KEY_DOWN_Pin) == 0) return 1;else return 0;}
static unsigned char IsKey_left_Down(void) {if ((KEY_LEFT_GPIO_Port->IDR & KEY_LEFT_Pin) == 0) return 1;else return 0;}
static unsigned char IsKey_right_Down(void) {if ((KEY_RIGHT_GPIO_Port->IDR & KEY_RIGHT_Pin) == 0) return 1;else return 0;}
static unsigned char IsKey_ok_Down(void) {if ((KEY_OK_GPIO_Port->IDR & KEY_OK_Pin) == 0) return 1;else return 0;}void bsp_KeyInit( void )
{int i = 0;KeyAct_Stru *pBtn;for( i =0; i < KEY_TOTAL; i++ ){pBtn = &s_tBtn[i];memset( pBtn, sizeof(KeyAct_Stru), 0 );}s_tBtn[0].KeyActFunc = IsKey_1_Down;s_tBtn[1].KeyActFunc = IsKey_2_Down;s_tBtn[2].KeyActFunc = IsKey_3_Down;s_tBtn[3].KeyActFunc = IsKey_up_Down;s_tBtn[4].KeyActFunc = IsKey_down_Down;s_tBtn[5].KeyActFunc = IsKey_left_Down;s_tBtn[6].KeyActFunc = IsKey_right_Down;s_tBtn[7].KeyActFunc = IsKey_ok_Down;
}
2.3.3 按鍵掃描函數
實現邏輯如下: 1) 當按鍵被按下后,檢測計數器開始工作(63~75行),bsp_KeyScan()的運行周期是1ms,當count值累加到門限值時,按鍵按下的狀態沒有改變,說明按鍵值有效。
2)檢測按鍵彈起:
step-1: 檢測按下標記為是否有效,如果該位有效,說明按鍵被按下過。
step-2:計數器開始工作,當計數器的值到達門限值后,彈起狀態有效,置位彈起標記。
3)鍵值位(102~108行)
檢測按下確認狀態位和彈起確認狀態位。當二者都有效時,存儲的鍵值有效。
源代碼
void bsp_KeyScan( unsigned char index )
{KeyAct_Stru *pBtn;pBtn = &s_tBtn[index]; if( pBtn->KeyActFunc() ) {// key first pressed if( pBtn->Count < KEY_FILTER_MAX_TIME ){pBtn->Count = KEY_FILTER_MAX_TIME;}else if( pBtn->Count < KEY_FILTER_NEXT_TIME ) {pBtn->Count++;}else{// confirm: key is presssed if( !pBtn->DownState ) {pBtn->DownState = 1;}pBtn->Count = 0;}}else{if( pBtn->DownState ){if( pBtn->Count > KEY_FILTER_MAX_TIME ) {pBtn->Count = KEY_FILTER_MAX_TIME;}else if( pBtn->Count > 0){pBtn->Count--;}else{//confirm: key is released if( !pBtn->ReleaseState ) {pBtn->ReleaseState = 1;}}}}// confirm key press action if( pBtn->ReleaseState && pBtn->DownState ){printf(" KEY-%d is pressed!\r\n", index);pBtn->State = 1;pBtn->ReleaseState = 0;pBtn->DownState = 0;}
}
2.3.4 使用鍵值
1)bsp_KeyMonitor
按鍵掃描函數,該函數必須放在一個以1ms為間隙掃描的任務里,其會周期性的掃描所有注冊的按鍵狀態
2)bsp_KeyGetValue
獲取鍵值函數,通過傳入key所對應ID的值,就能得到該鍵值
3 測試
3.1 編寫測試代碼
1) 第104行, 調用系統Tick函數,實現1ms Tick功能,
2)第117行,實現鍵盤掃描功能,其執行周期為1ms
3) 第127行,讀取鍵值
源代碼
void bsp_KeyMonitor( void )
{unsigned char index ;for( index = 0; index < KEY_TOTAL; index ++ ){bsp_KeyScan( index );}
}unsigned char bsp_KeyGetValue(KEY_ID keyID)
{unsigned char value;value = s_tBtn[keyID].State;s_tBtn[keyID].State = 0; // clear key statusreturn value;
}
3.2 測試
編譯程序,下載到板卡中,按下不同的按鍵,會打印不同的鍵值
源代碼
void tick_action( void )
{static bool flag_1s = 0;static unsigned int tick_cnt = 0;static unsigned int beforTick = 0;unsigned int currentTick;unsigned char val;currentTick = HAL_GetTick();if(beforTick != currentTick ){beforTick = currentTick;tick_cnt++;// 1s actionif( (tick_cnt % 1000) == 0) {flag_1s = true;;}//1ms actionbsp_KeyMonitor();}if( flag_1s ){flag_1s = false;HAL_GPIO_TogglePin(SYS_RUN_LED_GPIO_Port, SYS_RUN_LED_Pin);}val = bsp_KeyGetValue( KEY_1 );if( val ){test_can1_send();}val = bsp_KeyGetValue( KEY_2 );if( val ){test_can2_send();}
}
運行代碼后,可以看見: