嵌入式軟件開發人員需要掌握的一項基本技能是了解如何編寫驅動程序。在嵌入式系統中,通常有兩種類型的驅動程序:微控制器外圍驅動程序和通過 I2C、SPI 或 UART 等接口連接的外部設備驅動程序。在當今的許多情況下,微控制器供應商為其芯片提供示例驅動程序,這些驅動程序可以按原樣使用,也可能需要修改以進行生產。外部驅動程序可能包含偽代碼,但開發人員幾乎總是自己負責編寫驅動程序。
重要的是要意識到編寫驅動程序的方法不止一種,而且編寫驅動程序的方式會極大地影響系統性能、能耗以及我們在開發產品時喜歡跟蹤的許多其他因素。在這篇文章中,我們將研究幾種常見的驅動程序設計模式以及它們如何影響應用程序代碼。我們將從基礎開始,向更復雜的模式努力。
技術 #1 – 輪詢驅動程序
第一種技術,也是最基本的技術,是開發一個輪詢外圍設備(或外部設備)的驅動程序,以查看它是否準備好發送或接收信息。輪詢驅動程序很容易實現,因為它們通常只是輪詢一個標志。例如,模數轉換器 (ADC) 驅動程序可能會啟動轉換序列,然后簡單地阻止處理器執行并不斷檢查 ADC 完成標志。此代碼如下所示:
Adc_Start();
而(ADC_COMPLETE_FLAG == FALSE);
AdcResults = Adc_ReadAll();
返回 AdcResults;
如您所見,上面的代碼不斷地輪詢 ADC_COMPLETE_FLAG,大概是映射到一個硬件位,以便查看數據何時可用。雖然像這樣測試硬件位被稱為輪詢,但它會產生一些有用的特性來討論。
首先,當我們有一個使用輪詢的驅動程序時,在大多數實現中,該驅動程序將是一個阻塞驅動程序。這意味著一旦我們調用驅動程序,它不會從驅動程序返回,直到我們得到我們需要的結果。還有其他實現,我們可以讓驅動程序檢查一次結果然后返回。在這種情況下,應用程序負責輪詢驅動程序,我們認為驅動程序是非阻塞的。從設計的角度來看,由開發人員決定輪詢應該在哪里進行。在驅動程序中,應用程序不必這樣做,但如果應用程序這樣做,則可以靈活地執行其他活動并以較低的速率輪詢驅動程序。
其次,總的來說,輪詢很容易實現。通常,開發人員需要做的就是觀察寄存器中的一些位并監視它們以決定何時與設備交互。最后,雖然它很容易實現,但輪詢通常被認為是低效的。其他技術(例如使用中斷)可以在需要執行某些操作時通知 CPU,這使得輪詢效率相當低。我經常將民意調查與長途旅行中的一個孩子聯系起來,他不斷地問“我們到了嗎?”。民意調查不斷地問“你準備好了嗎?現在怎么樣?現在?”。
這給我們帶來了一個更高效但稍微復雜一點的驅動程序實現,即使用中斷。
技巧#2——中斷驅動的驅動程序
在驅動程序中使用中斷非常棒,因為它可以顯著提高代碼執行效率。中斷不是不斷檢查是否該做某事,而是告訴處理器驅動程序現在準備就緒,我們跳轉到處理中斷。一般來說,我們可以使用兩種類型的中斷驅動驅動機制:事件驅動和調度。當外圍設備發生需要處理的事件時,事件驅動驅動程序將觸發中斷。例如,我們可能有一個 UART 驅動程序,當緩沖區中接收到一個新字符時,它會觸發一個中斷。另一方面,我們可能有一個 ADC 驅動程序,它使用計時器來安排訪問以開始采樣或處理接收到的數據。
使用中斷驅動的驅動程序雖然效率更高,但會為設計增加額外的實現復雜性。首先,開發人員需要啟用適當的中斷以供驅動程序使用,例如接收、發送和緩沖區滿。我通常發現由于現代中斷控制器的復雜性,開發人員很難讓中斷工作。它們通常需要在通用寄存器中、外設級別設置中斷,有時甚至需要配置優先級和其他設置。幾年前,我整理了一份配置中斷的分步指南,可在此處下載。
接下來,使用中斷可能需要遵循一整套額外的最佳實踐。例如,最好的做法是:
- 保持中斷簡短
- 將共享變量聲明為 volatile
- 處理高優先級項目,然后卸載到應用程序進行處理
您不希望在事件發生時執行數千行代碼的驅動程序中斷。相反,您希望處理關鍵任務,例如從 UART 緩沖區中取出一個字符并將其放入應用程序的循環緩沖區中。
最后,我們還需要擔心中斷被禁用、中斷時序和運行速率、優先級以及是否有可能錯過中斷等問題。雖然其中一些項目看起來額外的復雜性可能不值得付出努力,但執行時間的改進可能是巨大的。例如,電池供電的設備可能會進入深度睡眠模式,只有在將字符存儲在緩沖區中時才醒來,然后再重新進入睡眠狀態。這樣做可以節省大量能源。
在某些情況下,在驅動程序中使用中斷確實是處理外圍事件的最佳方式。例如,您可以編寫一個輪詢 I2C 驅動程序,但編寫一個中斷 ack、nack 等傳輸序列中發生的不同事件的驅動程序會產生更清潔、更小和更高效的驅動程序。
我們將在下一篇文章中查看中斷驅動驅動程序的代碼。現在,讓我們看看我們可以用來編寫驅動程序的第三種技術,它是利用直接內存訪問 (DMA) 控制器。
技術#3 – DMA 驅動的驅動程序
有一些驅動程序會通過系統移動大量數據,例如 I2S 和 SDIO。管理這些類型接口上的緩沖區可能需要 CPU 不斷采取行動。如果 CPU 落后或必須處理另一個系統事件,則數據可能會丟失或延遲,這可能會給用戶帶來明顯的問題,例如音頻跳過。關注吞吐量的開發人員可以改為使用 DMA 控制器在微控制器周圍為 CPU 移動數據。
這些驅動程序背后的想法是 DMA 控制器可以通過以下方式在微控制器周圍移動數據:
- 內存外圍
- 記憶到記憶
- 內存到外設
使用 DMA 的優點是 CPU 可以在 DMA 通道為驅動程序移動數據時停止做其他事情,基本上可以同時完成兩件事。
雖然非常希望在驅動程序中使用 DMA 控制器來減少 CPU 執行的需要,但大多數微控制器的可用 DMA 通道數量有限。因此,不能編寫每個驅動程序來使用 DMA。相反,開發人員需要選擇將受到帶寬限制且將從 DMA 中受益匪淺的外設,例如用于外部存儲器、ADC 和通信通道的接口。
在沒有 I2S 或 SDIO 的應用程序中,開發人員可以使用 DMA 將傳入的 UART 字符移動到一個循環緩沖區中,一旦設置了某個限制,該緩沖區就會被處理。可以通過輪詢應用程序結構或通過 DMA 控制器設置中斷來監控此限制。可以想象,DMA 驅動程序是驅動程序最有效的實現,但根據開發人員的技能水平以及他們以前是否使用過 DMA,它們的實現也可能很復雜。這不應該阻止開發人員嘗試在其驅動程序中使用 DMA。
結論
在這篇文章中,我們研究了嵌入式開發人員可以用來為其微控制器外設和外部設備編寫驅動程序的三種主要技術。為了比較總結這些技術,下面的表 1 顯示了我們討論的每種技術,以及實現的相對復雜性和由此產生的執行效率。
表 1:驅動程序設計技術的相對復雜性和
效率。
技術 | 復雜 | 效率 |
輪詢 | 低的 | 低的 |
打斷 | 中等的 | 中等的 |
DMA | 中等的 | 高的 |
一般而言,開發人員應該默認使用中斷驅動程序實現而不是輪詢實現,除非正在使用的外設速度很快,即幾個 Mbps。DMA 可用于任何驅動程序,但我通常為需要高吞吐量的接口(例如外部存儲器或通信接口)保留 DMA 通道。您選擇的選項將高度依賴于最終應用程序。
在下一篇文章中,我們將通過研究如何為模數轉換器開發一個簡單的驅動程序來探索如何更深入地研究這些概念。
?
?
評論
查看更多