CM3 內核支持 256 個中斷,其中包含了 16 個內核中斷和 240個外部中斷,并且具有 256 級的可編程中斷設置。但STM32 并沒有使用CM3內核的全部東西,而是只用了它的一部分。STM32有 76 個中斷,包括16 個內核中斷和 60 個可屏蔽中斷,具有 16 級可編程的中斷優先級。而常用的就是這 60個可屏蔽中斷。
?NVIC 即嵌套向量中斷控制器,全稱 Nested vectored interrupt controller。屬于是內核的器件,其作用是對STM32中的中斷進行管理,因為M3內核中的中斷數量很多,當同時出現多個中斷時,優先處理哪個中斷?以及那些中斷不處理等,都要靠NVIC 進行控制。
在 MDK 內,與 NVIC 相關的寄存器,MDK 為其定義了如下的結構體:
typedef struct
{
vu32 ISER[2];
U32 RESERVEDO[30];
vu32 ICER[2];
u32 RSERVEDI[30];
vu32 ISPR[2];
u32 RESERVED2[30];
vu32 ICPR[2];
u32 RESERVED3[30];
vu32 IABR[2];
u32 RESERVED4[62];
vu32 IPR[15];
} NVIC TypeDef;
ISER[2]:ISER 全稱是:Interrupt Set-En able Registers ,這是一個中斷使能寄存器組上面說了 STM32 的可屏蔽中斷只有 60個,這里用了2個32 位的寄存器,總共可以表示64 個中斷。而 STM32 只用了其中的前 60 位。ISER[0]的 bit0~bit31 分別對應中斷 0~31。
ISER[1]的 bit0~27 對應中斷 32~59 ;這樣總共 60 個中斷就分別對應上了。你要使能某個中斷,必須設置相應的 ISER位為1,使該中斷被使能(這里僅僅是使能,還要配合中斷分組、屏蔽、I0 口映射等設置才算是一個完整的中斷設置)。
ICER[2]:全稱是:Interrupt Clear-Enable Registers ,是一個中斷除能寄存器組。該寄存器組與ISER 的作用恰好相反,是用來清除某個中斷的使能的。其對應位的功能,也和ICER 一樣。這里要專門設置一個ICER 來清除中斷位,而不是向 ISER 寫0來清除,是因為 NVIC的這些寄存器都是寫1有效的,寫0是無效的。
ISPR[2]:全稱是:Interrupt Set-Pending Registers ,是一個中斷掛起控制寄存器組。每個位對應的中斷和ISER 是一樣的。通過置1,可以將正在進行的中斷掛起,而執行同級或更高級別的中斷。寫0是無效的。
ICPR[2]:全稱是:Interrupt Clear-Pending Registers ,是一個中斷解掛控制寄存器組其作用與ISPR 相反,對應位也和 ISER 是一樣的。通過設置1,可以將掛起的中斷接掛寫 0無效。
IABR[2]:全稱是:Active Bit Registers,是一個中斷激活標志位寄存器組。對應位所代表的中斷和 ISER 一樣,如果為1,則表示該位所對應的中斷正在被執行。這是一個只讀寄存器,通過它可以知道當前在執行的中斷是哪一個。在中斷執行完了由硬件自動清零。
IPR[15]:全稱是:Interrupt Priority Registers ,是一個中斷優先級控制的寄存器組。這個寄存器組相當重要!STM32的中斷分組與這個寄存器組密切相關。IPR 寄存器組由15 個32bit 的寄存器組成,每個可屏蔽中斷占用 8bit,這樣總共可以表示 15*4-60 個可屏蔽中斷剛好和 STM32 的可屏蔽中斷數相等。IPR[0]的[31~24],[23~16],[15~8],[7~0]分別對應中中斷 3~0 ,依次類推,總共對應 60 個外部中斷。而每個可屏蔽中斷占用的 8bit 并沒有全部使用,而是只用了高4位。這 4位,又分為搶占優先級和子優先級。搶占優先級在前,子優先級在后。而這兩個優先級各占幾個位又要根據 SCB->AIRCR 中中斷分組的設置來決定。
STM32 將中斷分為5個組,組0~4 。該分組的設置是由 SCB->AIRCR 寄存器的 bit10~8來定義的。具體的分配關系如下表所示:
通過這個表,可以清楚的看到組 0~4 對應的配置關系,例如組設置為 4,那么此時所有的 60個中斷,每個中斷的中斷優先寄存器的高四位中的最高 4 位是搶占優先級,低 0位是響應優先級。每個中斷,你可以設置搶占優先級為0~15,響應優先級為0。搶占優先級的級別高于響應優先級。而數值越小所代表的優先級就越高。
假定設置中斷優先級組為 2,然后設置中斷 3(RTC 中斷)的搶占優先級為3,響應優先級為1。中斷6(外部中斷0)的搶占優先級為4,響應優先級為0。中斷7(外部中斷1)的搶占優先級為3,響應優先級為0。那么這 3 個中斷的優先級順序為:中斷 7>中斷 3>中斷 6。
如果兩個中斷的響應優先級和響應優先級都是一樣的話,則看哪個中斷先發生就先執高優先級的搶占優先級是可以打斷正在進行的低搶占優先級中斷的。
而搶占優先級相同的中斷,高優先級的響應優先級不可以打斷低響應優先級的中斷。上面例子中的中斷3 和中斷 7 都可以打斷中斷6 的中斷。而中斷 7 和中斷3 卻不可以相互打斷!
第一個介紹的是 NVIC 的分組函數 MY_NVIC_PriorityGroupConfig,該函數的參數NVIC_Group 為要設置的分組號,可選范圍為0~4 ,總共5 組。如果參數非法,將可能導致不可預料的結果。MY_NVIC_PriorityGroupConfig函數代碼如下:
MY_NVIC_PriorityGroupConfig(3);
void MY_NVIC_PriorityGroupConfig(u8 NVIC_Group)
{u32 temp, templ;temp1=(~NVIC_Group)&0x07;//取后三位templ<<=8;temp=SCB->AIRCR;/讀取先前的設置temp&=0X0000F8FF;//清空先前分組temp|=0X0SFA0000;//寫入鑰匙temp|-templ;SCB->AIRCR=temp;//設置分組
}
通過前面的介紹,我們知道 STM32的5 個分組是通過設置 SCB->AIRCR 的BITT[10:8]來實現的,而我們知道 SCB->AIRCR 的修改需要通過在高 16 位寫入0XO5FA 這個密鑰才能修改的,故在設置 AIRCR 之前,應該把密鑰加入到要寫入的內容的高 16 位,以保證能正常的寫入 AIRCR。在修改 AIRCR 的時候,我們一般采用讀-> 改-> 寫的步驟,來實現不改變 AIRCR 原來的其他設置。以上就是 MY_NVIC_PriorityGroupConfig函數設置中斷優先級分組的思路。
第二個函數是 NVIC 設置函數 MY_NVIC_Init,該函數有 4 個參數,分別為:
NVIC _PreemptionPriority 、NVIC_SubPriority 、NVIC_Channel、NVIC_Group。第一個參數NVIC_PreemptionPriority 為中斷搶占優先級數值,第二個參數 NVIC_SubPriority 為中斷子優先級數值,前兩個參數的值必須在規定范圍內,否則也可能產生意想不到的錯誤。第三個參數 NVIC _Channel 為中斷的編號(范圍為 0~59),最后一個參數 NVIC_Group 為中斷分組設置(范圍為0~4)。該函數代碼如下:
void MY_NVIC_Init(u8 NVIC__PreemptionPriority
u8 NVIC_SubPriority
u8 NVIC_Channel , u8 NVIC_Group)
{u32 temp;u8 IPRADDR=NVIC_Channel/4;/每組只能存4個,得到組地址u8 IPROFFSET=NVIC_Channel%4;//在組內的偏移IPROFFSET=IPROFFSET*8+4;// 得到偏移的確切位置MY_NVIC_PriorityGroupConfig(NVIC_Group)://設置分組temp=NVIC_PreemptionPriority<<(4-NVIC_Group);temp|=NVIC_SubPriority&(0x0f>>NVIC_Grup);temp&=0經//取低四位if(NVIC Channel<32)NVIC->ISER[0]=1<<NVIC_Channel;//使能中斷位(要清除的話,相反操作就 OK)else NVIC->ISER[1]|=1<<(NVIC_Channel-32);NVIC->IPR[IPRADDR]|=temp<<IPROFFSET;//設置響應優先級和搶斷優先級
}
通過前面的介紹,我們知道每個可屏蔽中斷的優先級的設置是在IPR寄存器組里面的,每個中斷占8位,但只用了其中的4個位,以上代碼就是根據中斷分組情況,來設置每個中斷對應的高4位的數值的。當然在該函數里面還引用MY_NVIC_PriorityGroupConfg 這個函數來設置分組。其實這個分組函數在每個系統里面只要設置一次就夠了,設置多次,則是以最后的那一次為準。但是只要多次設置的組號都是一樣,就沒事。否則前面設置的中斷會因為后面組的變化優先級會發生改變,這點在使用的時候要特別注意!
一個系統代碼里面,所有的中斷分組都要統一!!
以上代碼對要配置的中斷號默認是開啟中斷的。也就是ISER中的值設置為1了
通過上面兩個函數就實現了對 NVIC 的管理和配置。但是外部中斷的設置,還需要配相關寄存器才可以。下面就介紹
外部中斷的配置和使用。
中斷和事件的理解
中斷:要進入NVIC,有相應的中斷服務函數,需要CPU處理;
事件:不進入NVIC,僅用內部硬件自動控制,TIM,DMA,ADC等
STM32 的 EXTI控制器支持19個外部中斷/事件請求。每個中斷設有狀態位,每個中事件都有獨立的觸發和屏蔽設置。
STM32的19個外部中斷為(EXIT線說明):
線 0~15:對應外部 I0 口的輸入中斷。
線 16:連接到 PV 輸出。
線 17:連接到 RTC 鬧鐘事件。
線 18:連接到 USB 喚醒事件。
對于外部中斷 EXTI控制 MDK 定義了如下結構體:
ypedef struct
{
vu32 IMR;
vu32 EMR;
vu32 RTSR;
vu32 FTSR;
vu32 SWIER;
vu32 PR;
}EXTI TypeDef;
IMR :中斷屏蔽寄存器。這是一個 32 寄存器。但是只有前 19 位有效。當位 x 設置為1時,則開啟這個線上的中斷,否則關閉該線上的中斷。
該寄存器的主要作用只有一個,就是是否允許來自中斷線上的中斷信號進入NVIC中斷控制器。?
EMR:事件屏蔽寄存器,同IMR,只是該寄存器是針對事件的屏蔽和開啟。
RTSR:上升沿觸發選擇寄存器。該寄存器同IMR ,也是一個32為的寄存器,只有前 19 位有效。位 x 對應線x 上的上升沿觸發,如果設置為 1,則是允許上升沿觸發中斷/事件。否則,不允許。
?該寄存器主要用于控制輸入線進來的輸入信號,上升沿時是否在邊沿檢測電路被檢測出,20位共控制20條EXIT線;
FTSR:下降沿觸發選擇寄存器。同PTSR,不過這個寄存器是設置下降沿的。下降沿和上升沿可以被同時設置,這樣就變成了任意電平觸發了。
?該寄存器主要用于控制輸入線進來的輸入信號,下升沿時是否在邊沿檢測電路被檢測出,20位共控制20條EXIT線;?
SWIER:軟件中斷事件寄存器。通過向該寄存器的位x寫入1,在未設置 IMR 和EMR 的時候,將設置 PR 中相應位掛起。如果設置了IMR 和EMR 時將產生一次中斷。被設置的 SWIER 位,將會在 PR 中的對應位清除后清除。
PR:掛起寄存器。當外部中斷線上發生了選擇的邊沿事件,該寄存器的對應位會被置為1。0,表示對應線上沒有發生觸發請求。通過向該寄存器的對應位寫入1可以清除該位。在中斷服務函數里面經常會要向該寄存器的對應位寫1來清除中斷請求。
該寄存器的作用主要有兩個:
1、檢測外部中斷線上是否發生了選擇的邊沿事件,如果發生了,該位置1,并將信號傳遞給?與門電路,進而進入NVIC中;
2、在該位手動(軟件)寫入1,可以清除之前中斷信號的1,主要作用是進入中斷后,清除中斷位,防止多次進入中斷;
但是對于外部中斷來說,我們只需要掌握其中四個寄存器:上升沿觸發選擇寄存器(EXTI_RTSR)、下降沿觸發選擇寄存器(EXTI_FTSR)、掛起寄存器(EXTI_PR)與中斷屏蔽寄存器(EXTI_IMR)
通過以上配置就可以正常設置外部中斷了,但是外部I0 口的中斷,還需要一個寄存器配置,也就是 I0 復用里的外部中斷配置寄存器 EXTICR 。這是因為 STM32 任何一個 IO 口都可以配置成中斷輸入口,但是 10 口的數目遠大于中斷線數(16 個)。于是 STM32 就這樣設計,GPIOA~GPIOG 的[15:0]分別對應中斷線 15~0。這樣每個中斷線對應了最多 7個IO口,以線0為例:它對應了 GPIOA.0 、PIOB.0、GPIOC0 、GPIOD.0 、GPIOE.0、GPIOF.0、GPIOG.0。而中斷線每次只能連接到1個IO口上,這樣就需要 EXTICR 來決定對應的中斷線配置到哪個 GPIO 上了EXTICR 在 AFIO 的結構體中定義,如下:
typedef struct
{
vu32 EVCR;
vu32 MAPR;
vu32 EXTICR[4];
}AFIO TypeDef;
EXTICR 寄存器組,大小為4。每個 EXTICR 只用了其低 16 位。
比如我要設置 GPIOD.1 映射到1,則只要設置 EXTICR[0]的 bit[7:4] 為0011 即可。
默認都是 0000 即映射到 GPIOA。
void Ex_NVIC_Config(u8 GPIOx , u8 BITx, u8 TRIM)
{u8 EXTADDR;u8 EXTOFFSET;EXTADDR=BITx/4://得到中斷寄存器組的編號EXTOFFSET=(BITx%4)*4;RCC->APB2ENR|=0x01;//使能 io 復用時鐘AFIO->EXTICR[EXTADDR]|=GPIOx<<EXTOFFSET;//EXTI.BITx 映射到 GPIOx.BITx//自動設置EXTI->IMR|=1<<BITx;// 開啟 line BITx 上的中斷EXTI->EMR|=1<<BITx;//不屏蔽 line BITx 上的事件if(TRIM&0x01)EXTI->FTSR|=1<<BITx;//line BITx 上事件下降沿觸發if(TRIM&0x02)EXTI->RTSR|=1<<BITx://ine BITx 上事件上升降沿觸發
}
該函數有3 個參數:GPIOx 為 GPIOA~G(0~6 ),在 sys.h 里面有定義。代表要配置的 IO口。BITx則代表這個 IO 口的第幾位。TRIM 為觸發方式,低 2 位有效(0x01 代表下降觸發;0x02 代表上升沿觸發:0x03 代表意電平觸發)。
外部中斷配置函數只針對 GPIOA~G;不包括 fVD ,RTC 和 USB喚醒這三個;
參數:GPIOx:0~6 ,代表 GPIOA~G;BITx:需要使能的位;TRIM:觸發模式,1,下升沿;2 ,上降沿:3 ,任意電平觸發
該函數一次只能配置1個IO口,多個IO口,需多次調用 該函數會自動開啟對應中斷,以及屏蔽線.?
Ex_NVIC_Confg 完全是按照我們之前的分析來編寫的,首先根據 GPIOx 的位得到中斷寄存器組的編號,即 EXTICR 的編號,在 EXTICR 里面配置中斷線應該配置到 GPIOx的哪個位。然后使能該位的中斷及事件,最后配置觸發方式。這樣就完成了外部中斷的的配置了。從代碼中可以看到該函數默認是開啟中斷和事件的。其次還要注意的一點就是該函數一次只能配置一個 10 口,如果你有多個IO口需要配置,則多次調用這個函數就可以了。
STM32 的每個 IO 口都可以作為中斷輸入。要把 IO 口作為外部中斷輸入,有以下幾個步驟:
第一步 初始化 IO 口為輸入。
這一步設置你要作為外部中斷輸入的 IO 口的狀態,可以設置為上拉/下拉輸入,也可以設置為浮空輸入,但浮空的時候外部一定要帶上拉,或者下拉電阻。否則可能導致中斷不停的觸發。在干擾較大的地方,就算使用了上拉/下拉,也建議使用外部上拉/下拉電阻,這樣可以一定程度防止外部干擾帶來的影響。
第二步 開啟 IO 口復用時鐘,設置 IO 口與中斷線的映射關系。
STM32 的 IO 口與中斷線的對應關系需要配置外部中斷配置寄存器 EXTICR,這樣我們要先開啟復用時鐘,然后配置IO口與中斷線的對應關系。才能把外部中斷與中斷線連接起來。
第三步 開啟與該 IO 口相對的線上中斷/ 事件,設置觸發條件。
這一步,我們要配置中斷產生的條件,STM32可以配置成上升沿觸發,下降沿觸發,或者任意電平變化觸發,但是不能配置成高電平觸發和低電平觸發。這里根據自己的實際情況來配置。
第四步 配置中斷組(NVIC),并使能中斷。
這一步,我們就是配置中斷的分組,以及使能,對STM32的中斷來說,只有配置了NVIC的設置,并開啟才能被執行,否則是不會執行到中斷服務函數里面去的。
第五步 編寫中斷服務函數。
這是中斷設置的最后一步,中斷服務函數,是必不可少的,如果在代碼里面開啟了中斷,但是沒編寫中斷服務函數,就可能引起硬件錯誤,從而導致程序崩潰!所以在開啟了某個中斷后,一定要記得為該中斷編寫服務函數。在終端服務函數里面編寫你要執行的中斷后的操作。
點亮一個LED燈,按一下亮,按一下滅
通過寄存器實現
Void Exti_Init(void)
{RCC->APB2ENRI=1<<5;//使能PORTD時鐘
//第一步 初始化 IO 口為輸入GPIOD->CRH&=0XFFFFFOFF;//PD10設置成輸入GPIOD->CRHI=0X00000800;GPIOD->ODR|=1<<10;//PD10上拉
//第二、三步 開啟 IO 口復用時鐘,開啟與該 IO 口相對的線上中斷/ 事件,設置觸發條件Ex_NVIC_Config(GPIO_D,10,FTIR);//下降沿觸發
//第四步 配置中斷組(NVIC),并使能中斷。MY_NVIC_Init(2,1,EXTI15_10_IRchannel,2);//搶占2,子優先
}
//第五步 編寫中斷服務函數
void EXTI15_10_IRHandler(void)
{delay_ms(10);if(KEY5==0)LEDO=!LEDO;EXTI->PR=1<<10;//清除LINE10上的中斷標志位
}