對於大部分嵌入式產品來說,完整的開發過程基本包含對外部資訊的採集(包括不限於感測器,按鍵,網路端),分析處理後,輸出顯示到其它裝置或控制類比電路,生成需要的脈衝,電壓或電流。在這種背景下,很多模組和介面被提出並被設計出來,如adc, dac, pwm, uart, i2c, spi, qspi, can, eth, sdio, usb, ble, wifi, fsmc, hdmi 等,來應對不同場景的需求。而如何讓這些模組正常工作,並能夠控制外部的晶片滿足最後功能的需求,驅動的概念應用而生,它是應用或系統存取實際硬體的橋樑,如果各種外部硬體是嵌入式系統的五官和手腳,驅動就是其中用於聯繫它們和大腦的神經,在嵌入式整個產品中當然佔有舉足輕重的地位。
講到嵌入式驅動開發,很多時候都會理解爲嵌入式linux平臺下的驅動開發,這種理解當然有其原因,對於大部分微控制器專案,因爲需求和設計比較簡單,開發中往往並沒有驅動的概念,更加不會有嵌入式linux開發中嚴格分層的概念,但是對暫存器的設定和讀寫實現對外部硬體的控制,它們事實上也屬於驅動的範疇,不過驅動的工作當然遠不止如此,根據我在嵌入式產品中的開發經驗,驅動開發大致包含如下:
1.對暫存器的設定和修改,完成晶片對於介面的功能設定需求,如模組時鐘的開關,gpio的使能,複用功能的設定,中斷的使能關斷等初始化操作, 然後在通過封裝的讀寫介面,將時序和電平反映外部的引腳上。
2.對外部裝置或硬體的存取,通過介面以及設定或者獲取晶片引腳的狀態,如電平的高低,輸入或輸出電壓的大小,但對於大部分產品來說,我們還需要外部的裝置或者硬體來進一步擴充套件來滿足實際功能需求,如epprom, sdram, psram,nandflash/norflash, sdcard, cmos, lcd, keyboard, phy等,這就需要我們在讀寫的基礎上新增對這些外部硬體裝置內部狀態的讀取和功能的設定,以應對目前複雜的需求情況。
從事微控制器的開發,這兩部分就是整個驅動層需要實現的全部功能,接觸過ST的晶片的都知道,無論是最初的標準庫還是HAL庫,都是通過對底層暫存器的封裝,將對模組的具體功能需求的理解和暫存器的操作,轉變爲對庫介面的掌握,或者圖形化的介面設定,通過簡單的幾步,便完成了整個對暫存器的修改和設定,實現功能需求。如果開發實際的專案,這種方法是正確且高效的,不過如果是學習入門階, 體驗下這種便捷也是有利無害,但是如果只通過這種點幾下就認爲底層設定已經熟悉,從而放棄了繼續去深入瞭解晶片模組和暫存器相關的知識,就本末倒置了,ST Cubemx並不是一項通用的微控制器驅動開發技術,不提TI,NXP,新塘等仍然佔據了部分市場,HAL本身的效能問題,例如中斷中複雜的狀態設計和超時處理本身就令人詬病,遇到問題時的維護或者優化,所需要的成本並不低,還是要重新理解掌握,而且工作面試中關注的也往往是對模組的理解(如ADC的精度,串列埠的流控,波特率等), 這些是圖形化的自動生成程式碼的過程中無法學習掌握的,雖然不同廠商晶片的內部模組佈局和暫存器的功能會不一致,但相同功能模組需要設定的資訊是基本一致的,不僅對於微控制器,對於嵌入式Linux也一樣,所以掌握通用技術纔是學習的重點,當然真正開發就以方便快捷爲主,學習是爲了自己,追求的是長期發展,工作是爲了利益,追求的是效率,明白這一點十分重要,也不會對圖形化有疑惑了。
微控制器領域驅動的圖形化發展會幫工程師解決了對內部暫存器的存取設定問題,如果只是簡單的需求,如輸出高低電平,採樣電壓數據,輸出PWM控制波形,串列埠輸出寫字串,對於這些設計來說,圖形化是解決了驅動相關的問題,不過現實往往不會這麼簡單,對外部裝置或硬體的存取也是作爲嵌入式開發者必須解決的問題,從我剛入門接觸的需要單線介面存取的ds18b20, 到後來複雜的PHY,CMOS,USB應用,很多外部模組都需要讀取資訊來判斷狀態或者獲得數據,寫入資訊來設定功能,這部分知識其實才是整個驅動中最龐大,複雜且難以掌握的部分,當然這並不是說這部分有多困難,畢竟如果介面封裝的合理,這部分也不過是重複讀寫的操作而已,但是很多時候我們都只是使用通用的,或者對應外設廠商的給予的配好功能的暫存器設定,保證寫入正確正常工作就可以,那麼難點在哪兒?其實就是需要瞭解的太多了,而且同一型別的晶片功能可能類似,其內部需要暫存器可能差別很大,像我接觸過的攝像頭GC0308,OV9281, OV7725,雖然都是使用I2C進行設定,但其中關於暫存器相關的經驗很難重複利用,甚至後面的OV系列的暫存器功能都是從裝置的廠商獲得驅動後,在上面基礎上修改的,講到這,你可能認爲這部分沒法積累經驗,但這是不對的,事實上CMOS偵錯經驗中可以提取的知識有很多, 需影象幀和行的概念,通過時鐘對影格率的控制, 影象曝光時間,輸出影象的格式(YUV,RGB565, RGB888), 瞭解影象模糊,影象行錯位,影象拼接,顏色偏色等的原因分析和軟硬體解決辦法,這部分經驗如果沒有相應行業的經驗積累,特別是在學習情況下,是很難總結的,就像非通訊行業很難理解交換晶片的工作原理和機制 機製(即使從事過通訊行業,對於交換晶片的手冊的複雜程度仍然記憶猶新), 這種和行業掛鉤的知識,我開始也不怎麼重視,直到去原廠負責相應模組的功能規劃,驗證和方案開發的時候,纔去深入去瞭解,這部分經驗重要嗎?不好說,但這一定是驅動中需要大量時間和經驗積累的技術,也是驅動中遇到問題最難解決的部分,這部分經驗也很難被量化,但又在驅動開發中佔有重要地位。大部分人把這部分理解爲移植和修改,當然這是大部分開發的真實情況,這也是我認爲比較矛盾的地方。
上面聊了很多,其實並非僅針對微控制器相關的硬體操作,對於嵌入式Linux來說,這兩部分也是客觀存在的,當然對外部裝置或硬體的存取在嵌入式Linux開發中仍然是很重要的部分,不過往往這兩部分對於嵌入式Linux都有拿來即用的方案(包含原廠和方案商都可能提供), 所以更加被忽視, 特別是對剛入門的人來說,這部分經驗用工作來積累是合理的,因爲這部分的學習和掌握往往都配合着硬體的偵錯和分析,需要示波器,邏輯分析儀,電烙鐵,各種電氣元器件的支援,如果不是大學實驗室或者工作後,滿足這一套需求,對於大部分人很難承受(當然包含我), 但如果本身也在工作中,因爲接手時系統已經穩定,就不在乎這部分的技需要掌握的知識,是不應該的。
不過對於嵌入式Linux驅動開發來說,當然不只和硬體的互動,對於一個完整的分層明顯的系統,如何將驅動模組新增到內核中,並滿足應用的需求,也是需要掌握的重點之一,這裏就提到了嵌入式Linux驅動開發的重點,也是大部分學習中關注的部分,當然也與這部分確定且可以系統的學習有關。
3. 與嵌入式Linux內核的互動介面,對於從微控制器轉行到嵌入式Linux的開發者,套着微控制器的開發經驗,往往會比較迷惑,對於LED的操作這類簡單的應用,微控制器的操作可能只要幾行就實現功能,但在嵌入式Linux驅動的開發的第一課中,第一印象就是複雜且迷惑,不過在經歷過多個模組的開發應用後,其實這部分並沒有如同第二類那麼複雜和多變,是有比較系統的思路去理解整個流程的,其實關於嵌入式linux驅動部分也是可以進一步分解成幾個方面去進一步學習。
(1)描述驅動模組作者,協定,功能,以及配合lsmod載入,rmmod刪除,導出爲其它內核模組存取的介面,包括不限於
MODULE_AUTHOR("xxx"); //模組作者
MODULE_LICENSE("GPL v2"); //模組許可協定
MODULE_DESCRIPTION("xxxxxxx"); //模組許描述
MODULE_ALIAS("xxxxxx"); //模組別名
module_init(func); //載入的入口函數
module_exit(func); //刪除的釋放函數
這部分應該是比較簡單的,初次接觸應該也能夠了解。
(2).將裝置新增到匯流排,並建立裝置的相關介面,以及刪除時釋放相應釋放的介面(當然嵌入式Linux也引入了虛擬匯流排進一步簡化了該流程),具體如下
//註冊或申請裝置號
register_chrdev_region(...)
alloc_chrdev_region(...)
//將裝置介面,實際存取的硬體(open, read, write,close)等介面ops,與裝置號關聯
cdev_init(...)
cdev_add(...)
//建立裝置物件,用於上層應用的通過/dev/*來存取
class_create(...)
device_create(...)
//釋放驅動相關的資源
device_destroy(...)
class_destroy(...)
cdev_del(...)
unregister_chrdev_region(...)
在這個基礎上,又引入了虛擬匯流排的概念,使用
platform_device_unregister(...)
platform_device_register(...)
可以更方便的開發和總結完成對於驅動的開發。
當然,比微控制器開發來說,嵌入式Linux需要更多的考慮同步/非同步,多核存取的問題,對於中斷和事件的處理也會更加複雜,不過這部分並沒有困難到無法掌握,在工作中遇到問題,解決問題,在充分的總結即可,不過如果看過我之前關於如何自學嵌入式Linux的文章
就會發現我遺漏了關於裝置樹相關的說明,從邏輯上講,DTS是對晶片硬體介面的抽象化範例,是對內部暫存器的封裝轉換,來解決Linux內核中不同板級的驅動程式碼的冗餘問題,而在驅動開發中,它也需要通過內核介面的存取,配合完整封裝的INPUT子系統,SPI框架,I2C框架,USB框架等,來快速應用完成對外部硬體裝置的操作。放在上面任意一類講並不合適,它是Linux後期爲解決實際問題引入的新的機制 機製,但目前已經被ARM系大部分廠商所接受,而且逐漸成爲主流,所以理解裝置樹機制 機製,並將其與晶片內部硬體的設定關聯,從而實現滿足需求的應用方案在驅動開發中已經成爲主流,也是嵌入式Linux驅動開發必須掌握的知識之一。
看到這裏,應該可以把嵌入式驅動開發需要知識歸類於以下方面知識掌握:
1.對晶片本身模組,暫存器和內部匯流排資訊的學習瞭解
2.對外部多樣功能晶片的熟悉,特別是對自己所在行業需要瞭解的晶片,如影象行業的CMOS,通訊行業的交換晶片,PHY,物聯網行業BLE模組,WIFI模組,2G/3G/4G/nb-IOT模組,工控行業的各種感測器的採樣, 電機控制等知識
瞭解這些,對於微控制器領域的驅動方面已經完全足夠了,不過對於嵌入式Linux驅動來說,在這基礎上
3.理解和驅動相關的內核介面,匯流排執行機制 機製,上層應用的存取介面,在實現應用的介面上,同時能夠掌握DTS語法,能夠將裝置樹上的資訊與晶片本身硬體的知識在腦海中對應聯繫,完成修改和新增的功能,並能夠將其正確的用於驅動中。
講述了這些,大致嵌入式驅動的整體有了清晰的認知,這不是學習的方法路徑,而且從實踐的角度說明驅動關注的是什麼,什麼是需要去瞭解和掌握的,這部分也是我這幾年工作積累的收穫之一,嵌入式驅動開發是值得鑽研並掌握的一門技術,學習的深入了,也會理解很多Linux內核設計的機制 機製,體驗最強大開源專案的魅力,但對於入門我還是不建議去一個一個外設模組去按部就班的去瞭解,上來要先建立整體的概念,從產品或者需求去拆解,不僅學到的是怎麼做,還會學到爲什麼這麼做,以及怎麼才能 纔能做的更好。