4x4矩陣鍵盤

来源:https://www.cnblogs.com/vinccc/archive/2018/01/11/8259696.html
-Advertisement-
Play Games

4x4矩陣鍵盤實拍照如下圖。其構成是4行(L1:4)x 4列(R1:4)共16個按鍵,當第n行、第m列的按鈕(n, m)按下時,引腳 Ln 與 Rm 導通: 有一篇文章,對矩陣鍵盤的介面講解得很詳細。概括起來說,按鍵檢測分為3個階段。第一個階段,掃描行。行I/O口設為input模式,使用上拉電阻。列 ...


4x4矩陣鍵盤實拍照如下圖。其構成是4行(L1:4)x 4列(R1:4)共16個按鍵,當第n行、第m列的按鈕(n, m)按下時,引腳 Ln 與 Rm 導通:

 

 

一篇文章,對矩陣鍵盤的介面講解得很詳細。概括起來說,按鍵檢測分為3個階段。第一個階段,掃描行。行I/O口設為input模式,使用上拉電阻。列I/O口設為output模式,輸出0。逐行掃描,某一行若沒有按鍵按下,則在上拉電阻的作用下pin值讀取為1;若該行任一按鍵按下,則被按鍵短路到列I/O口,因此pin值讀為0。檢測到有按鍵被按下後,進入第二階段,列掃描,以確定被按下的按鍵的列。列掃描階段,行/列的I/O模式互換,即:行I/O口設置為output模式,輸出0;列I/O口設為input模式,使用上拉電阻。類似於行掃描,逐列進行掃描,當讀取到pin值為0則表明被按下的按鍵屬於該列。通過第一、二階段,就能確定被按下的按鍵。第三階段,監聽被按下的按鍵的列I/O口,直到pin值為1,即表明按鍵被鬆開。

關於上拉/下拉電阻,這裡有一篇介紹文章。上拉電阻的作用在於,在常態下,按鈕開放,IO口被“往上拉”到VDD,讀數為1;當按鈕閉合,I/O口通過按鈕短路到VSS,讀數為0;而VDD通過上拉電阻和按鈕與VSS連通。若沒有上拉電阻的存在,則VDD與VSS短路,會造成災難性的後果,這顯然是必須避免的。使用上拉電阻時,按鈕開放時,pin值為1;當按鈕閉合時,pin值為0。即,pin值與按鈕閉合狀態相反,這稱為“負邏輯”。

在前述矩陣鍵盤 的介面演算法中,三個階段都使用了上拉電阻。其檢測邏輯為負邏輯。

STM32的I/O口內部電路中包含有上拉電阻和下拉電阻,可以通過程式啟用或禁用。

流水燈實驗的硬體基礎上,增加矩陣鍵盤介面。4x4矩陣鍵盤共有16個按鍵,4個LED剛好可以顯示16個二進位值(0-0x0F)。

矩陣鍵盤的按鍵檢測是分階段進行的,因此,程式的主體結構特別適合使用“狀態機”設計模式。下列代碼中,4個行I/O口的Label依次為R1:4,列I/O口為C1:4。首先定義狀態結構體及3個實例:

typedef struct {
    void (*enter)();
    uint8_t (*loop)();
} App_ScanningState;

#define App_STAY 0
#define App_LEAVE 1

void rowScanningEnter();
uint8_t rowScanningLoop();
void colScanningEnter();
uint8_t colScanningLoop();
void colScanningPressedEnter();
uint8_t colScanningPressedLoop();

App_ScanningState rowScanning = { rowScanningEnter, rowScanningLoop };
App_ScanningState colScanning = { colScanningEnter, colScanningLoop };
App_ScanningState colScanningPressed = { colScanningPressedEnter, colScanningPressedLoop };

App_ScanningState *currState = &rowScanning;

 

結構體 App_ScanningState 表示1個狀態,當進入該狀態時,調用其 (函數指針)成員enter() 。在程式主迴圈中,則調用其 loop() 成員。loop() 函數返回值為 App_STAY 或 App_LEAVE,若返回前者,則表明應該停留在該狀態,下次主迴圈將再次調用此狀態的 loop() 函數;反之,若返回後者,則表明應該切換到下一個狀態。

rowScanning, colScanning, colScanningPressed 3個App_ScanningState實例,分別為行掃描階段、列掃描階段及第三階段(檢測按鍵鬆開)。程式初始時為行掃描狀態,例如,使用CubeMX自動生成的初始化代碼。程式主迴圈內的代碼為:

    if (App_LEAVE != currState->loop()) {
        return;
    }

    // Button released
    if (currState == &colScanningPressed) {
        lightLedsUp(key);
    }

    // Next state
    currState = currState == &rowScanning ? &colScanning //
            :
                currState == &colScanning ? &colScanningPressed //
                        : &rowScanning;
    currState->enter();

  

首先,調用當前狀態的 loop() 函數,其返回值表明是否應該切換到下一個狀態。如果切換到下一個狀態,則調用其 enter() 函數。如果是離開第三階段,則已檢測到一次按鍵事件(按下並鬆開),根據按鍵鍵值(0-15)點亮LED。點亮LED的函數定義如下,其無外乎按位依次點亮或熄滅每一個LED:

#define BIT_TO_PIN_VALUE(key, bit) ( (1 & (key >> bit)) ? GPIO_PIN_SET : GPIO_PIN_RESET )

void lightLedsUp(uint8_t key) {
    HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, BIT_TO_PIN_VALUE(key, 3));
    HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, BIT_TO_PIN_VALUE(key, 2));
    HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, BIT_TO_PIN_VALUE(key, 1));
    HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, BIT_TO_PIN_VALUE(key, 0));
}

 

對於行掃描狀態,進入該狀態時,應該對行、列的I/O口進行設置。也即,在其enter() 實現中設置行I/O口為input模式,並啟用其內部上拉電阻;列I/O為output模式,並輸出0。其 loop() 實現則依次檢測行I/O口是否讀數為0,若讀數為0,則表明該行有按鍵按下,記下行號,並離開本狀態:

#define configInputPullUp(port, pin, GPIO_InitStruct) { \
/*        HAL_GPIO_WritePin(port, pin, GPIO_PIN_RESET); */ \
        (GPIO_InitStruct)->Pin = pin ; \
        (GPIO_InitStruct)->Mode = GPIO_MODE_INPUT ; \
        (GPIO_InitStruct)->Pull = GPIO_PULLUP ; \
        (GPIO_InitStruct)->Speed = GPIO_SPEED_FREQ_LOW; \
        HAL_GPIO_Init(port, GPIO_InitStruct) ; \
}

#define configOutputLow(port, pin, GPIO_InitStruct) { \
        (GPIO_InitStruct)->Pin = pin ; \
        (GPIO_InitStruct)->Mode = GPIO_MODE_OUTPUT_PP ; \
        (GPIO_InitStruct)->Pull = GPIO_NOPULL ; \
        (GPIO_InitStruct)->Speed = GPIO_SPEED_FREQ_LOW; \
        HAL_GPIO_Init(port, GPIO_InitStruct) ; \
        HAL_GPIO_WritePin(port, pin, GPIO_PIN_RESET);  \
}

#define DEBOUNCE_DELAY 5

void rowScanningEnter() {

    GPIO_InitTypeDef GPIO_InitStruct;

    // Row pins: input, pull-up enabled
    configInputPullUp(R1_GPIO_Port, R1_Pin, &GPIO_InitStruct);
    configInputPullUp(R2_GPIO_Port, R2_Pin, &GPIO_InitStruct);
    configInputPullUp(R3_GPIO_Port, R3_Pin, &GPIO_InitStruct);
    configInputPullUp(R4_GPIO_Port, R4_Pin, &GPIO_InitStruct);

    // Col pins: output 0
    configOutputLow(C1_GPIO_Port, C1_Pin, &GPIO_InitStruct);
    configOutputLow(C2_GPIO_Port, C2_Pin, &GPIO_InitStruct);
    configOutputLow(C3_GPIO_Port, C3_Pin, &GPIO_InitStruct);
    configOutputLow(C4_GPIO_Port, C4_Pin, &GPIO_InitStruct);

}

GPIO_PinState checkPressedLow(GPIO_TypeDef *port, uint16_t pin) {
    if (GPIO_PIN_RESET == HAL_GPIO_ReadPin(port, pin)) {
        // Delay & read again
        HAL_Delay(DEBOUNCE_DELAY);
        return HAL_GPIO_ReadPin(port, pin);
    }

    return GPIO_PIN_SET;
}

uint8_t rowScanningLoop() {

    if (GPIO_PIN_RESET == checkPressedLow(R1_GPIO_Port, R1_Pin)) {
        key = 0;
        return App_LEAVE;
    }
    if (GPIO_PIN_RESET == checkPressedLow(R2_GPIO_Port, R2_Pin)) {
        key = 1 << 2;
        return App_LEAVE;
    }
    if (GPIO_PIN_RESET == checkPressedLow(R3_GPIO_Port, R3_Pin)) {
        key = 2 << 2;key
        return App_LEAVE;
    }
    if (GPIO_PIN_RESET == checkPressedLow(R4_GPIO_Port, R4_Pin)) {
        key = 3 << 2;
        return App_LEAVE;
    }

    return App_STAY;
}

 

註意,在讀取pin值時,為了de-bouncing,增加了一個5ms的延時重讀。一般,de-bouncing延時取5-10ms。

列掃描狀態的實現與行掃描相類似,這裡便不再給出代碼了。需要說明的是,程式中使用了一個位元組型全局變數 key 用來保存鍵值,其第2-3位為行號(0-3),第0-1位為列號(0-3),因此,key 的值為0-0x0F,依次對應16個按鍵。

而第三階段無需改變I/O口設置,只需檢測被按下按鍵所在的列是否讀取pin值為1。讀取pin值為1表明按鍵被鬆開,應該離開此狀態,切換回行掃描狀態:

uint8_t colScanningPressedLoop() {

    int col = 3 & key;

    if (0 == col) {
        if (GPIO_PIN_SET == HAL_GPIO_ReadPin(C1_GPIO_Port, C1_Pin)) {
            return App_LEAVE;
        }
    } else if (1 == col) {
        if (GPIO_PIN_SET == HAL_GPIO_ReadPin(C2_GPIO_Port, C2_Pin)) {
            return App_LEAVE;
        }
    } else if (2 == col) {
        if (GPIO_PIN_SET == HAL_GPIO_ReadPin(C3_GPIO_Port, C3_Pin)) {
            return App_LEAVE;
        }
    } else { // 3== col
        if (GPIO_PIN_SET == HAL_GPIO_ReadPin(C4_GPIO_Port, C4_Pin)) {
            return App_LEAVE;
        }
    }

    return App_STAY;
}

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 首先,建立一個GridView,放入DataTable如圖: 打開欄目設計器的話,是對數據進行操作: 我對aa bb兩行數據,進行了隱藏操作: 這樣可以看到,aa bb在GridView中是隱藏了的,但是如果沒有對新數據進行操作的話,導出的Excel是包含了aa bb兩列的,所以,對新數據進行保存操 ...
  • 網站發佈後經常遇到 字體文件請求不到的問題 配置webConfig可解決該問題 ...
  • 一. 開發環境 1. 此開發平臺主要用來開發基於.NET 4.0及以上版本的應用 2. 點擊此下載 Visual Studio 2012 Ultimate 中文版開發工具 3. 點擊此下載 DXperience-13.2.6 及破解文件 4. 點擊此下載 軟媒虛擬光碟機 5. 點擊此下載需要的 MyS ...
  • 最近公司要遷移.net 2.0的框架到.net core上,先搭建一個簡單環境,利用 IIS +Kestrel 運行了下,經過一下午折騰,成功了,記錄下。 放在有道雲筆記了,這裡是連接. https://note.youdao.com/share/?id=c28de2285dfa746b801656 ...
  • 執行表達式樹 本節主要展示如何去執行表達式樹。運行一個可能含有返回值或只是執行一個操作,比如方法調用的表達式樹。 只有表示lambda表達式的表達式樹能夠被執行。它是一個 "LambdaExpression" 或 "Expression" 類型。為了執行這些表達式樹,調用 "Compile" 方法來 ...
  • STM32的“中斷”機制很複雜,看了PM(Cortex-m4)和RM,對它只瞭解了一個大概。首先,與“中斷”相關的術語就有 exception, interrupt, event 三個。Cortex-m4核中包含一個NVIC控制器,用於處理 exception。而 interrupt 是屬於 exc ...
  • 用戶,組及許可權 基本概念 1)用戶,組,許可權 用戶: 獲取資源的標識。(代表一個用戶)/etc/passwd 組: 許可權的集合,用於方便的指派許可權,不能登錄和使用。(代表一類用戶 邏輯容器 可放用戶 可關聯許可權)/etc/group 許可權: 表示資源的訪問能力。 2)/etc/passwd文件的文件 ...
  • linux系統遵循的基本原則 由目標單一的小程式組成,組合小程式完成複雜任務; 一切皆文件; 儘量避免捕捉用戶介面; 配置文件保存為純文本文件; Linux命令行常識 命令格式 選項: 短選項: 多個短選項可以結合: a b = ab 長選項: 參數: 命令的作用對象; 命令類型 內置命令: she ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...