許多微控制器都帶有一個生態系統,其中包括外圍驅動程序、RTOS、中間件甚至示例應用程序代碼。許多嵌入式開發人員可以將大部分時間花在高級應用程序代碼上,而忽略了滿足硬件的軟件。問題是,雖然這個預構建的生態系統可以加速開發,但這種加速通常是以時鐘周期和執行效率為代價的。
在今天的文章中,我們將探討開發人員可以應用的幾個技巧,以幫助提高其中斷服務例程回調的效率,這些回調與許多微控制器軟件框架緊密集成。
先決條件 #1 – 測量 ISR 執行時間
加速軟件執行的第一步是停止并進行一些測量。你如何知道你的中斷處理程序是使用過多的 CPU 時間還是運行緩慢?你量一下!開發人員可以利用幾種不同的選項來測量中斷執行時間。
首先,只需切換 GPIO 線!我經常將測試 GPIO 線初始化為高電平,然后當我進入 ISR 時,我會將 GPIO 線切換為低電平,然后在退出 ISR 時再次將 GPIO 線切換為高電平。結果是一個低電平有效信號,它近似代表 ISR 執行時間。測量值是近似值的原因是它沒有考慮切換 GPIO 線的時間,我們假設它可以忽略不計(但如果你使用的是框架代碼,則可能不是!)。這種方法產生了一個簡單且易于測量的波形,如下所示:
第二種方法,我將簡要提及的是使用跟蹤軟件。如果你使用的是 RTOS,RTOS 通常會記錄系統中發生的事件,包括進入和退出中斷服務程序。嵌入式開發人員可以使用他們的跟蹤分析器來了解他們的中斷服務程序執行了多長時間。
現在乍一看,上面測得的 24.3 us 對于 ISR 來說似乎并不算太糟糕。這實際上取決于應用程序的好壞,但總的來說,我們希望 ISR 執行時間盡可能短。在這個例子中,我設置了一個輸入捕捉外設來測量輸入信號的頻率。如果頻率只有區區 20 KHz,這個 ISR 將占用大約 50% 的 CPU 周期!
技巧 #1 – 在 ISR 中調用的內聯函數
首先,從 ISR 調用函數是個壞主意!函數調用開銷會給中斷增加一大堆浪費的時鐘周期,這將延遲返回到定期安排的代碼執行。但問題是許多現代框架都這樣做!例如,如果你查看 STM32CubeIDE 生成的定時器中斷,你會看到如下內容:
現在,我添加了 GPIO HAL 調用,但你可以看到,默認情況下,中斷會調用 HAL_TIM_IRQHandler,這是 STM32 上所有定時器的通用中斷處理程序。 (對于可重用和可移植的代碼來說,這是一個很棒的框架理念,但它可能對時間敏感的代碼有害)。 如果我們檢查 HAL_TIM_IRQHandler 的定義,我們會發現以下內容:
這里沒有試圖告訴編譯器我們處于 ISR 中,因此編譯器可能會添加函數調用的代碼并向 ISR 添加無用的循環。 事實上,這個函數會有條件地檢查并調用幾個函數,這可能會使事情變得更糟。 內聯函數可能會減少執行時間,但會以稍大的代碼大小為代價。 只需將 inline 關鍵字添加到函數定義中即可完成,如下所示:
進行前后測量,在這種情況下,我發現我可以將中斷執行時間縮短 0.2 us。不是很大,但在時間敏感的應用程序中,它是一些東西。
提示 #2 – 自定義默認中斷服務程序 (ISR)
預構建的框架通常會將外圍類型的中斷處理集中在一起。例如,我們剛剛看到的定時器中斷,它傳遞了一個定時器對象,然后有一堆條件語句來決定它應該做什么。該框架是為重用而不是執行速度而構建的。如果我重寫我的中斷以刪除所有這些通用函數調用,中斷執行時間變為 21.712,現在為我們節省了 2.5 us (10.3%)!對于我們正在查看的數字,它似乎并不多,但如果這是一個高頻中斷,那可能是大量的 CPU 使用率。
提示 #3 – 優化中斷服務程序 (ISR) 回調函數
我經常注意到,編寫各種功能的示例代碼是為了向嵌入式開發人員展示如何完成某事。例如,許多供應商將提供輸入捕獲代碼,以顯示如何計算信號的占空比和頻率。這太棒了,除了代碼通常是在中斷服務程序中執行的。這是次優的。事實上,我在整個博客中展示的示例都與使用輸入捕獲計算頻率有關。當你測量信號頻率時,21.712 us 是中斷運行的較長時間。
示例代碼就是這樣,一個例子。算法通常是正確的,但它們不是以生產意圖的方式完成的。他們可能不會考慮重要的考慮因素,例如 CPU 負載和實時響應。他們只是想向你展示他們的部分可以做你需要的事情,測量頻率或任何功能。
今天的嵌入式開發人員擁有如此多的示例代碼和如此多的開箱即用的框架供我們利用,這真是太棒了。需要注意的是,這段代碼可能不是為我們自己的目的而設計或實現的。它通常被快速編寫以展示一個特性或功能,而不是為生產而設計的。