周立功教授新書(shū)《面向AMetal框架與接口的編程(上)》,對(duì)AMetal框架進(jìn)行了詳細(xì)介紹,通過(guò)閱讀這本書(shū),你可以學(xué)到高度復(fù)用的軟件設(shè)計(jì)原則和面向接口編程的開(kāi)發(fā)思想,聚焦自己的“核心域”,改變自己的編程思維,實(shí)現(xiàn)企業(yè)和個(gè)人的共同進(jìn)步。
第四章為面向接口的編程,本文內(nèi)容包括:4.4 事件驅(qū)動(dòng)和4.5 鍵盤(pán)管理。
4.4 事件驅(qū)動(dòng)
>>> 4.4.1 中斷與事件驅(qū)動(dòng)
1. 中斷
到目前為止,幾乎所有的程序都依賴(lài)輪詢(xún)通信。那些代碼只是一遍一遍地巡檢外圍功能部件,并在需要的時(shí)候?yàn)橥鈬O(shè)備提供服務(wù)。可想而知,輪詢(xún)?cè)L問(wèn)不僅消耗了大量的 MCU資源,而且將導(dǎo)致非常不穩(wěn)定的反應(yīng)時(shí)間。
為了有效地解決上述可能導(dǎo)致整個(gè)系統(tǒng)癱瘓的問(wèn)題,計(jì)算機(jī)專(zhuān)家提出了一種“實(shí)時(shí)”的解決方案,通過(guò)“中斷”使可預(yù)見(jiàn)的反應(yīng)時(shí)間維持在幾微秒之內(nèi)。所謂中斷是指當(dāng) MCU 正在處理某件事情的時(shí)候,外部發(fā)生的某一“事件”請(qǐng)求 MCU 迅速去處理,于是 MCU 暫時(shí)中止當(dāng)前的工作,轉(zhuǎn)去處理所發(fā)生的事件。當(dāng)中斷服務(wù)處理完該事件以后,再回到原來(lái)被中止的地方繼續(xù)原來(lái)的工作。
但也有可能突發(fā)事務(wù)請(qǐng)求中斷時(shí),可能出現(xiàn)在正常程序流程的任何地方,在正常程序流程中可以選擇響應(yīng)或不響應(yīng)這個(gè)中斷請(qǐng)求,突發(fā)事件的處理可能會(huì)改變整個(gè)程序的狀態(tài),從而也改變了后續(xù)的正常程序流程。
比如,在一次會(huì)議上你正在按照計(jì)劃做報(bào)告,這時(shí)手機(jī)鈴聲響了,此時(shí),你有兩種選擇,一是你覺(jué)得正在進(jìn)行的報(bào)告更重要,你可以?huà)鞌嚯娫?huà)或干脆關(guān)機(jī),等會(huì)后再去處理這個(gè)來(lái)電;二是你認(rèn)為這個(gè)電話(huà)很重要或很快就可處理完畢(不影響做報(bào)告),你可以暫停報(bào)告轉(zhuǎn)而接聽(tīng)這個(gè)電話(huà),當(dāng)接聽(tīng)完畢后,你再繼續(xù)做報(bào)告,前提是你必須記住接電話(huà)前講到哪里了,當(dāng)然如果你足夠機(jī)敏的話(huà),在這次通話(huà)中你所接收到的信息可能會(huì)改變你隨后的報(bào)告內(nèi)容。
由此可見(jiàn),通過(guò)中斷方式允許系統(tǒng)在執(zhí)行主程序時(shí)可以響應(yīng)并處理其它任務(wù),進(jìn)而中斷驅(qū)動(dòng)系統(tǒng)給人們一種假象,MCU 可以同時(shí)執(zhí)行多個(gè)任務(wù)。而事實(shí)上 MCU 不能同時(shí)執(zhí)行 1條以上的指令,它只是暫停主程序轉(zhuǎn)去執(zhí)行其它程序,完成后再返回繼續(xù)執(zhí)行主程序。
從這個(gè)角度來(lái)看,中斷響應(yīng)非常類(lèi)似于函數(shù)的調(diào)用過(guò)程。它們兩者之間的差別在于中斷的響應(yīng)是由“事件”發(fā)起的,而不像函數(shù)調(diào)用那樣,它是在主程序流程中預(yù)先設(shè)定的,中斷是系統(tǒng)響應(yīng)一些和主程序異步事件,這些事件何時(shí)將主程序中斷是預(yù)先未知的。有了中斷就可以實(shí)現(xiàn)主機(jī)與外設(shè)并行工作,支持多程序并發(fā)運(yùn)行,支持實(shí)時(shí)處理功能。
2. 事件驅(qū)動(dòng)
在現(xiàn)實(shí)生活中,“發(fā)生的某件事情”就是事件,事實(shí)上很多程序都對(duì)“發(fā)生的事情”做出反應(yīng)。比如,移動(dòng)或點(diǎn)擊鼠標(biāo)、按鍵、或經(jīng)過(guò)一定的時(shí)間都是基于事件的驅(qū)動(dòng)程序。
事件驅(qū)動(dòng)程序只是“原地不動(dòng)”,什么也不做,等待有事件發(fā)生,一旦事件確實(shí)發(fā)生了,它們就會(huì)做出反應(yīng),完成所有必要的工作來(lái)處理這個(gè)事件。其實(shí),Windows 操作系統(tǒng)就是事件驅(qū)動(dòng)程序的一個(gè)很好的示例,當(dāng)啟動(dòng)計(jì)算機(jī)運(yùn)行 Windows 時(shí),它只是“原地不動(dòng)”,不會(huì)啟動(dòng)任何程序,你也不會(huì)看到鼠標(biāo)光標(biāo)在屏幕上移動(dòng)。不過(guò),如果你開(kāi)始移動(dòng)或點(diǎn)擊鼠標(biāo),就會(huì)有情況發(fā)生。
為了讓事件驅(qū)動(dòng)程序“看到”有事件發(fā)生,它必須“尋找”這些事件,程序必須不斷地掃描計(jì)算機(jī)內(nèi)存中用于事件發(fā)生的部分,即只要程序在運(yùn)行就會(huì)不斷尋找事件。顯然,只要移動(dòng)或點(diǎn)擊了鼠標(biāo)或按下了按鍵,就會(huì)發(fā)生事件,這些事件在哪里呢?比如,在內(nèi)存中存儲(chǔ)事件的部分就是事件隊(duì)列,事件隊(duì)列就是發(fā)生的所有事件的列表,這些事件按它們發(fā)生的順序排列。
如果需要編寫(xiě)一個(gè)游戲,則程序必須知道用戶(hù)什么時(shí)候按下一個(gè)按鍵或移動(dòng)了鼠標(biāo)。而這些按鍵動(dòng)作、點(diǎn)擊或移動(dòng)鼠標(biāo)都是事件,而且程序必須知道如何應(yīng)對(duì)這些事件,它必須處理事件,程序中處理某個(gè)事件的部分稱(chēng)為 事件處理器。而事實(shí)上并不是發(fā)生的每一個(gè)事件都要處理,比如,在桌面移動(dòng)鼠標(biāo)就會(huì)產(chǎn)生成百上千個(gè)事件,因?yàn)槭录h(huán)運(yùn)行得非常快。每一個(gè)瞬間即使鼠標(biāo)只是移動(dòng)了一點(diǎn)點(diǎn),也會(huì)生成一個(gè)新的事件。不過(guò)你的程序可能并不關(guān)心鼠標(biāo)的每一個(gè)小小的移動(dòng),它可能只關(guān)心用戶(hù)什么時(shí)候點(diǎn)擊某個(gè)部分,因此你的程序可以忽略鼠標(biāo)移動(dòng)事件,只關(guān)注鼠標(biāo)點(diǎn)擊事件。
事件驅(qū)動(dòng)程序中,對(duì)于所關(guān)心的各種事件會(huì)有相應(yīng)的事件處理器。如果你有一個(gè)游戲使用鍵盤(pán)上的方向來(lái)控制一艘船的移動(dòng),可能要為 keyDown 事件寫(xiě)一個(gè)處理器;相反,如果使用鼠標(biāo)控制這艘船,就可能為 mouseMove 事件寫(xiě)一個(gè)事件處理器。
另一種有用的事件是軟件定時(shí)器事件,定時(shí)器會(huì)按設(shè)定的間隔生成事件,就像鬧鐘一樣,如果設(shè)定好鬧鐘,并將鬧鐘打開(kāi),每天它都會(huì)在固定的時(shí)刻響起來(lái)。比如,(宏觀上)同時(shí)處理兩個(gè)事件。其中,一個(gè)為鍵盤(pán)輸入事件,另一個(gè)為時(shí)間事件,用于顯示運(yùn)行的時(shí)間,每秒顯示一次。
顯然,可以在 main()函數(shù)設(shè)置一個(gè)循環(huán),依次檢查是否有鍵盤(pán)輸入和時(shí)間是否到 1 秒?其實(shí)都可以直接調(diào)用固定的函數(shù)來(lái)實(shí)現(xiàn)“鍵盤(pán)輸入處理代碼”和“時(shí)間處理代碼”,但這樣不夠靈活,此時(shí)可以用中斷機(jī)制來(lái)實(shí)現(xiàn),即由硬件來(lái)實(shí)現(xiàn)對(duì)事件的檢測(cè)并調(diào)用指定的函數(shù),這樣一來(lái)使用注冊(cè)回調(diào)函數(shù)機(jī)制也就成為了必然。而注冊(cè)回調(diào)函數(shù)就是事先用一個(gè)函數(shù)指針變量保存指定的函數(shù),然后在事件發(fā)生時(shí),通過(guò)這個(gè)函數(shù)指針變量調(diào)用指定的函數(shù)。
>>>4.4.2 軟件定時(shí)器
我們知道,數(shù)碼管顯示主要做兩件事,其一,每隔 5ms 調(diào)用一次 digitron_disp_scan()動(dòng)態(tài)掃描顯示函數(shù),其次,當(dāng)需要改變顯示內(nèi)容時(shí),則調(diào)用緩沖區(qū)操作接口,修改緩沖區(qū)中的內(nèi)容。由于 MCU 設(shè)計(jì)了類(lèi)似于鬧鐘那樣的特定性的周期性的中斷時(shí)鐘節(jié)拍源,因此由時(shí)鐘節(jié)拍源實(shí)現(xiàn)的定時(shí)器也是一個(gè)周期性的定時(shí)器,并產(chǎn)生周期性的中斷,這個(gè)中斷可以看做系統(tǒng)心臟的脈動(dòng)。即當(dāng)計(jì)數(shù)值等于定時(shí)時(shí)間時(shí),則定時(shí)器立即觸發(fā)中斷,計(jì)數(shù)器重新開(kāi)始計(jì)數(shù),如此周而復(fù)始循環(huán)計(jì)數(shù)。
顯然,可以使用定時(shí)器的周期性的中斷實(shí)現(xiàn)自動(dòng)掃描顯示,即每隔 5ms 觸發(fā)中斷自動(dòng)調(diào)用 digitron_disp_scan(),這樣就可以將 MCU 解放出來(lái)執(zhí)行其它的任務(wù),從而得到更好的性能,其相應(yīng)的接口函數(shù)詳見(jiàn)表 4.3。程序員先調(diào)用軟件定時(shí)器函數(shù),然后等待操作完成。通常程序員提供一個(gè)由函數(shù)指針指定的回調(diào)函數(shù),當(dāng)操作完成后,中斷系統(tǒng)會(huì)調(diào)用回調(diào)函數(shù)。
表 4.3 軟件定時(shí)器接口函數(shù)
1. am_softimer_t 類(lèi)型
從面向?qū)ο蟮慕嵌葋?lái)看,類(lèi)相當(dāng)于 C 語(yǔ)言的結(jié)構(gòu)體,這里的 am_softimer_t 是用 typedef自定義的一個(gè)對(duì)用戶(hù)隱藏的結(jié)構(gòu)體類(lèi)型。即:
在使用軟件定時(shí)器時(shí),需要使用該類(lèi)型定義一個(gè)軟件定時(shí)器實(shí)例(對(duì)象),實(shí)例的本質(zhì)是定義一個(gè)結(jié)構(gòu)體變量。比如:
顯然,對(duì)象是類(lèi)型的實(shí)例,即 timer 是 am_softimer_t 類(lèi)型的一個(gè)實(shí)例。
2. 初始化軟件定時(shí)器
事先將指定的函數(shù)保存在函數(shù)指針 p_func 中(注冊(cè)),當(dāng)定時(shí)時(shí)間到時(shí),則通過(guò) p_func調(diào)用指定的函數(shù),即注冊(cè)函數(shù)回調(diào)機(jī)制。
其中的 p_timer 為使用 am_softimer_t 類(lèi)型定義的軟件定時(shí)器實(shí)例,當(dāng)定時(shí)時(shí)間到,則調(diào)用 p_func 指向的函數(shù)(注冊(cè)回調(diào)函數(shù)),am_pfnvoid_t 是 AMetal 聲明的函數(shù)指針類(lèi)型,其定義(am_types.h)如下:
由此可見(jiàn),p_func 指向的函數(shù)類(lèi)型是無(wú)返回值,具有一個(gè) void*型參數(shù)的函數(shù)。p_arg為用戶(hù)自定義的參數(shù),在定時(shí)時(shí)間到調(diào)用回調(diào)函數(shù)時(shí),會(huì)將此處設(shè)置的 p_arg 作為作為參數(shù)傳遞給回調(diào)函數(shù);如果不使用此參數(shù),則設(shè)置為 NULL。如果返回 AM_OK,說(shuō)明軟件定時(shí)器初始化成功;如果返回-AM_EINVAL,說(shuō)明由于參數(shù)錯(cuò)誤導(dǎo)致初始化失敗。初始化函數(shù)的使用范例詳見(jiàn)程序清單 4.26。
程序清單 4.26 am_softimer_init ()函數(shù)范例程序
其中的 am_softtimer_init()函數(shù)(A)與用戶(hù)自定義的任務(wù)函數(shù)(C)同屬于上層模塊的函數(shù),timer_callback()函數(shù)(B)為下層模塊的函數(shù)。由于事先已經(jīng)將 timer_callback()的地址 time_callback 保存在 p_func 中了,因此,當(dāng) am_softtimer_init()調(diào)用 timer_callback()時(shí),僅需將用戶(hù)自定義的任務(wù)函數(shù)的入口地址作為實(shí)參傳遞給 timer_callback()的形參,即可通過(guò)函數(shù)指針變量 p_arg 在某個(gè)時(shí)刻回調(diào)用戶(hù)自定義的任務(wù)函數(shù),即在函數(shù) A 調(diào)用函數(shù) B 中直接調(diào)用回調(diào)函數(shù) C。即只要在每次調(diào)用 timer_callback()時(shí),給出不同的函數(shù)名作為實(shí)參,即可回調(diào)相應(yīng)的函數(shù),卻不必修改 timer_callback()。
3. 啟動(dòng)軟件定時(shí)器
啟動(dòng)定時(shí)器并設(shè)置定時(shí)時(shí)間(單位 ms),然后定時(shí)器開(kāi)始計(jì)數(shù)。當(dāng)計(jì)數(shù)值等于定時(shí)時(shí)間時(shí),則定時(shí)器立即觸發(fā)中斷,計(jì)數(shù)器重新開(kāi)始計(jì)數(shù),如此周而復(fù)始循環(huán)計(jì)數(shù)。當(dāng)定時(shí)器觸發(fā)中斷時(shí),則程序跳轉(zhuǎn)到調(diào)用 am_softimer_init()時(shí) p_func 指向的函數(shù),其函數(shù)原型為:
p_timer 為使用 am_softimer_t 類(lèi)型定義的軟件定時(shí)器實(shí)例,ms 為定時(shí)時(shí)間,單位 ms。如果返回 AM_OK,說(shuō)明啟動(dòng)定時(shí)器成功;如果返回-AM_EINVAL,說(shuō)明失敗參數(shù)錯(cuò)誤。設(shè)置定時(shí)器以實(shí)現(xiàn)數(shù)碼管自動(dòng)掃描顯示的代碼詳見(jiàn)程序清單 4.27。
程序清單 4.27 自動(dòng)掃描顯示實(shí)現(xiàn)
程序中,digitron_softimer_set()函數(shù)初始化并啟動(dòng)了一個(gè)軟件定時(shí)器,并在定時(shí)器回調(diào)函數(shù)中調(diào)用了數(shù)碼管掃描函數(shù),進(jìn)而實(shí)現(xiàn)了數(shù)碼管自動(dòng)掃描。
為了更方便的使用自動(dòng)掃描,可以將 digitron_softimer_set()合并到 digitron_init()中,形成一個(gè)新的 digitron_init_with_softimer(),當(dāng)用戶(hù)需要數(shù)碼管初始化后自動(dòng)掃描時(shí),只需調(diào)用該帶軟件定時(shí)器的初始化函數(shù)即可,詳見(jiàn)程序清單 4.28。
程序清單 4.28 digitron1.h 文件內(nèi)容
如程序清單 4.29 所示為再次迭代的 0~59 秒循環(huán)顯示程序。
程序清單 4.29 0~59 秒計(jì)數(shù)器范例程序(3)
既然程序是每隔 1s 計(jì)數(shù)器加 1 后更新緩沖區(qū)數(shù)據(jù)的,那么同樣可以使用軟件定時(shí)器實(shí)現(xiàn)每秒加 1 的操作,迭代后的代碼詳見(jiàn)程序清單 4.30。
程序清單 4.30 0~59 秒計(jì)數(shù)器范例程序(4)
當(dāng)啟動(dòng)軟件定時(shí)器后,秒計(jì)數(shù)器加1和更新緩沖區(qū)數(shù)據(jù)的工作自動(dòng)在timer_sec_callback()函數(shù)中完成,不再需要主程序干預(yù)。現(xiàn)在 while(1)主循環(huán)什么事情都不用做,同樣實(shí)現(xiàn)了 0~59的循環(huán)顯示。這樣一來(lái),數(shù)碼管就會(huì)獨(dú)立地工作了,那么在 while(1)主循環(huán)中,就可以直接去做其它事情。以后遇到“每隔一定時(shí)間做某件事”的問(wèn)題,均可使用軟件定時(shí)器來(lái)實(shí)現(xiàn)。
雖然用軟件定時(shí)器實(shí)現(xiàn)自動(dòng)掃描顯示的方法非常巧妙,流程也更加清晰,且程序還可以去做其它的事情,但卻是以犧牲程序空間為代價(jià)的,即軟件定時(shí)器要占用一個(gè)硬件定時(shí)器,以及 438 個(gè)字節(jié)的 Flash 和 12 個(gè)字節(jié)的 RAM。同時(shí)在使用軟件定時(shí)器時(shí),由于新建一個(gè)軟件定時(shí)器必須定義一個(gè)定時(shí)器實(shí)例,每個(gè)定時(shí)器實(shí)例還要占用 24 字節(jié),因此要根據(jù)硬件資源做出取舍。
4. 關(guān)閉軟件定時(shí)器
當(dāng)軟件定時(shí)器關(guān)閉時(shí),如果再次啟動(dòng),則調(diào)用 am_softimer_start()重新啟動(dòng)。即:
其中的 p_timer 為使用 am_softimer_t 類(lèi)型定義的軟件定時(shí)器實(shí)例,如果返回 AM_OK,說(shuō)明停止定時(shí)器;如果返回-AM_EINVAL,即參數(shù)錯(cuò)誤導(dǎo)致關(guān)閉失敗,詳見(jiàn)程序清單 4.31。
程序清單 4.31 am_softimer_stop ()范例程序
現(xiàn)在不妨在程序清單 4.30 的基礎(chǔ)上,再增加一個(gè)小功能,即每秒加一、蜂鳴器“嘀”一聲,詳見(jiàn)程序清單 4.32。
程序清單 4.32 0~59 秒計(jì)數(shù)器+蜂鳴器綜合范例程序(1)
通過(guò)運(yùn)行發(fā)現(xiàn),雖然計(jì)數(shù)器在每秒加 1 時(shí),蜂鳴器也會(huì)發(fā)出“嘀”的一聲,但數(shù)碼管的某位卻會(huì)熄滅一下。如果覺(jué)得看起來(lái)還不夠明顯,不妨將蜂鳴器的鳴叫時(shí)間增加到 500ms。奇怪!為何連顯示都不正常了呢?
雖然此前在 main()函數(shù)的 while(1)主循環(huán)中也使用了延時(shí),但在主程序的延時(shí)期間,軟件定時(shí)器定時(shí)時(shí)間到而產(chǎn)生的中斷事件是可以搶占 MCU 的,所以不會(huì)影響其它事件的繼續(xù)運(yùn)行。如果在中斷環(huán)境中調(diào)用 buzzer_beep(),程序必須等到蜂鳴器鳴叫結(jié)束后才會(huì)返回,這樣一來(lái)就會(huì)使回調(diào)函數(shù)產(chǎn)生 100ms 的延時(shí),從而導(dǎo)致 MCU 被完全占用,不僅 while(1)主循環(huán)無(wú)法執(zhí)行,而且連其它的中斷事件也無(wú)法執(zhí)行。比如,另一個(gè)軟件定時(shí)器中的數(shù)碼管動(dòng)態(tài)掃描也就無(wú)法執(zhí)行了,所以在這 100ms 時(shí)間內(nèi),無(wú)法實(shí)現(xiàn)數(shù)碼管動(dòng)態(tài)掃描,于是只有一個(gè)數(shù)碼管顯示,另外一個(gè)數(shù)碼管無(wú)法顯示而處于熄滅的狀態(tài)。
在這種情況下,應(yīng)盡可能地將相應(yīng)功能設(shè)計(jì)為異步模式,即啟動(dòng)軟件定時(shí)器,設(shè)定蜂鳴器鳴叫時(shí)間,打開(kāi)蜂鳴器,函數(shù)立即返回。待定時(shí)時(shí)間到,則自動(dòng)調(diào)用回調(diào)函數(shù),然后在回調(diào)函數(shù)中關(guān)閉蜂鳴器并停止定時(shí)器。這就是使用軟件定時(shí)器實(shí)現(xiàn) buzzer_beep_async()的由來(lái),異步模式的優(yōu)點(diǎn)是無(wú)需等待,函數(shù)立即返回,即可在任意地方調(diào)用該函數(shù)了,再也不會(huì)因?yàn)?/p>
延時(shí)而帶來(lái)副作用,詳見(jiàn)程序清單 4.33。
程序清單 4.33 實(shí)現(xiàn)蜂鳴器異步鳴叫函數(shù)
基于此,將 buzzer_beep_async()添加到 buzzer.h 以利于復(fù)用,詳見(jiàn)程序清單 4.34。
程序清單 4.34 0~59 秒計(jì)數(shù)器+蜂鳴器綜合范例程序(2)
4.5 鍵盤(pán)管理
>>> 4.5.1 獨(dú)立按鍵
1. 消抖方法
對(duì)于質(zhì)量不太好或者長(zhǎng)期使用簧片氧化磨損的按鍵來(lái)說(shuō),常常會(huì)產(chǎn)生一種被稱(chēng)為“抖動(dòng)”的現(xiàn)象。如圖 4.12(a)所示為單觸點(diǎn)按鍵的無(wú)消抖電路,當(dāng)按鍵未按下時(shí),則輸出 Y 為高電平;當(dāng)按下時(shí),則輸出 Y 為低電平。但由于按鍵的機(jī)械特性和人手指的不穩(wěn)定性等綜合因素,致使按鍵盤(pán)剛按下的瞬間,因接觸不良而產(chǎn)生的反復(fù)跳動(dòng)現(xiàn)象,即“抖動(dòng)”,同樣在按鍵釋放的瞬間也可能產(chǎn)生“抖動(dòng)”,結(jié)果輸出 Y 在這一瞬間產(chǎn)生了多個(gè)窄脈沖干擾,這些脈沖信號(hào)的寬度一般可達(dá)毫秒,詳見(jiàn)圖 4.12 (b)。
圖 4.12 無(wú)消抖按鍵電路及波形
“抖動(dòng)”的脈沖寬度一般有幾十到幾百微秒,但也可能達(dá)到毫秒級(jí),這對(duì)運(yùn)行速度很快的數(shù)字電路會(huì)產(chǎn)生很大的影響。如果將發(fā)生“抖動(dòng)”現(xiàn)象的按鍵連接到計(jì)數(shù)電路的時(shí)鐘輸入端,則檢測(cè)到每按一次鍵都會(huì)產(chǎn)生一串極不穩(wěn)定的脈沖。
對(duì)實(shí)際的產(chǎn)品來(lái)說(shuō),按鍵在長(zhǎng)時(shí)間的使用中永不產(chǎn)生“抖動(dòng)”是不可能的,但只要預(yù)防可能產(chǎn)生的“抖動(dòng)”即可。抖動(dòng)其實(shí)只持續(xù)了一小段時(shí)間,軟件延時(shí)就是在按鍵產(chǎn)生“抖動(dòng)”的這段時(shí)間里,用“拖延時(shí)間”的方法避開(kāi),從而消除因“抖動(dòng)”而產(chǎn)生的錯(cuò)誤信號(hào),其示意圖詳見(jiàn)圖 4.13。在按下鍵的瞬間啟動(dòng)定時(shí)器開(kāi)始延時(shí),延時(shí) td 時(shí)間后再判斷按鍵是否仍然按下,若仍按下則本次按鍵有效,否則本次按鍵無(wú)效。延時(shí)消抖由于過(guò)程比較復(fù)雜,比較適合用軟件實(shí)現(xiàn),因此稱(chēng)為軟件消抖。
圖 4.13 延時(shí)消抖
2. 電路原理
一般來(lái)說(shuō),在用法上按鍵可分為獨(dú)立按鍵和矩陣鍵盤(pán)兩大類(lèi)。LPC824 的 P0_10、P0_11是標(biāo)準(zhǔn)的開(kāi)漏結(jié)構(gòu),無(wú)內(nèi)部上拉電阻,因此連接按鍵時(shí)必須加上拉電阻。其它的 14 個(gè) GPIO口均有可編程使能的內(nèi)部上拉電阻,雖然 MCU 內(nèi)部有幾十 KΩ以上的上拉電阻,但均屬于弱上拉,所以在實(shí)際的應(yīng)用中,一般都會(huì)外接一個(gè)阻值適中的上拉電阻,以提高可靠性。
對(duì)于獨(dú)立按鍵來(lái)說(shuō),要求比較簡(jiǎn)單,既不考慮多個(gè)鍵同時(shí)按下,也不考慮長(zhǎng)按的情況。僅識(shí)別是否有鍵按下的情況,即有鍵按下一次執(zhí)行一次操作。如圖 4.14 所示是一個(gè)獨(dú)立按鍵電路圖,只要將 AM824-Core的 J14_1 與 J14_2 短接,則 KEY 鍵接入 PIO0_1。
圖 4.14 獨(dú)立按鍵電路圖
由于一次按鍵的時(shí)間通常都是上百毫秒,相對(duì)于 MCU 來(lái)說(shuō)是很長(zhǎng)的,因此不需要時(shí)時(shí)刻刻不斷地檢測(cè)按鍵,只需要每隔一定的時(shí)間(如 10ms)檢測(cè) GPIO 的電平即可。其檢測(cè)方法如下(1 表示高電平、0 表示低電平):
(1)當(dāng)無(wú)鍵按下時(shí),由于 PIO0_1 內(nèi)部自帶弱上拉電阻,因此 PIO0_1 為 1;
(2)當(dāng) KEY 按下時(shí),則 PIO0_1 為 0。在下一次掃描(延時(shí) 10ms 去抖動(dòng))后,如果PIO0_1 為 1,說(shuō)明錯(cuò)誤觸發(fā);如果 PIO0_1 還是 0,說(shuō)明確實(shí)有鍵按下,執(zhí)行相應(yīng)的操作;
(3)當(dāng) KEY 釋放時(shí),則 PIO0_1 為 1,在下一次掃描(延時(shí) 10ms 去抖動(dòng))后,如果PIO0_1 為 0,說(shuō)明錯(cuò)誤觸發(fā);如果 PIO0_1 還是 1,說(shuō)明按鍵已經(jīng)釋放,執(zhí)行相應(yīng)的操作。
3. Key 軟件包
AMetal 提供了獨(dú)立按鍵初始化和按鍵掃描函數(shù)接口(key1.h),詳見(jiàn)程序清單 4.35。
程序清單 4.35 key1.h 接口
如程序清單 4.36 所示為獨(dú)立按鍵的范例程序,如果有鍵按下,則蜂鳴器“嘀”一聲;當(dāng)按鍵釋放后,則 LED0 翻轉(zhuǎn)。
程序清單 4.36 獨(dú)立按鍵范例程序
顯然,每隔 10ms 調(diào)用一次 key1_scan(),即可根據(jù) key_return 的值判斷按鍵事件的產(chǎn)生,但這又是“每隔一段時(shí)間做某事”。如果使用軟件定時(shí)器定時(shí)自動(dòng)掃描,則無(wú)需在 while(1)中每隔 10ms 調(diào)用一次 key1_scan(),詳見(jiàn)程序清單 4.37。
程序清單 4.37 添加軟件定時(shí)器后的按鍵范例程序
程序中新增了一個(gè)初始化軟件定時(shí)器 key1_softimer_set(),并啟動(dòng)軟件定時(shí)器以 10ms的時(shí)間間隔,通過(guò) key1_softimer_callback()回調(diào) key1_scan()實(shí)現(xiàn)按鍵掃描。當(dāng)按鍵事件發(fā)生(返回值不為 0xFF)時(shí),則調(diào)用 key1_process()按鍵處理程序,根據(jù)掃描得到的返回值判斷按鍵事件的發(fā)生。在 key1_process()按鍵處理程序中,當(dāng)有鍵按下時(shí),蜂鳴器“嘀”一聲;當(dāng)按鍵釋放時(shí),LED0 翻轉(zhuǎn)。由于 key1_process()是在中斷環(huán)境的回調(diào)函數(shù)中調(diào)用的,因此不能出現(xiàn)阻塞式語(yǔ)句,必須調(diào)用異步模式下的 buzzer_beep_async()。
在這里,與軟件定時(shí)器相關(guān)的代碼直接放在主程序中,而在實(shí)際使用時(shí),更希望將實(shí)現(xiàn)和聲明分別放在 key1.c 和 key1.h 中,因此需要增加一個(gè)接口函數(shù):
雖然按鍵與數(shù)碼管都可以使用軟件定時(shí)器實(shí)現(xiàn)自動(dòng)掃描,但它們之間卻存在一定的差異,數(shù)碼管只要自動(dòng)掃描即可,但對(duì)于按鍵自動(dòng)掃描,當(dāng)掃描到按鍵事件發(fā)生時(shí),還必須通知應(yīng)用程序做相應(yīng)的處理。而實(shí)際上在封裝模塊時(shí),并不知道應(yīng)用程序要做什么事,唯一的辦法是采用注冊(cè)回調(diào)機(jī)制。當(dāng)按鍵事件發(fā)生時(shí),調(diào)用相應(yīng)的注冊(cè)函數(shù)。如果需要使用軟件定時(shí)器,則在初始化時(shí)注冊(cè)一個(gè)函數(shù),以便按鍵事件發(fā)生時(shí)調(diào)用。定義回調(diào)函數(shù)類(lèi)型為:
重新定義帶軟件定時(shí)器的初始化函數(shù)類(lèi)型為:
為了便于使用,將上述函數(shù)聲明和回調(diào)函數(shù)類(lèi)型定義添加到程序清單 4.38 所示的 key1.h中,其相關(guān)實(shí)現(xiàn)代碼添加到程序清單 4.39 所示的 key1.c 中。
程序清單 4.38 key1.h 文件內(nèi)容
程序清單 4.39 新增使用軟件定時(shí)器自動(dòng)掃描的程序(key1.c)
當(dāng)有鍵按下時(shí),則蜂鳴器“嘀”一聲;當(dāng)按鍵釋放時(shí),則 LED0 翻轉(zhuǎn),經(jīng)過(guò)迭代后的代碼詳見(jiàn)程序清單 4.40。
程序清單 4.40 使用軟件定時(shí)器自動(dòng)進(jìn)行按鍵掃描范例程序
>>> 4.5.2 矩陣鍵盤(pán)
獨(dú)立按鍵必須占用一個(gè) I/O 口,當(dāng)按鍵數(shù)目較多時(shí),這種每個(gè)按鍵占用一個(gè)口的方法就顯得很浪費(fèi)了。如何用盡可能少的 I/O 口去管理較多的按鍵呢?矩陣形式鍵盤(pán)電路就是使用最多的一種,如圖 4.15 所示就是一種典型的矩陣式 2×2 鍵盤(pán)電路。采用矩陣鍵盤(pán)方式進(jìn)行排列,其中 KR0、KR1 為行線(xiàn),KL0、KL1 為列線(xiàn)。
圖 4.15 2×2 矩陣鍵盤(pán)
該接法將口線(xiàn)分成行線(xiàn)(row)和列線(xiàn)(column),如果將它變成比較容易理解的拓?fù)浣Y(jié)構(gòu),就是兩組垂直交叉的平行線(xiàn),每個(gè)交叉點(diǎn)就是一個(gè)按鍵位置,按鍵的兩端分別接在行線(xiàn)和列線(xiàn)上。其最大優(yōu)點(diǎn)是組合靈活,假如有16 個(gè) I/O 可用于擴(kuò)展做鍵盤(pán)電路,我們可以將它接成 6×10、5×11 或 8×8 等多種接法,當(dāng)然,使用效率最高的是 8×8 的接法,它最多可實(shí)現(xiàn) 64 個(gè)按鍵。
MiniPort-Key 按鍵模塊集成了 4 個(gè)按鍵,通過(guò) MiniPort B(排母)與 AM824-Core 相連,同時(shí)引出其余不用的 I/O,實(shí)現(xiàn)模塊的橫向堆疊,其對(duì)應(yīng) AM824-Core 的 MiniPort 接口的 J4的功能定義詳見(jiàn)圖 4.16。
圖 4.16 按鍵模塊實(shí)物與接口定義圖
2×2 的矩陣鍵盤(pán)共有 4 個(gè)按鍵,分別為 KEY0~KEY3。KR0、KR1 為行線(xiàn)(row),KL0、KL1 為列線(xiàn)(column)。假設(shè)選擇 KL0、KL1 為輸入,當(dāng)無(wú)鍵按下時(shí),由于內(nèi)部弱上拉作用,此時(shí)讀取電平為高電平。當(dāng) KEY0 按下時(shí),KL1 依然為高電平,而 KL0 在 KR0 輸出低電平時(shí)就會(huì)得到低電平。顯然,只有 KR0、KR1 輸出為低電平時(shí),KL0、KL1 才能得到低電平,這就是逐行掃描鍵盤(pán)的方法,即行線(xiàn)為輸出,列線(xiàn)為輸入,每次掃描一行,掃描該行時(shí),對(duì)應(yīng)行線(xiàn)輸出為低電平,其余行線(xiàn)輸出為高電平,然后讀取所有列線(xiàn)的電平,若有列線(xiàn)讀到低電平,則表明該行與讀到低電平的列對(duì)應(yīng)的交叉點(diǎn)有按鍵按下。逐列掃描法恰好相反,其列線(xiàn)為輸出,行線(xiàn)為輸入,但基本原理還是一樣的。AMetal 針對(duì)矩陣鍵盤(pán)提供了相應(yīng)的 matrixkey.h 接口,詳見(jiàn)程序清單 4.41。
程序清單 4.41 matrixkey.h 接口
如程序清單4.42所示是使用上述接口的范例程序,即當(dāng)有鍵按下時(shí),蜂鳴器在發(fā)出“嘀”的一聲的同時(shí),通過(guò) LED0 和 LED1 的組合顯示按鍵編號(hào)。比如,KEY0 鍵按下時(shí),兩個(gè) LED燈均熄滅。KEY1 按下時(shí)顯示 01,即 LED0 亮,LED1 熄滅,依此類(lèi)推。
程序清單 4.42 矩陣鍵盤(pán)范例程序
為了節(jié)省引腳,還可以將數(shù)碼管與矩陣鍵盤(pán)結(jié)合起來(lái)使用,如圖4.17 所示的數(shù)碼管的 2個(gè) com 端與矩陣鍵盤(pán)的列線(xiàn)是復(fù)用的,PIO0_17與 PIO0_23 既是數(shù)碼管的 com0、com1,又是矩陣鍵盤(pán)的列線(xiàn) KL0、KL1這樣設(shè)計(jì)反而節(jié)省了引腳。作為鍵盤(pán)掃描時(shí)需將列線(xiàn)配置為輸入,作為數(shù)碼管掃描時(shí)需將 com 端設(shè)置為輸出。
圖 4.17 LED 顯示器電路圖
為了不影響數(shù)碼管的顯示,在鍵盤(pán)掃描結(jié)束后,必須將管腳恢復(fù)為輸出狀態(tài)。這是由
函數(shù)實(shí)現(xiàn)的。鍵盤(pán)掃描只需要每隔 10ms 進(jìn)行一次,而數(shù)碼管掃描需要每隔 5ms 進(jìn)行一次,當(dāng)它們同時(shí)使用時(shí),可以在按鍵掃描的 10ms 內(nèi)進(jìn)行 2 次數(shù)碼管掃描。
利用 4 個(gè)按鍵和數(shù)碼管,實(shí)現(xiàn)一個(gè)按鍵調(diào)節(jié)值的小應(yīng)用,各個(gè)按鍵的功能定義如下:
-
KEY0:進(jìn)入設(shè)置狀態(tài)。點(diǎn)擊后進(jìn)入設(shè)置狀態(tài),默認(rèn)個(gè)位不斷閃爍,再次點(diǎn)擊后回到正常運(yùn)行狀態(tài);
-
KEY2:切換當(dāng)前調(diào)節(jié)的位。當(dāng)進(jìn)入設(shè)置狀態(tài)后,當(dāng)前調(diào)節(jié)的位會(huì)不斷地閃爍。點(diǎn)擊該鍵可以切換當(dāng)前調(diào)節(jié)的位,由個(gè)位切換到十位,或由十位切換到個(gè)位;
-
KEY1:也稱(chēng)為+1 鍵,將當(dāng)前正在閃爍的位的值加 1;
-
KEY3:也稱(chēng)為-1 鍵,將當(dāng)前正在閃爍的位的值減 1。
其相應(yīng)的范例程序詳見(jiàn)程序清單 4.43。
程序清單 4.43 矩陣鍵盤(pán)+數(shù)碼管范例程序(2)
-
定時(shí)器
+關(guān)注
關(guān)注
23文章
3298瀏覽量
118991 -
周立功
+關(guān)注
關(guān)注
38文章
130瀏覽量
38222
原文標(biāo)題:周立功:面向接口的編程——事件驅(qū)動(dòng)和鍵盤(pán)管理
文章出處:【微信號(hào):ZLG_zhiyuan,微信公眾號(hào):ZLG致遠(yuǎn)電子】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
HarmonyOS應(yīng)用自定義鍵盤(pán)解決方案

智慧水務(wù)信息化管理系統(tǒng)—水廠水務(wù)生產(chǎn)運(yùn)行管理系統(tǒng),用數(shù)據(jù)驅(qū)動(dòng)供水智慧化升級(jí)!# 智慧水務(wù)管理系統(tǒng)# 智慧水務(wù)軟
光伏電站智能分析管理系統(tǒng)讓電站管理更簡(jiǎn)單

簡(jiǎn)要分析園區(qū)智能光儲(chǔ)充能量管理系統(tǒng)設(shè)計(jì)及應(yīng)用

設(shè)備管理系統(tǒng),終結(jié)設(shè)備管理難題

智能化管理系統(tǒng):驅(qū)動(dòng)未來(lái)管理與效率革命
納祥科技NX1722,一種帶鍵盤(pán)掃描的8段4位 LED 驅(qū)動(dòng)控制方案

太陽(yáng)能藍(lán)牙鍵盤(pán)專(zhuān)用 微光微能量收集芯片-MF9006

高壓柵極驅(qū)動(dòng)器的功率損耗分析

使用TLC5951進(jìn)行鍵盤(pán)背光

驅(qū)動(dòng)芯片在應(yīng)用中的常見(jiàn)問(wèn)題分析與解決

鍵盤(pán)、按鈕和側(cè)鍵的ESD保護(hù)

評(píng)論