非阻塞實現高效鍵盤掃描功能(STM32F4XX)

目錄

概述

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();}
}

運行代碼后,可以看見:

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/713873.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/713873.shtml
英文地址,請注明出處:http://en.pswp.cn/news/713873.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

springboot,druid動態數據源切換

關鍵字&#xff1a;springboot&#xff0c;druid數據庫連接池&#xff0c;兩個數據源&#xff08;可以切換成多個&#xff09;&#xff0c;事務管理 關于druid簡介傳送門&#xff1a;https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98 具體分為四…

Doris【數據模型】

一、數據模型簡介 在 Doris 中&#xff0c;數據以表&#xff08;Table&#xff09;的形式進行邏輯上的描述。 一張表包括行&#xff08;Row&#xff09;和列&#xff08;Column&#xff09;。Row 即用戶的一行數據。Column 用于描述一行數據中不同的字段。 Column 可以分為兩…

autoware.universe中跟蹤模塊詳解,一看就懂!

目錄 問題:閱讀關鍵點:總結問題: 根據對預測模塊代碼的分析,發現預測框出現在點云前方的原因在于跟蹤框出現在點云前方 對rviz上的目標進行觀察后發現 車輛的檢測框先出來一段時間后,跟蹤框和預測框同步一塊出來 跟蹤框總是超出點云一部分 閱讀關鍵點: 每個跟蹤器最少要統計…

7.1.2 Selenium的用法1

目錄 1. 初始化瀏覽器對象和訪問頁面 2. 查找節點及節點交互 2.1 查找單個節點 &#xff08;1&#xff09;獲取方法1——特定方法 &#xff08;2&#xff09;通用方法 2.2 查找多個節點 2.3 節點交互 3. 動作鏈 4. 執行 JavaScript 之下拉進度條 5. 獲取節點信息 5.…

谷歌seo推廣秒收錄怎么做?

谷歌SEO推廣秒收錄想要做到&#xff0c;可以利用我們光算科技獨家技術&#xff0c;GSI快速收錄&#xff0c;通過技術手段和操作&#xff0c;幫你的網站快速被谷歌發現和記錄 這項技術具體核心就是GPC爬蟲池系統&#xff0c;這個系統是專門研究谷歌搜索引擎優化的規律和算法創造…

【QT】QTableView或QTableWidget 搭配QLineEdit實現數據的搜索顯示

在 Qt 中&#xff0c;QTableView 和 QTableWidget 都可以用來實現數據的搜索和顯示&#xff0c;但它們的適用場景和實現方式有所不同&#xff1a; QTableView 適用場景&#xff1a;QTableView 適用于更復雜的場景&#xff0c;尤其是當需要處理大量數據或需要高度定制化的表格時…

66-ES6:var,let,const,函數的聲明方式,函數參數,剩余函數,延展操作符,嚴格模式

1.JavaScript語言的執行流程 編譯階段&#xff1a;構建執行函數&#xff1b;執行階段&#xff1a;代碼依次執行 2.代碼塊&#xff1a;{ } 3.變量聲明方式var 有聲明提升&#xff0c;允許重復聲明&#xff0c;聲明函數級作用域 訪問&#xff1a;聲明后訪問都是正常的&…

拿下邊界機器進行內網滲透的方案

拿下機器后&#xff0c;使用代理訪問內網 windows環境&#xff1a;reGeorg與proxifier Linux環境&#xff1a;reGeorg與proxychains&#xff0c; 使用nmap等工具進行掃描&#xff0c;發現web服務的主機和其它信息。有時這些邊界機器上會記錄一些 內網服務器上的一些信息&…

QT C++實現點擊按鍵彈出窗口并顯示圖片/視頻|多窗口應用程序的設計和開發

一、介紹 首先&#xff0c;QT界面開發中主要大體分為2種多窗口的形式&#xff1a; 嵌入式&#xff1a; 新生成的窗口嵌入在主窗口內部獨立窗口&#xff1a; 以彈窗形式的新窗口生成和展示 這里就講解最簡單的&#xff1a;點擊案件后&#xff0c;跳出一個新窗口 二、代碼實…

利用FFMPEG 將RTSP流的音頻G711 轉碼為AAC 并 推流到RTMP

之前我們的視頻轉碼項目中 是沒有加入音頻的 現在 需要加入音頻 &#xff0c;由于RTMP只支持AAC的 音頻流 而有的RTSP流的音頻編碼并不是AAC 大多數都是G711編碼 還分為G711A 和G711U 之前用ffmpeg命令行可以直接 完成轉碼 并推送到RTMP 但是考慮到無法獲取更詳細的狀…

Qt篇——QTableWidget保存表格數據到Excel文件中,讀Excel內容到QTableWidget

表格和excel例子如下圖所示&#xff1a; 一、QTableWidget保存表格數據到Excel文件中 代碼如下&#xff1a; &#xff08;pro文件中添加QT axcontainer&#xff09; #include <QAxObject>void MainWindow::saveTableToExcel() {QDateTime current_date_time QDateTi…

六、MQTT源碼簡單瀏覽

1、MQTT程序分層 1.1、MQTT客戶端工作流程 (1)連接MQTT服務器。 (2)客戶端向服務器發送訂閱主題。 (3)客戶端等待MQTT的消息。 (4)客戶端向服務器發送消息。 2.2、MQTT程序結構 APP層 while循環或一個進程中&#xff1a;等待消息&#xff0c;處理消息&#xff1b; 發送消…

[法規規劃|方案實操]數據資產入表,城投將獲融資新渠道

2023年8月&#xff0c;財政部發布了《企業數據資源相關會計處理暫行規定》&#xff0c;并從2024年1月1日開始實施&#xff0c;標志著數據資產正式納入企業的資產負債表。這一舉措被視為數據資產從理論走向實踐的重大一步。 數據資產入表對城投運營模式的影響 隨著全球經濟格局…

Vue3速成

文章目錄 day 11. 創建vue3工程3. 響應式數據4. 計算屬性 day 25. watch 監視6. watchEffect7. 標簽的ref屬性8. 回顧TS中的接口_泛型_自定義類型 day 1 1. 創建vue3工程 相關代碼如下&#xff1a; ## 創建vue工程 npm create vuelastest## 安裝node_modules npm install //…

JSON 文件里的 “$schema” 是干什么用的?

最近我在做一些前端項目&#xff0c;我發現有的配置文件&#xff0c;比如 .prettierrc.json 或者 tsconfig.json 里面都會看到一個 $schema 字段&#xff0c;有點好奇&#xff0c;就查了一下。 什么是 JSON Schema JSON Schema是一種基于JSON (JavaScript Object Notation) 的…

【Leetcode】2369. 檢查數組是否存在有效劃分

文章目錄 題目思路代碼結果 題目 題目鏈接 給你一個下標從 0 開始的整數數組 nums &#xff0c;你必須將數組劃分為一個或多個 連續 子數組。 如果獲得的這些子數組中每個都能滿足下述條件 之一 &#xff0c;則可以稱其為數組的一種 有效 劃分&#xff1a; 子數組 恰 由 2 個…

MATLAB算法實戰應用案例精講-【圖像處理】三維重建(最終篇)

目錄 前言 相機定標和三維重建 針孔相機模型和變形 三維成像 一、機器視覺系統組成

大數據智能化-長視頻領域

隨著數字化時代的到來&#xff0c;長視頻領域的發展迎來了新的機遇和挑戰。在這一背景下&#xff0c;大數據智能化技術的應用成為長視頻行業提升用戶體驗、優化運營管理的重要手段之一。本文將從優愛騰3大長視頻背景需求出發&#xff0c;分析靜態資源CDN、視頻文件存儲與分發、…

網絡安全、信息安全、計算機安全,有何區別?

這三個概念都存在&#xff0c;一般人可能會混為一談。 究竟它們之間是什么關系&#xff1f;并列&#xff1f;交叉&#xff1f; 可能從廣義上來說它們都可以用來表示安全security這樣一個籠統的概念。 但如果從狹義上理解&#xff0c;它們應該是有區別的&#xff0c;區別在哪呢&…

力扣hot100題解(python版36-40題)

36、二叉樹的中序遍歷 給定一個二叉樹的根節點 root &#xff0c;返回 它的 *中序 遍歷* 。 示例 1&#xff1a; 輸入&#xff1a;root [1,null,2,3] 輸出&#xff1a;[1,3,2]示例 2&#xff1a; 輸入&#xff1a;root [] 輸出&#xff1a;[]示例 3&#xff1a; 輸入&am…