深入理解異常和中斷(Cortex-M3)

_huaj發表於2024-10-06
  • 中斷一般是由硬體(如外設和外部輸入引腳)產生的事件。

  • 異常一般指CPU內部產生的打斷。但是,也可以把中斷稱為一種異常

  • 我們一般稱為系統異常和外部中斷

中斷管理

STM32F10x_StdPeriph_Driver為例,介紹和中斷和異常相關的檔案。

  • stm32f10x.h:由ST提供,定義了各種外設暫存器基地址,各種外設初始化的結構體。

  • system_stm32f10x.c:由ST提供,提供如SystemInit的系統時鐘初始化函式,由啟動檔案去呼叫。

  • system_stm32f10x.h:ST提供。

  • misc.c:由ST提供,是標準庫的檔案,提供了操作NVIC和Systick的函式,如NVIC的初始化。

  • core_cm3.c:由ARM公司提供,包含核心設定的彙編函式。

  • core_cm3.h:由ARM公司提供,包含了核心的一些暫存器基地址定義,暫存器位的設定,NVIC的訪問函式。

Cortex-M處理器具有多個用於中斷和異常管理的可程式設計暫存器,這些暫存器多數位於NVIC和系統控制塊(SCB)中。實際上,SCB是作為NVIC的一部分實現的,不過CMSIS-Core將其暫存器定義在了單獨的結構體中。

NVIC和SCB位於系統控制空間(SCS),地址從0xE000E000開始,大小為4KB。SCS中還有SysTick定時器、儲存器保護單元(MPU)以及用於除錯的暫存器等。該地址區域中基本上所有的暫存器都只能由執行在特權訪問等級的程式碼訪問。唯一的例外為軟體觸發中斷暫存器(STIR),它可被設定為非特權模式訪問。

image-20241004164453755

中斷優先順序

對於Cortex-M處理器(包括ARMv6-M和ARMv7-M)異常是否能被處理器接受以及何時被處理器接受並執行異常處理,是由異常的優先順序和處理器當前的優先順序決定的。更高優先順序的異常(優先順序編號更小)可以搶佔低優先順序的異常(優先順序編號更大),這就是異常/中斷巢狀的情形。有些異常(復位、NMI和HardFault)具有固定的優先順序,其優先順序由負數表示,

  • Cortex-M3和Cortex-M4處理器在設計上具有3個固定的最高優先順序以及256個可程式設計優先順序(具有最多128個搶佔等級,可程式設計優先順序的實際數量由晶片設計商決定

  • 中斷優先順序由優先順序暫存器控制,寬度為3~8位。優先順序的減少是透過去除優先順序配置暫存器的最低位(LSB)實現的。對於未實現的位,讀出總是為0。

  • STM32F1的中斷優先順序暫存器是4位。

優先順序分組

利用系統控制塊(SCB)中一個名為優先順序分組的配置暫存器,每個具有可程式設計優先順序的優先順序配置暫存器可被分為兩部分。上半部分(左邊的位)為搶佔優先順序,而下半部分則為子優先順序響應優先順序)。

  • 搶佔優先順序決定執行一箇中斷處理時能否產生另外一箇中斷。子優先順序(響應優先順序)只會用在具有兩個相同分組優先順序的異常同時產生的情形,此時,具有更高子優先順序(數值更小)的異常會被首先處理。
  • 若兩個中斷同時被確認,且它們具有相同的分組/搶佔優先順序和子優先順序,則異常編號更小的中斷的優先順序更高(IRQ#0的優先順序高於IRQ#1的)。

向量表重定位

向量表:當Cortex-M處理器接受了某異常請求後,處理器需要確定該異常處理(若為中斷則是ISR)的起始地址。該資訊位於儲存器內的向量表中,向量表預設從地址0開始,向量地址則為異常編號乘4,如圖所示。向量表一般被定義在微控制器供應商提供的啟動程式碼中。

image-20241004212546232

向量表重定位特性提供了一個名為SCB->向量表偏移暫存器(VTOR)的可程式設計暫存器。該暫存器將正在使用的儲存器的起始地址定義為向量表。

在使用VTOR時,需要將向量表大小擴充套件為下一個2的整數次方,且新向量表的基地址必須要對齊到這個數值。

應用向量表重定位的情形:

  1. 使用bootloader時,需要將中斷向量表定位到app中。
  2. 應用程式從外部裝置載入到RAM中執行,需要修夠重定位到新的向量表
  3. 動態修改。ROM中可能會有一箇中斷的多個處理例項,在應用的不同階段之間進行切換。

中斷輸入和掛起

每個中斷都有多個屬性:

  • 每個中斷都可被禁止(預設)使能
  • 每個中斷都可被掛起(等待服務的請求)或解除掛起
  • 每個中斷都可處於活躍(正在處理)非活躍狀態
  • 掛起狀態的意思是,中斷被置於一種等待處理器處理的狀態。

    • 中斷的掛起狀態被儲存在NVIC的可程式設計暫存器中,當NVIC的中斷輸人被確認後,它就會引發該中斷的掛起狀態。即便中斷請求被取消,掛起狀態仍會為高。這樣,NVIC可以處理脈衝中斷請求。
  • 當中斷正被處理時,它就會處於活躍狀態。注意在中斷人口處,多個暫存器會被自動壓入棧中,這也被稱作壓棧。同時,ISR的起始地址會被從向量表中取出。

    • 在中斷服務完成後,處理器會執行異常返回,之前自動壓棧的暫存器會被恢復出來,而且被中斷的程式也會繼續執行。中斷的活躍狀態會被自動清除。

image-20241005140737142

異常處理流程

異常進入流程

  1. 多個暫存器和返回地址被壓入當前使用的棧。這樣就可以將異常處理用普通C函式
    實現。若處理器處於執行緒模式且正使用程序棧指標(PSP),則PSP指向的棧區域就會用於該壓棧過程,否則就會使用主棧指標(MSP)指向的棧區域。
  2. 取出異常向量(異常處理/ISR的起始地址)。為了減少等待時間,這一步可能會和壓
    棧操作並行執行。
  3. 取出待執行異常處理的指令。在確定了異常處理的起始地址後,指令就會被取出。
  4. 更新多個NVIC暫存器和核心暫存器,其中包括掛起狀態和異常的活躍狀態,處理器
    核心中的暫存器包括程式狀態暫存器(PSR)、連結暫存器(LR)、程式計數器(PC)以及棧指標(SP).
    根據壓棧時實際使用的棧,在異常處理開始前,MSP或PSP的數值會相應地被自動調整。PC也會被更新為異常處理的起始地址,而連結暫存器(LR)則會被更新為名為EXC_RETURN的特殊值。該數值為32位,且高27位為1。低5位中有些部分用於儲存異常流程的狀態資訊(如壓棧時使用的哪個棧)。該數值用於異常返回。

執行異常處理

  • 棧操作使用主棧指標(MSP)

  • 處理器執行在特權訪問等級

如更高優先順序的異常產生,則會搶佔當前的異常處理,這是異常巢狀。當相同或更低優先順序的異常產生,則處於掛起狀態,等待當前處理完成後才會得到處理。

在異常處理的結尾,程式程式碼執行的返回會引起EXC_RETURN數值被載入到程式計數器中(PC),並觸發異常返回機制。

異常返回

由於使用了EXC_RETURN數值觸發異常返回,異常處理(包括中斷服務程式)就可以和
普通的C函式/子例程一樣實現。在生成程式碼時,C編譯器將LR中的EXC_RETURN數值
作為普通返回地址處理。由於EXC_RETURN機制,函式一般不會返回到地址0xFO000000~0xFFFFFFFF。

EXC_RETURN

處理器進入異常處理或中斷服務程式(ISR)時,連結暫存器(LR)的數值會被更新為EXC_RETURN數值。當利用BX、POP或儲存器載入指令(LDR或LDM)被載入到程式暫存器中時,該數值用於觸發異常返回機制。EXC_RETURN中的一些位用於提高異常流程的其他資訊。

思考:為什麼異常處理可以用C語言函式實現?

對於ARM Cortex-M處理器,異常返回機制由一個特殊的地址EXC_RETURN觸發,該數值在異常入口處產生且被儲存在連結暫存器(LR)中。當該數值由某個允許的異常返回指令寫入PC時,它就會觸發異常返回流程。

EXC_RETURN機制的設計允許程式設計師像編寫普通函式一樣編寫中斷服務程式,這是因為它提供了一種從中斷返回的標準化方法,使得中斷服務程式(ISR)的編寫和呼叫流程與普通函式的呼叫和返回流程非常相似。

  1. 自動化的上下文儲存和恢復:當異常發生時,Cortex-M3處理器會自動將必要的暫存器(如R0-R3、R12、LR、PC以及程式狀態暫存器)儲存到棧中。異常處理完畢後,這些暫存器的值會自動從棧中恢復,這使得程式設計師可以使用C語言編寫異常處理程式而不需要手動管理暫存器的儲存和恢復。
  2. EXC_RETURN機制:在進入異常處理函式之前,連結暫存器(LR)會被賦予一個特殊的值EXC_RETURN。當中斷或異常處理程式執行完畢後,處理器會將LR中的EXC_RETURN值載入到程式計數器(PC)中,觸發異常返回序列。這個序列會根據EXC_RETURN的值來恢復之前儲存在堆疊中的暫存器值,包括PC、LR以及其他必要暫存器,從而允許異常處理程式像普通C函式一樣返回。
  3. 程式設計模型:Cortex-M3處理器的程式設計模型允許在C語言中使用一組標準的暫存器和呼叫約定,這使得C語言編寫的函式能夠遵循與組合語言相同的約定,從而在異常處理中使用C語言成為可能。
  4. 處理器和編譯器的支援:Cortex-M3處理器的架構和編譯器的支援使得C語言編寫的異常處理程式可以得到高效的執行。編譯器能夠生成符合Cortex-M3架構要求的目的碼,包括自動的暫存器儲存和恢復指令。

中斷控制用的NVIC暫存器

  • 中斷設定使能暫存器:ISER,寫1設定使能
  • 中斷清除使能暫存器:ICER,寫1清除使能
  • 中斷設定掛起暫存器:ISPR,寫1設定掛起狀態
  • 中斷清除掛起暫存器:ICPR,寫1清除掛起狀態
  • 中斷活躍位暫存器:IABR,活躍狀態位,只讀
  • 中斷優先順序暫存器:IP,每個中斷都有一箇中斷優先順序暫存器
  • 軟體觸發中斷暫存器:STIP,寫中斷編號設定相應的掛起狀態

除了軟體觸發中斷暫存器(STIR)外,所有這些暫存器都只能在特權等級訪問。STIR預設只能在特權等級訪問,不過可以配置為非特權等級訪問。

用於異常或中斷遮蔽的特殊暫存器

PRIMASK

在許多應用中,可能都需要暫時禁止所有中斷以執行一些時序關鍵的任務,此時可以使用
PRIMASK暫存器。PRIMASK暫存器只能在特權狀態訪問。PRIMASK用於禁止除NMI和HardFault外的所有異常,它實際上是將當前優先順序改為0(最高的可程式設計等級)。

FAULTMASK

從行為來說,FAULTMASK和PRIMASK很類似,只是它實際上會將當前優先順序修改為-1,這樣甚至是HardFault處理也會被遮蔽。當FAULTMASK置位時,只有NMI異常處理才能執行。

BASEPRI

有些情況下,可能只想禁止優先順序低於某特定等級的中斷,此時,就可以使用BASEPRI暫存器。要實現這個目的,只需簡單地將所需的遮蔽優先順序寫入BASEPRI暫存器。例如,若要屏敲優先順序小於等於0x60的所有異常,則可以將這個數值寫入BASEPRI。

BASEPRI無法在非特權狀態設定。

設定中斷的步驟

  1. 設定優先順序分組。優先順序分組預設為0(優先順序暫存器中只有第0位用於子優先順序),這一步是可選的。
  2. 設定中斷的優先順序。中斷的優先順序預設為0(最高的可程式設計優先順序),這一步也是可選的。
  3. 在NVIC或外設中使能中斷

若存在大量的巢狀中斷,除了使能中斷外,還應該確保棧空間足夠。由於在處理模式中,中斷處理總是使用主棧指標(MSP),主棧應該有足夠應對最壞情況的棧空間(最大數量的巢狀中斷/異常)。計算棧空間時應該考慮中斷處理使用的棧以及每級棧幀使用的棧。

軟體中斷

可以利用軟體程式碼觸發異常或中斷,之所以要這麼做,最常見的原因為,允許多工環境中處於非特權狀態的應用任務,可以訪問一些需要在特權狀態下才能執行的系統服務。根據要觸發的異常或中斷的型別,應該使用不同的方法。

  • 若要觸發一箇中斷(異常型別16或之上),最簡單的方法為使用CMSIS-Core函式NVIC_SetPendingIRQ:

  • 若要觸發SVC異常,則需要執行SVC指令。

思考:1.軟中斷有什麼用處?

軟中斷(Software Interrupt,也稱為SWI)通常用於作業系統中的系統呼叫(system call)。它允許使用者態的程式請求核心態的服務。例如,在嵌入式系統中,軟中斷可以用於請求作業系統的服務,如檔案操作、記憶體分配或其他需要核心許可權的操作。此外,軟中斷也可以用於除錯目的,比如在某些系統中,它們可以用來觸發看門狗定時器或其他診斷功能。

思考:2.除了除錯過程中可以使用軟中斷,還有什麼時候可以使用軟中斷?

  • 系統呼叫:在作業系統中,軟中斷常用於處理系統呼叫,使得使用者態程式能夠請求核心態的服務。
  • 任務排程:在實時作業系統中,軟中斷可以用於任務排程,允許任務之間進行上下文切換。
  • 觸發看門狗定時器:在某些系統中,軟中斷可以用於定期觸發看門狗定時器,防止系統因為死鎖而停止響應。
  • 記憶體管理:在某些嵌入式系統中,軟中斷可以用於記憶體管理操作,如動態記憶體分配和釋放。
  • 裝置驅動:軟中斷可以用於裝置驅動程式中,以便在特定條件下通知作業系統。
  • 異常處理:在某些情況下,軟中斷可以用於處理特定的異常情況,比如硬體故障或系統錯誤。

軟中斷提供了一種靈活的方式來處理各種系統級任務,而不需要直接干預硬體中斷,這有助於提高系統的可維護性和可擴充套件性。

深入瞭解異常處理

對於Cortex-M處理器,可以將異常處理或中斷服務程式(ISR)實現為普通的C程式/函式

用於ARM架構的C編譯器遵循ARM的一個名為AAPCS(ARM架構過程呼叫標準,參考文獻13)的規範。根據這份標準,C函式可以修改R0~R3、R12、R14(LR)以及PSR。若C函式需要使用R4~R11,就應該將這些暫存器儲存到棧空間中,並且在函式結束前將它們恢復。

R0~R3、R12、LR以及PSR被稱作“呼叫者儲存暫存器”,若在函式呼叫後還需要使用這些暫存器的數值,在進行呼叫前,呼叫子程式的程式程式碼需要將這些暫存器的內容儲存到記憶體中(如棧)。這些由處理器硬體完成

R4~R11為“被呼叫者儲存暫存器”,被呼叫的子程式或函式需要確保這些暫存器在函式結束時不會發生變化(與進入函式時的數值一樣)。這些暫存器的數值可能會在函式執行過程中變化,不過需要在函式退出前將它們恢復為初始值這些需要手動去儲存

棧幀

在異常入口處被壓入棧空間的資料塊為棧幀。對於Cortex-M3或不具有浮點單元的Cortex-M4處理器,棧幀都是8個字大小的,對於具有浮點單元的Cortex-M4,棧幀則可能是8或26個字。

AAPCS的另外一個要求為,棧指標的數值在函式入口和出口處應該是雙字對齊的。因此,若在中斷產生時棧幀未對齊到雙字地址上,Cortex-M3和Cortex-M4處理器會自動插人一個字。這樣,可以保證棧指標位於異常處理的開始處。“雙字棧對齊”特性是可程式設計的,若異常未完全符合AAPCS,則可以將該特性關閉。

image-20241006164016151

中斷等待

中斷等待表示從中斷請求開始到中斷處理開始執行間的時間。對於Cortex-M3和Cortex-M4處理器,若中斷系統為零等待的,而且假定系統設計允許取向量和壓棧同時執行,則中斷等待為12個週期,其中包括暫存器壓棧、取向量以及取中斷處理的指令。

除了儲存器裝置或外設產生的等待狀態外,其他情況也可能會加大中斷等待時間:

  • 處理器正處理另外一個相同或更高優先順序的異常。
  • 偵錯程式訪問儲存器系統。
  • 處理器正執行非對齊傳輸。從處理器的角度來看,它可能是單次傳輸,不過由於匯流排介面需要將非對齊傳輸轉換為多個對齊傳輸,從匯流排等級來看它可能會佔用幾個週期。

末尾連鎖(中斷咬尾)

若某個異常產生時處理器正在處理另一個具有相同或更高優先順序的異常,該異常就會進入掛起狀態。在處理器執行完當前的異常處理後,它可以繼續執行掛起的異常/中斷請求。處理器不會從棧中恢復暫存器(出棧)然後再將它們存入棧中(壓棧),而是跳過出棧和壓棧過程並會盡快進入掛起異常的異常處理。這樣,兩個異常處理間隔的時間就會降低很多。對於無等待狀態的儲存器系統,末尾連鎖的中斷等待時間僅為6個時鐘週期

image-20241006165356969

丟中斷的情況1:中斷請求產生於中斷懸起過程

  1. 有一箇中斷請求產生,NVIC響應並將中斷懸起,此時並未進入中斷處理。
  2. 在懸起期間,又產生相同的中斷請求,那麼中斷一直懸起並進入中斷處理,而產生相同的中斷請求則會被忽略。

image-20241006170101827

丟中斷的情況2:中斷請求產生於中斷處理過程

  1. 有一箇中斷請求產生,NVIC響應並將中斷懸起並進入中斷處理懸起位解除
  2. 進入中斷,還未將中斷標誌位清除(如外設上某個中斷的標誌位),這時,再次發生依次中斷請求,產生中斷標誌位,然後中斷處理中將這個標誌位清除。
  3. 則兩個中斷標誌位都被清除,第二個中斷進入中斷處理無法判斷是產生何種中斷型別

image-20241006171524707

思考:1.什麼是丟中斷?為什麼會丟中斷?

丟中斷是指系統面對連續相同的中斷請求中只響應了一個,或在中斷過程再次產生中斷,而清除兩個的標誌位,導致中斷處理無法判斷型別而丟。

為什麼會丟?主要還是中斷請求產生的時機。

思考:2.中斷的懸起狀態是什麼時候被恢復的?

懸起狀態直到進入中斷處理才被恢復。

思考:3.為什麼中斷要快進快出?

中斷處理程式需要“快進快出”(即快速進入和快速退出)的原因主要有以下幾點:

  1. 最小化中斷延遲
  • 實時性要求:在許多嵌入式系統和實時作業系統中,對響應時間有嚴格的要求。中斷處理程式必須儘快完成,以確保系統的實時效能。
  • 減少主程式的阻塞時間:中斷處理程式執行時間越長,主程式被中斷的時間就越長,這可能導致主程式的執行受到影響,甚至錯過其他重要的事件。
  1. 保持系統穩定性
  • 避免堆疊溢位:長時間執行的中斷處理程式可能會消耗大量堆疊空間,特別是在嵌入式系統中堆疊資源有限的情況下,容易導致堆疊溢位。
  • 防止資料丟失:如果中斷處理程式執行時間過長,可能會錯過新的中斷請求,導致資料丟失或系統狀態不一致。
  1. 提高系統吞吐量
  • 高頻率中斷:在某些應用中,如高速通訊或感測器資料採集,中斷可能頻繁發生。如果每個中斷處理程式都執行很長時間,系統將無法處理所有中斷請求,從而降低整體吞吐量。
  • 多工排程:在多工系統中,中斷處理程式需要儘快返回,以便作業系統能夠及時排程其他任務,保持系統的高效執行。
  1. 簡化設計
  • 明確職責:中斷處理程式的主要職責是快速響應中斷並儲存必要的上下文資訊。複雜的處理邏輯可以放在主程式或其他後臺任務中進行,這樣可以使設計更加清晰和易於維護。
  • 減少錯誤:長時間執行的中斷處理程式更容易引入錯誤,因為它們可能涉及更多的狀態管理和複雜邏輯。透過將複雜邏輯移出中斷處理程式,可以減少潛在的錯誤源。

思考:4.要滿足中斷的快進快出,又想在中斷產生後向執行緒傳送通知,怎麼才能快速通知?

在中斷處理程式中,快速通知執行緒通常需要使用高效且低延遲的機制。以下是一些常見的方法,可以幫助你在中斷產生後快速通知執行緒:

  1. 標誌位(Flag)

使用一個全域性標誌位來通知執行緒有新的中斷事件發生。這是最簡單的方法,但需要注意標誌位的原子操作。

  1. 訊號量(Semaphore)

使用訊號量可以更安全地進行執行緒同步。訊號量提供了一種原子操作的方式,確保執行緒不會錯過任何中斷事件。

  1. 事件佇列(Event Queue)

使用事件佇列可以將多箇中斷事件儲存在一個佇列中,並由執行緒定期處理這些事件。這適用於需要處理多箇中斷事件的情況。

相關文章