幾乎每個嵌入式系統(tǒng)都使用中斷服務(wù)程序。如果你需要跟蹤時間,你可能會有一個產(chǎn)生系統(tǒng)滴答的定時器中斷。如果你有一個USART,你可能正在使用中斷。使用DMA進行更高效的數(shù)據(jù)傳輸?你可能正在使用中斷。
中斷是嵌入式系統(tǒng)不可或缺的一部分。不幸的是,寫得不好的ISR會導(dǎo)致系統(tǒng)出現(xiàn)爭用情況、響應(yīng)能力差,甚至CPU使用過度。
在本帖中,我們將探討撰寫高效ISR的幾種最佳實踐。
1.保持簡短快速
中斷超級酷!當(dāng)你的代碼正在執(zhí)行并且發(fā)生重要事件時,程序會被中斷以跳轉(zhuǎn)到中斷服務(wù)例程。發(fā)生跳轉(zhuǎn)時,寄存器的當(dāng)前狀態(tài)需要存儲在中斷幀中。
中斷幀被推送到堆棧上,并進入ISR。中斷運行,然后中斷幀恢復(fù),應(yīng)用程序恢復(fù)。可以想象,每次運行中斷服務(wù)程序時,這個過程都會消耗CPU周期并產(chǎn)生開銷。周期數(shù)因處理器和架構(gòu)而異,但中斷兩端的周期數(shù)可能在12到數(shù)百個之間。
雖然我們對中斷開銷無能為力,但我們可以控制中斷中使用的周期數(shù)。可以想象,ISR執(zhí)行所花費的CPU周期越多,對我們的應(yīng)用程序代碼的影響就越大。長而慢的中斷會導(dǎo)致程序不同部分的抖動和其他時序問題。它們甚至?xí)?dǎo)致其他中斷丟失或延遲!
經(jīng)驗法則是讓你的中斷短而快!
2.不要調(diào)用函數(shù)
如果你希望你的ISR短而快,你應(yīng)該避免在ISR內(nèi)部進行函數(shù)調(diào)用。函數(shù)(尤其是那些開銷巨大或執(zhí)行復(fù)雜任務(wù)的函數(shù))會大大增加ISR的執(zhí)行時間。增加的執(zhí)行時間會導(dǎo)致中斷丟失或其他關(guān)鍵任務(wù)的延遲處理,從而可能導(dǎo)致系統(tǒng)不穩(wěn)定。
當(dāng)在ISR中調(diào)用一個函數(shù)時,它涉及到額外的步驟,如將當(dāng)前上下文推送到堆棧上、跳轉(zhuǎn)到函數(shù)代碼并返回到ISR。這些額外的步驟消耗了寶貴的CPU周期。如果函數(shù)包含阻塞調(diào)用、等待I/O操作或依賴中斷期間可能處于不一致狀態(tài)的資源,則可能會嚴重影響系統(tǒng)的響應(yīng)能力和可預(yù)測性。
現(xiàn)在,你可能認為編寫ISR會違反軟件開發(fā)最佳實踐。畢竟,我們不應(yīng)該模塊化我們的代碼嗎?沒有職能,我們的ISR不會變得混亂嗎?
使用一些技巧來規(guī)避“不調(diào)用函數(shù)”的最佳實踐:
1)使用靜態(tài)編譯或預(yù)處理程序。
根據(jù)你的語言,復(fù)雜的計算或至少部分計算可能會在編譯時執(zhí)行。通過在編譯時執(zhí)行這些計算,ISR在運行時需要執(zhí)行的工作將會減少。
2)內(nèi)嵌函數(shù)
你仍然可以將你的ISR代碼放入函數(shù)中,但是不要將它們作為常規(guī)函數(shù),而是使用inline關(guān)鍵字。inline關(guān)鍵字會建議編譯器不要調(diào)用該函數(shù),而是應(yīng)該將該函數(shù)的內(nèi)容“復(fù)制并粘貼”到調(diào)用者中。
內(nèi)聯(lián)函數(shù)將消除與函數(shù)調(diào)用相關(guān)的開銷。被警告!只是給編譯器的一個建議!你必須在程序集中驗證該函數(shù)實際上是內(nèi)聯(lián)的。
注意:今天大多數(shù)編譯器會采納我們的建議,但你不能毫無疑問地相信它!
通過避免ISR中的函數(shù)調(diào)用,你可以保持中斷處理的效率和可靠性,確保你的系統(tǒng)在各種條件下保持響應(yīng)和穩(wěn)定。
3.將進程卸載到其他線程
中斷并不是用來做很多繁重的工作的。我們希望中斷短而快,這意味著它應(yīng)該做最少需要做的事情。例如,如果你正在通過USART接收作為數(shù)據(jù)包一部分的字節(jié),則不會在中斷中處理該數(shù)據(jù)包。你處理該字節(jié),然后設(shè)置一個標志來指示程序的另一部分應(yīng)該處理該數(shù)據(jù)。
通過將密集的進程卸載到其他線程,你可以確保ISR保持高效和快速響應(yīng)。這種方法可以在裸機或多線程環(huán)境中使用。以下是你如何有效地將處理任務(wù)轉(zhuǎn)移到ISR之外的方法:
1)設(shè)置標志
使用簡單的標志來指示事件已經(jīng)發(fā)生。主程序或另一個線程可以監(jiān)視這些標志,并在安全的情況下進行必要的處理。
使用隊列
實現(xiàn)隊列將數(shù)據(jù)從ISR傳遞到其他線程。這樣,ISR可以快速將數(shù)據(jù)排隊并返回處理中斷,同時主程序或?qū)S霉ぷ骶€程可以處理排隊的數(shù)據(jù)。
3)線程同步
確保ISR和其他線程之間的正確同步,以避免競爭情況和數(shù)據(jù)損壞。根據(jù)需要使用互斥、信號量或其他同步機制。
通過遵循這些實踐,你可以在嵌入式系統(tǒng)中保持高水平的性能和可靠性。這將確保ISR保持快速和高效,同時安全地處理和控制更復(fù)雜的處理。
4.對共享變量使用volatile
當(dāng)ISR和主程序共享變量時,將它們聲明為volatile是至關(guān)重要的。volatile關(guān)鍵字告訴編譯器,變量值可能會在程序流的控制之外隨時更改,從而防止編譯器應(yīng)用假定值不會意外更改的特定優(yōu)化。
如果沒有volatile關(guān)鍵字,編譯器可能會優(yōu)化掉必要的讀取或?qū)懭耄瑥亩鴮?dǎo)致不可預(yù)測的行為和難以診斷的錯誤。Volatile將為你做三件事:
1)阻止優(yōu)化
編譯器假定非易失性變量不會改變,除非程序顯式修改它。對于共享變量,這種假設(shè)是錯誤的,因為ISR可以隨時更改變量。將變量聲明為volatile會阻止編譯器優(yōu)化必要的讀取或?qū)懭搿?/span>
2)確保數(shù)據(jù)是新鮮的
使用volatile時,編譯器總是從內(nèi)存中讀取值,而不是使用寄存器中的緩存值。這確保了主程序看到ISR寫入的最新值,反之亦然。
結(jié)論
中斷服務(wù)例程對于每個嵌入式系統(tǒng)都至關(guān)重要。如果你想讓你的系統(tǒng)反應(yīng)靈敏和高效,你必須正確地實現(xiàn)你的中斷。我無法告訴你我遇到性能差的系統(tǒng)的頻率,根本原因是ISR寫得不好。
如果你遵循本文中的最佳實踐,你的中斷會表現(xiàn)得更好,引起的問題也會更少。