1. 程式人生 > >STM32F10xxx_異常與中斷

STM32F10xxx_異常與中斷

STM32F10xxx_異常與中斷

[TOC]

更新記錄

version status description date author
V1.0 C Create Document 2018.10.27 John Wan

status: C―― Create, A—— Add, M—— Modify, D—— Delete。


1、異常與中斷的概念

  對於幾乎所有的微控制器,中斷都是一種常見的特性。中斷一般是由硬體(如外設和外部輸入引腳)產生的時間,它會引起程式偏離正常的流程(如給外設提供服務),所有的Cortex-M處理器都會提供一個專門用於中斷處理的巢狀向量中斷控制器(NVIC)

。那麼除了中斷請求以外,還有其他需要服務的事件,將其稱為"異常"。在ARM的說法中,中斷也是一種異常。除了NVIC系統控制塊(SCB)暫存器中也包含了一些常用與系統異常的暫存器。

典型微控制器中的各種中斷源

圖1 典型微控制器中的各種中斷源《ARM Cortex-M3與Cortex-M4權威指南CnR3》P157

注:後續的用詞,中斷:即表示外設中斷;系統異常:編號從1~15的各種異常;異常:指系統異常與中斷。

2、異常的流程

graph TD A(接收請求:處理器確認外設的中斷請求)-->B B(保護現場:處理器暫停當前執行的任務)-->C C(中斷處理:處理器執行外設的中斷服務程式ISR)-->D D(還原現場:處理器繼續執行之前暫停的任務)

2.1 中斷請求的來源

  • 系統異常
  • 外設中斷

  Cortex-M處理器的異常架構具有多種特性,支援多個系統異常和外部中斷。編號1~15的為系統異常,16及以上的則為中斷輸入。包括所有中斷在內的多數異常,都具有可程式設計的優先順序,一些系統異常則具有固定的優先順序。不同的微控制器的中斷編號、優先順序都可能不同,具體的中斷編號由晶片商根據需求進行配置,查閱對應的指導手冊。

系統異常列表

圖2 系統異常列表《ARM Cortex-M3與Cortex-M4權威指南CnR3P158》

中斷列表

圖3 中斷列表《ARM Cortex-M3與Cortex-M4權威指南CnR3》P159

  而CMSIS-Core定義的中斷標識是用列舉實現,從數值0開始(代表中斷#0)表示中斷編號,系統異常的編號為負數。之所以CMSIS-Core使用另外一條編號系統,是因為這樣可稍微提高部分API函式的效率(例如設定優先順序)。

2.2 處理器接受中斷請求的條件

  • 處理器正在執行(未被暫停或處於復位狀態)
  • 異常處於使能狀態(NMI和HardFault為特殊情況,他們總是使能的)
  • 異常的優先順序高於當前等級
  • 異常未被異常遮蔽暫存器遮蔽

2.2.1 異常的使能狀態

  1. 中斷的使能

  由NVIC暫存器中的中斷設定使能暫存器(ISER)中斷清除使能暫存器(ICER)控制,可進行字、半字或位元組進行訪問。需要說明的是ISER/ICER暫存器都是32位寬,每個位代表一箇中斷輸入,如果存在32個以上的外部中斷,則ISER/ICER暫存器可能不止一個。需要強調的是該暫存器只進行中斷的設定,即異常編號為16開始的外部中斷#0,不包括前16個異常型別的系統異常。

  1. 系統異常的使能

  由SCB暫存器中的系統處理控制和狀態暫存器(SHCSR)控制,其中只能設定異常編號為4、5、6的儲存管理錯誤、匯流排錯誤、使用錯誤三種系統異常的使能

2.2.2 異常的優先順序

  每個異常都有一個優先順序,其中絕大部分的優先等級可編輯,程式設計範圍為0~255,極少部分的系統異常的優先等級是固定的且優先順序為負數,這樣可方便處理器進行判斷是否應該接受異常以及何時接受並執行異常處理,大致分為兩種決定的場景:

  • 正常的流程被中斷
  • 中斷的流程被中斷(中斷的巢狀)
  1. 優先順序的定義

  只有更高優先順序的異常(優先順序編號更小)可以搶佔低優先順序的異常(優先順序編號更大)Cortex-M3處理器在設計上具有3個固定的最高優先順序以及256個可程式設計優先順序(最多具有128個搶佔等級),可程式設計優先順序的實際數量由晶片設計商決定,多數使用Cortex-M3的晶片,支援的優先順序較少,這樣可降低NVIC的複雜度,降低功耗且增加速度。

  在Cortex-M3處理器中每個異常都存在一個8bit的優先順序暫存器來設定其中斷優先順序等級(例如後面講的NVIC->IP[n]),而該8bit的優先順序暫存器進一步分為兩個部分:搶佔優先順序與子優先順序,可利用系統控制塊SCB中的應用中斷和復位控制暫存器AIRCR的BIT[10:8](見圖4)來配置優先順序的分組共23=8個分組(見圖5),而這裡的分組就決定了8bit的優先順序暫存器中搶佔優先順序佔幾位,子優先順序佔幾位:

SCB_AIRCR優先順序分組

圖4 SCB_AIRCR優先順序分組《ARM Cortex-M3與Cortex-M4權威指南CnR3》P180

Cortex-M3優先順序分組定義

圖5 Cortex-M優先順序分組定義《ARM Cortex-M3與Cortex-M4權威指南CnR3》P162

  上面是Cortex-M3的最大允許設定範圍,前面提到,可程式設計優先順序的實際數量根據晶片商的各種需求可進行裁剪設計,通過晶片商的手冊可以查詢,例如在STM32F10xxx中就限制了只能使用8bit的優先順序暫存器高四位BIT[7:4],稱其有效寬度,那麼優先順序分組的方式就只有5個分組,在這5個分組中,搶佔優先順序最多隻能佔4bit,即搶佔優先順序等級最多隻能設定24 = 16個,見下圖。1

STM32F10xxx的優先順序分組配置

圖6 STM32F10xxx的優先順序分組配置《STM32F10XXX_programming_mannual_PM0056_ENV6》P135

  在處理器已經在執行一箇中斷處理時能否產生另外一箇中斷,是由該中斷的搶佔優先順序決定的。子優先順序只會用在具有兩個相同分組優先順序的異常同時產生的情形,此時,具有更高子優先順序(數值更小)的異常會被首先處理。

問題:
(1)為什麼前面提到Cortex-M3有256個可程式設計優先順序,而最多隻具備128個搶佔優先順序?
如圖5,優先順序分組時總會配置一種情況,即達到分組的最大寬度時,搶佔優先順序最多隻能分配7位,保留一位的子優先順序。

(2)為什麼8bit的優先順序暫存器的寬度設定是通過移除LSB用高位表示,而不是通過移除MSB用低位表示?
便於不同處理器之間進行移植,按照這種方式,在具有4位優先順序配置暫存器的裝置上寫的程式,就可能會在具有3位優先順序配置暫存器的裝置上執行。例如某應用程式,IRQ#0的優先順序為0x05,IRQ#1的優先順序為0x03,那麼移除了最高位bit2之後,則IRQ#0的優先順序會變為0x01,高於IRQ#1的優先順序。如果IRQ#0的優先順序為0x50,IRQ#1的優先順序為0x30,那麼移除最低位bit0之後,還是IRQ#0的優先順序較低。

  1. 中斷的優先順序

  每個中斷都有對應的優先順序暫存器,可根據在系統控制塊SCB應用中斷和復位控制暫存器AIRCR配置的優先順序分組所規定的搶佔優先順序範圍和子優先順序範圍來設定搶佔優先順序等級以及子優先順序等級。其優先順序分組有幾種情況由晶片商決定。該暫存器可通過位元組、半字和字訪問,優先順序暫存器的數量取決於晶片中實際存在的外部中斷數。可通過中斷優先順序暫存器NVIC->IP[n]來進行設定.

中斷優先順序暫存器

圖7 中斷優先順序暫存器《ARM Cortex-M3與Cortex-M4權威指南CnR3》P176
  1. 系統異常的優先順序

  關於可編輯的系統異常優先順序,原理同中斷的優先順序設定,其可通過系統處理優先順序暫存器 SCB->SHP[0~11]來進行設定。

系統處理優先順序暫存器

圖8 系統處理優先順序暫存器《ARM Cortex-M3與Corte-M4權威指南CnR3》P181
  1. CMSIS-Core設定異常優先順序
core_cm3.h

優先順序分組
static __INLINE void NVIC_SetPriorityGrouping(uint32_t PriorityGroup)   //設定優先順序分組
static __INLINE uint32_t NVIC_GetPriorityGrouping(void)                 //讀取優先順序分組

優先順序等級
static __INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)//設定異常的優先順序
static __INLINE uint32_t NVIC_GetPriority(IRQn_Type IRQn)               //讀取異常的優先順序
  1. STM32F10xxx庫檔案中設定異常優先順序
misc.h

/** @defgroup Preemption_Priority_Group 
  * @{
  */

#define NVIC_PriorityGroup_0         ((uint32_t)0x700) /*!< 0 bits for pre-emption priority
                                                            4 bits for subpriority */
#define NVIC_PriorityGroup_1         ((uint32_t)0x600) /*!< 1 bits for pre-emption priority
                                                            3 bits for subpriority */
#define NVIC_PriorityGroup_2         ((uint32_t)0x500) /*!< 2 bits for pre-emption priority
                                                            2 bits for subpriority */
#define NVIC_PriorityGroup_3         ((uint32_t)0x400) /*!< 3 bits for pre-emption priority
                                                            1 bits for subpriority */
#define NVIC_PriorityGroup_4         ((uint32_t)0x300) /*!< 4 bits for pre-emption priority
                                                            0 bits for subpriority */

#define IS_NVIC_PRIORITY_GROUP(GROUP) (((GROUP) == NVIC_PriorityGroup_0) || \
                                       ((GROUP) == NVIC_PriorityGroup_1) || \
                                       ((GROUP) == NVIC_PriorityGroup_2) || \
                                       ((GROUP) == NVIC_PriorityGroup_3) || \
                                       ((GROUP) == NVIC_PriorityGroup_4))

#define IS_NVIC_PREEMPTION_PRIORITY(PRIORITY)  ((PRIORITY) < 0x10)

#define IS_NVIC_SUB_PRIORITY(PRIORITY)  ((PRIORITY) < 0x10)

#define IS_NVIC_OFFSET(OFFSET)  ((OFFSET) < 0x000FFFFF)


misc.c

/** @defgroup MISC_Private_Defines
  * @{
  */

#define AIRCR_VECTKEY_MASK    ((uint32_t)0x05FA0000)


/**
  * @brief  Configures the priority grouping: pre-emption priority and subpriority.
  * @param  NVIC_PriorityGroup: specifies the priority grouping bits length. 
  *   This parameter can be one of the following values:
  *     @arg NVIC_PriorityGroup_0: 0 bits for pre-emption priority
  *                                4 bits for subpriority
  *     @arg NVIC_PriorityGroup_1: 1 bits for pre-emption priority
  *                                3 bits for subpriority
  *     @arg NVIC_PriorityGroup_2: 2 bits for pre-emption priority
  *                                2 bits for subpriority
  *     @arg NVIC_PriorityGroup_3: 3 bits for pre-emption priority
  *                                1 bits for subpriority
  *     @arg NVIC_PriorityGroup_4: 4 bits for pre-emption priority
  *                                0 bits for subpriority
  * @retval None
  */
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
  /* Check the parameters */
  assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
  
  /* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
  SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}
  1. 中斷的設定步驟

(1)設定優先順序分組,STM32F10XXX預設的優先順序分組是2位搶佔優先順序,2位子優先順序。(SCB->AIRCR的復位為0x05FA0000,如果不進行設定bit[10:8]為0x05)

(2)設定中斷的搶佔優先順序與子優先順序。

(3)在NVIC或外設中使能中斷

2.2.3 異常的遮蔽

用於異常遮蔽的特殊暫存器,包括PRIMASK、FAULTMASK、BASEPRI:

  1. PRIMASK

  在許多應用中,可能都需要暫時禁止所有中斷以執行一些時序關鍵的任務,此時可以使用PRIMASK暫存器。該暫存器只能在特權狀態下訪問。

  PRIMASK暫存器用於禁止除NMIHardFault外的所有異常。實際上它是將當前優先順序改為0(最高可程式設計等級),以不被其它異常所中斷

CMSIS-Core的core_cm3.c中提供以下函式進行查詢與設定:

/**
 * @brief  Return the Priority Mask value
 *
 * @return PriMask
 *
 * Return state of the priority mask bit from the priority mask register
 */
__ASM uint32_t __get_PRIMASK(void)
{
  mrs r0, primask
  bx lr
}

/**
 * @brief  Set the Priority Mask value
 *
 * @param  priMask  PriMask
 *
 * Set the priority mask bit in the priority mask register
 */
__ASM void __set_PRIMASK(uint32_t priMask)
{
  msr primask, r0
  bx lr
}


core_cm3.h中:

#define __enable_irq                __enable_interrupt        /*!< global Interrupt enable */
#define __disable_irq               __disable_interrupt       /*!< global Interrupt disable */

關於下面的core_cm3.h中可參考開關中斷

  1. FAULTMASK

  行為上與PRIMASK暫存器類似,只是它實際上會將當前優先順序修改為-1,這樣甚至是HardFlaut處理也會被遮蔽,則當FAULTMASK置位時,只有NMI異常處理才能執行

CMSIS-Core的core_cm3.c中提供以下函式:

/**
 * @brief  Return the Fault Mask value
 *
 * @return FaultMask
 *
 * Return the content of the fault mask register
 */
__ASM uint32_t  __get_FAULTMASK(void)
{
  mrs r0, faultmask
  bx lr
}

/**
 * @brief  Set the Fault Mask value
 *
 * @param  faultMask  faultMask value
 *
 * Set the fault mask register
 */
__ASM void __set_FAULTMASK(uint32_t faultMask)
{
  msr faultmask, r0
  bx lr
}

core_cm3.h中:

static __INLINE void __enable_fault_irq()         { __ASM ("cpsie f"); }
static __INLINE void __disable_fault_irq()        { __ASM ("cpsid f"); }

  FAULTMASK也只能在特權狀態下訪問,不過不能在NMIHardFault處理中設定。

  FAULTMASK會在退出異常處理被自動清除,從NMI中退出時除外。因此可以利用此特性,如果在低優先順序的異常處理中觸發了一個高優先的異常(NMI除外),但是想要先處理完低優先順序的異常處理再進行高優先的處理,那麼可以:

  • 設定FAULTMASK禁止所有異常(NMI除外)
  • 設定高優先順序異常的掛起狀態
  • 退出處理

  由於在FAULTMASK置位時,掛起的高優先順序異常處理無法執行,高優先順序的異常就會在FAULTMASK被清除前繼續保持掛起狀態,低優先順序處理完成後才會將其清除。因此,可以強制讓高優先順序處理在低優先順序處理結束後開始執行。

  1. BASEPRI

  有些情況下,可能只想禁止優先順序低於某特定等級的中斷,只需將所需的遮蔽優先順序寫入BASEPRI暫存器。只能在特權狀態下訪問。

CMSIS-Core的core_cm3.c中提供以下函式:

/**
 * @brief  Return the Base Priority value
 *
 * @return BasePriority
 *
 * Return the content of the base priority register
 */
__ASM uint32_t  __get_BASEPRI(void)
{
  mrs r0, basepri
  bx lr
}

/**
 * @brief  Set the Base Priority value
 *
 * @param  basePri  BasePriority
 *
 * Set the base priority register
 */
__ASM void __set_BASEPRI(uint32_t basePri)
{
  msr basepri, r0
  bx lr
}

2.2.4 異常的請求設定與查詢

  1. 中斷輸入與掛起行為

  每個中斷都有多個屬性:

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

  為了支援這些屬性,NVIC中包含了多個可程式設計暫存器,用於中斷使能控制、掛起狀態和只讀的活躍狀態位。

  當NVIC的中斷輸入被確認後,它就會引發該中斷的掛起狀態,被儲存在NVIC的可程式設計暫存器中,即使該中斷請求在處理器還沒來的急處理,就已經被取消,只要在NVIC中已經被掛起過,那麼就算有效,這就是NVIC支援脈衝中斷請求特性。

  當中斷正被處理時,它就會處於活躍狀態,處理完成,活躍狀態自動清除。但對於許多微控制器設計,外設會產生電平觸發的中斷,因此ISR必須要手動清除中斷請求,如寫入外設中的某個暫存器。

(1)、當中斷處於活躍狀態是,處理器無法在中斷完成和異常返回前再次接受同一個中斷請求。
(2)、若中斷請求產生時處理器正在處理另一個具有更高優先順序的中斷,而在處理器隊該中斷請求做出響應之前,通過軟體程式碼將掛起狀態清除掉,那麼該請求就會被取消且不會再得到處理。
(3)、若外設持續保持某個中斷請求,那麼即使軟體嘗試著清除該掛起狀態,掛起狀態還是會再次置位的。
(4)、若在得到處理後,中斷源仍在繼續保持中斷請求,那麼這個中斷就會再次進入掛起狀態且再次得到處理器的服務。
(5)、對於脈衝中斷請求,若在處理器開始處理前,中斷請求訊號產生了多次,它們會被當作一次中斷請求。
(6)、中斷的掛起狀態可以在其正被處理時再次置位。
(7)、即使中斷被禁止了,它的掛起狀態仍可置位。在這種情況下,若中斷稍後被使能了,可以被觸發並得到服務。有些時候,這種情況並不是所希望的,因此需要在使能NVIC中的中斷前手動清除掛起狀態。

  1. 中斷掛起的設定、清除、查詢

  中斷的掛起狀態可通過中斷設定掛起暫存器 NVIC->ISPR[n]中斷清除掛起暫存器 NVIC->ICPR[n]訪問,若存在32個以上的外部中斷輸入,則暫存器可能不止一個。

CMSIS-Core中core_m3.h提供以下函式:

static __INLINE uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn)
static __INLINE void NVIC_SetPendingIRQ(IRQn_Type IRQn)
static __INLINE void NVIC_ClearPendingIRQ(IRQn_Type IRQn)
  1. 中斷的活躍狀態查詢

  每個外部中斷都有一個活躍狀態位,當處理器開始執行中斷處理時,該位會被置1,而在執行中斷返回時會被清零,通過中斷活躍狀態暫存器 NVIC->IABR[n]進行查詢。如果被搶佔,還是會處於活躍狀態。當外部中斷的數量超過32個,暫存器不止一個。

CMSIS-Core中core_m3.h提供以下函式:

static __INLINE uint32_t NVIC_GetActive(IRQn_Type IRQn)
  1. 系統異常的掛起設定、清除、查詢

  通過中斷控制和狀態暫存器 SCB->ICSR進行訪問。

  1. 系統異常的活躍狀態查詢

  通過系統處理控制和狀態暫存器 SCB->SHCSR進行訪問。

2.3 如何暫停當前任務進入異常流程

2.4 如何執行異常處理,並觸發異常返回

2.5 如何進行異常返回,執行暫停的任務


  1. 參考《STM32中文參考手冊_V10》P130,《STM32F10xxx_20xxx_21xxx_L1xxx_Programming_Mannual_PM0056_ENV6》P135 Binary Point