STM32之中斷
1、NVIC
Nested vectored interrupt controller :可巢狀向量中斷控制器 (NVIC)
- NVIC 特性
- 82個可遮蔽中斷 ##不包括核心的16箇中斷
- 16個可程式設計優先順序 ##適用於全部中斷
- 低延遲異常和中斷處理
- 電源管理控制
- 系統控制暫存器的實現
NVIC與處理器核心介面緊密耦合, 實現了高效快速的中斷響應。所有的中斷,包括核心異常都被 NVIC 所管理.
2、中斷向量表
其實中斷向量表在STM32F4XX啟動檔案裡面就可以看出來,詳情可看 :STM32F4XX啟動檔案分析
3、EXTI(External interrupt/event controller) 外部中斷/事件控制器
主要特性
- 在每個中斷/事件線都有獨立的觸發和遮蔽功能
- 每個中斷線有專用的狀態標誌位
- 可產生高達 23 個軟體事件/中斷請求
- 以比APB2時鐘週期更短的脈衝寬度檢測外部訊號
中斷與事件配置
硬體中斷選擇
配置23線為中斷源可參考以下配置步驟 :
- 配置 23 中斷線的遮蔽位 (EXTI_IMR)
- 配置中斷線的觸發選擇位 (EXTI_RTSR and EXTI_FTSR)
- 配置控制 NVIC IRQ 通道的使能與遮蔽位來使來自 23 線的一箇中斷可以被正確的應答,NVIC IRQ 被對映到外部中斷控制器(EXTI)
硬體事件選擇
配置23線為事件源可參考以下配置步驟 :
- 配置 23 事件線的遮蔽位 (EXTI_EMR)
- 配置事件線的觸發選擇位 (EXTI_RTSR and EXTI_FTSR)
軟體 中斷/事件 選擇
23 線可以被配置為軟體 中斷/事件 線。下面的操作步驟可以產生一個軟體中斷.
- 配置 23 事件/中斷 線的遮蔽位 (EXTI_IMR, EXTI_EMR)
- 設定軟體中斷暫存器的應答位 (EXTI_SWIER)
外部 中斷/事件 對映
STM32F407ZG 的 140 個 GPIO 引腳都與一個外部中斷線相連,具體如圖所示:
上述一共用到了16根 EXTI 線,其餘 7 根 EXTI 線的連線使用如下:
- EXTI 16 連線到 PVD 輸出(PVD:掉電檢測)
- EXTI 17 連線到 RTC Alarm 事件
- EXTI 18 連線到 USB OTG FS Wakeup 事件
- EXTI 19 連線到 Ethernet Wakeup 事件
- EXTI 20 連線到 USB OTG HS (configured in FS) Wakeup 事件
- EXTI 21 連線到 RTC Tamper and TimeStamp 事件
- EXTI 22 連線到 RTC Wakeup 事件
中斷與事件的區別
一個硬體中斷/事件的產生:
- input line 輸入外部訊號
- 邊緣檢測電路檢測電平變化(電平變化檢測可以人為配置,並且上升沿檢測與下降沿檢測是獨立的)
- 經過一個或門,此或門連線電平檢測電路的輸出與軟體事件/中斷暫存器。也因此任意一條線的值為真,那麼輸出就為真,所以可以產生軟觸發中斷或者事件
- 或門輸入訊號分別經過兩個與門,另個與門分別再與中斷遮蔽暫存器與事件遮蔽暫存器連線,控制中斷或者事件的產生。這兩個也是獨立的,所以可以同時產生中斷以及事件
- 如果是中斷的話,輸出訊號會再經過中斷掛起請求暫存器,如果此時晶片正處於不可被中斷打斷的時候,可以配置中斷掛起暫存器來暫時掛起一箇中斷。需要軟體參與
- 如果是事件的話,輸出訊號直接輸出到一個脈衝發生器裡面,脈衝發生器可以產生一個脈衝,調動相應的硬體完成此次事件響應。無需軟體參與
DMA傳輸的例子:
- 如果配置為中斷的話,需要在中斷產生之後,進入中斷處理函式,在中斷處理函式中觸發DMA操作,然後進行DMA。
- 如果配置為事件的話,直接由事件最終輸出脈衝來觸發DMA操作,不需要經過中斷處理函式進行DMA的觸發。
事件可以降低CPU的負荷,提高了響應速度
4、核心最重要的兩個模組(SCB:System controller block NVIC:Nested vectored interrupt controller)
核心的外設
SCB
地址 | 暫存器名 | 讀寫許可權 | 特權 | 復位值 | 作用描述 |
---|---|---|---|---|---|
0xE000E008 | ACTLR | RW | Privileged | 0x00000000 | 輔助控制暫存器 |
0xE000ED00 | CPUID | RO | Privileged | 0x410FC240 | CPU的ID號碼 |
0xE000ED04 | ICSR | RW | Privileged | 0x00000000 | 中斷控制與狀態暫存器 |
0xE000ED08 | VTOR | RW | Privileged | 0x00000000 | 中斷向量表偏移,一般只取兩個值,第29位為1表示SRAM區,為0表示code區 |
0xE000ED0C | AIRCR | RW | Privileged | 0xFA050000 | 應用程式中斷以及復位 |
0xE000ED10 | SCR | RW | Privileged | 0x00000000 | 系統控制 |
0xE000ED14 | CCR | RW | Privileged | 0x00000200 | 配置與控制 |
0xE000ED18 | SHPR1 | RW | Privileged | 0x00000000 | 系統中斷處理函式優先順序暫存器1 |
0xE000ED1C | SHPR2 | RW | Privileged | 0x00000000 | 系統中斷處理函式優先順序暫存器2 |
0xE000ED20 | SHPR3 | RW | Privileged | 0x00000000 | 系統中斷處理函式優先順序暫存器3 |
0xE000ED24 | SHCRS | RW | Privileged | 0x00000000 | 系統中斷處理函式控制與狀態 |
0xE000ED28 | CFSR | RW | Privileged | 0x00000000 | 配置異常狀態暫存器 |
0xE000ED28 | MMSRb | RW | Privileged | 0x00 | 記憶體管理異常狀態暫存器 |
0xE000ED29 | BFSRb | RW | Privileged | 0x00 | 匯流排異常狀態暫存器 |
0xE000ED2A | UFSRb | RW | Privileged | 0x0000 | 使用異常狀態暫存器 |
0xE000ED2C | HFSR | RW | Privileged | 0x00000000 | 硬體異常狀態暫存器 |
0xE000ED34 | MMAR | RW | Privileged | Unknown | 記憶體管理異常地址暫存器 |
0xE000ED38 | BFAR | RW | Privileged | Unknown | 匯流排異常地址暫存器 |
0xE000ED3C | AFSR | RW | Privileged | 0x00000000 | 輔助異常狀態暫存器 |
NVIC
地址 | 暫存器名 | 讀寫許可權 | 特權 | 復位值 | 作用描述 |
---|---|---|---|---|---|
0xE000E100-0xE000E11C | NVIC_ISER0-NVIC_ISER7 | RW | Privileged | 0x00000000 | 中斷使能 |
0XE000E180-0xE000E19C | NVIC_ICER0-NVIC_ICER7 | RW | Privileged | 0x00000000 | 中斷禁止 |
0XE000E200-0xE000E21C | NVIC_ISPR0-NVIC_ISPR7 | RW | Privileged | 0x00000000 | 中斷掛起 |
0XE000E280-0xE000E29C | NVIC_ICPR0-NVIC_ICPR7 | RW | Privileged | 0x00000000 | 中斷恢復 |
0xE000E300-0xE000E31C | NVIC_IABR0-NVIC_IABR7 | RW | Privileged | 0x00000000 | 中斷啟用 |
0xE000E400-0xE000E4EF | NVIC_IPR0-NVIC_IPR59 | RW | Privileged | 0x00000000 | 中斷優先順序 |
0xE000EF00 | STIR | WO | Configurable | 0x00000000 | 軟體觸發中斷 |
5、優先順序分組的概念
Cortex M4 的優先順序分組如下圖所示
核心優先順序的分組:
要注意的是,在 STM32F407ZG 只使用了 4bits 的位(高4位),也就是說分組情況如下
STM32組編號 | PRIGROUP | Binary point | Group priority bits | Subpriority bits | Group priorities | subpriorities |
---|---|---|---|---|---|---|
0 | 0b111 | b.yyyyyyyy | none | [7:4] | 1 | 16 |
1 | 0b110 | bx.yyyyyyy | [7] | [6:4] | 2 | 8 |
2 | 0b101 | bxx.yyyyyy | [7:6] | [5:4] | 4 | 4 |
3 | 0b100 | bxxx.yyyyy | [7:5] | [4] | 8 | 2 |
4 | 0b011 | bxxxx.yyyy | [7:4] | none | 16 | 1 |
在STM32中組編號恰好與核心手冊中的是反的,這樣設計的原因是為了相容性,也就是說如果程式移植到了只支援3位優先順序設定的系統中也能夠執行。另外有三種設計方式分別是:使用高 4bits,組編號不反轉;使用低 4bits,組編號不反轉;使用低 4bits,組編號反轉。這三種方法如果按照核心分組寫出來之後會發現會有優先順序完全一樣的情況出現,所以不可取。
- 要了解優先順序分組,就要明確兩個概念:搶佔優先順序(組優先順序)、響應優先順序(子優先順序)
- 搶佔優先順序:可以被中斷巢狀。也就是在一箇中斷髮生的時候,另一個搶佔優先順序比此中斷級別高的中斷可以打斷正在進行的中斷,直到更高優先順序的中斷執行完畢之後,才會返回來繼續執行這個被打斷的中斷
- 響應優先順序:不可以被中斷巢狀。也就是說在多箇中斷同時發生的時候,只能夠優先相應較高優先順序的中斷,並且如果在中斷過程中有更高優先順序中斷髮生的時候,正在進行的中斷也不能夠被打斷。
搶佔優先順序與響應優先順序的關係有點像 TCP/IP 協議中的網路號與子網號的區別,兩個中斷也是先比較搶佔優先順序然後才是比較響應優先順序
6、程式編寫
#define SUM_NVIC_PRIOTITY_BITS 4 //一共用了4個位
/* 中斷優先順序分組
* group_num : 分組號,上面有列出各個分組對應的優先順序
*/
static void set_priority_group(u8 group_num)
{
u32 temp = 0, temp1 = 0;
group_num = group_num % (SUM_NVIC_PRIOTITY_BITS + 1); //因為只有5個組,所以限制數量
temp1 = (((~group_num) & 0x07) << 8); //取反區低三位
temp = SCB->AIRCR;
temp &= 0x0000F8FF; //清除8-10位
temp |= 0x05FA0000; //必須寫5FA位,是作為鑰匙的作用,不寫的話寫入的分組是無效的
temp |= temp1;
SCB->AIRCR = temp;
}
/* 設定優先順序分組
* g_priority :搶佔優先順序 sub_priority :響應優先順序
* irq_num :中斷號 prioritygroup :優先順序組
*/
void NVIC_set_priority(u8 g_priority, u8 sub_priority, IRQn_Type irq_num, u8 prioritygroup)
{
int32_t sub_priority_bits = 0;
sub_priority_bits = SUM_NVIC_PRIOTITY_BITS - prioritygroup;
ASSERT(sub_priority_bits >= 0); //斷言,如果小於0就報錯
/* 原型
#define ASSERT(x) while(!(x)){ \
printf("Assert failed!!! File:%s Function:%s Line:%d\r\n", __FILE__, __FUNCTION__, __LINE__); \
delay_ms(1000); \
}
*/
set_priority_group(prioritygroup); //設定優先順序分組
/* 參考核心標頭檔案寫的 */
if(irq_num < 0) //要判斷是否小於0原因是:核心標頭檔案中把核心的中斷設定為小於0的列舉型別,而其他的都是大於0的,參照核心標頭檔案
{
/* 根據核心標頭檔案中的 SCB 結構體對應核心手冊部分推算 */
SCB->SHP[((uint32_t)(irq_num) & 0xF)-4] = ((g_priority << sub_priority_bits) | sub_priority) << (8 - SUM_NVIC_PRIOTITY_BITS);
}
else
{
NVIC->IP[(uint32_t)(irq_num)] = ((g_priority << sub_priority_bits) | sub_priority) << (8 - SUM_NVIC_PRIOTITY_BITS);
}
}
/* 使能相應的中斷,為必須的 */
void NVIC_enable_irq(IRQn_Type irq)
{
NVIC->ISER[(uint32_t)((int32_t)irq) >> 5] |= (uint32_t)(1 << ((uint32_t)((int32_t)irq) % 32));
}
void EX_irq_config(GPIOx_SELECT gpiox,GPIOx_pn_SELECT gpiox_n,GPIO_IRQ_TRIGGER trigger)
{
RCC->APB2ENR |= (1 << 14); //使能SYSCFG模組,只有使能之後對SYSCFG暫存器的設定才會有效
gpio_init(gpiox, gpiox_n, BYM_PULL_UP, BYM_GPI, BYM_HIGH_LEVEL, BYM_PUSH_PULL); //初始化GPIO為輸入,內部上拉
SYSCFG->EXTICR[gpiox_n/4] &= ~(0xF << ((gpiox_n % 4) * 4)); //
SYSCFG->EXTICR[gpiox_n/4] |= (gpiox << ((gpiox_n % 4) * 4));//對映 Px_n 到 EXTIn中斷線
EXTI->IMR |= (1 << gpiox_n); //解除遮蔽
EXTI->RTSR |= ((trigger & 0x01) << gpiox_n); //設定觸發沿
EXTI->FTSR |= ((trigger >> 1) << gpiox_n);
}
/* 只支援外部中斷的中斷標誌清除 */
void EX_irq_clear(u8 irq)
{
EXTI->PR |= (1 << irq);
}
7、測試
測試搶佔優先順序不同的情況
主程式
NVIC_set_priority(1, 2, EXTI3_IRQn, 2);
NVIC_set_priority(2, 1, EXTI2_IRQn, 2);
EX_irq_config(BYM_GPIOE, BYM_Px3, IRQ_BOTHEDGE);
EX_irq_config(BYM_GPIOE, BYM_Px2, IRQ_BOTHEDGE);
NVIC_enable_irq(EXTI3_IRQn);
NVIC_enable_irq(EXTI2_IRQn);
中斷服務程式
void EXTI3_IRQHandler(void)
{
if(0 == gpio_get(BYM_GPIOE, BYM_Px3))
{
printf("Key 1 down \r\n");
delay_ms(1000);
printf("Key 1 end \r\n");
}
EX_irq_clear(3);
}
void EXTI2_IRQHandler(void)
{
if(0 == gpio_get(BYM_GPIOE, BYM_Px2))
{
printf("Key 2 down \r\n");
delay_ms(1000);
printf("Key 2 end \r\n");
}
EX_irq_clear(2);
}
先按下 GPE3(對應EXTI3),立馬按下 GPE2(對應EXTI2),有下面的輸出結果(不會被打斷)
Key 1 down Key 1 end
先按下 GPE2(對應EXTI2),立馬按下 GPE3(對應EXTI3),有下面的輸出結果(EXTI2的中斷被EXTI3打斷了)
Key 2 down Key 1 down Key 2 end Key 1 end
測試搶佔優先順序相同而響應優先順序不同的情況
主程式改變如下
NVIC_set_priority(1, 2, EXTI3_IRQn, 2);
NVIC_set_priority(1, 1, EXTI2_IRQn, 2);
EX_irq_config(BYM_GPIOE, BYM_Px3, IRQ_BOTHEDGE);
EX_irq_config(BYM_GPIOE, BYM_Px2, IRQ_BOTHEDGE);
NVIC_enable_irq(EXTI3_IRQn);
NVIC_enable_irq(EXTI2_IRQn);
先按下 GPE3(對應EXTI3),立馬按下 GPE2(對應EXTI2),有下面的輸出結果(不會被打斷)
Key 1 down Key 1 end
先按下 GPE2(對應EXTI2),立馬按下 GPE3(對應EXTI3),有下面的輸出結果(不會被打斷)
Key 2 down Key 2 end
8、程式編寫思路
- 設定優先順序分組,使用 SCB 的 AIRCR 暫存器(重要的是寫入鑰匙,5FA)
- 具體規劃分組內部搶佔與響應優先順序,如果中斷是核心的,使用 SCB 3. 控制模組,如果是外部的,使用 NVIC 模組
- 使能 SYSCFG 模組時鐘,對映相應的中斷線,如果是IO口中斷還要設定跳變沿以及初始化IO管腳
- 解除中斷或者事件遮蔽(EXTI暫存器)
- NVIC使能相應的中斷
- 編寫中斷服務程式