在嵌入式系統(tǒng)開發(fā)中,中斷是十分重要的知識(shí)點(diǎn),在大部分單片機(jī)構(gòu)建的應(yīng)用產(chǎn)品中,基本都是以前后臺(tái)方式(大循環(huán)加中斷)的方式來實(shí)現(xiàn)功能,在主循環(huán)中處理應(yīng)用,并在中斷中處理外部的觸發(fā)信號(hào),以及對(duì)響應(yīng)時(shí)間有要求的應(yīng)用,如用于時(shí)間相關(guān)處理的定時(shí)器中斷,對(duì)按鍵響應(yīng)的外部中斷,用于通訊的收發(fā)和異常處理的串口中斷,SPI中斷, 網(wǎng)絡(luò)中斷等。另外,對(duì)于大部分RTOS來說,如Cortex-M系統(tǒng)中的systick中斷和PendSV中斷,又是實(shí)現(xiàn)基于隊(duì)列和任務(wù)調(diào)度算法的RTOS的核心。
1 異常類型
在單片機(jī)開發(fā)中,對(duì)于中斷的表示方法也因?yàn)閮?nèi)核的不同有很大的差異,如51使用中斷號(hào)來表示指定中斷,而ARM Cortex-M內(nèi)核中則使用中斷向量表的方式配合內(nèi)核中的NVIC控制器來實(shí)現(xiàn)中斷的處理,不過考慮到目前的主流單片機(jī)方案,因此以典型的Cortex-M3內(nèi)核說明單片機(jī)中的中斷控制機(jī)制, 另外Cortex-M系列中的其它內(nèi)核中的中斷流程也基本一致。
Cortex-M3內(nèi)核支持256個(gè)中斷,其中 16個(gè)內(nèi)核中斷和240個(gè)外部中斷,并具有256級(jí)可編程中斷設(shè)置,其中015為系統(tǒng)異常,主要處理系統(tǒng)執(zhí)行中產(chǎn)生的復(fù)位,錯(cuò)誤,主動(dòng)觸發(fā)的SVC,異常等,編號(hào)16255則是由芯片設(shè)計(jì)廠商自定義設(shè)計(jì),用于滿足芯片功能需求的中斷(芯片廠商可以自由定制,不超過最大編號(hào)且不重復(fù)即可)。STM32并沒有使用Cortex-M3的全部?jī)?nèi)容,而是使用了一部分。 STM32有84個(gè)中斷,包括16個(gè)內(nèi)核中斷和68個(gè)可屏蔽中斷,具有16級(jí)可編程中斷優(yōu)先級(jí) 。STM32F103 在內(nèi)核水平上搭載了一個(gè)異常響應(yīng)系統(tǒng), 支持為數(shù)眾多的系統(tǒng)異常和外部中斷。其中系統(tǒng)異常有 8 個(gè)(如果把 Reset 和 HardFault 也算上的話就是 10 個(gè)), 外部中斷有 60個(gè) 。除了個(gè)別異常的優(yōu)先級(jí)被定死外,其它異常的優(yōu)先級(jí)都是可編程的。
下面以STM32F1舉例,有關(guān)具體的系統(tǒng)異常和外部中斷可在標(biāo)準(zhǔn)庫文件 stm32f10x.h 這個(gè)頭文件查詢到,在 IRQn_Type 這個(gè)結(jié)構(gòu)體里面包含了Cortex-M的異常聲明。系統(tǒng)異常清單見下表。
表1 系統(tǒng)異常清單
表2 STM32F103 外部中斷清單
…
反映在軟件實(shí)現(xiàn)就是在startup_xxx.s啟動(dòng)文件中定義的中斷向量表,具體結(jié)構(gòu)如下:
其中External Interrupts就是由廠商定義的中斷類型,另外中斷號(hào)為0的位置為空,設(shè)計(jì)上就用來存儲(chǔ)堆棧指針。
在芯片在上電的過程中就是執(zhí)行復(fù)位機(jī)制根據(jù)SCB->VTOR查詢向量表,找到Reset_Handler入口,并加載__initial_sp到堆棧指針R13中,后續(xù)就可以正常的工作了。
在上述結(jié)構(gòu)中,系統(tǒng)中斷是在內(nèi)核定義時(shí)確定的,外部中斷在芯片設(shè)計(jì)時(shí)被確定,將中斷編號(hào)和指定外設(shè)的中斷觸發(fā)信號(hào)綁定,就構(gòu)建了完整的中斷向量表。
2 中斷向量表
中斷向量表每一位為一個(gè)32bit的地址。每一個(gè)地址對(duì)應(yīng)一個(gè)中斷函數(shù)的地址(第一位除外)。除了第一位以外,所有地址的目標(biāo)都為尋址寄存器(PC)。當(dāng)相應(yīng)中斷觸發(fā)時(shí),ARM Cortex-M硬件會(huì)自動(dòng)把中斷向量表中相應(yīng)的中斷函數(shù)地址裝載入尋址寄存器(PC)然后開始執(zhí)行中斷函數(shù)。如上所述,前16位為ARM保留的系統(tǒng)中斷,建議讀者熟記。之后的中斷為芯片自定義的外部中斷,可以在使用時(shí)查詢手冊(cè)或者廠商提供的驅(qū)動(dòng)程序。表中每個(gè)向量大小都是 4 字節(jié),除了第 0 個(gè)向量外,其余向量都是函數(shù)地址,這個(gè)表集中保存了系統(tǒng)全部的中斷處理函數(shù)(xxxIRQHandler)地址。
對(duì)于內(nèi)嵌 Flash 的 MCU 來說,初始中斷向量表一般會(huì)被要求固定鏈接到 Flash 起始地址處,因?yàn)橄到y(tǒng)啟動(dòng)總是從 Flash 起始地址獲取第 0(初始棧)、1個(gè)向量(初始PC,復(fù)位函數(shù)ResetHandler)來開始應(yīng)用程序代碼的執(zhí)行。對(duì)于一些包含 BootROM 或者沒有內(nèi)部 Flash 的 MCU,初始中斷向量表也許可以放到 Flash 中的其他地址處,這要取決于具體芯片設(shè)計(jì)。
當(dāng)應(yīng)用程序執(zhí)行起來后,如果發(fā)生了中斷,系統(tǒng)會(huì)根據(jù)發(fā)出請(qǐng)求的外設(shè)中斷號(hào)來中斷向量表里找到對(duì)應(yīng)的外設(shè)中斷響應(yīng)函數(shù)并去執(zhí)行。Cortex-M 內(nèi)核(除了CM0)模塊 SCB 里有個(gè)專門的 VTOR 寄存器用來控制中斷向量表首地址(注意,地址需要 128 字節(jié)對(duì)齊),程序運(yùn)行起來后用戶可以配置 SCB->VTOR 寄存器來重設(shè)中斷向量表地址。
2.1 中斷向量表定義
中斷向量表可以通過匯編語言定義也可以通過C語言定義。以下列出兩種方式的示例程序。
- C語言定義
這里我們定義了一個(gè)數(shù)組,數(shù)組的每一項(xiàng)對(duì)應(yīng)相應(yīng)的中斷函數(shù)。如上所述,數(shù)組的第一項(xiàng)為初始棧指針,第二項(xiàng)為入口函數(shù)地址。余下的所有中斷都指向了一個(gè)通用中斷函數(shù)。開發(fā)者可以根據(jù)需求替代相應(yīng)的中斷函數(shù)。
上文還提到中斷向量表需要放置于閃存起始地址處。這里__attribute__((section(".vectors")))為gcc的特定語法(如果開發(fā)者使用IAR或者其他編譯器,語法有所不同),目的是告訴編譯器在鏈接所有對(duì)象文件(objects)時(shí)把_vector[]數(shù)組放在鏈接腳本(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為示例。同樣的,匯編程序中也定義了默認(rèn)中斷函數(shù)。所有中斷也都指向了默認(rèn)的中斷函數(shù)Default_Handler(默認(rèn)為無限循環(huán))。
/**
* @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
之后是中斷向量表。這里為了縮減示例代碼長(zhǎng)度,略去了中間的中斷函數(shù)定義。注意在初始處 .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
……
在中斷向量表的定義之后,程序還將所有函數(shù)定義為.weak。也就是說如果開發(fā)者在其他地方重新定義了同樣名稱的中斷函數(shù),那么默認(rèn)的中斷函數(shù)實(shí)現(xiàn)會(huì)被自動(dòng)覆蓋。weak是GNU GCC編譯器定義的關(guān)鍵詞,如果采用其他編譯器會(huì)有對(duì)應(yīng)的關(guān)鍵詞。
/*******************************************************************************
*
* 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默認(rèn)的中斷向量表地址位于閃存起始地址處。但是ARM Cortex-M3/4系列提供了一個(gè)中斷向量表偏移寄存器(Vector Table Offset Reigster)。系統(tǒng)中中斷向量表的位置是0x00000000加上偏移寄存器的值。上電復(fù)位后這個(gè)寄存器值為0,所以中斷向量表默認(rèn)位于0x00000000閃存起始處。這個(gè)寄存器的目的是為了讓開發(fā)者可以重新設(shè)置中斷向量表的位置。
中斷向量表偏移寄存器第29位(bit 29)定義了中斷向量表的位置。0表示位于閃存程序(code)中,1表示位于內(nèi)存中(SRAM)。
中斷向量表寄存器低7位(bit 6~0)為系統(tǒng)預(yù)留位。
偏移地址寄存器值有對(duì)齊要求。這個(gè)要求和系統(tǒng)中斷數(shù)量或者說中斷向量表長(zhǎng)度相關(guān)。偏移地址寄存器值至少是128 (32words = 128bytes)的整數(shù)倍,這也意味著中斷偏移寄存器中地址的低7位始終會(huì)是0。如果系統(tǒng)中斷數(shù)量大于16個(gè),則總中斷數(shù)為ARM預(yù)留的16個(gè)中斷加上n個(gè)系統(tǒng)中斷。如果(n+2)不為2的指數(shù),則向上找到最近的2的指數(shù)m。每個(gè)地址為4bytes所以對(duì)齊要求為m*4。例如系統(tǒng)有21個(gè)中斷,加上ARM預(yù)留的16個(gè)中斷位,則中斷向量表有效長(zhǎng)度為37words。最近的2的指數(shù)值為64words = 256bytes。所以偏移寄存器的值必須為256的整數(shù)倍。
3 NVIC 簡(jiǎn)介
在講如何配置中斷優(yōu)先級(jí)之前,我們需要先了解下 NVIC。NVIC 是嵌套向量中斷控制器,控制著整個(gè)芯片中斷相關(guān)的功能,它跟內(nèi)核緊密耦合,是內(nèi)核里面的一個(gè)外設(shè)。但是各個(gè)芯片廠商在設(shè)計(jì)芯片的時(shí)候會(huì)對(duì) Cortex-M內(nèi)核里面的 NVIC 進(jìn)行裁剪,把不需要的部分去掉,所以說 STM32 的 NVIC 是 Cortex-M的 NVIC 的一個(gè)子集。
3.1 NVIC 寄存器簡(jiǎn)介
在固件庫中, NVIC 的結(jié)構(gòu)體定義可謂是頗有遠(yuǎn)慮,給每個(gè)寄存器都預(yù)留了很多位,恐怕為的是日后擴(kuò)展功能。不過 STM32F103 可用不了這么多,只是用了部分而已,具體使用了多少可參考《Cortex-M3內(nèi)核編程手冊(cè)》的NVIC 寄存器映射。
[ps] NVIC 結(jié)構(gòu)體定義,來自固件庫頭文件: core_cm3.h。
在配置中斷的時(shí)候我們一般只用 ISER、 ICER 和 IP 這三個(gè)寄存器, ISER 用來使能中斷, ICER 用來失能中斷,IP 用來設(shè)置中斷優(yōu)先級(jí)。
3.2 NVIC 中斷配置固件庫
固件庫文件 core_cm3.h 的最后,還提供了 NVIC 的一些函數(shù),這些函數(shù)遵循 CMSIS 規(guī)則,只要是 Cortex-M3 的處理器都可以使用,具體如下:
表3符合 CMSIS 標(biāo)準(zhǔn)的 NVIC 庫函數(shù)
NVIC庫函數(shù) | 描述 |
---|---|
void NVIC_EnableIRQ(IRQn_Type IRQn) | 使能中斷 |
void NVIC_DisableIRQ(IRQn_Type IRQn) | 失能中斷 |
void NVIC_SetPendingIRQ(IRQn_Type IRQn) | 設(shè)置中斷懸起位 |
void NVIC_ClearPendingIRQ(IRQn_Type IRQn) | 清除中斷懸起位 |
uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn) | 獲取懸起中斷編號(hào) |
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority) | 設(shè)置中斷優(yōu)先級(jí) |
uint32_t NVIC_GetPriority(IRQn_Type IRQn) | 獲取中斷優(yōu)先級(jí) |
void NVIC_SystemReset(void) | 系統(tǒng)復(fù)位 |
這些庫函數(shù)我們?cè)诰幊痰臅r(shí)候用的都比較少,甚至基本都不用。
4 優(yōu)先級(jí)的定義
4.1 優(yōu)先級(jí)定義
在 NVIC 有一個(gè)專門的寄存器:中斷優(yōu)先級(jí)寄存器 NVIC_IPRx,用來配置外部中斷的優(yōu)先級(jí), IPR 寬度為 8bit,原則上每個(gè)外部中斷可配置的優(yōu)先級(jí)為 0~255,數(shù)值越小,優(yōu)先級(jí)越高。但是絕大多數(shù) CM3 芯片都會(huì)精簡(jiǎn)設(shè)計(jì),以致實(shí)際上支持的優(yōu)先級(jí)數(shù)減少,在F103 中,只使用了高 4bit,如下所示:
表4 STM32F103 使用 4bit 表達(dá)優(yōu)先級(jí)
用于表達(dá)優(yōu)先級(jí)的這 4bit,又被分組成搶占優(yōu)先級(jí)和子優(yōu)先級(jí)。如果有多個(gè)中斷同時(shí)響應(yīng),搶占優(yōu)先級(jí)高的就會(huì) 搶占 搶占優(yōu)先級(jí)低的優(yōu)先得到執(zhí)行,如果搶占優(yōu)先級(jí)相同,就比較子優(yōu)先級(jí)。如果搶占優(yōu)先級(jí)和子優(yōu)先級(jí)都相同的話,就比較他們的硬件中斷編號(hào),編號(hào)越小,優(yōu)先級(jí)越高。
4.2 優(yōu)先級(jí)分組
優(yōu)先級(jí)的分組由內(nèi)核外設(shè) SCB 的應(yīng)用程序中斷及復(fù)位控制寄存器 AIRCR 的PRIGROUP[10:8]位決定,STM32F103 分為了 5 組,具體如下:主優(yōu)先級(jí)=搶占優(yōu)先級(jí)。
表5優(yōu)先級(jí)分組
設(shè)置優(yōu)先級(jí)分組可調(diào)用庫函數(shù) NVIC_PriorityGroupConfig()實(shí)現(xiàn),有關(guān) NVIC 中斷相關(guān)的庫函數(shù)都在庫文件 misc.c 和 misc.h 中。
/**
* 配置中斷優(yōu)先級(jí)分組:搶占優(yōu)先級(jí)和子優(yōu)先級(jí)
* 形參如下:
* @arg NVIC_PriorityGroup_0: 0bit for 搶占優(yōu)先級(jí)
* 4 bits for 子優(yōu)先級(jí)
* @arg NVIC_PriorityGroup_1: 1 bit for 搶占優(yōu)先級(jí)
* 3 bits for 子優(yōu)先級(jí)
* @arg NVIC_PriorityGroup_2: 2 bit for 搶占優(yōu)先級(jí)
* 2 bits for 子優(yōu)先級(jí)
* @arg NVIC_PriorityGroup_3: 3 bit for 搶占優(yōu)先級(jí)
* 1 bits for 子優(yōu)先級(jí)
* @arg NVIC_PriorityGroup_4: 4 bit for 搶占優(yōu)先級(jí)
* 0 bits for 子優(yōu)先級(jí)
* @注意 如果優(yōu)先級(jí)分組為 0,則搶占優(yōu)先級(jí)就不存在,優(yōu)先級(jí)就全部由子優(yōu)先級(jí)控制
*/
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
// 設(shè)置優(yōu)先級(jí)分組
SCB- >AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}
表6 優(yōu)先級(jí)分組真值表
5 中斷編程
在配置每個(gè)中斷的時(shí)候一般有 3 個(gè)編程要點(diǎn):
1、使能外設(shè)某個(gè)中斷,這個(gè)具體由每個(gè)外設(shè)的相關(guān)中斷使能位控制。比如串口有發(fā)送完成中斷,接收完成中斷,這兩個(gè)中斷都由串口控制寄存器的相關(guān)中斷使能位控制。
2、初始化 NVIC_InitTypeDef 結(jié)構(gòu)體,配置中斷優(yōu)先級(jí)分組,設(shè)置搶占優(yōu)先級(jí)和子優(yōu)先級(jí),使能中斷請(qǐng)求。 NVIC_InitTypeDef 結(jié)構(gòu)體在固件庫頭文件 misc.h 中定義。
typedef struct {
uint8_t NVIC_IRQChannel; // 中斷源
uint8_t NVIC_IRQChannelPreemptionPriority; // 搶占優(yōu)先級(jí)
uint8_t NVIC_IRQChannelSubPriority; // 子優(yōu)先級(jí)
FunctionalState NVIC_IRQChannelCmd; // 中斷使能或者失能
} NVIC_InitTypeDef;
有關(guān) NVIC 初始化結(jié)構(gòu)體的成員我們一一解釋下:
1) NVIC_IROChannel:用來設(shè)置中斷源,不同的中斷中斷源不一樣,且不可寫錯(cuò),即使寫錯(cuò)了程序也不會(huì)報(bào)錯(cuò),只會(huì)導(dǎo)致不響應(yīng)中斷。具體的成員配置可參考 stm32f10x.h 頭文件里面的 IRQn_Type 結(jié)構(gòu)體定義,這個(gè)結(jié)構(gòu)體包含了所有的中斷源。
typedef enum IRQn {
//Cortex-M3 處理器異常編號(hào)
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 外部中斷編號(hào)
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:搶占優(yōu)先級(jí),具體的值要根據(jù)優(yōu)先級(jí)分組來確定,具體參考表6優(yōu)先級(jí)分組真值表 。
3) NVIC_IRQChannelSubPriority:子優(yōu)先級(jí),具體的值要根據(jù)優(yōu)先級(jí)分組來確定,具體參考表6優(yōu)先級(jí)分組真值表 。
4) NVIC_IRQChannelCmd:中斷使能( ENABLE)或者失能( DISABLE)。操作的是 NVIC_ISER 和 NVIC_ICER 這兩個(gè)寄存器。
3、編寫中斷服務(wù)函數(shù)
在啟動(dòng)文件 startup_stm32f10x_hd.s 中我們預(yù)先為每個(gè)中斷都寫了一個(gè)中斷服務(wù)函數(shù),只是這些中斷函數(shù)都是為空,為的只是初始化中斷向量表。實(shí)際的中斷服務(wù)函數(shù)都需要我們重新編寫,為了方便管理我們把中斷服務(wù)函數(shù)統(tǒng)一寫在 stm32f10x_it.c 這個(gè)庫文件中。關(guān)于中斷服務(wù)函數(shù)的函數(shù)名必須跟啟動(dòng)文件里面預(yù)先設(shè)置的一樣,如果寫錯(cuò),系統(tǒng)就在中斷向量表中找不到中斷服務(wù)函數(shù)的入口,直接跳轉(zhuǎn)到啟動(dòng)文件里面預(yù)先寫好的空函數(shù),并且在里面無限循環(huán),實(shí)現(xiàn)不了中斷。
審核編輯:湯梓紅
-
單片機(jī)
+關(guān)注
關(guān)注
6030文章
44500瀏覽量
632195 -
嵌入式
+關(guān)注
關(guān)注
5060文章
18980瀏覽量
302254 -
中斷
+關(guān)注
關(guān)注
5文章
895瀏覽量
41353 -
Cortex-M
+關(guān)注
關(guān)注
2文章
227瀏覽量
29710 -
Systick
+關(guān)注
關(guān)注
0文章
62瀏覽量
13031
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論