UCosIII在Cortex-M3核單片機上IAP跳轉APP時引起HardFault錯誤原因分析

        最近變得好懶散,什麼也不想做,回家也是對着電腦發呆,我好像失去了什麼,但是我好像本來什麼也沒有啊。沒有擁有,又何談失去呢,看來是庸人自擾,也許發呆只是潛意識裏逃避進取的藉口罷了。非要說失去了什麼,可能就是光陰了,不管你是在發呆還是在拼命,時間從來不會停下前進的步伐。以此說來,在過去的歲月裏,不管你做了什麼,失去了什麼,獲得了什麼,都應無怨無悔,這本來就是你自己的選擇。發呆久了,人也變得遲鈍,敲幾行字清醒一下,不能秀逗了嘛。畢竟當你什麼也沒有的時候,還要在程序裏討得一碗殘羹冷炙,飢寒交迫中求得一線希望,黑暗中等待黎明。

        在項目中遇到MCU出現HardFault錯誤,查了好久,結合網上找到的零散的解決問題的方法,不斷驗證後解決了問題。在這裏寫成博客,當作學習筆記,也給遇到類似問題的程序猿提供解決問題的思路。

        一、問題描述

       項目中使用LPC1857單片機,這是一款NXP出的Cortex-M3內核MCU,我們做了一個bootload代碼,用於IAP升級。在bootloader代碼中我們使用了官方移植好的ucosIII系統,當檢測到APP應用程序格式正確時,執行跳轉功能,直接從IAP代碼跳到APP代碼中運行,但是跳轉後在運行APP代碼時MCU出現HardFault錯誤。

        二、解決思路

       1、HardFault錯誤原因

        既然MCU出現了HardFault錯誤,我們首先要搞清楚什麼原因引起的。在《CORTEX-M3權威指南》裏,對引起HardFault錯誤的可能原因進行了詳細的描述,這裏我們也轉述相關基礎知識點,便於大家理解。

        Cortex-M3內核提供四種異常:總線fault、存儲器管理fault、用法異常和硬fault。當前三種異常沒有使能時,它們的異常狀態會上報到硬fault,所以引起HardFault錯誤的原因有以上四種情況。每種異常中又有多個標誌位用於指示引起異常的具體原因,下面對每個標誌位做簡單的介紹。

       (1)總線Fault:在取址、數據讀/寫、取中斷向量、進入/退出中斷時寄存器堆棧操作(入棧/出棧)時檢測到內存訪問錯誤。      

可能的原因

STKERR

(自動)入棧期間出錯

1. 堆棧指針的值被破壞

2. 堆棧用量太大,到達了未定義存儲器的區域

3. PSP未經初始化就使用

UNSTKERR

(自動)出棧期間出錯。如果沒有發生過 STKERR,則最可能的就是在異常

處理期間把 SP的值破壞了

IMPRECISERR

與設備之間傳送數據的過程中發生總線錯誤。可能是因爲設備未經初始化而

引起;或者在用戶級訪問了特權級的設備,或者傳送的數據單位尺寸不能爲 設備所接受。此時,有可能是 LDM/STM指令造成了非精確總線 fault。

PRECISERR

在數據訪問期間的總線錯誤。通過 BFAR 可以獲取具體的地址。發生 fault

的原因同上。

IBUSERR

同 MemManage fault中的 IACCVIOL

        (2)存儲器管理Fault:檢測到內存訪問違反了MPU定義的區域。

可能的原因

MSTKERR

入棧時發生錯誤(異常響應序列開始時)

1)堆棧指針的值被破壞

2)堆棧容易過大,已經超出 MPU允許的 region範圍

MUNSTKERR

出棧時發生錯誤(異常響應序列終止時)。入棧時沒有發生錯誤,出棧時卻出

錯,總令人有些匪夷所思,可能的原因是

1. 異常服務例程破壞了堆棧指針

2. 異常服務例程更改了 MPU配置

DACCVIOL

內存訪問保護違例。這是 MPU發揮作用的體現。常常是用戶應用程序企圖訪

問特權級 region所致

IACCVIOL

1. 內存訪問保護違例。常常是用戶應用程序企圖訪問特權級 region。在這種情

況下,入棧的 PC給出的地址,就是產生問題的代碼之所在

2. 跳轉到不可執行指令的 regions

3. 異常返回時,使用了無效的 EXC_RETURN值

4. 向量表中有無效的向量。例如,異常在向量建立之前就發生了,或者加載的 是用於傳統 ARM內核的可執行映像

5. 在異常處理期間,入棧的 PC值被破壞了

        (3)用法Fault:檢測到未定義的指令異常,未對齊的多重加載/存儲內存訪問。如果使能相應控制位,還可以檢測出除數爲零以及其他未對齊的內存訪問。

可能的原因

DIVBYZERO

當 DIV_0_TRP置位時則發生了除數爲零的情況。引發此 fault的指令可以從入棧

的 PC讀取

UNALIGNED

當 UNALIGN_TRP 置位時發生未對齊訪問。引發此 fault 的指令可以從入棧的 PC讀取  

 


NOCP

企圖執行一個協處理器指令。引發此 fault的指令可以從入棧的 PC讀取

INVPC

1. 異常返回時使用了無效的 EXC_RETURN,例如

1) 當 EXC_RETURN=0xFFFF_FFF1時卻要返回線程模式

2) 當 EXC_RETURN=0xFFFF_FFF9時卻要返回 handler模式

2. 無效的異常活動狀態,例如

1) 當前異常的活動狀態已經清除了,卻在此時執行異常返回。往往是因爲濫 用 VECTCLRACTIVE或清除了 SHCSR中活動狀態所致

2) 在尚有異常的活動位置位時,卻要返回線程模式

3. 由於堆棧指針錯誤導致了 IPSR 的值不正確。對於 INVPC fault,入棧的 PC 指出了該 fault服務例程在何處搶佔了其它代碼。這個問題往往是由比較隱晦 的程序錯誤造成的,欲詳細調查該問題的原因,最好使用 ITM的跟蹤功能。

4. ICI/IT位對當前指令無效。當 LDM/STM指令被異常打斷後,在異常服務例程 中又更改了入棧的 PC。結果在中斷返回時,非零的 ICI 位段作用到了不使用 ICI位段的指令上。如果是其它原因破壞了 PSR的值,也可能導致此 fault。

INVSTATE

1. 加載到 PC 中的跳轉地址值是偶數(LSB=0)。通過檢查入棧 PC 的值,一下子

就可以查出該問題。

2. 向量地址的 LSB=0,診斷方法同上。

3. 入棧的 PSR在異常處理過程中被破壞,使得在返回時內核嘗試進入 ARM狀態。

UNDEFINSTR

1. 使用了 CM3不支持的指令

2. 代碼段中的數據被破壞

3. 連接時加載了 ARM目標碼。請檢查編譯階段的設置

4. 指令對齊的問題。例如,在使用GNU工具鏈時,忘記了在.ascii後使用.align, 就有可能導致下一條指令沒有對齊

        (4)硬Fault:如果上面的總線fault、存儲器管理Fault、用法Fault的處理程序不能被執行(例如禁能了總線Fault、存儲器管理Fault、用法Fault異常或者在這些異常處理程序執行過程中又出現了Fault)則觸發硬Fault。

可能的原因

DEBUGEVF

因調試事件導致的 fault

1. 斷點/觀察點事件

2. 在硬 fault服務例程的執行過程中,沒有使能監視器異常(MON_EN=0)也 沒有使能停機調試(C_DEBUGEN=0),卻執行了 BKPT指令。缺省時,有些 C編譯器可能會在半主機代碼中使用 BKPT指令。

FORCED

這是 fault“上訪”的情況

1. 試圖在 SVC/監視器服務例程中執行 SVC/BKPT,或者在其它擁有相同或更 高優先級的服務例程中執行 SVC/BKPT。

2. 發生了 fault,但是它的服務例程被除能

3. 發生了 fault,但是當前處理器在響應同級或更高優先級的異常

4. 發生了 fault,但是它被掩蔽了

VECTBL

取向量失敗,

1. 在取向量過程中發生總線 fault

2. 向量表偏移量設置有誤

        2、HardFault錯誤定位方法

        (1)在MDK中使用JTAG在線調試,出現HardFault異常後,可以查看引起錯誤的原因,定位是哪種錯誤引起的單片機異常,具體方法如下圖所示:


        在該項目中,我們查看的結果是總線fault中的STKERR引起的,也就是棧指針溢出引起的,至於程序中哪段代碼引起的,要通過下面的方法定位。

        (2)定位出錯的代碼行

        在線調試模式下,查看左側寄存器欄中Banked確定現在使用的是哪個堆棧,MSP或者是PSP,確定以後,在內存查看窗口,輸入堆棧的地址,以這個地址開始的8個32位數值,依次是R0,R1,R2,R3,R12,R14,R15,XPSR的數值,據此判定你的堆棧地址是不是對的(有時需要考慮堆棧的增長方向)。R14,R15的地址就是我們出錯的代碼所在的地址,需要在這個地址基礎上,首先偶數對齊,然後向上減去8個字節。需要考慮的是,在使用MSP的時候,有出錯的地方並不一定在R14,R15處,而是在XPSR往後的第二個地址處,在這個附近查找,排除故障。

        該項目中,最後定位出錯的代碼行是(MOVS R0,#0;MSR PSP,R0;),這兩句彙編語句是ucosiii啓動任務時調用的,該代碼在os_cpu_a.s文件中,意思使MCU的從棧指針PSP指向地址0。

        3、ucosIII對棧指針MSP和PSP的使用

        既然是ucosIII中使用PSP棧指針引起的HardFault,我們就要搞清楚ucosIII中對PSP的使用方法。這個要查看ucosIII的啓動代碼了,由於啓動代碼是用匯編寫的,逐行逐行的分析太累也太難理解,有興趣的可以自己網上找找資料。如果你想在底層開發有所突破,還是要認真看看這段彙編代碼的,大家都不會的你會了,你就是專家。這裏我們只看重點的地方。

        啓動文件是os_cpu_a.s,ucosIII啓動任務時,使用了(MOVS R0,#0;MSR PSP,R0;)使PSP指向地址0,目的是把PSP作爲首次進行任務切換的標誌位,如果PSP等於0表示第一次進行任務切換,這時不需要把R4-R11壓棧,因爲在這之前還沒有任務運行。如果不爲0,就要先執行R4-R11壓棧操作。除了這裏,還有一段代碼很重要(MSR     PSP, R0;ORR     LR, LR, #0x04),這兩句彙編,前一句使PSP指向當前任務的棧,後一句的意思是在PENDSV中斷中執行完任務切換後,恢復使用PSP作爲普通任務的棧指針,因爲CORTEX-M3在中斷中使用的是MSP棧指針。對於MSP和PSP的用法,請自己查找資料學習,這裏不再介紹。

        4、產生HardFault的原因

        從上面的分析,我們可以知道ucosIII啓動時先把PSP棧指針指向地址0,在PendSV中斷中使用MSP棧指針執行完任務切換後,恢復使用PSP棧指針作爲普通任務的棧指針。在bootloader代碼中ucosIII啓動前,MCU默認使用MSP棧指針作爲任務棧指針,這時使PSP指向地址0沒有任何問題,因爲MCU這時根本沒有使用PSP。ucosIII啓動後,任務的棧指針就被ucosIII切換成了PSP,這時我們在普通任務裏跳轉到APP代碼運行,那麼APP代碼運行時使用的棧指針還是PSP。APP代碼中也要運行ucosIII的啓動代碼,這樣PSP正在被MCU調用的時候突然被ucosIII的啓動代碼把值修改成了0,由於地址0超出了MCU的內存地址空間,所以MCU內核就會報STKERR異常,即棧指針溢出,進而產生HardFault異常。

        5、解決方案

        bootloader代碼中,在執行跳轉到APP之前,先把棧指針修改成MSP,這樣跳轉到APP之後,MCU使用的是MSP棧指針,那麼ucosIII就可以隨意修改PSP的值了。把MCU使用的棧指針PSP修改成MSP的代碼是__set_CONTROL(0)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章