第五章為深入淺出AMetal,本文內容為5.3 鍵盤掃描接口和5.4 PWM 接口。
5.3 鍵盤掃描接口
>>> 5.3.1 單個獨立按鍵
1. 按鍵行為
如圖5.1 所示,從按鍵的操作行為來看,共有3 種確定的方式,即無鍵按下、有鍵按下和按鍵釋放。并用ret_flag 標志來區分這3 種按鍵的操作行為,其分別為0xFF、0 和1(通常用0xFF表示無效值)。當然還有可能發生的錯誤觸發,ret_flag 也為0xFF。
圖5.1 獨立按鍵電路圖
由于一次按鍵的時間通常為上百毫秒,相對于MCU 來說是很長的,因此不需要時時刻刻地檢測按鍵,只需要每隔一定的時間(如10ms)檢測GPIO 的電平即可。其檢測方法如下(1 表示高電平、0 表示低電平):
(1)無鍵按下時,由于PIO0_1 內部自帶弱上拉電阻,因此PIO0_1 為1;
(2)當KEY 按下時,則PIO0_1 為0。在下一次掃描(延時10ms 去抖動)后,如果PIO0_1 為1,說明錯誤觸發;如果PIO0_1 還是0,說明確實有鍵按下,則將ret_flag 標志置0,執行相應的操作;
( 3)當KEY 釋放時,則PIO0_1 由0 回到1,在下一次掃描(延時10ms 去抖動)后,如果PIO0_1 為0,說明錯誤觸發;如果PIO0_1 還是1,說明按鍵已經釋放,則將ret_flag標志置1,執行相應的操作。
由此可見,無論是否有鍵按下,都要每隔10ms 去掃描GPIO 的狀態,那不妨將每次掃描獲得的值稱為當前鍵值(key_current_value)。由于按鍵存在抖動,因此需要將當前鍵值與下一次掃描得到的鍵值比較,以排除錯誤觸發。由于下一次掃描的鍵值還是未知的,因此必須將本次的鍵值保存起來,等到下次掃描獲取鍵值后,再與保存的鍵值比較。所以在每次掃描結束時,將key_current_value 的值轉存到key_last_value 變量中。那么,對于每一次新的掃描來講,key_last_value 始終保存了上次鍵值,如果新掃描獲得key_current_value 鍵值與key_last_value 上次掃描的鍵值相等,說明該鍵值為有效值,然后將該鍵值保存到最終鍵值key_final_value 變量中,否則是錯誤觸發。
2. GPIO 狀態
從GPIO 的狀態來看,分別為1/1、1/0、0/0、0/1 四種狀態,初始化時:
key_last_value = 1,key_final_value = 1,ret_flag = 0xFF
其中,1/0 的“1”與“0”分別表示掃描前后獲得的當前鍵值,每次掃描時都將ret_flag初始化為0xFF。
(1)1/1:如果GPIO 始終為高電平,說明狀態沒有發生轉移(無鍵按下)
key_last_value = 1,key_current_value = 1,key_final_value = 1,ret_flag = 0xff
(2)1/0:如果GPIO 的狀態從1 轉移到0(有鍵按下),延時10ms 去抖動key_last_value = 1,key_current_value = 0,key_final_value = 1,ret_flag = 0xff0/0:如果GPIO 的狀態還是0(確實有鍵按下),執行相應的操作
key_last_value = 0,key_current_value = 0
因為兩次掃描獲得key_current_value 鍵值都為0,所以將鍵值保存到key_final_value 中,同時置ret_flag 為0,即key_final_value = 0,ret_flag = 0,說明確實有鍵按下,否則視為錯誤觸發。
(3)0/1:如果GPIO 的狀態從0 轉移到1(按鍵釋放),延時10ms 去抖動
key_last_value = 0,key_current_value = 1,key_final_value = 0,ret_flag = 0xff
1/1:如果GPIO 還是1(按鍵確實釋放),執行相應的操作
key_last_value = 1,key_current_value = 1
因為兩次掃描獲得key_current_value 鍵值都為1,所以將鍵值保存到key_final_value 中,同時置ret_flag 為1,即key_final_value = 1,ret_flag = 1,說明按鍵已經釋放,否則視為錯誤觸發。
3. 相關函數與示例
板級初始化和獨立按鍵掃描函數詳見程序清單5.42。
程序清單5.42 獨立按鍵掃描程序(key1.c)
用于判斷連續掃描的當前鍵值是否相等的檢測過程,沒有使用一個循環語句,且在掃描程序中避免了延時10ms 所帶來的影響,從而極大地提高MCU 的運行效率。為了便于后續使用,不妨將key1.c 封裝為key1.h 接口,詳見程序清單5.43。
程序清單5.43 key1.h 文件內容
如果key_scan()的返回值為0,說明確實有鍵按下;如果key_scan()的返回值為1,說明按鍵已經釋放;如果返回值為0xFF,說明無鍵按下。其相應的測試程序詳見程序清單5.44,如果有鍵按下,則蜂鳴器“嘀”一聲;當按鍵釋放后,則LED0 翻轉。
程序清單5.44 獨立按鍵范例程序
>>> 5.3.2 多個獨立按鍵
多個獨立按鍵與單個獨立按鍵的掃描原理是一樣的,在單個獨立按鍵中,每個變量僅使用了一位。由于變量的一位的0 或1 值就可以表示按鍵的2 種狀態,所以,當需要多個按鍵時,則可以充分利用多個位,每位對應一個獨立按鍵。同時,由于獨立按鍵的按下狀態對應的電平并不一定全是低電平,因此將按鍵對應的管腳和按下鍵對應的電平保存到一個結構體數組中。比如:
上述代碼段定義的4 個按鍵對應的管腳分別為PIO0_1、PIO0_2、PIO0_3、PIO0_5,并假設其對應鍵按下的電平分別為1、0、1、0。而在這里僅有一個獨立按鍵,因此結構體數組中只包含一個按鍵的信息:
按照一位對應一個按鍵的思想,在key1.c 的基礎上,修改支持多個獨立按鍵的程序keyn.c,詳見程序清單5.45。
程序清單5.45 支持多個獨立按鍵的掃描程序(keyn.c)
同時,將函數接口的聲明和相關類型的定義存放在keyn.h 中,詳見程序清單5.46。
程序清單5.46 keyn.h 文件內容
顯然,多個獨立按鍵的程序設計思想與獨立按鍵還是一樣的,絕大部分程序都保持一樣。所不同的是多個獨立按鍵的操作是通過多個位操作來實現的。如果只有一個獨立按鍵,則檢測到鍵值變化時,就一定是該鍵狀態變化。而對于多個獨立按鍵來說,如果僅檢測到鍵值的變化,還無法區分是哪個按鍵發生了變化。在這里巧妙地使用了一個change 變量,用于標志位的變化情況。由于存在多個按鍵,因此掃描函數的返回值不能簡單地使用0、1 來表示按下和釋放,還必須包含區分是哪一個按鍵的信息。其規則如下:
返回值的類型為8 位無符號數,使用最高位來表示按下(0)或釋放(1),低7 位用于區分具體的按鍵,值為 0 ~ N-1,N 為按鍵的個數,0~N-1 具體表示的按鍵與數組g_key_info[]中元素的一一對應。因此,當第i 個按鍵按下時,其返回值為i“ret_flag = i;”。第i 個按鍵按下時,返回值應該在i 值的基礎上,將最高位置1,即“ret_flag = (1 << 7) | i;”。
為了方便后續使用,先不考慮軟件定時器,將上述程序添加到key.h 接口。使用多個按鍵的范例程序,同樣按照鍵按下蜂鳴器發出“嘀”的一聲,等按鍵釋放后,LED0 燈翻轉的要求編程,詳見程序清單5.47。
程序清單5.47 支持多個獨立按鍵范例程序
如果使用軟件定時器,那么還需要新增一個軟件定時器初始化函數。比如:
由于使用軟件定時器后,會自動調用keyn_scan()函數,當發現按鍵事件時,則調用按鍵處理程序。現在的問題是,在封裝模塊時,并不知道按鍵處理程序的作用,所以必須使用回調機制。即通過應用程序注冊一個函數,當事件發生時,然后自動調用已經注冊的函數。即:
則重新定義帶軟件定時器的初始化函數,比如:
將上述函數聲明和回調函數的定義添加到keyn.h 文件(程序清單5.48),同時將該函數的實現代碼添加到keyn.c 文件(程序清單5.49)。
程序清單5.48 keyn.h 文件內容
顯然,當按鍵事件發生時,即自動調用初始化函數時注冊的回調函數。
程序清單5.49 新增帶軟件定時器的初始化接口
>>> 5.3.3 矩陣鍵盤
雖然矩陣連接法可以提高I/O 的使用效率,但要區分和判斷按鍵動作的方法卻比較復雜,所以這種接法一般多用在計算機中。下面仍然以圖4.15 所示的2×2 的矩陣鍵盤電路為例,詳細介紹逐行逐列鍵盤掃描發的程序設計方法,其相應的接口詳見程序清單5.50 所示的matrixkey.h 和與接口相應的實現詳見程序清單5.51 所示的matrixkey.c。
程序清單5.50 matrixkey.h 文件內容
程序清單5.51 matrixkey.c 文件內容
為了使其它代碼盡可能復用之前多個獨立按鍵的程序,因此將掃描到的按鍵狀態與鍵值的位一一對應,KEY0 對應bit0 ,KEY1 對應bit1……當有鍵按下時,其對應位為0;當按鍵釋放時,其對應位為1。同時為了獲取鍵值,還必須在初始化函數中,將行線配置為輸出,列線配置為輸入。由此可見,矩陣鍵盤和獨立按鍵的主要區別在于鍵值的獲取方式,為了使代碼盡可能地復用,也將矩陣鍵盤的各個按鍵分別與鍵值的位對應起來。即:當有鍵按下時,其對應位為0;當按鍵釋放時,其對應位為1。
如圖5.2 所示的數碼管的2 個com 端與矩陣鍵盤的列是復用的,PIO0_17 與PIO0_23 既是數碼管的com0、com1,又是矩陣鍵盤的列線KL0、KL1 這樣設計反而節省了引腳。作為鍵盤掃描時需將列線配置為輸入,作為數碼管掃描時需將com 端設置為輸出。顯然,在矩陣鍵盤與數碼管聯合使用時都要進行上述操作,則完全有必要將其封裝為一個接口,即將matrixkey_scan_with_digtron()添加到程序清單5.52 所示的matrixkey.c 中,并將新的接口函數添加到程序清單5.53 所示的matrixkey.h 中。
圖5.2 LED 顯示器電路圖
程序清單5.52 matrixkey_scan_with_digtron()函數實現
在這里,新增了一個保存開始掃描前鍵盤狀態的g_col_level[]數組。在矩陣鍵盤掃描結束后,不僅會將列引腳恢復為輸出狀態,而且還會將其引腳電平恢復為掃描前的狀態,從而使得整個鍵盤掃描程序結束后不影響與數碼管公共引腳的狀態。
程序清單5.53 matrixkey.h 文件內容
5.4 PWM 接口
大小和方向隨時間發生周期性變化的電流稱為交流,交流中最基本的波形稱為正弦波,除此之外的波形稱為非正弦波。計算機、電視機、雷達等裝置中使用的信號稱為脈沖波、鋸齒波等,其電壓和電流波形都是非正弦交流的一種。
PWM(Pulse Width Modulation)就是脈沖寬度調制的意思,一種脈沖編碼技術,即可以按照信號電平改變脈沖寬度。而脈沖寬度調制波的周期也是固定的,用占空比(高電平/周期,有效電平在整個信號周期中的時間比率,范圍為0~100%)來表示編碼數值。PWM可以用于對模擬信號電平進行數字編碼,也可以通過高電平(或低電平)在整個周期中的時間來控制輸出的能量,從而控制電機轉速或LED 亮度。
PWM 信號是由計數器和比較器產生的,比較器中設定了一個閾值,計數器以一定的頻率自加。當計數器的值小于閾值時,則輸出一種電平狀態,比如,高電平。當計數器的值大于閾值,則輸出另一種電平狀態,比如,低電平。當計數器溢出清0 時,又回到最初的電平狀態,即I/O 引腳發生了周期性的翻轉而形成PWM 波形,詳見圖5.3。
圖5.3 PWM 波形圖
當計數器的值小于閾值時,則輸出高電平;當計數器的值大于閾值時,則輸出低電平。閾值為45,計數器的值最大為100。PWM 波形有三個關鍵點:起始點①,此時計數器的值為0;計數器值達到閾值②,I/O 狀態發生翻轉;計數器達到最大值③,I/O 狀態發生翻轉,計數器的值回到0 重新開始計數。
>>> 5.4.1 初始化
在使用PWM 通用接口前,必須先完成PWM 的初始化,以獲取到標準的PWM 實例句柄。在LPC82x 中,能夠提供PWM 輸出功能的外設有SCT(State Configurable Timer),其實質是一個狀態可編程定時器,可以用作普通定時器、輸入捕獲、PWM 輸出等,功能非常強大。在這里,僅僅將其作為PWM 功能使用,AMetal 提供了將SCT 用作PWM 功能的實例初始化函數。其函數原型為:
函數的返回值為am_pwm_handle_t 類型的PWM 實例句柄,該句柄將作為PWM 通用接口中handle 參數的實參。類型am_pwm_handle_t(am_pwm.h)定義如下:
由于函數返回的PWM 實例句柄僅作為參數傳遞給PWM 通用接口,不需要對該句柄作其它任何操作,因此,完全不需要對該類型作任何了解。需要特別注意的是,若函數返回的實例句柄的值為NULL,則表明初始化失敗,該實例句柄不能被使用。
直接調用該實例初始化函即可完成SCT 的初始化,并獲取對應的實例句柄:
SCT 用作PWM 功能時,支持6 個通道,即可同時輸出6 路PWM,各通道對應的I/O口詳見表5.8。
表5.8 各通道對應的I/O 口
>>> 5.4.2 PWM 接口函數
AMetal 提供了3 個PWM 標準輸出接口函數,詳見表5.9。
表5.9 PWM 標準接口函數
1. 配置PWM 通道
配置一個PWM 通道的周期時間和高電平時間,其函數原型為:
如果返回AM_OK,說明配置成功;如果返回-AM_EINVAL,說明配置失敗,范例程序詳見程序清單5.54。
程序清單5.54 am_pwm_config()范例程序
2. 使能通道輸出
使能通道輸出,使相應通道開始輸出波形,其函數原型為:
若返回AM_OK,說明使能成功,開始輸出波形;若返回-AM_EINVAL,說明使能失敗,范例程序詳見程序清單5.55。
程序清單5.55 am_pwm_enable()范例程序
3. 禁能通道輸出
禁能通道輸出,關閉相應通道的波形輸出,其函數原型為:
若返回AM_OK,說明禁能成功;若返回-AM_EINVAL,說明禁能失敗,范例程序詳見程序清單5.56。
程序清單5.56 am_pwm_disable()范例程序
>>> 5.4.3 蜂鳴器接口函數
在蜂鳴器的發聲程序中,雖然延時時間只有500us,但對于MCU 來說非常耗費資源,因為延時期間無法做其它任何想做的事情。我們不妨用MCU 的PWM 輸出功能來實現,即通過PWM 直接輸出一個脈沖寬度調制波形來驅動蜂鳴器發聲。假定波形的周期為1ms,且高低電平占用的時間相等,即占空比為50%,范例程序詳見程序清單5.57。
程序清單5.57 蜂鳴器發聲范例程序
為了方便控制蜂鳴器,基于PWM 接口函數編寫蜂鳴器通用接口,將接口函數的聲明和實現分別放到buzzer.h 和buzzer.c 文件中。當需要使用蜂鳴器時,直接調用蜂鳴器相關接口即可,buzzer.h 詳見程序清單5.58。
程序清單5.58 蜂鳴器通用接口
當接口定義好后,則將實現全部放到buzzer.c 文件,詳見程序清單5.59。
程序清單5.59 蜂鳴器通用接口實現
-
PWM
+關注
關注
114文章
5181瀏覽量
213809
原文標題:周立功:深入淺出AMetal——鍵盤掃描接口和 PWM 接口
文章出處:【微信號:ZLG_zhiyuan,微信公眾號:ZLG致遠電子】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論