在嵌入式系統開發中,中斷是十分重要的知識點,在大部分單片機構建的應用產品中,基本都是以前后臺方式(大循環加中斷)的方式來實現功能,在主循環中處理應用,并在中斷中處理外部的觸發信號,以及對響應時間有要求的應用,如用于時間相關處理的定時器中斷,對按鍵響應的外部中斷,用于通訊的收發和異常處理的串口中斷,SPI中斷, 網絡中斷等。另外,對于大部分RTOS來說,如Cortex-M系統中的systick中斷和PendSV中斷,又是實現基于隊列和任務調度算法的RTOS的核心。
1 異常類型
在單片機開發中,對于中斷的表示方法也因為內核的不同有很大的差異,如51使用中斷號來表示指定中斷,而ARM Cortex-M內核中則使用中斷向量表的方式配合內核中的NVIC控制器來實現中斷的處理,不過考慮到目前的主流單片機方案,因此以典型的Cortex-M3內核說明單片機中的中斷控制機制, 另外Cortex-M系列中的其它內核中的中斷流程也基本一致。
Cortex-M3內核支持256個中斷,其中 16個內核中斷和240個外部中斷,并具有256級可編程中斷設置,其中015為系統異常,主要處理系統執行中產生的復位,錯誤,主動觸發的SVC,異常等,編號16255則是由芯片設計廠商自定義設計,用于滿足芯片功能需求的中斷(芯片廠商可以自由定制,不超過最大編號且不重復即可)。STM32并沒有使用Cortex-M3的全部內容,而是使用了一部分。 STM32有84個中斷,包括16個內核中斷和68個可屏蔽中斷,具有16級可編程中斷優先級 。STM32F103 在內核水平上搭載了一個異常響應系統, 支持為數眾多的系統異常和外部中斷。其中系統異常有 8 個(如果把 Reset 和 HardFault 也算上的話就是 10 個), 外部中斷有 60個 。除了個別異常的優先級被定死外,其它異常的優先級都是可編程的。
下面以STM32F1舉例,有關具體的系統異常和外部中斷可在標準庫文件 stm32f10x.h 這個頭文件查詢到,在 IRQn_Type 這個結構體里面包含了Cortex-M的異常聲明。系統異常清單見下表。
表1 系統異常清單
表2 STM32F103 外部中斷清單
…
反映在軟件實現就是在startup_xxx.s啟動文件中定義的中斷向量表,具體結構如下:
其中External Interrupts就是由廠商定義的中斷類型,另外中斷號為0的位置為空,設計上就用來存儲堆棧指針。
在芯片在上電的過程中就是執行復位機制根據SCB->VTOR查詢向量表,找到Reset_Handler入口,并加載__initial_sp到堆棧指針R13中,后續就可以正常的工作了。
在上述結構中,系統中斷是在內核定義時確定的,外部中斷在芯片設計時被確定,將中斷編號和指定外設的中斷觸發信號綁定,就構建了完整的中斷向量表。
2 中斷向量表
中斷向量表每一位為一個32bit的地址。每一個地址對應一個中斷函數的地址(第一位除外)。除了第一位以外,所有地址的目標都為尋址寄存器(PC)。當相應中斷觸發時,ARM Cortex-M硬件會自動把中斷向量表中相應的中斷函數地址裝載入尋址寄存器(PC)然后開始執行中斷函數。如上所述,前16位為ARM保留的系統中斷,建議讀者熟記。之后的中斷為芯片自定義的外部中斷,可以在使用時查詢手冊或者廠商提供的驅動程序。表中每個向量大小都是 4 字節,除了第 0 個向量外,其余向量都是函數地址,這個表集中保存了系統全部的中斷處理函數(xxxIRQHandler)地址。
對于內嵌 Flash 的 MCU 來說,初始中斷向量表一般會被要求固定鏈接到 Flash 起始地址處,因為系統啟動總是從 Flash 起始地址獲取第 0(初始棧)、1個向量(初始PC,復位函數ResetHandler)來開始應用程序代碼的執行。對于一些包含 BootROM 或者沒有內部 Flash 的 MCU,初始中斷向量表也許可以放到 Flash 中的其他地址處,這要取決于具體芯片設計。
當應用程序執行起來后,如果發生了中斷,系統會根據發出請求的外設中斷號來中斷向量表里找到對應的外設中斷響應函數并去執行。Cortex-M 內核(除了CM0)模塊 SCB 里有個專門的 VTOR 寄存器用來控制中斷向量表首地址(注意,地址需要 128 字節對齊),程序運行起來后用戶可以配置 SCB->VTOR 寄存器來重設中斷向量表地址。
2.1 中斷向量表定義
中斷向量表可以通過匯編語言定義也可以通過C語言定義。以下列出兩種方式的示例程序。
- C語言定義
這里我們定義了一個數組,數組的每一項對應相應的中斷函數。如上所述,數組的第一項為初始棧指針,第二項為入口函數地址。余下的所有中斷都指向了一個通用中斷函數。開發者可以根據需求替代相應的中斷函數。
上文還提到中斷向量表需要放置于閃存起始地址處。這里__attribute__((section(".vectors")))為gcc的特定語法(如果開發者使用IAR或者其他編譯器,語法有所不同),目的是告訴編譯器在鏈接所有對象文件(objects)時把_vector[]數組放在鏈接腳本(linker script)中的.vectors段落(section)。在鏈接腳本中,.vector段落被定義于閃存起始處。
#ifndef ARMV7M_PERIPHERAL_INTERRUPTS
# error ARMV7M_PERIPHERAL_INTERRUPTS must be defined to the number of I/O interrupts to be supported
#endif
extern void exception_common(void);
unsigned _vectors[] __attribute__((section(".vectors"))) =
{
/* Initial stack */
IDLE_STACK,
/* Reset exception handler */
(unsigned)&__start,
/* Vectors 2 - n point directly at the generic handler */
[2 ... (15 + ARMV7M_PERIPHERAL_INTERRUPTS)] = (unsigned)&exception_common
};
- Assembly語言定義
這里以STM32Cube生成的startup_stm32f103xe.s為示例。同樣的,匯編程序中也定義了默認中斷函數。所有中斷也都指向了默認的中斷函數Default_Handler(默認為無限循環)。
/**
* @brief This is the code that gets called when the processor receives an
* unexpected interrupt. This simply enters an infinite loop, preserving
* the system state for examination by a debugger.
*
* @param None
* @retval : None
*/
.section .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:
b Infinite_Loop
.size Default_Handler, .-Default_Handler
之后是中斷向量表。這里為了縮減示例代碼長度,略去了中間的中斷函數定義。注意在初始處 .section .isr_vector,"a",%progbits語句指定了g_pfnVectors在鏈接是需要被放置在isr_vector段落,也就是閃存起始地址處。
/******************************************************************************
*
* The minimal vector table for a Cortex M3. Note that the proper constructs
* must be placed on this to ensure that it ends up at physical address
* 0x0000.0000.
*
******************************************************************************/
.section .isr_vector,"a",%progbits
.type g_pfnVectors, %object
.size g_pfnVectors, .-g_pfnVectors
g_pfnVectors:
.word _estack
.word Reset_Handler
.word NMI_Handler
.word HardFault_Handler
.word MemManage_Handler
.word BusFault_Handler
.word UsageFault_Handler
.word 0
.word 0
.word 0
.word 0
.word SVC_Handler
.word DebugMon_Handler
.word 0
.word PendSV_Handler
.word SysTick_Handler
.word WWDG_IRQHandler
.word PVD_IRQHandler
……
在中斷向量表的定義之后,程序還將所有函數定義為.weak。也就是說如果開發者在其他地方重新定義了同樣名稱的中斷函數,那么默認的中斷函數實現會被自動覆蓋。weak是GNU GCC編譯器定義的關鍵詞,如果采用其他編譯器會有對應的關鍵詞。
/*******************************************************************************
*
* Provide weak aliases for each Exception handler to the Default_Handler.
* As they are weak aliases, any function with the same name will override
* this definition.
*
*******************************************************************************/
.weak NMI_Handler
.thumb_set NMI_Handler,Default_Handler
.weak HardFault_Handler
.thumb_set HardFault_Handler,Default_Handler
.weak MemManage_Handler
.thumb_set MemManage_Handler,Default_Handler
.weak BusFault_Handler
.thumb_set BusFault_Handler,Default_Handler
.weak UsageFault_Handler
.thumb_set UsageFault_Handler,Default_Handler
……
2.2 中斷向量表偏移寄存器
ARM Cortex-M默認的中斷向量表地址位于閃存起始地址處。但是ARM Cortex-M3/4系列提供了一個中斷向量表偏移寄存器(Vector Table Offset Reigster)。系統中中斷向量表的位置是0x00000000加上偏移寄存器的值。上電復位后這個寄存器值為0,所以中斷向量表默認位于0x00000000閃存起始處。這個寄存器的目的是為了讓開發者可以重新設置中斷向量表的位置。
中斷向量表偏移寄存器第29位(bit 29)定義了中斷向量表的位置。0表示位于閃存程序(code)中,1表示位于內存中(SRAM)。
中斷向量表寄存器低7位(bit 6~0)為系統預留位。
偏移地址寄存器值有對齊要求。這個要求和系統中斷數量或者說中斷向量表長度相關。偏移地址寄存器值至少是128 (32words = 128bytes)的整數倍,這也意味著中斷偏移寄存器中地址的低7位始終會是0。如果系統中斷數量大于16個,則總中斷數為ARM預留的16個中斷加上n個系統中斷。如果(n+2)不為2的指數,則向上找到最近的2的指數m。每個地址為4bytes所以對齊要求為m*4。例如系統有21個中斷,加上ARM預留的16個中斷位,則中斷向量表有效長度為37words。最近的2的指數值為64words = 256bytes。所以偏移寄存器的值必須為256的整數倍。
3 NVIC 簡介
在講如何配置中斷優先級之前,我們需要先了解下 NVIC。NVIC 是嵌套向量中斷控制器,控制著整個芯片中斷相關的功能,它跟內核緊密耦合,是內核里面的一個外設。但是各個芯片廠商在設計芯片的時候會對 Cortex-M內核里面的 NVIC 進行裁剪,把不需要的部分去掉,所以說 STM32 的 NVIC 是 Cortex-M的 NVIC 的一個子集。
3.1 NVIC 寄存器簡介
在固件庫中, NVIC 的結構體定義可謂是頗有遠慮,給每個寄存器都預留了很多位,恐怕為的是日后擴展功能。不過 STM32F103 可用不了這么多,只是用了部分而已,具體使用了多少可參考《Cortex-M3內核編程手冊》的NVIC 寄存器映射。
[ps] NVIC 結構體定義,來自固件庫頭文件: core_cm3.h。
在配置中斷的時候我們一般只用 ISER、 ICER 和 IP 這三個寄存器, ISER 用來使能中斷, ICER 用來失能中斷,IP 用來設置中斷優先級。
3.2 NVIC 中斷配置固件庫
固件庫文件 core_cm3.h 的最后,還提供了 NVIC 的一些函數,這些函數遵循 CMSIS 規則,只要是 Cortex-M3 的處理器都可以使用,具體如下:
表3符合 CMSIS 標準的 NVIC 庫函數
NVIC庫函數 | 描述 |
---|---|
void NVIC_EnableIRQ(IRQn_Type IRQn) | 使能中斷 |
void NVIC_DisableIRQ(IRQn_Type IRQn) | 失能中斷 |
void NVIC_SetPendingIRQ(IRQn_Type IRQn) | 設置中斷懸起位 |
void NVIC_ClearPendingIRQ(IRQn_Type IRQn) | 清除中斷懸起位 |
uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn) | 獲取懸起中斷編號 |
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority) | 設置中斷優先級 |
uint32_t NVIC_GetPriority(IRQn_Type IRQn) | 獲取中斷優先級 |
void NVIC_SystemReset(void) | 系統復位 |
這些庫函數我們在編程的時候用的都比較少,甚至基本都不用。
4 優先級的定義
4.1 優先級定義
在 NVIC 有一個專門的寄存器:中斷優先級寄存器 NVIC_IPRx,用來配置外部中斷的優先級, IPR 寬度為 8bit,原則上每個外部中斷可配置的優先級為 0~255,數值越小,優先級越高。但是絕大多數 CM3 芯片都會精簡設計,以致實際上支持的優先級數減少,在F103 中,只使用了高 4bit,如下所示:
表4 STM32F103 使用 4bit 表達優先級
用于表達優先級的這 4bit,又被分組成搶占優先級和子優先級。如果有多個中斷同時響應,搶占優先級高的就會 搶占 搶占優先級低的優先得到執行,如果搶占優先級相同,就比較子優先級。如果搶占優先級和子優先級都相同的話,就比較他們的硬件中斷編號,編號越小,優先級越高。
4.2 優先級分組
優先級的分組由內核外設 SCB 的應用程序中斷及復位控制寄存器 AIRCR 的PRIGROUP[10:8]位決定,STM32F103 分為了 5 組,具體如下:主優先級=搶占優先級。
表5優先級分組
設置優先級分組可調用庫函數 NVIC_PriorityGroupConfig()實現,有關 NVIC 中斷相關的庫函數都在庫文件 misc.c 和 misc.h 中。
/**
* 配置中斷優先級分組:搶占優先級和子優先級
* 形參如下:
* @arg NVIC_PriorityGroup_0: 0bit for 搶占優先級
* 4 bits for 子優先級
* @arg NVIC_PriorityGroup_1: 1 bit for 搶占優先級
* 3 bits for 子優先級
* @arg NVIC_PriorityGroup_2: 2 bit for 搶占優先級
* 2 bits for 子優先級
* @arg NVIC_PriorityGroup_3: 3 bit for 搶占優先級
* 1 bits for 子優先級
* @arg NVIC_PriorityGroup_4: 4 bit for 搶占優先級
* 0 bits for 子優先級
* @注意 如果優先級分組為 0,則搶占優先級就不存在,優先級就全部由子優先級控制
*/
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
// 設置優先級分組
SCB- >AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}
表6 優先級分組真值表
5 中斷編程
在配置每個中斷的時候一般有 3 個編程要點:
1、使能外設某個中斷,這個具體由每個外設的相關中斷使能位控制。比如串口有發送完成中斷,接收完成中斷,這兩個中斷都由串口控制寄存器的相關中斷使能位控制。
2、初始化 NVIC_InitTypeDef 結構體,配置中斷優先級分組,設置搶占優先級和子優先級,使能中斷請求。 NVIC_InitTypeDef 結構體在固件庫頭文件 misc.h 中定義。
typedef struct {
uint8_t NVIC_IRQChannel; // 中斷源
uint8_t NVIC_IRQChannelPreemptionPriority; // 搶占優先級
uint8_t NVIC_IRQChannelSubPriority; // 子優先級
FunctionalState NVIC_IRQChannelCmd; // 中斷使能或者失能
} NVIC_InitTypeDef;
有關 NVIC 初始化結構體的成員我們一一解釋下:
1) NVIC_IROChannel:用來設置中斷源,不同的中斷中斷源不一樣,且不可寫錯,即使寫錯了程序也不會報錯,只會導致不響應中斷。具體的成員配置可參考 stm32f10x.h 頭文件里面的 IRQn_Type 結構體定義,這個結構體包含了所有的中斷源。
typedef enum IRQn {
//Cortex-M3 處理器異常編號
NonMaskableInt_IRQn = -14,
MemoryManagement_IRQn = -12,
BusFault_IRQn = -11,
UsageFault_IRQn = -10,
SVCall_IRQn = -5,
DebugMonitor_IRQn = -4,
PendSV_IRQn = -2,
SysTick_IRQn = -1,
//STM32 外部中斷編號
WWDG_IRQn = 0,
PVD_IRQn = 1,
TAMP_STAMP_IRQn = 2,
//限于篇幅,中間部分代碼省略,具體的可查看庫文件 stm32f10x.h
DMA2_Channel2_IRQn = 57,
DMA2_Channel3_IRQn = 58,
DMA2_Channel4_5_IRQn = 59
} IRQn_Type;
2) NVIC_IRQChannelPreemptionPriority:搶占優先級,具體的值要根據優先級分組來確定,具體參考表6優先級分組真值表 。
3) NVIC_IRQChannelSubPriority:子優先級,具體的值要根據優先級分組來確定,具體參考表6優先級分組真值表 。
4) NVIC_IRQChannelCmd:中斷使能( ENABLE)或者失能( DISABLE)。操作的是 NVIC_ISER 和 NVIC_ICER 這兩個寄存器。
3、編寫中斷服務函數
在啟動文件 startup_stm32f10x_hd.s 中我們預先為每個中斷都寫了一個中斷服務函數,只是這些中斷函數都是為空,為的只是初始化中斷向量表。實際的中斷服務函數都需要我們重新編寫,為了方便管理我們把中斷服務函數統一寫在 stm32f10x_it.c 這個庫文件中。關于中斷服務函數的函數名必須跟啟動文件里面預先設置的一樣,如果寫錯,系統就在中斷向量表中找不到中斷服務函數的入口,直接跳轉到啟動文件里面預先寫好的空函數,并且在里面無限循環,實現不了中斷。
審核編輯:湯梓紅
-
單片機
+關注
關注
6035文章
44554瀏覽量
634634 -
嵌入式
+關注
關注
5082文章
19104瀏覽量
304802 -
中斷
+關注
關注
5文章
898瀏覽量
41470 -
Cortex-M
+關注
關注
2文章
229瀏覽量
29752 -
Systick
+關注
關注
0文章
62瀏覽量
13071
發布評論請先 登錄
相關推薦
評論