動(dòng)架構(gòu):從FSP三層設(shè)計(jì)到中斷DMA實(shí)戰(zhàn))
1. 從“能用”到“好用”RA系列驅(qū)動(dòng)為何值得深究在嵌入式開發(fā)或者工業(yè)控制領(lǐng)域提到“驅(qū)動(dòng)”這個(gè)詞很多工程師的第一反應(yīng)往往是“能用就行”。我們習(xí)慣了從芯片廠商那里拿到一個(gè)SDK包找到對(duì)應(yīng)的外設(shè)驅(qū)動(dòng)文件復(fù)制粘貼到自己的工程里編譯通過(guò)功能跑通項(xiàng)目就算完成了。至于這個(gè)驅(qū)動(dòng)是怎么寫的、為什么這么寫、有沒有更好的寫法似乎很少有人去深究。這種“黑盒”式的使用方式在項(xiàng)目初期確實(shí)能快速推進(jìn)但一旦遇到性能瓶頸、穩(wěn)定性問(wèn)題或者需要深度定制時(shí)就會(huì)讓人束手無(wú)策只能對(duì)著晦澀的寄存器手冊(cè)和一堆看似能跑但不知其所以然的代碼發(fā)愁。今天我想聊的RA系列微控制器的驅(qū)動(dòng)恰恰是打破這種“黑盒”思維的一個(gè)絕佳切入點(diǎn)。RA系列作為瑞薩電子主推的Arm Cortex-M內(nèi)核MCU產(chǎn)品線其配套的靈活配置軟件包FSP中的驅(qū)動(dòng)庫(kù)在設(shè)計(jì)理念和代碼質(zhì)量上與我早年接觸過(guò)的許多“祖?zhèn)鳌彬?qū)動(dòng)代碼有著天壤之別。它不僅僅是一堆讓你“能用”的API函數(shù)集合更像是一份由芯片原廠工程師編寫的、關(guān)于“如何正確、高效使用本芯片”的最佳實(shí)踐教科書。深入理解它你學(xué)到的將不僅僅是操作某個(gè)特定外設(shè)而是一整套面向現(xiàn)代MCU的驅(qū)動(dòng)設(shè)計(jì)方法論。這對(duì)于提升你的代碼質(zhì)量、調(diào)試效率乃至系統(tǒng)架構(gòu)能力都有著遠(yuǎn)超預(yù)期的價(jià)值。2. 架構(gòu)透視FSP驅(qū)動(dòng)庫(kù)的三層設(shè)計(jì)哲學(xué)很多傳統(tǒng)的驅(qū)動(dòng)庫(kù)喜歡把所有東西揉在一起一個(gè).c文件可能長(zhǎng)達(dá)幾千行里面混雜著硬件抽象、業(yè)務(wù)邏輯甚至一些調(diào)試信息。RA系列的FSP驅(qū)動(dòng)庫(kù)在架構(gòu)上就清晰得多它采用了典型的分層設(shè)計(jì)我們可以將其粗略地分為硬件抽象層HAL、驅(qū)動(dòng)層Driver和實(shí)例層Instance。理解這三層的關(guān)系是高效使用和定制驅(qū)動(dòng)的基礎(chǔ)。2.1 硬件抽象層HAL與芯片寄存器對(duì)話的“翻譯官”這是最底層的一環(huán)直接與芯片的物理寄存器打交道。HAL層的代碼通常是高度硬件相關(guān)的它通過(guò)宏定義、內(nèi)聯(lián)函數(shù)等方式將繁瑣的位操作比如設(shè)置某個(gè)控制寄存器的特定位、讀取狀態(tài)寄存器的某個(gè)標(biāo)志封裝成一個(gè)個(gè)語(yǔ)義清晰的函數(shù)或宏。例如對(duì)于一個(gè)UART外設(shè)HAL層會(huì)提供R_UART_HAL_Write()和R_UART_HAL_Read()這樣的函數(shù)它們內(nèi)部就是直接的寄存器讀寫操作。這一層的價(jià)值在于隔離硬件差異。不同型號(hào)的RA芯片其外設(shè)的寄存器地址、位域定義可能有細(xì)微差別。HAL層將這些差異消化掉對(duì)上層的驅(qū)動(dòng)層提供統(tǒng)一的接口。作為應(yīng)用開發(fā)者你幾乎不需要直接調(diào)用HAL層的函數(shù)但當(dāng)你需要追蹤一個(gè)極其底層的硬件問(wèn)題時(shí)比如某個(gè)中斷標(biāo)志為什么沒被清除讀懂HAL層的代碼是唯一的途徑。2.2 驅(qū)動(dòng)層Driver提供完整功能服務(wù)的“經(jīng)理”驅(qū)動(dòng)層是我們最常打交道的部分比如UART Driver,I2C Master Driver,GPT Timer Driver。這一層建立在HAL層之上它實(shí)現(xiàn)了某個(gè)外設(shè)的完整功能邏輯。例如UART驅(qū)動(dòng)不僅負(fù)責(zé)發(fā)送和接收單個(gè)字節(jié)還管理著發(fā)送/接收緩沖區(qū)、處理中斷、提供輪詢和中斷/DMA等多種傳輸模式、甚至包含超時(shí)控制和錯(cuò)誤處理。驅(qū)動(dòng)層通過(guò)一個(gè)名為ctrl的結(jié)構(gòu)體來(lái)維護(hù)其運(yùn)行狀態(tài)。這個(gè)結(jié)構(gòu)體是驅(qū)動(dòng)的“大腦”里面包含了配置參數(shù)波特率、數(shù)據(jù)位等、內(nèi)部狀態(tài)機(jī)、緩沖區(qū)指針、各種標(biāo)志位以及一個(gè)指向底層HAL操作的接口表。當(dāng)你調(diào)用R_UART_Open()初始化一個(gè)驅(qū)動(dòng)實(shí)例時(shí)系統(tǒng)會(huì)為這個(gè)ctrl結(jié)構(gòu)體分配內(nèi)存并進(jìn)行初始化。之后所有的操作如R_UART_Write()都會(huì)通過(guò)這個(gè)ctrl結(jié)構(gòu)體來(lái)找到對(duì)應(yīng)的硬件資源和內(nèi)部狀態(tài)。驅(qū)動(dòng)層的API設(shè)計(jì)通常是阻塞式、非阻塞式回調(diào)和DMA傳輸兼?zhèn)涞囊詽M足不同應(yīng)用場(chǎng)景對(duì)實(shí)時(shí)性和效率的要求。2.3 實(shí)例層Instance與配置工具連接硬件與軟件的“橋梁”這是FSP非常有特色的一環(huán)。在傳統(tǒng)的開發(fā)中我們需要手動(dòng)編寫代碼來(lái)初始化一個(gè)外設(shè)填充一個(gè)龐大的配置結(jié)構(gòu)體設(shè)置幾十個(gè)參數(shù)稍有不慎就會(huì)出錯(cuò)。FSP通過(guò)“實(shí)例Instance”和圖形化配置工具極大地簡(jiǎn)化了這一過(guò)程。在FSP的語(yǔ)境下一個(gè)“實(shí)例”代表一個(gè)被邏輯配置和初始化的外設(shè)使用單元。例如你的系統(tǒng)里要用到兩個(gè)UART一個(gè)連接調(diào)試終端115200波特率一個(gè)連接傳感器9600波特率。那么你會(huì)在配置工具里創(chuàng)建兩個(gè)UART實(shí)例比如g_uart0和g_uart1并分別對(duì)它們進(jìn)行圖形化的參數(shù)配置波特率、引腳映射、中斷優(yōu)先級(jí)等。配置工具如RASC的核心作用就是根據(jù)你的圖形化配置自動(dòng)生成所有底層的初始化代碼和這個(gè)實(shí)例的ctrl結(jié)構(gòu)體定義。它會(huì)在生成的hal_data.c文件中為你創(chuàng)建好一個(gè)已經(jīng)填充了所有參數(shù)的uart_instance_t g_uart0這樣的實(shí)例結(jié)構(gòu)體。這個(gè)結(jié)構(gòu)體里就包含了指向?qū)?yīng)驅(qū)動(dòng)層ctrl結(jié)構(gòu)體的指針以及所有的配置信息。你的應(yīng)用代碼只需要這樣操作/* 打開初始化這個(gè)UART實(shí)例 */ fsp_err_t err R_UART_Open(g_uart0_ctrl, g_uart0_cfg); if (FSP_SUCCESS ! err) { /* 錯(cuò)誤處理 */ } /* 使用該實(shí)例進(jìn)行數(shù)據(jù)發(fā)送 */ err R_UART_Write(g_uart0_ctrl, p_data, length);這種設(shè)計(jì)將配置和代碼完美分離。硬件工程師或系統(tǒng)架構(gòu)師可以在圖形界面完成所有外設(shè)的資源配置而軟件工程師則專注于業(yè)務(wù)邏輯通過(guò)清晰的實(shí)例接口調(diào)用驅(qū)動(dòng)功能大大減少了因配置錯(cuò)誤導(dǎo)致的低級(jí)Bug。3. 核心機(jī)制拆解以中斷處理和DMA集成為例理解了架構(gòu)我們?cè)賮?lái)深入兩個(gè)最能體現(xiàn)RA驅(qū)動(dòng)設(shè)計(jì)優(yōu)勢(shì)的機(jī)制中斷處理和DMA集成。這是驅(qū)動(dòng)從“簡(jiǎn)單能用”邁向“穩(wěn)定高效”的關(guān)鍵。3.1 中斷回調(diào)機(jī)制如何優(yōu)雅地處理異步事件輪詢Polling方式簡(jiǎn)單但低效會(huì)白白消耗CPU周期。RA的驅(qū)動(dòng)普遍采用回調(diào)函數(shù)Callback機(jī)制來(lái)處理中斷事件這是一種非常“現(xiàn)代”的異步編程模型。以UART接收中斷為例其工作流程如下配置階段在圖形化配置工具中使能UART的接收中斷并設(shè)置一個(gè)中斷優(yōu)先級(jí)。同時(shí)在代碼中你需要實(shí)現(xiàn)一個(gè)回調(diào)函數(shù)例如user_uart_callback(uart_callback_args_t *p_args)。注冊(cè)階段在調(diào)用R_UART_Open()之前或之后通過(guò)驅(qū)動(dòng)提供的API通常是R_UART_CallbackSet()將這個(gè)回調(diào)函數(shù)注冊(cè)到對(duì)應(yīng)的驅(qū)動(dòng)實(shí)例中。驅(qū)動(dòng)會(huì)把這個(gè)函數(shù)指針保存在它的ctrl結(jié)構(gòu)體里。運(yùn)行階段當(dāng)硬件UART接收到數(shù)據(jù)并觸發(fā)中斷時(shí)芯片的中斷控制器會(huì)跳轉(zhuǎn)到FSP為這個(gè)UART實(shí)例預(yù)先設(shè)置好的中斷服務(wù)程序ISR。這個(gè)ISR是驅(qū)動(dòng)庫(kù)的一部分它的代碼是高度優(yōu)化的匯編或C語(yǔ)言主要做幾件事保存現(xiàn)場(chǎng)。清除硬件中斷標(biāo)志防止重復(fù)進(jìn)入。從接收數(shù)據(jù)寄存器RDR讀取數(shù)據(jù)存放到驅(qū)動(dòng)內(nèi)部緩沖區(qū)。判斷接收是否完成例如收到指定長(zhǎng)度或終止符如果完成則構(gòu)造一個(gè)uart_callback_args_t類型的參數(shù)結(jié)構(gòu)體。這個(gè)結(jié)構(gòu)體非常有用它包含了事件類型如UART_EVENT_RX_COMPLETE、數(shù)據(jù)指針、數(shù)據(jù)長(zhǎng)度等信息。調(diào)用你注冊(cè)的用戶回調(diào)函數(shù)user_uart_callback并將那個(gè)參數(shù)結(jié)構(gòu)體傳遞給它。恢復(fù)現(xiàn)場(chǎng)退出中斷。用戶處理在你的user_uart_callback函數(shù)里你可以根據(jù)p_args-event來(lái)判斷發(fā)生了什么事件然后安全地處理數(shù)據(jù)比如將數(shù)據(jù)復(fù)制到應(yīng)用層的隊(duì)列中。因?yàn)檫@是在中斷上下文調(diào)用的所以這個(gè)函數(shù)必須遵循ISR的編寫原則快進(jìn)快出不要調(diào)用可能阻塞的函數(shù)如某些printf。注意這里有一個(gè)至關(guān)重要的細(xì)節(jié)。驅(qū)動(dòng)層的中斷服務(wù)程序ISR是通用的、由FSP提供的。它通過(guò)ctrl結(jié)構(gòu)體找到當(dāng)前實(shí)例的用戶回調(diào)函數(shù)并執(zhí)行。這意味著中斷處理的“重活”數(shù)據(jù)搬運(yùn)、狀態(tài)更新由高效的官方代碼完成而“輕活”事件通知、數(shù)據(jù)轉(zhuǎn)移則由你的應(yīng)用代碼處理。這種分工既保證了中斷響應(yīng)的高效性又給了應(yīng)用層最大的靈活性。3.2 DMA集成釋放CPU壓力的關(guān)鍵對(duì)于高速數(shù)據(jù)流如音頻采集、圖像傳輸、高速通信即使使用中斷每個(gè)字節(jié)都進(jìn)一次中斷的 overhead 也是不可接受的。RA驅(qū)動(dòng)與DMA控制器的集成設(shè)計(jì)得非常緊密。在配置工具中當(dāng)你為一個(gè)外設(shè)比如UART、SPI、ADC配置傳輸模式時(shí)可以選擇“DMA”模式。以UART發(fā)送為例你配置UART實(shí)例使用DMA發(fā)送并關(guān)聯(lián)一個(gè)DMA通道例如通道0。配置工具會(huì)自動(dòng)生成DMA通道的配置并將其與UART的發(fā)送請(qǐng)求線例如DMAC_REQ_UART0_TX綁定。在你的應(yīng)用代碼中調(diào)用R_UART_Write()時(shí)傳入數(shù)據(jù)指針和長(zhǎng)度。驅(qū)動(dòng)層并不會(huì)像中斷模式那樣去啟動(dòng)發(fā)送并等待而是會(huì)配置DMA通道的源地址你的數(shù)據(jù)緩沖區(qū)、目標(biāo)地址UART的發(fā)送數(shù)據(jù)寄存器TDR、傳輸數(shù)據(jù)量。啟動(dòng)DMA通道。函數(shù)立即返回非阻塞。DMA控制器在后臺(tái)無(wú)需CPU干預(yù)自動(dòng)將數(shù)據(jù)從內(nèi)存搬運(yùn)到UART的TDR寄存器。UART硬件則自動(dòng)將TDR中的數(shù)據(jù)串行化發(fā)送出去。當(dāng)DMA完成全部數(shù)據(jù)的傳輸后會(huì)觸發(fā)一個(gè)DMA傳輸完成中斷。這個(gè)中斷同樣由FSP的DMA驅(qū)動(dòng)管理它會(huì)調(diào)用你為這個(gè)DMA通道注冊(cè)的回調(diào)函數(shù)通知你發(fā)送完成。這個(gè)過(guò)程將CPU徹底解放出來(lái)。CPU只需要發(fā)起一次傳輸請(qǐng)求就可以去處理其他任務(wù)直到DMA完成整個(gè)數(shù)據(jù)塊的搬運(yùn)后才被通知。對(duì)于接收也是同理。這種“驅(qū)動(dòng)DMA”的深度集成是實(shí)現(xiàn)高性能、低功耗嵌入式系統(tǒng)的基石。RA的FSP通過(guò)圖形化配置和統(tǒng)一的API讓這件原本很復(fù)雜的事情變得相當(dāng)直觀。4. 實(shí)戰(zhàn)中的配置陷阱與性能調(diào)優(yōu)經(jīng)驗(yàn)看懂了原理不等于能寫好代碼。在實(shí)際項(xiàng)目中使用RA驅(qū)動(dòng)我踩過(guò)不少坑也總結(jié)出一些讓系統(tǒng)更穩(wěn)健、更高效的經(jīng)驗(yàn)。4.1 時(shí)鐘配置一切正常工作的前提這是最基礎(chǔ)也最容易出錯(cuò)的地方。RA驅(qū)動(dòng)嚴(yán)重依賴底層時(shí)鐘系統(tǒng)的正確配置。例如你配置UART波特率為115200這個(gè)值是根據(jù)你給UART模塊提供的時(shí)鐘源頻率PCLK計(jì)算出來(lái)的。如果PCLK的時(shí)鐘源選錯(cuò)或者分頻系數(shù)算錯(cuò)波特率就會(huì)不準(zhǔn)。常見坑點(diǎn)時(shí)鐘源未啟動(dòng)在RA中很多外設(shè)時(shí)鐘如PCLKA、PCLKB默認(rèn)是關(guān)閉的以省電。你必須在配置工具的“Clocks”頁(yè)面上明確使能你所用外設(shè)對(duì)應(yīng)的總線時(shí)鐘。分頻器配置沖突系統(tǒng)時(shí)鐘有多級(jí)分頻器。有時(shí)你修改了主時(shí)鐘分頻卻忘了它會(huì)影響下游的PCLK導(dǎo)致所有基于該P(yáng)CLK的外設(shè)如多個(gè)UART、SPI頻率一起跑偏。配置工具生成的代碼被覆蓋FSP配置工具生成的時(shí)鐘初始化代碼通常在hal_entry.c的R_BSP_WarmStart函數(shù)中。如果你在main函數(shù)里或其他地方手動(dòng)調(diào)用了修改時(shí)鐘的代碼可能會(huì)覆蓋掉之前的配置導(dǎo)致驅(qū)動(dòng)工作異常。避坑指南始終以配置工具為主盡量全部時(shí)鐘配置都在RASC圖形界面完成不要手動(dòng)寫寄存器修改。雙重驗(yàn)證使用調(diào)試器在初始化后直接讀取相關(guān)時(shí)鐘控制寄存器的值或者用IO口翻轉(zhuǎn)法測(cè)量PCLK頻率與理論值進(jìn)行比對(duì)。理解時(shí)鐘樹花點(diǎn)時(shí)間看看芯片數(shù)據(jù)手冊(cè)中的時(shí)鐘框圖搞清楚HOCO,MOCO,PLL,Main Clock,Sub Clock之間的關(guān)系以及ICLK,PCLKA,PCLKB,PCLKD的走向。4.2 中斷優(yōu)先級(jí)與嵌套系統(tǒng)穩(wěn)定的核心當(dāng)你的系統(tǒng)同時(shí)使用多個(gè)帶中斷的驅(qū)動(dòng)如UART接收、定時(shí)器、ADC采樣完成中斷優(yōu)先級(jí)配置就至關(guān)重要。問(wèn)題場(chǎng)景假設(shè)你有一個(gè)高優(yōu)先級(jí)的定時(shí)器中斷用于電機(jī)控制和一個(gè)低優(yōu)先級(jí)的UART接收中斷用于接收調(diào)試命令。如果配置不當(dāng)可能會(huì)發(fā)生數(shù)據(jù)丟失UART正在低速處理接收中斷比如將數(shù)據(jù)存入隊(duì)列此時(shí)高速的定時(shí)器中斷不斷發(fā)生并搶占導(dǎo)致UART接收緩沖區(qū)溢出數(shù)據(jù)丟失。優(yōu)先級(jí)反轉(zhuǎn)雖然不常見但若驅(qū)動(dòng)代碼中使用了信號(hào)量等同步機(jī)制且中斷優(yōu)先級(jí)配置不合理可能導(dǎo)致。配置經(jīng)驗(yàn)合理規(guī)劃優(yōu)先級(jí)組Cortex-M內(nèi)核允許你將中斷優(yōu)先級(jí)分為“搶占優(yōu)先級(jí)”和“子優(yōu)先級(jí)”。對(duì)于RA我通常的策略是將最緊急、執(zhí)行時(shí)間最短的中斷如PWM保護(hù)、緊急故障設(shè)為最高搶占優(yōu)先級(jí)將執(zhí)行時(shí)間較長(zhǎng)、但實(shí)時(shí)性要求高的如定時(shí)器控制環(huán)設(shè)為中高優(yōu)先級(jí)將通信類、非實(shí)時(shí)性的如UART、I2C設(shè)為較低優(yōu)先級(jí)。在配置工具中清晰設(shè)定FSP配置工具中每個(gè)驅(qū)動(dòng)實(shí)例都有“Interrupt Priority”選項(xiàng)。務(wù)必根據(jù)你的系統(tǒng)設(shè)計(jì)在這里明確設(shè)置而不是使用默認(rèn)值。注意中斷服務(wù)程序ISR的執(zhí)行時(shí)間即使是你自己寫的回調(diào)函數(shù)也要盡量短小精悍。如果確實(shí)有大量工作要做應(yīng)該只在回調(diào)中設(shè)置標(biāo)志位或發(fā)送消息然后由主循環(huán)或低優(yōu)先級(jí)任務(wù)來(lái)處理。4.3 內(nèi)存與緩沖區(qū)管理防止溢出和踩內(nèi)存驅(qū)動(dòng)內(nèi)部通常會(huì)使用緩沖區(qū)。例如UART驅(qū)動(dòng)在中斷模式下會(huì)有一個(gè)由ctrl結(jié)構(gòu)體管理的環(huán)形緩沖區(qū)Ring Buffer。潛在風(fēng)險(xiǎn)緩沖區(qū)溢出你的應(yīng)用層生產(chǎn)數(shù)據(jù)調(diào)用Write的速度超過(guò)了硬件發(fā)送的速度或者消費(fèi)數(shù)據(jù)從回調(diào)中取數(shù)據(jù)的速度跟不上硬件接收的速度都會(huì)導(dǎo)致驅(qū)動(dòng)內(nèi)部緩沖區(qū)溢出。好的驅(qū)動(dòng)會(huì)返回FSP_ERR_OVERFLOW之類的錯(cuò)誤但更關(guān)鍵的是你的應(yīng)用層要有應(yīng)對(duì)策略如流控、丟包重傳。指針生命周期當(dāng)你調(diào)用R_UART_Write(p_ctrl, p_data, length)時(shí)p_data指向的緩沖區(qū)必須保證在DMA傳輸完成或中斷發(fā)送完成之前其內(nèi)容不能被修改或釋放。如果p_data是局部變量函數(shù)返回后棧空間可能被覆蓋將導(dǎo)致發(fā)送亂碼或內(nèi)存錯(cuò)誤。最佳實(shí)踐為驅(qū)動(dòng)分配靜態(tài)或全局緩沖區(qū)對(duì)于重要的數(shù)據(jù)通道使用靜態(tài)數(shù)組或全局變量作為數(shù)據(jù)緩沖區(qū)確保其生命周期與整個(gè)應(yīng)用一致。檢查返回值每次調(diào)用驅(qū)動(dòng)API后務(wù)必檢查其返回的fsp_err_t錯(cuò)誤碼。特別是Write和Read操作要處理FSP_ERR_OVERFLOW和FSP_ERR_UNDERFLOW。合理設(shè)置緩沖區(qū)大小在驅(qū)動(dòng)實(shí)例的配置結(jié)構(gòu)體中通常可以設(shè)置接收/發(fā)送緩沖區(qū)的大小。根據(jù)你的數(shù)據(jù)吞吐量和系統(tǒng)實(shí)時(shí)性要求估算一個(gè)合理值并留有一定余量。不要盲目使用默認(rèn)值。4.4 低功耗模式下的驅(qū)動(dòng)行為RA芯片支持豐富的低功耗模式Sleep, Software Standby, Deep Software Standby等。當(dāng)CPU進(jìn)入低功耗模式時(shí)外設(shè)時(shí)鐘可能會(huì)被關(guān)閉這直接影響到依賴時(shí)鐘工作的驅(qū)動(dòng)。關(guān)鍵點(diǎn)外設(shè)時(shí)鐘門控在進(jìn)入低功耗模式前驅(qū)動(dòng)可能需要執(zhí)行一些操作來(lái)安全地停止當(dāng)前活動(dòng)如完成最后一次DMA傳輸、刷新緩沖區(qū)。FSP的驅(qū)動(dòng)通常提供了R_XXX_Close()函數(shù)它不僅僅是釋放資源也會(huì)將外設(shè)置于一個(gè)安全的狀態(tài)。喚醒源配置如果你希望某個(gè)外設(shè)如UART收到數(shù)據(jù)、RTC定時(shí)到能將系統(tǒng)從低功耗模式喚醒那么必須在進(jìn)入低功耗前正確配置該外設(shè)的中斷和喚醒功能。這通常涉及芯片級(jí)BSP的配置而不僅僅是驅(qū)動(dòng)層的配置。狀態(tài)恢復(fù)從低功耗模式喚醒后系統(tǒng)時(shí)鐘和外設(shè)時(shí)鐘需要重新穩(wěn)定。你的應(yīng)用代碼需要重新初始化驅(qū)動(dòng)嗎不一定。對(duì)于設(shè)計(jì)良好的驅(qū)動(dòng)如果低功耗模式?jīng)]有關(guān)閉該外設(shè)的電源域喚醒后驅(qū)動(dòng)可能保持原有狀態(tài)。但更安全的做法是在喚醒后的初始化流程中重新調(diào)用R_XXX_Open()或至少進(jìn)行一些必要的配置檢查。一個(gè)常見的做法是在進(jìn)入低功耗的流程中先調(diào)用R_XXX_Close()關(guān)閉所有不用于喚醒的外設(shè)驅(qū)動(dòng)在喚醒后的流程中再重新初始化它們。對(duì)于作為喚醒源的外設(shè)則需要在進(jìn)入低功耗前保持其開啟和中斷使能狀態(tài)。5. 超越默認(rèn)驅(qū)動(dòng)定制化與源碼級(jí)調(diào)試FSP提供的驅(qū)動(dòng)已經(jīng)覆蓋了絕大多數(shù)常見用例且經(jīng)過(guò)了嚴(yán)格測(cè)試穩(wěn)定性有保障。但在某些極端情況下你可能需要對(duì)其進(jìn)行定制或優(yōu)化。5.1 何時(shí)需要修改驅(qū)動(dòng)源碼不建議你直接修改FSP庫(kù)目錄下的驅(qū)動(dòng)源碼/ra/fsp/src/...因?yàn)檫@會(huì)使得你的項(xiàng)目與官方庫(kù)版本綁定未來(lái)升級(jí)FSP時(shí)會(huì)非常麻煩。FSP提供了更好的機(jī)制在項(xiàng)目目錄中復(fù)制并覆蓋。你可以在你的項(xiàng)目目錄下創(chuàng)建一個(gè)相同的文件路徑例如my_project/ra_gen/driver/src/r_uart.c。當(dāng)你編譯時(shí)編譯器會(huì)優(yōu)先使用你項(xiàng)目目錄下的這個(gè)副本而不是FSP庫(kù)里的那個(gè)。這樣你的修改是獨(dú)立于FSP庫(kù)的。那么什么情況下需要這么做呢修復(fù)緊急Bug雖然罕見但如果你在官方驅(qū)動(dòng)中發(fā)現(xiàn)了一個(gè)影響你項(xiàng)目的Bug并且等不及官方發(fā)布新版本可以臨時(shí)在此修復(fù)。極致的性能優(yōu)化例如你需要將某個(gè)中斷服務(wù)程序ISR的壓棧/出棧操作從默認(rèn)的通用版本替換為針對(duì)你特定寄存器使用場(chǎng)景的手寫匯編版本以減少幾個(gè)時(shí)鐘周期的開銷。添加特殊硬件支持你的硬件設(shè)計(jì)可能用到了某個(gè)芯片的非常規(guī)功能而標(biāo)準(zhǔn)驅(qū)動(dòng)沒有支持。例如利用某個(gè)外設(shè)的特定測(cè)試模式。警告這是一把雙刃劍。修改驅(qū)動(dòng)源碼意味著你需要完全理解該段代碼的邏輯并承擔(dān)由此帶來(lái)的所有風(fēng)險(xiǎn)穩(wěn)定性、兼容性。務(wù)必做好版本管理和詳細(xì)的修改注釋。絕大多數(shù)需求其實(shí)都可以通過(guò)配置、回調(diào)函數(shù)和應(yīng)用層代碼的組合來(lái)實(shí)現(xiàn)無(wú)需動(dòng)到底層驅(qū)動(dòng)。5.2 利用調(diào)試器深入驅(qū)動(dòng)內(nèi)部當(dāng)遇到棘手的驅(qū)動(dòng)問(wèn)題時(shí)比如數(shù)據(jù)偶爾丟失、中斷不觸發(fā)僅靠打印日志是不夠的。你需要像外科手術(shù)一樣使用調(diào)試器如J-Link配合SEGGER Ozone或IAR/Keil的調(diào)試器進(jìn)行源碼級(jí)調(diào)試。關(guān)鍵調(diào)試技巧在驅(qū)動(dòng)的ISR中設(shè)置斷點(diǎn)直接在FSP提供的驅(qū)動(dòng)中斷服務(wù)程序入口處設(shè)斷點(diǎn)。當(dāng)斷點(diǎn)觸發(fā)時(shí)你可以查看調(diào)用棧確認(rèn)中斷是否如期發(fā)生檢查傳入的參數(shù)是否正確。監(jiān)視ctrl結(jié)構(gòu)體將驅(qū)動(dòng)實(shí)例的g_uart0_ctrl添加到觀察窗口。你可以實(shí)時(shí)查看其內(nèi)部狀態(tài)機(jī)的變化、緩沖區(qū)讀寫指針的位置、錯(cuò)誤標(biāo)志位等。這比任何打印信息都直觀。檢查寄存器現(xiàn)場(chǎng)當(dāng)程序停在ISR中時(shí)打開寄存器的查看窗口直接對(duì)比硬件寄存器的值如UART的狀態(tài)寄存器SSR與驅(qū)動(dòng)代碼中讀取和判斷的值是否一致。這能幫你判斷是硬件問(wèn)題還是軟件邏輯問(wèn)題。使用數(shù)據(jù)斷點(diǎn)如果你懷疑某個(gè)全局變量或緩沖區(qū)在異常地被修改可以對(duì)其地址設(shè)置數(shù)據(jù)寫入斷點(diǎn)。當(dāng)驅(qū)動(dòng)或你的代碼意外修改了它時(shí)調(diào)試器會(huì)立刻中斷幫你定位到元兇。通過(guò)這種深入的調(diào)試你不僅能解決問(wèn)題更能加深對(duì)驅(qū)動(dòng)運(yùn)行機(jī)制的理解真正做到“知其然也知其所以然”。我個(gè)人在多個(gè)RA系列項(xiàng)目中的體會(huì)是花時(shí)間去深入理解FSP驅(qū)動(dòng)的設(shè)計(jì)初期看起來(lái)像是“浪費(fèi)時(shí)間”但長(zhǎng)遠(yuǎn)來(lái)看這筆投資回報(bào)率極高。它讓你從被動(dòng)的API調(diào)用者轉(zhuǎn)變?yōu)橹鲃?dòng)的系統(tǒng)構(gòu)建者。當(dāng)你能預(yù)判配置可能帶來(lái)的問(wèn)題能快速定位驅(qū)動(dòng)層的異常甚至能根據(jù)需求對(duì)驅(qū)動(dòng)進(jìn)行安全可控的定制時(shí)你對(duì)整個(gè)嵌入式系統(tǒng)的掌控力就完全不在一個(gè)層次了。RA的驅(qū)動(dòng)庫(kù)就像一份精心編寫的手冊(cè)讀懂了它你手里的這顆芯片才能真正為你所用。