問(wèn)Hard Fault排查:從時(shí)鐘使能到MPU配置的避坑指南)
直接說(shuō)結(jié)論這活兒我調(diào)了兩天最后發(fā)現(xiàn)不是芯片壞了不是代碼寫(xiě)錯(cuò)了是時(shí)鐘沒(méi)配好就急著訪(fǎng)問(wèn)TCM導(dǎo)致的。最近項(xiàng)目里把主控?fù)Q成了STM32N6本來(lái)想用DTCM做音頻緩沖結(jié)果一上電跑沒(méi)多久就進(jìn)Hard Fault調(diào)試器一看PC指針直接飛到0xFFFFFFFE典型的取指異常。后來(lái)把整個(gè)啟動(dòng)流程翻了個(gè)底朝天總算把問(wèn)題根因挖出來(lái)了順手把TCM訪(fǎng)問(wèn)的各種坑都踩了一遍寫(xiě)出來(lái)給后面用這顆芯片的人避雷。先說(shuō)下環(huán)境開(kāi)發(fā)板是N6官方的NUCLEO-N6IDE用的STM32CubeIDE 1.16HAL庫(kù)版本是1.4.0RTOS用的ThreadX代碼里通過(guò)MPU配置了應(yīng)用安全區(qū)和應(yīng)用非安全區(qū)功能TCM這塊走的是DTCM地址0x20000000。這套組合是目前N6開(kāi)發(fā)比較典型的搭配如果你是剛拿到N6的芯片手冊(cè)準(zhǔn)備開(kāi)搞這篇應(yīng)該能幫你省下不少時(shí)間。1. 問(wèn)題現(xiàn)象Hard Fault來(lái)得毫無(wú)征兆現(xiàn)象是這樣的系統(tǒng)啟動(dòng)后RTOS調(diào)度器正常運(yùn)行幾個(gè)任務(wù)跑起來(lái)都正常但一旦某個(gè)任務(wù)里對(duì)DTCM地址進(jìn)行寫(xiě)操作比如往0x20000100寫(xiě)一段音頻數(shù)據(jù)系統(tǒng)馬上跳進(jìn)Hard Fault。更詭異的是有時(shí)候不是馬上掛而是寫(xiě)了幾十次之后才掛看起來(lái)像是隨機(jī)性的。第一次遇到這種情況我第一反應(yīng)是懷疑MPU配置錯(cuò)了因?yàn)镹6這顆芯片的MPU配置比以前的M4/M7復(fù)雜不少而且我確實(shí)用了安全區(qū)/非安全區(qū)的功能。于是我把MPU的Region配置全部打出來(lái)對(duì)照手冊(cè)一個(gè)字段一個(gè)字段檢查結(jié)果沒(méi)發(fā)現(xiàn)問(wèn)題。又懷疑是DMA沖突把DMA關(guān)掉測(cè)試問(wèn)題依舊。后來(lái)實(shí)在沒(méi)辦法把Hard Fault的現(xiàn)場(chǎng)完整抓下來(lái)分析通過(guò)調(diào)試器讀取HFSR、CFSR、BFAR、MMFAR這幾個(gè)關(guān)鍵的Fault狀態(tài)寄存器發(fā)現(xiàn)CFSR里的IBUSERR位被置1這說(shuō)明問(wèn)題出在指令總線(xiàn)錯(cuò)誤上也就是CPU去取指令的時(shí)候訪(fǎng)問(wèn)了一個(gè)不允許訪(fǎng)問(wèn)的地址。再一看BFAR指向的地址是0x20000000附近——這不就是DTCM的地址嗎CPU取指令跑到DTCM去了這本身就很有問(wèn)題但更重要的是為什么取指令會(huì)訪(fǎng)問(wèn)DTCM這里插一句Debug下連接ST-LINK的SWD接口其實(shí)能直接把N6的Fault狀態(tài)寄存器全讀出來(lái)這個(gè)信息非常關(guān)鍵。如果你連芯片的調(diào)試接口都不用光靠串口打印Fault信息那基本是在盲人摸象。后面我會(huì)詳細(xì)講怎么看這幾個(gè)寄存器。2. 問(wèn)題定位從Fault狀態(tài)寄存器倒推根因2.1 先看懂Hard Fault的“案發(fā)現(xiàn)場(chǎng)”ARM Cortex-M系列的Fault機(jī)制是分級(jí)的Hard Fault是最高級(jí)別的錯(cuò)誤但它只是個(gè)“匯總”真正的原因要看下面這幾個(gè)寄存器HFSRHardFault Status RegisterHard Fault的狀態(tài)看FORCED位是否置1如果置1說(shuō)明是由其他Fault升級(jí)過(guò)來(lái)的。CFSRConfigurable Fault Status Register這個(gè)寄存器其實(shí)是三個(gè)寄存器的合體包括MMFSRMemManage Fault Status Register、BFSRBus Fault Status Register、UFSRUsage Fault Status Register分別對(duì)應(yīng)內(nèi)存管理錯(cuò)誤、總線(xiàn)錯(cuò)誤、用法錯(cuò)誤。BFARBus Fault Address Register記錄總線(xiàn)錯(cuò)誤發(fā)生的地址。MMFARMemManage Fault Address Register記錄內(nèi)存管理錯(cuò)誤發(fā)生的地址。調(diào)試器連接后我在Fault處理函數(shù)里加了斷點(diǎn)程序一進(jìn)Hard Fault就停下來(lái)然后手工讀這幾個(gè)寄存器。讀出來(lái)的結(jié)果是這樣的HFSR 0x40000000 // FORCED位置1說(shuō)明有次級(jí)Fault CFSR 0x00008200 // IBUSERR位置1指令總線(xiàn)錯(cuò)誤 BFAR 0x20000000 // 出錯(cuò)地址指向DTCM看到這個(gè)結(jié)果思路就清晰了CPU在訪(fǎng)問(wèn)DTCM地址的時(shí)候指令總線(xiàn)層面報(bào)錯(cuò)了。換句話(huà)說(shuō)CPU試圖從DTCM取指令但芯片不允許因?yàn)镈TCM這個(gè)區(qū)域根本沒(méi)有被配置成可執(zhí)行屬性。但這里有個(gè)問(wèn)題代碼里并沒(méi)有任何函數(shù)指針指向DTCM為什么CPU會(huì)去DTCM取指令這就得回到ARM的異常處理機(jī)制和N6的啟動(dòng)流程上找答案了。2.2 疑點(diǎn)追蹤為什么CPU會(huì)去DTCM取指令我先懷疑的是中斷向量表出了問(wèn)題。因?yàn)樵贑ortex-M系列里中斷向量表是放在Flash起始地址的中斷發(fā)生時(shí)會(huì)從向量表取中斷服務(wù)函數(shù)的地址。如果向量表被錯(cuò)誤地修改或者啟動(dòng)代碼里對(duì)VTOR向量表偏移寄存器的設(shè)置不對(duì)CPU就可能跑到奇怪的地方去取指令。但是我又想向量表在Flash里不在TCM里這個(gè)路徑解釋不通。再往下追我注意到一個(gè)細(xì)節(jié)N6這顆芯片用的是雙核架構(gòu)一個(gè)Cortex-M55主核加一個(gè)神經(jīng)處理單元NPU而且M55支持TrustZone所以有安全狀態(tài)和非安全狀態(tài)之分。在TrustZone架構(gòu)下中斷控制器NVIC的處理方式還帶上了安全屬性中斷向量表也可以配置成從非安全區(qū)取向量或者從安全區(qū)取向量。問(wèn)題恰恰出在這里。我啟用了應(yīng)用安全區(qū)和應(yīng)用非安全區(qū)功能但中斷向量表的配置沒(méi)有跟上。具體來(lái)說(shuō)中斷發(fā)生時(shí)NVIC根據(jù)當(dāng)前的執(zhí)行狀態(tài)要去對(duì)應(yīng)的向量表取中斷服務(wù)函數(shù)的地址。如果這個(gè)向量表地址配置不對(duì)或者對(duì)應(yīng)區(qū)域的執(zhí)行權(quán)限沒(méi)有設(shè)置好CPU取指令的時(shí)候就會(huì)觸發(fā)總線(xiàn)錯(cuò)誤進(jìn)而升級(jí)為Hard Fault。這就能解釋為什么問(wèn)題看起來(lái)是“隨機(jī)”的因?yàn)橹挥性谥袛嘤|發(fā)的時(shí)候才會(huì)走到這一步而我的測(cè)試代碼里SysTick中斷是最頻繁的所以看起來(lái)像是跑一段時(shí)間就掛實(shí)際上是每次SysTick觸發(fā)都有概率出錯(cuò)只不過(guò)有時(shí)候碰巧取到的地址還能執(zhí)行有時(shí)候就直接掛了。3. 核心原因分析N6時(shí)鐘配置與TCM訪(fǎng)問(wèn)權(quán)限的聯(lián)動(dòng)關(guān)系3.1 時(shí)鐘配置是這一切的起點(diǎn)先說(shuō)結(jié)論STM32N6這顆芯片和以前的STM32系列最大的不同是它的TCM接口并不是直接掛在系統(tǒng)總線(xiàn)上的而是通過(guò)AXI總線(xiàn)矩陣和時(shí)鐘域交叉連接。在默認(rèn)狀態(tài)下TCM區(qū)域的時(shí)鐘可能沒(méi)有完全使能或者時(shí)鐘頻率配置不對(duì)導(dǎo)致CPU訪(fǎng)問(wèn)TCM時(shí)總線(xiàn)矩陣返回了一個(gè)錯(cuò)誤響應(yīng)。我當(dāng)時(shí)用的時(shí)鐘配置是外部25MHz晶振PLL倍頻到800MHz主頻這是N6的正常工作頻率。但我漏了一個(gè)細(xì)節(jié)N6的DTCM和ITCM在默認(rèn)狀態(tài)下是由一個(gè)獨(dú)立的時(shí)鐘域控制的叫TCM時(shí)鐘域。這個(gè)時(shí)鐘域需要你在時(shí)鐘樹(shù)里單獨(dú)使能并且要和CPU主頻配置成同步關(guān)系。如果TCM時(shí)鐘域沒(méi)有使能或者使能了但頻率配置不對(duì)那么CPU一旦去訪(fǎng)問(wèn)TCM地址總線(xiàn)矩陣因?yàn)闀r(shí)鐘域不一致會(huì)返回一個(gè)錯(cuò)誤響應(yīng)。這個(gè)錯(cuò)誤響應(yīng)在指令總線(xiàn)上就表現(xiàn)為IBUSERR最終升級(jí)成Hard Fault。我后來(lái)在STM32CubeMX里仔細(xì)檢查時(shí)鐘樹(shù)發(fā)現(xiàn)TCM時(shí)鐘默認(rèn)是關(guān)閉的。CubeMX里有個(gè)選項(xiàng)叫“Enable TCM Clock”默認(rèn)是灰色的需要先在A(yíng)dvanced Settings里勾選然后重新生成代碼。勾選之后系統(tǒng)會(huì)生成一段初始化代碼專(zhuān)門(mén)用來(lái)使能TCM時(shí)鐘域。我把這段代碼加上之后Hard Fault問(wèn)題就消失了。3.2 再深挖一層MPU配置與執(zhí)行權(quán)限時(shí)鐘問(wèn)題解決了之后我又深入測(cè)了一輪發(fā)現(xiàn)還有一個(gè)隱藏的問(wèn)題如果把DTCM配置成可執(zhí)行的倒是不會(huì)再觸發(fā)IBUSERR但這樣會(huì)引入安全隱患因?yàn)楣粽呖梢岳镁彌_區(qū)溢出把惡意代碼寫(xiě)到DTCM然后跳過(guò)去執(zhí)行繞過(guò)Flash區(qū)的執(zhí)行權(quán)限保護(hù)。這是我在配置MPU時(shí)特別注意的。N6的MPU配置里每個(gè)Region都有Execute NeverXN位如果你把DTCM所在的Region配置成XN那么即使DTCM地址被訪(fǎng)問(wèn)了只要是指令取指就會(huì)被拒絕。這樣做的代價(jià)是如果你的確需要從DTCM執(zhí)行代碼那就得把這個(gè)Region的XN位清掉。但一般情況下TCM是用來(lái)放數(shù)據(jù)的不應(yīng)該設(shè)成可執(zhí)行所以XN位必須置1。我遇到的情況是我把MPU的Region配置寫(xiě)好了XN位也置1了但仍然出現(xiàn)Hard Fault。后來(lái)排查發(fā)現(xiàn)是Region的優(yōu)先級(jí)配置問(wèn)題。N6的MPU支持16個(gè)Region每個(gè)Region有一個(gè)優(yōu)先級(jí)號(hào)優(yōu)先級(jí)號(hào)小的Region優(yōu)先級(jí)更高。我把DTCM的Region優(yōu)先級(jí)配置成了8但另一個(gè)無(wú)關(guān)的Region優(yōu)先級(jí)是8兩個(gè)Region優(yōu)先級(jí)相同覆蓋范圍又有重疊結(jié)果MPU的行為就變得不確定了。ARM的MPU設(shè)計(jì)里如果兩個(gè)Region的優(yōu)先級(jí)相同且地址范圍重疊實(shí)際生效的是編號(hào)較大的Region還是編號(hào)較小的Region這在不同的芯片上表現(xiàn)可能不一樣。N6的參考手冊(cè)里明確寫(xiě)了默認(rèn)情況下編號(hào)較大的Region優(yōu)先級(jí)更高或者相反取決于具體實(shí)現(xiàn)我在配置的時(shí)候沒(méi)有留意這個(gè)細(xì)節(jié)導(dǎo)致DTCM的MPU配置被另一個(gè)Region覆蓋了XN位失效CPU就能去TCM取指了。所以我在MPU配置上踩的坑其實(shí)是兩個(gè)一個(gè)是時(shí)鐘沒(méi)使能另一個(gè)是MPU Region優(yōu)先級(jí)重疊導(dǎo)致XN位失效。這兩個(gè)問(wèn)題疊加在一起構(gòu)成了Hard Fault的完整根因。4. 完整解決方法與實(shí)操步驟4.1 第一步確認(rèn)TCM時(shí)鐘已使能在STM32CubeMX里打開(kāi)Clock Configuration找到TCM相關(guān)的時(shí)鐘選項(xiàng)。如果你的芯片封裝和CubeMX版本不同這個(gè)選項(xiàng)的位置可能略有差異但關(guān)鍵詞是“TCM”或“DTCM/ITCM”。把TCM時(shí)鐘使能并把分頻系數(shù)設(shè)置成和CPU主頻同步。生成代碼之后在SystemClock_Config函數(shù)里你會(huì)看到類(lèi)似這樣的初始化代碼static void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct {0}; __HAL_RCC_TCM_CLK_ENABLE(); // 使能TCM時(shí)鐘 RCC_OscInitStruct.OscillatorType RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState RCC_PLL_ON; ... }關(guān)鍵就是這行__HAL_RCC_TCM_CLK_ENABLE()沒(méi)有它TCM就是一坨不可訪(fǎng)問(wèn)的地址。如果你是手動(dòng)寫(xiě)寄存器對(duì)應(yīng)的操作是操作RCC的時(shí)鐘使能寄存器具體位要看參考手冊(cè)的RCC章節(jié)。這里有個(gè)操作心得要分享不要完全信任CubeMX的圖形界面生成代碼之后一定要搜一下整個(gè)工程里有沒(méi)有TCM_CLK_ENABLE或類(lèi)似的關(guān)鍵字確認(rèn)它真的在SystemClock_Config里被調(diào)用了。我遇到過(guò)一次CubeMX里勾選上了但生成的代碼因?yàn)槟撤N原因沒(méi)包含這行初始化導(dǎo)致問(wèn)題反復(fù)出現(xiàn)。4.2 第二步修正MPU配置避免Region優(yōu)先級(jí)沖突MPU配置這塊我建議直接在代碼里手動(dòng)寫(xiě)不要完全依賴(lài)CubeMX的MPU配置界面因?yàn)镹6的MPU支持TrustZone界面配置的靈活度有限而且很容易出現(xiàn)我遇到的Region優(yōu)先級(jí)問(wèn)題。核心配置邏輯如下void MPU_Config(void) { MPU_Region_InitTypeDef MPU_InitStruct {0}; HAL_MPU_Disable(); // 配置DTCM區(qū)域地址0x20000000大小512KB具體大小看芯片型號(hào) MPU_InitStruct.Enable MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress 0x20000000; MPU_InitStruct.Size MPU_REGION_SIZE_512KB; MPU_InitStruct.AccessPermission MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable MPU_REGION_NOT_BUFFERABLE; MPU_InitStruct.IsCacheable MPU_REGION_CACHEABLE; MPU_InitStruct.IsShareable MPU_REGION_NOT_SHAREABLE; MPU_InitStruct.Number MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable 0x00; MPU_InitStruct.DisableExec MPU_INSTRUCTION_ACCESS_DISABLE; // XN位置1 MPU_InitStruct.IsSecure MPU_REGION_SECURE; // 安全區(qū)屬性 HAL_MPU_ConfigRegion(MPU_InitStruct); HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); }重點(diǎn)看兩個(gè)參數(shù)MPU_REGION_NUMBER0和MPU_INSTRUCTION_ACCESS_DISABLE。MPU_REGION_NUMBER0意味著這個(gè)Region的優(yōu)先級(jí)最高不會(huì)再被其他Region覆蓋MPU_INSTRUCTION_ACCESS_DISABLE就是XN位置1禁止從DTCM取指令。至于其他Region比如Flash、SRAM、外設(shè)區(qū)域建議按實(shí)際需求配置但優(yōu)先級(jí)要錯(cuò)開(kāi)不要出現(xiàn)兩個(gè)Region優(yōu)先級(jí)相同且地址范圍重疊的情況。這個(gè)坑特別隱蔽因?yàn)镸PU的查找邏輯是逐項(xiàng)匹配的優(yōu)先級(jí)相同的情況下最終生效哪個(gè)Region和芯片內(nèi)部的硬件實(shí)現(xiàn)有關(guān)不同型號(hào)可能不一樣最穩(wěn)妥的方法就是不要制造這種歧義。4.3 第三步確認(rèn)中斷向量表的配置在啟用了TrustZone安全/非安全功能之后N6的中斷向量表有兩種處理方式一種是使用默認(rèn)的向量表放在Flash起始地址SCB-VTOR保持默認(rèn)值。另一種是使用非安全向量表通過(guò)SCB-VTOR設(shè)置到非安全區(qū)域。我的需求是讓所有的中斷都在非安全狀態(tài)下處理所以我把非安全中斷向量表放在了一個(gè)固定的RAM地址并在安全狀態(tài)下完成了初始化然后把控制權(quán)交給非安全狀態(tài)。這一點(diǎn)如果你不用TrustZone可能根本遇不到但只要啟用了就一定要在啟動(dòng)代碼里檢查。N6的參考手冊(cè)里有個(gè)專(zhuān)門(mén)的章節(jié)講“Exception entry behavior in Security state”簡(jiǎn)單說(shuō)就是如果當(dāng)前是安全狀態(tài)中斷向量從安全向量表取如果是非安全狀態(tài)中斷向量從非安全向量表取。這兩個(gè)向量表分別有獨(dú)立的VTOR設(shè)置SCB-VTOR和NS_Disaster-VTOR其中NS_Disaster是非安全別名寄存器區(qū)域。我最終的做法是保持安全向量表在Flash起始地址同時(shí)把非安全向量表也復(fù)制到了非安全RAM區(qū)域并設(shè)置了對(duì)應(yīng)的VTOR。因?yàn)槲业腞TOS和所有應(yīng)用任務(wù)都在非安全區(qū)運(yùn)行這樣每個(gè)中斷都能正確地從非安全向量表里取到服務(wù)函數(shù)地址。這里有個(gè)提醒復(fù)制中斷向量表不是簡(jiǎn)單地把Flash里的數(shù)組拷到RAM里就行因?yàn)镹6的向量表里不僅有函數(shù)地址還包含了一些啟動(dòng)配置比如堆棧指針的初始值。如果你在運(yùn)行中重新設(shè)置VTOR一定要確保新的向量表里第一項(xiàng)仍然是正確的堆棧指針值否則第一次壓棧就會(huì)把棧指針搞壞那又是另一個(gè)Hard Fault。4.4 第四步利用調(diào)試器驗(yàn)證修復(fù)效果修復(fù)完成后不要急著跑應(yīng)用邏輯先做一輪針對(duì)性的驗(yàn)證。我的驗(yàn)證方法是在Hard Fault處理函數(shù)里保留調(diào)試斷點(diǎn)如果再有Fault發(fā)生能讓調(diào)試器停下來(lái)。寫(xiě)一個(gè)簡(jiǎn)單的壓力測(cè)試任務(wù)死循環(huán)往DTCM地址寫(xiě)數(shù)據(jù)同時(shí)在另一個(gè)任務(wù)里頻繁觸發(fā)SysTick和外部中斷。跑24小時(shí)觀(guān)察是否有Fault發(fā)生。如果一切正常再用調(diào)試器讀一次HFSR和CFSR寄存器確認(rèn)它們是0說(shuō)明Fault狀態(tài)已經(jīng)被清除了。另外N6的調(diào)試器支持cortex_m_debug插件如果你用OpenOCD的話(huà)可以在GDB里直接查看TCM區(qū)域的內(nèi)容這對(duì)我確認(rèn)DTCM的數(shù)據(jù)寫(xiě)入是否真正生效很有幫助。我在壓力測(cè)試時(shí)確實(shí)通過(guò)GDB看到了DTCM地址上的數(shù)據(jù)在持續(xù)變化說(shuō)明TCM訪(fǎng)問(wèn)恢復(fù)正常了。5. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄5.1 Hard Fault的相關(guān)狀態(tài)寄存器速查表很多新手遇到Hard Fault第一反應(yīng)是加打印信息但Fault發(fā)生的時(shí)候CPU已經(jīng)異常了打印信息往往不可靠。正確的做法是用調(diào)試器讀取狀態(tài)寄存器。我把常用的寄存器整理成了一張速查表方便大家對(duì)照寄存器關(guān)鍵位含義排查方向HFSRFORCED (bit30)是否有其他Fault升級(jí)為Hard Fault如果置1去看CFSRCFSRIBUSERR (bit8)指令總線(xiàn)錯(cuò)誤檢查取指地址是否可執(zhí)行CFSRPRECISERR (bit1)精確數(shù)據(jù)總線(xiàn)錯(cuò)誤檢查數(shù)據(jù)訪(fǎng)問(wèn)地址是否合法CFSRIACCVIOL (bit0)指令訪(fǎng)問(wèn)違例檢查MPU執(zhí)行權(quán)限配置BFAR-總線(xiàn)錯(cuò)誤發(fā)生時(shí)的地址看這個(gè)地址屬于哪個(gè)區(qū)域MMFAR-內(nèi)存管理錯(cuò)誤發(fā)生時(shí)的地址看這個(gè)地址是否在MPU允許范圍內(nèi)這個(gè)表看起來(lái)簡(jiǎn)單但實(shí)際排查的時(shí)候非常有用。我建議你Fault處理函數(shù)的第一行就寫(xiě)一個(gè)死循環(huán)等待調(diào)試器不要急著恢復(fù)執(zhí)行否則現(xiàn)場(chǎng)很容易被后續(xù)的中斷覆蓋。5.2 我踩過(guò)的三個(gè)典型坑第一個(gè)坑是CubeMX生成代碼不完整。N6在CubeMX里的支持還比較新某些配置選項(xiàng)勾選后不會(huì)立即在代碼里體現(xiàn)尤其是TCM時(shí)鐘這種“高級(jí)選項(xiàng)”。解決方法就是我在前面說(shuō)的生成代碼后全工程搜索關(guān)鍵寄存器操作確認(rèn)真的有初始化代碼。第二個(gè)坑是MPU Region優(yōu)先級(jí)沖突。這個(gè)坑最隱蔽因?yàn)榧幢隳闩渲缅e(cuò)了系統(tǒng)大多數(shù)時(shí)候還是正常的只有在特定條件下才出問(wèn)題表現(xiàn)出來(lái)就是“偶發(fā)性Hard Fault”。我建議你在啟用MPU后把所有Region的配置打印出來(lái)對(duì)照檢查優(yōu)先級(jí)和地址范圍確保沒(méi)有重疊且優(yōu)先級(jí)不沖突。第三個(gè)坑是結(jié)構(gòu)體對(duì)齊。這個(gè)坑和TCM沒(méi)有直接關(guān)系但我在做音頻數(shù)據(jù)處理時(shí)遇到了一并分享給大家。如果你在TCM里定義一個(gè)結(jié)構(gòu)體數(shù)組然后又通過(guò)指針強(qiáng)制轉(zhuǎn)換去訪(fǎng)問(wèn)很容易觸發(fā)對(duì)齊錯(cuò)誤。在Cortex-M55這種支持非對(duì)齊訪(fǎng)問(wèn)的核上對(duì)齊問(wèn)題不一定觸發(fā)Fault但會(huì)降低訪(fǎng)問(wèn)效率有時(shí)候還會(huì)因?yàn)榫幾g器的優(yōu)化行為導(dǎo)致意外。解決方法是聲明結(jié)構(gòu)體時(shí)加上__attribute__((aligned(4)))或__attribute__((packed))根據(jù)你的實(shí)際需求選擇。5.3 給新手的調(diào)試建議如果你剛接觸STM32N6我建議你先不要直接上完整系統(tǒng)而是按下面這個(gè)順序來(lái)先跑一個(gè)裸機(jī)點(diǎn)燈程序確認(rèn)芯片和調(diào)試環(huán)境正常。再跑一個(gè)GPIO中斷程序確認(rèn)中斷向量表正常。然后加上MPU配置確認(rèn)非安全區(qū)/安全區(qū)的功能正常。最后再上RTOS和應(yīng)用邏輯。每一步都用調(diào)試器驗(yàn)證不要跳躍。因?yàn)镹6的復(fù)雜度和F103完全不是一個(gè)量級(jí)跳過(guò)中間步驟直接上大系統(tǒng)出了問(wèn)題還真不好定位。我調(diào)TCM這個(gè)問(wèn)題時(shí)就是因?yàn)橹苯犹搅俗詈笠徊綄?dǎo)致很難判斷是時(shí)鐘問(wèn)題、MPU問(wèn)題、還是中斷向量表問(wèn)題走了不少?gòu)澛贰A硪粋€(gè)建議是把所有Fault相關(guān)的中斷都指向同一個(gè)函數(shù)在這個(gè)函數(shù)里讀寄存器并保存到全局變量這樣即使不上調(diào)試器你也可以通過(guò)串口把寄存器值發(fā)給上位機(jī)分析。雖然調(diào)試器更方便但不排除有些場(chǎng)景就是連不上調(diào)試器這時(shí)候這個(gè)備用方案就很重要了。我當(dāng)時(shí)是在串口中斷里加了個(gè)簡(jiǎn)單的輸出把HFSR、CFSR、BFAR的值發(fā)出來(lái)才真正確認(rèn)了問(wèn)題指向。6. 后續(xù)還能怎么擴(kuò)展這個(gè)問(wèn)題解決之后我又在N6上試了一些別的TCM用法順便分享一下。N6的ITCM和DTCM都是緊耦合內(nèi)存訪(fǎng)問(wèn)延遲極低放關(guān)鍵代碼和熱數(shù)據(jù)非常合適。但如果你的代碼量比較大ITCM空間不夠可以考慮只把最頻繁調(diào)用的那幾個(gè)中斷服務(wù)函數(shù)放到ITCM里其他代碼留在Flash中運(yùn)行。這樣既能享受ITCM的低延遲又不會(huì)因?yàn)榭臻g不足而遷就。放代碼到ITCM的操作很簡(jiǎn)單在GCC環(huán)境下用__attribute__((section(.itcm)))修飾即可。但要注意鏈接腳本里必須有對(duì)應(yīng)的段定義否則鏈接會(huì)報(bào)錯(cuò)。我是直接在鏈接腳本里新增了一個(gè).itcm段然后把中斷服務(wù)函數(shù)放進(jìn)去實(shí)測(cè)中斷響應(yīng)時(shí)間確實(shí)短了一些但幅度沒(méi)有想象中大因?yàn)檫@顆芯片的Flash加速器做得很不錯(cuò)除非你的中斷頻率特別高并且對(duì)延遲敏感否則沒(méi)有必要大動(dòng)干戈。如果你是在做音頻信號(hào)處理、圖像處理這些數(shù)據(jù)密集型應(yīng)用把緩沖區(qū)放到DTCM是非常合理的用法因?yàn)樗墓谋仍L(fǎng)問(wèn)外部SDRAM低延遲也更穩(wěn)定。但一定要記住做完MPU配置后要確認(rèn)緩沖區(qū)所在的地址空間沒(méi)有被錯(cuò)誤地設(shè)置成Cacheable且不可緩沖否則DMA和CPU之間的數(shù)據(jù)一致性可能會(huì)出問(wèn)題。我當(dāng)時(shí)還試過(guò)把RTOS的任務(wù)棧放到DTCM效果還不錯(cuò)。ThreadX在N6上跑的時(shí)候任務(wù)切換的上下文保存和恢復(fù)會(huì)頻繁訪(fǎng)問(wèn)棧放在DTCM里能明顯減少總線(xiàn)競(jìng)爭(zhēng)。不過(guò)N6的DTCM容量有限如果任務(wù)數(shù)量多棧就分不過(guò)來(lái)這需要你根據(jù)實(shí)際工程做取舍。我當(dāng)時(shí)的做法是給高優(yōu)先級(jí)任務(wù)分配DTCM棧普通任務(wù)繼續(xù)用SRAM這樣資源和性能都能兼顧。最后再說(shuō)一個(gè)細(xì)節(jié)N6的TCM訪(fǎng)問(wèn)問(wèn)題和D-Cache、I-Cache的狀態(tài)也有關(guān)系。如果你在啟動(dòng)階段使能了Cache然后又對(duì)TCM地址做了一些特殊操作可能因?yàn)镃ache預(yù)取導(dǎo)致意外的總線(xiàn)訪(fǎng)問(wèn)。我在調(diào)試過(guò)程中曾經(jīng)瞬間懷疑過(guò)自己的Cache配置后來(lái)核對(duì)下來(lái)發(fā)現(xiàn)不是Cache的問(wèn)題。但如果你也遇到類(lèi)似情況建議先關(guān)Cache跑一遍復(fù)現(xiàn)測(cè)試再開(kāi)Cache對(duì)比這樣能快速排除一個(gè)干擾項(xiàng)。問(wèn)題定位和解決的經(jīng)驗(yàn)就是這些了。TCM這個(gè)坑說(shuō)大不大但確實(shí)隱蔽如果對(duì)N6的時(shí)鐘域和MPU配置邏輯不熟很容易繞圈子。希望這篇能幫你少踩幾個(gè)坑。