ST32 NVIC interrupt priority 基本觀念
當我們在使用STM32中的NVIC中斷時會發現,中斷的優先等級分成了preemption priority (可搶奪優先等級)與subpriority(副優先等級)兩種,當我們在設定每一個interrupt
channel的時候都必須設定這兩組數值。
到底這兩優先等級有甚麼不一樣:
1. Preemption Priority
當中斷發生時,擁有較高Preemption priority的interrupt channel可以將較低preemptrion
priority的interrupt chnannel打斷優先被處理。假如兩個interrupt channel擁有相同peemption
priority,則不會發生中斷被打斷的情況發生,後到的interrupt event必須等到現有的中斷被處理完畢後才能被處理。
2. Subpriority
在兩個interrupt channel的preemption priority相同的前提下,如果兩個subpriority不同的interrupt
event同時發生,則subpriority高的interrupt會先被處理,但是如果低優先等級的interrupt channel event 已經在執行,則不能被打斷,高subpriority的event必須等到現有的event被處理完才能被處理。
3. STM32中斷優先等級暫存器
(Interrupt Priority Registers)
STM32每個Interrupt
Channel都擁有屬於自己的interrupt priority register, STM32中每個priority
register是4個bit(Cortex-M3中定義8-bits),這4-bits可以依據功能分成下面五組
NVIC_PriorityGroup_0
=> 0 bits for pre-emption priority, 4 bits for subpriority
NVIC_PriorityGroup_1
=> 1 bits for pre-emption priority, 3 bits for subpriority
NVIC_PriorityGroup_2
=> 2 bits for pre-emption priority, 2 bits for subpriority
NVIC_PriorityGroup_3
=> 3 bits for pre-emption priority, 1 bits for subpriority
NVIC_PriorityGroup_4
=> 4 bits for pre-emption priority, 0 bits for subpriority
preemption
priority與subpriority共用這4bits暫存器位置,因此如果設定成NVIC_PriorityGroup_0,暫存器的值將會被辨識為subpriority必須注意到一件事情,那就是假使priority設定值超過了所選定Group中分配給他的位數,將會導致不可預期的錯誤。
4.NVIC管理了中斷的優先順序,某中斷的啓用與否,以及中斷等待位元的設定及清除。所以在設定一個中斷時,一定要設定他的NVIC,才能正確運作。Cortex-M3把每個中斷分配了兩個優先順序,分別為主順序(
Preemption Priority)和(Subpriority)次要順序,數字較小的優先。所以如果有兩個中斷擁有同樣的主要順序,擁有較小的次要順序中斷會優先被執行。而如果一個中斷正在執行,核心收到一個有較小的主要順序中斷的話,則原先的中段就會被搶走。
下面用一個範例來說明:
NVIC_InitTypeDef
NVIC_InitStructure;
// 定義Priority Group
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel
= RTC_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority
= 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority
= 1;
NVIC_InitStructure.NVIC_IRQChannelCmd
= ENABLE;
NVIC_Init(&NVIC_InitStructure);
一開始先定義了Priority Group為NVIC_PriorityGroup_2,
因此其中會有2bits是給preemption priority,2bits給subpriority
使用因此不論是preemption priority或者subpriority的值範圍都在0~3(2bits可以表是的最大數值),
另外假使一個interrupt channel的preempriton prirority與其他所有的interrupt channel都不一樣,
則這個interrupt channel的subpriority可以設為任意值。
因此其中會有2bits是給preemption priority,2bits給subpriority
使用因此不論是preemption priority或者subpriority的值範圍都在0~3(2bits可以表是的最大數值),
另外假使一個interrupt channel的preempriton prirority與其他所有的interrupt channel都不一樣,
則這個interrupt channel的subpriority可以設為任意值。
STM32提供了20種的的外部中斷來源,雖然很多GPIO 都指向了同樣的向量
從這個圖我們看得出來,PA0~PG0所產生的外部中斷事件都會指向EXTI0這個向量(向量就當中斷發生時程式計數器會指向的位置)PA1~PG1指向EXTI1,以此類推。也就是說,如果PA0或PC0或者說Px0所產生的向量,都會去執行EXTI0的ISR,而在這個ISR裡面我們再去看中斷等待 Flag 來分辨到底是哪個Pin所產生的中斷。
如果我想要設定一個外部中斷,我需要做以下的步驟:
- 設定對應向量的NVIC。
- 對於GPIO,設定AFIO_EXTICRx寄存器來選擇正確的Pin(例如,PA0)
- 設定觸發條件(上緣,下緣或上下緣)。
//結構宣告
GPIO_InitTypeDef
GPIO_InitStructure;
NVIC_InitTypeDef
NVIC_InitStructure;
EXTI_InitTypeDef
EXTI_InitStructure;
//設定PA0的GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA
| RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //內部拉高
GPIO_InitStructure.GPIO_Speed
= GPIO_Speed_10MHz;
GPIO_Init(GPIOA,
&GPIO_InitStructure);
//把EXTI0連到PA0
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,
GPIO_PinSource0);
//設定EXTI0
EXTI_InitStructure.EXTI_Line
= EXTI_Line0;
EXTI_InitStructure.EXTI_Mode
= EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger
= EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd
= ENABLE;
EXTI_Init(&EXTI_InitStructure);
//設定NVIC
NVIC_InitStructure.NVIC_IRQChannel
= EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority
= 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority
= 0;
NVIC_InitStructure.NVIC_IRQChannelCmd
= ENABLE;
NVIC_Init(&NVIC_InitStructure);
5. 優先順序衝突的處理
具有高搶佔式優先順序的中斷可以在具有低搶佔式優先順序的中斷處理過程中被回應,即中斷的嵌套,或者說高搶佔式優先順序的中斷可以嵌套低搶佔式優先順序的中斷。
當兩個中斷源的搶佔式優先順序相同時,這兩個中斷將沒有嵌套關係,當一個中斷到來後,如果正在處理另一個中斷,這個後到來的中斷就要等到前一個中斷處理完之後才能被處理。如果這兩個中斷同時到達,則中斷控制器根據他們的回應優先順序高低來決定先處理哪一個;如果他們的搶佔式優先順序和回應優先順序都相等,則根據他們在中斷表中的排位元順序決定先處理哪一個。
6. Cortex-M3中對中斷優先順序的定義
既然每個中斷源都需要被指定這兩種優先順序,就需要有相應的寄存器位元記錄每個中斷的優先順序;在Cortex-M3中定義了8個比特位用於設置中斷源的優先順序,這8個比特位可以有8種分配方式,如下: 所有8位用於指定回應優先順序
最高1位用於指定搶佔式優先順序,最低7位用於指定回應優先順序 最高2位用於指定搶佔式優先順序,最低6位用於指定回應優先順序 最高3位用於指定搶佔式優先順序,最低5位用於指定回應優先順序 最高4位用於指定搶佔式優先順序,最低4位用於指定回應優先順序 最高5位用於指定搶佔式優先順序,最低3位用於指定回應優先順序 最高6位用於指定搶佔式優先順序,最低2位用於指定回應優先順序 最高7位用於指定搶佔式優先順序,最低1位用於指定回應優先順序 這就是優先順序分組的概念。
7. stm32中對中斷優先順序的定義
Cortex-M3允許具有較少中斷源時使用較少的寄存器位指定中斷源的優先順序,因此STM32把指定中斷優先順序的寄存器位減少到4位,這4個寄存器位元的分組方式如下: 第0組:所有4位用於指定回應優先順序
第1組:最高1位用於指定搶佔式優先順序,最低3位用於指定回應優先順序 第2組:最高2位用於指定搶佔式優先順序,最低2位用於指定回應優先順序 第3組:最高3位用於指定搶佔式優先順序,最低1位用於指定回應優先順序 第4組:所有4位用於指定搶佔式優先順序
AIRC(Application Interrupt and Reset Register)寄存器中有用於指定優先順序的 4 bits。這4個bits用於分配preemption優先順序和sub優先順序,在STM32的固件庫中定義如下:
/* Preemption Priority Group */
#define NVIC_PriorityGroup_0 ((u32)0x700)/* 0 bits for pre-ption priority 4 bits for subpriority */
#define NVIC_PriorityGroup_1 ((u32)0x600)/* 1 bits for pre-emption priority 3 bits for subpriority */
#define NVIC_PriorityGroup_2 ((u32)0x500)/* 2 bits for pre-emption priority 2 bits for subpriority */
#define NVIC_PriorityGroup_3 ((u32)0x400)/* 3 bits for pre-emption priority 1 bits for subpriority */
#define NVIC_PriorityGroup_4 ((u32)0x300)/* 4 bits for pre-emption priority 0 bits for subpriority */
可以通過調用STM32的固件庫中的函數NVIC_PriorityGroupConfig()選擇使用哪種優先順序分組方式,這個函數的參數有下列5種:
NVIC_PriorityGroup_0 => 選擇第0組
NVIC_PriorityGroup_1 => 選擇第1組
NVIC_PriorityGroup_2 => 選擇第2組
NVIC_PriorityGroup_3 => 選擇第3組
NVIC_PriorityGroup_4 => 選擇第4組
接下來就是指定中斷源的優先順序,下面以一個簡單的例子說明如何指定中斷源的搶佔式優先順序和回應優先順序:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); // 選擇使用優先順序分組第1組
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQChannel; // 使能EXTI0中斷
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 指定搶佔式優先順序別1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 指定回應優先順序別0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_Init(&NVIC_InitStructure);
// 使能EXTI9_5中斷
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 指定搶佔式優先順序別0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 指定回應優先順序別1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
-------------------------------------------------------------------------------- 要注意的幾點是:
PS: 如果指定的搶佔式優先順序別或回應優先順序別超出了選定的優先順序分組所限定的範圍,將可能得到意想不到的結果;
[ IRQ interrupt Source]
麼實際中斷會去執行的程式要寫在哪裡?答案就在stm32f10x_it.c這個檔案內。打開來看只會看到一堆沒有任何功能的函數,這些函數的名稱其實就是中斷向量會執行的函數,那麼我們想要用的中斷向量應該要叫什麼名字呢?這個其實是定義在STM32的啟動程式裡,也就是startup_stm32f10x_md.s這個檔案裡。參考下圖:
看到
EXTI0_IRQHandler ;EXTI Line 0 這行,就是代表EXTI Line
0中斷所產生的向量函數名稱,所以我們在stm32f10x_it.c裡面加入這個為名稱的函數後,EXTI Line
0產生的中斷就會被執行這個函數了。就像稍早所說的,進入向量後我們要用中斷等待位元來分辨到底是哪個事件(在這裡就是哪個Pin)觸發的中斷,所以我們加上這個if函數,並在最後執行完清除這個等待位元:void
EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0)
!= RESET){ //分辨中段來源
//實際執行的code放這
}
EXTI_ClearITPendingBit(EXTI_Line0);
//清除等待位元
}
如此一來就完成了一個完整的中斷設定!如果以我的這個範例,我想要按一下按鈕,LED亮,再按一下,LED滅。另外,我加了一個除彈跳的機制(稍後講解),所以我的EXTI0的ISR應該是這樣寫:
void
EXTI0_IRQHandler(void) {
if(EXTI_GetITStatus(EXTI_Line0)
!= RESET) {
Delay(0xFFFF); //debounce
if(GPIO_ReadInputDataBit(GPIOA,
GPIO_Pin_0)) { //debounce
if(btn) {
btn=0;
GPIO_Set Bits(GPIOB,
GPIO_Pin_8);
}
else {
btn++;
GPIO_ResetBits(GPIOB,
GPIO_Pin_8);
}
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
[按鈕的彈跳問題]
我們所使用的機械式按鈕,靠的是金屬接觸來導通,而在接觸的那當下,不可能很完美的馬上從高電位變低電位(或低變高),一定會有彈跳的效應,若不處理會使程式誤判 為連續按下多次 導致誤動作 處理彈跳效應有兩種方法
**(
延遲法) 用於 Key input
ext_interrupt 時 每當進入中斷向量時程式再等待幾毫秒後( >2ms) 之後判斷目前的按鈕狀態 ,再繼續執行。
** (中斷計數法) 用於 System timer IRQ
( 50HZ .. 100HZ) 定時中斷監控周邊IO 變化, 建立(u8 Key_buf) 值, 每次中斷來時 判讀GPIO = 0 時 key_buf = 0; GPIO = 1 key_buf++; 並設上限 if(key_buf<10 key_buf="" span="">後續以 key_buf > 5 判定 key 動作成立10>
沒有留言:
張貼留言