實驗(二)按鍵開關輸入
History Key
- New content
Removed content
Recent Versions
Choose two versions to compare, or click the link to view it.
(教學教材內容保留所有版權~欲轉載請註明出處~謝謝)
實驗(二) 按鍵開關輸入單元 與 控制模組的設計
實驗目的:
本節在實現按鍵開關輸入單元模組,並且使用回呼函式的方式來設計一個模組。
實驗一:
準位輸入,按住SW1則LED全亮,放開則全滅。
實驗二:
防彈跳設計,每按一下SW2 LED向左移位一次;每按一下SW1則LED向右移位一次。
實驗三:
單擊設計,按一下SW1則LED全亮,再按一下SW1則LED全滅。
實驗四:
自保持開關功能設計。按一下SW1則LED全亮,按一下SW2則LED全滅。
I/O腳位定義:
按鈕SW1:pe.6
按鈕SW2:pe.7
模組函式規畫:
/// ======== START Public 函式 ========
void Initial_SW1( void ); /// 初始化 IO
void SW1_Control( void ); /// 按鈕事件控制
/// ======== END Public 函式 ========
/// ======== START SW1 Callback 函式 ========
extern void SW1_Up( void ); /// Callback 函式,當 SW1 放開時會被持續叫用
extern void SW1_Down( void ); /// Callback 函式,當 SW1 按住時會被持續叫用
extern void SW1_Press( void ); /// Callback 函式,當 SW1 按下時會被叫用一次
extern void SW1_Release( void ); /// Callback 函式,當 SW1 放開時會被叫用一次
/// ======== END SW1 Callback 函式 ========
模組函式設計:
操作函式設計:
按鈕模組與上一個LED單元最大的差別在於,LED模組是被動的元件,僅僅作為顯示狀態使用,而按鍵模組主要在接收外部的輸入訊號,而系統永遠無法預測這個輸入訊號什麼時候會進來,因此便產生二個問題:
一、什麼時候要去偵測這個輸入訊號。
二、若是偵測到輸入訊號該怎麼處理。
控制函式的設計:
針對第一個問題,我們可以設計一個控制函式來讓系統叫用,模組提供這個函式給系統在需要時或在特定時間叫用,類似一個服務窗口,系統只要呼叫這個函式,模組便會去偵測相關I/O狀態,然後作出反應,決定應該怎麼處理。
站在系統設計師的立場,在系統設計階段,自然很明瞭按鈕按下去後應該作什麼事,譬如:讓LED燈亮起來,但是,站在模組設計師的觀點,在設計模組的階段並無從了解系統設計時將要拿這個模組來完成什麼任務,記住模組設計的原則是要能重覆使用,也就是模組要設計的能夠通用,能夠適應在不同的系統需求上。
在這裡我們使用callback function 的技巧來實現一個按鈕的動作,由模組定義好一系列的「事件反應函式」,當按鈕事件發生時,模組便會叫用相應的函式,而這些函式是由系統設計師要負責去實作出來的,也就是你希望按鍵「按下」時作「什麼事」,流程如下所示:
系統à( 控制函式偵測事件à反應函式 )à事件處理
中間括號的部份就就是模組事先設計好的函式,其它部份則是在系統設計時才需要去實現它。
對照前面小節「模組函式的規畫」便可以了解它們的關係:
系統à( SW1_Control()àSW1_Press() )à事件處理
其中 「SW1_Control」 XXX_Control 會常常出現在往後的模組裡,作為一個主系統「控制」一個模組「裝置」的處理函式,主系統透過這個函式,讓這個裝置能「經常性」的運作或在「必要時」運作起來,主系統剩下的就只要負責處理要系程邏輯處理的部份,看看常裝置產生「事件」時,要作何處理,而同一個Control事件偵測函式,也有可能偵測到許多不同的事件,就看設計者要怎麼細份你這個模組能夠反應的動作,譬如本單元的按鈕,就細分了「按住」、「放開」、「被按下的瞬間」、「被放開的瞬間」。
C語言教室:
外部函式宣告:
extern void SW1_Up( void );
宣告系統存在一個模組外的函式叫SW1_Up(),實作可以在系統設計階段完成,宣告好就可以在模組設計時預先叫用,亦可稱為回呼函式。
物件導向的程式設計原則:
前面小節講節其實也就是以物件導向的方式來分析一個模組、規劃一個系統,以系統設計而言,物件導向是非常有用的一套方法,適用在高階電腦語言,但其實低階如MCU一樣可以這樣子來分析。
控制函式的實作:
前面談到的都還只是模組的分析部份,其實這也是設計一個模組或一個系統最重要的部份,把「界面」作出來,把殼先作好,分析的越完整(不一定是分的越細),在實作的時候就能更專注於單一功能的設計(高內聚力-high cohension),在整合的時候就能更專注於系統架構的規畫(低耦合力-low coupling)。
void Initial_SW1( void ) ,I/O的初始化,略。
void SW1_Control( void )
{
if( SW1 ) /// 若按鍵是處於放開的狀態
{
SW1_Up(); /// 回呼-按鍵處於放開的狀態
if( sw1_hold_count > SW1_HOLD_COUNT_MAX )/// 若之前是有效的按鍵
SW1_Release(); /// 回呼-按鍵放開確認
sw1_hold_count = 0;
return;
}
/// 按鍵在按住的狀態
if( sw1_hold_count > SW1_HOLD_COUNT_MAX ) /// 按超過防彈跳時間
SW1_Down(); /// 回呼-按鍵處於被按下的狀態
else
sw1_hold_count++;
if( sw1_hold_count == SW1_HOLD_COUNT_MAX ) /// 確定按下的動作
SW1_Press(); /// 回呼-按鍵按下確認
}
這便是整個按鈕事件的控制程式,我們透過一個sw1_hold_count變數來計數,作為按鍵防彈跳的處理,並且依據SW1腳位的變化來判斷目前是發生了什麼事件,其中SW1的腳位有作PULL-HIGH的設定,也就是說,當有按鍵按下時,其準位為0,當按鍵放開時其準位為1。
sw1_hold_count其實就是拿來作計時,系統若是用固定的頻率來叫用這個控制程式,便可以計算出按鈕被按住持續了多久的時間,拿來作為防彈跳時間的依據,甚至可以拿來作為長按鍵時間計算的事件,譬如SW1_PressoOver3Sec()。
控制函式的運作原理就是在監控模組元件事件的發生,當事件發生時去呼叫對應的回呼函式,把主控權交還給系統作處理。
問題討論:
這邊就會衍生幾個問題,也是自行設計裝置模組很需要考慮的問題: 一、主系統要多久一次叫用這個SW1_Control() 函式?這就關係到裝置sample rate的問題,高速裝置或比較需要即時的裝置,主系統就必須給予更快的系統分時,或者,乾脆給一個獨立的核心來負責這個裝置。
二、各裝置自身的SW1_Control() 的執行時間需要多久,這會關係到整個系統的分時,會不會因為要執行這個裝置而造成別的模組時序出問題。
三、SW1_Control() 是否會造成系統 hold 住的問題,有些裝置會需要 wait io 反應,若是 io 出問題,或模組 bug 或其它因素,造成叫用這個函式時整個系統 hold 住,這也是要非常注意的一件事。
解決這些問題的方法可以考慮幾個方向...
一、單純硬體解決,提升硬體的可靠度或系統時脈。
二、將 CONTROL 更細分其時間片段,讓裝置間影響的幅度縮小。
三、每個裝置設計時再更仔細的作硬體檢查及錯誤偵測的動作。
四 、設計系統軟體 WATCHDOG TIMER,讓裝置出問題時能自動回復。
這些方向的改進固然能使系更加穩定,尤其軟體方面的二三四點,一個細慮慎密的模組設計自然能把這些問題一一克服,但是設計師要考量的是,系統複雜度、花費的開發時間、硬體MCU有限的資源、軟體後續整合、再利用之間的一個取捨,使用多個核心來分類控管這些裝置,或許是更容易解決之道。
實驗實作:
實驗一:
準位輸入,按住SW1則LED全亮,放開則全滅。
系統設計時只需把設計好的模組引入(include),再不用慮按鈕底層的程式設計,直接按照系統需求作設計即可。
void FPPA0 (void)
{ /// 初始化...略
while (1)
{ /// 按鈕控制 LOOP
SW1_Control(); /// 叫用按鈕的控制函式
}
}
void SW1_Press( void ) /// Callback 函式,當 SW1 按下時會被叫用一次
{
LED_ON(); /// 按鈕按下的事件處理
}
void SW1_Release( void ) /// Callback 函式,當 SW1 放開時會被叫用一次
{
LED_OFF(); /// 按鈕放開的事件處理
}
實驗二:
防彈跳設計,每按一下SW2 LED向左移位一次;每按一下SW1則LED向右移位一次。
引入二個按鈕模組,並在不同的事件反應程式作處理即可,略。
實驗三:
單擊設計,按一下SW1則LED全亮,再按一下SW1則LED全滅。
略。
實驗四:
自保持開關功能設計。按一下SW1則LED全亮,按一下SW2則LED全滅。
略。.......................略。