習(xí)之外部中斷)
1. 中斷常見(jiàn)的書(shū)上關(guān)于中斷舉的例子差不多就是這樣“你正在看書(shū)廚房的水開(kāi)了你起來(lái)去關(guān)火關(guān)了火以后繼續(xù)看書(shū)”這種例子說(shuō)他好吧差不多把中斷的概念描述清楚了但是根本沒(méi)辦法和單片機(jī)的聯(lián)系起來(lái)因?yàn)檫@種例子所描述的中斷的概念光看“中斷”這兩個(gè)字也能猜個(gè)八九不離十說(shuō)了等于沒(méi)說(shuō)實(shí)際上從正面去講中斷的概念也并不復(fù)雜我甚至覺(jué)得不舉這種例子反而更好理解計(jì)算機(jī)程序正常情況都是從上到下依次執(zhí)行的當(dāng)發(fā)生一些內(nèi)部或者外部事件的時(shí)候CPU本來(lái)應(yīng)該繼續(xù)向下執(zhí)行的但是卻沒(méi)有繼續(xù)向下執(zhí)行反而從當(dāng)前執(zhí)行的代碼處離開(kāi)去執(zhí)行別處的代碼這個(gè)過(guò)程就叫中斷這些內(nèi)部或者外部的事件就叫做中斷源。這里指討論外部中斷等后面學(xué)到了其他的中斷在討論其他的。1.1 NVIC與EXTI眾所周知單片機(jī)的MCU是由內(nèi)核和片上外設(shè)組成的內(nèi)核就是一般說(shuō)的某某內(nèi)核的芯片比如戰(zhàn)艦開(kāi)發(fā)板采用 ARM Cortex-M3 內(nèi)核內(nèi)核就包括CPU核心以及一些內(nèi)部外設(shè)內(nèi)部外設(shè)的內(nèi)是相對(duì)內(nèi)核說(shuō)的而片上外設(shè)就是內(nèi)核外面的那些外設(shè)下圖我框起來(lái)的部分Cortex-M3’s internal peripherals這部分映射的地址就是控制內(nèi)部外設(shè)的寄存器地址而右邊的那些(我的截圖不完整只截了一部分)非常多的就是片上外設(shè)的寄存器地址了。NVIC是內(nèi)部外設(shè)的一種EXTI則是片上外設(shè)他們兩個(gè)搭配起來(lái)就構(gòu)成了外部中斷的完整配置了。1.1.1 外部中斷與Cortex-M3內(nèi)核所謂外部中斷與M3內(nèi)核的關(guān)系其實(shí)就是介紹M3內(nèi)核的NVIC在啟動(dòng)文件中可以看到一個(gè)叫做g_pfnVectors的變量(我用的不是keil編譯器有可能和keil的啟動(dòng)文件會(huì)有些許差異估計(jì)應(yīng)該差不了很多)這個(gè)變量是指向中斷向量表的指針中斷向量表里存的就是中斷服務(wù)函數(shù)的地址當(dāng)我們進(jìn)行了一些恰當(dāng)?shù)呐渲靡院笕绻獠坑|發(fā)了某個(gè)中斷CPU就會(huì)去這個(gè)表里找對(duì)應(yīng)函數(shù)的地址然后去該地址執(zhí)行相應(yīng)的中斷服務(wù)函數(shù)比如在正點(diǎn)原子的外部中斷例程中有一個(gè)頭文件是exti.h,里面有一個(gè)宏定義是#define KEY0_INT_IRQHandler EXTI4_IRQHandler這個(gè)宏的右邊就是下圖啟動(dòng)文件的第159行左邊就是對(duì)應(yīng)的中斷服務(wù)函數(shù)因?yàn)镃語(yǔ)言的函數(shù)名就是函數(shù)的地址所以這樣寫(xiě)沒(méi)啥問(wèn)題這也能解釋為什么最底層的中斷函數(shù)無(wú)人調(diào)用了當(dāng)中斷觸發(fā)的時(shí)候由CPU去自己找對(duì)應(yīng)的函數(shù)。中斷向量表的這些函數(shù)順序是芯片公司出廠就定義好的我們只能服從不能隨意修改。NVIC的寄存器介紹不在《STM32F10xxx參考手冊(cè)》中需要去《Cortex-M3權(quán)威指南》中查找相關(guān)寄存器的介紹關(guān)于這部分寄存器在代碼中的定義位于頭文件core_cm3.h中代碼中的名稱(chēng)和權(quán)威指南中的名稱(chēng)不太一樣我在下面的表做個(gè)記錄寄存器功能權(quán)威指南中的名稱(chēng)代碼中的名稱(chēng)中斷使能ISERSETENA中斷失能ICERCLRENA中斷掛起ISPRSETPEND中斷解掛ICPRCLRPEND中斷激活標(biāo)志寄存器IABRACTIVE中斷優(yōu)先級(jí)寄存器IPPRI還有一個(gè)軟件觸發(fā)中斷寄存器這個(gè)我目前還不知道在那些場(chǎng)合會(huì)用到估計(jì)是操作系統(tǒng)吧反正也和外部中斷沒(méi)關(guān)系后面遇到了再說(shuō)。中斷的使能與失能寄存器SETENAs和CLRENAs表示中斷的使能和去使失能寄存器s可以取0到7,總共8對(duì)寄存器每個(gè)寄存器又有32位按照每一位來(lái)配置一個(gè)中斷來(lái)看最多可以配置32*8256個(gè)中斷而M3內(nèi)核的SETENA7和CLRENA7只使用了16位所以M3最多可以支持240個(gè)中斷而在STM32F103ZET6中被進(jìn)一步裁減只使用了60位也就是這兩組寄存器中s0,1的情況其他的外設(shè)寄存器使能和失能一般是寫(xiě)1使能寫(xiě)0失能NVIC比較特殊使能某個(gè)中斷就給SETENAs對(duì)應(yīng)的位寫(xiě)1失能就是給CLRENAs對(duì)應(yīng)位寫(xiě)1,寫(xiě)0沒(méi)啥用如果使能和失能寄存器的對(duì)應(yīng)位都寫(xiě)1的話那就是哪個(gè)后執(zhí)行哪個(gè)生效先失能后使能那中斷有效先使能后失能那中斷無(wú)效不過(guò)應(yīng)該沒(méi)人會(huì)這么干吧中斷的掛起與解掛起寄存器中斷在執(zhí)行的時(shí)候來(lái)了高優(yōu)先級(jí)的中斷會(huì)打斷當(dāng)前中斷而來(lái)了同優(yōu)先級(jí)或者低優(yōu)先級(jí)的中斷那么中斷就不能立即得到響應(yīng)此時(shí)中斷被掛起。中斷的掛起狀態(tài)可以通過(guò)“中斷掛起寄存器(SETPEND)”和“中斷解掛起寄存器(CLRPEND)”來(lái)讀取還可以通過(guò)軟件寫(xiě)入對(duì)應(yīng)位來(lái)手動(dòng)掛起中斷不過(guò)使用情況不多一般都是硬件置位與清零。如果需要軟件寫(xiě)入的話具體的使用方法和前面的使能和失能寄存器相同。中斷激活標(biāo)志寄存器當(dāng)CPU進(jìn)入了某個(gè)中斷服務(wù)函數(shù)以后執(zhí)行了第一條匯編指令之后中斷激活標(biāo)志寄存器的對(duì)應(yīng)位就會(huì)被硬件置1這是一個(gè)只讀寄存器通過(guò)讀這個(gè)寄存器就能知道當(dāng)前正在執(zhí)行哪個(gè)中斷。這類(lèi)寄存器和之前一樣也是總共8個(gè)除了最后一個(gè)寄存器使用了16位之外其余的每一位對(duì)應(yīng)一個(gè)中斷源。中斷優(yōu)先級(jí)配置寄存器通過(guò)配置這個(gè)寄存器就可以配置兩個(gè)或以上的中斷同時(shí)觸發(fā)的時(shí)候先執(zhí)行哪個(gè)再執(zhí)行哪個(gè)這類(lèi)寄存器總共有240個(gè)和之前一樣STM32只用到了60個(gè)每一個(gè)寄存器對(duì)應(yīng)一個(gè)中斷源但是這類(lèi)寄存器只有八位所以它們相鄰的寄存器之間的地址偏移量是1上面提到的其他的寄存器偏移量是4。每個(gè)寄存器可以把寄存器的8位分成兩組數(shù)來(lái)分別表示搶占優(yōu)先級(jí)和響應(yīng)優(yōu)先級(jí)搶占優(yōu)先級(jí)高的中斷可以先執(zhí)行并且搶占優(yōu)先級(jí)高的中斷可以打斷搶占優(yōu)先級(jí)低的中斷搶占優(yōu)先級(jí)相同的情況下響應(yīng)優(yōu)先級(jí)高的先執(zhí)行搶占優(yōu)先級(jí)相同但是響應(yīng)優(yōu)先級(jí)高的中斷不能打斷響應(yīng)優(yōu)先級(jí)低的中斷如果這倆優(yōu)先級(jí)都一樣那就按自然優(yōu)先級(jí)來(lái)決定執(zhí)行順序自然優(yōu)先級(jí)就是在中斷向量表里的先后順序。搶占優(yōu)先級(jí)和響應(yīng)優(yōu)先級(jí)的英文翻譯是preempt priority和subpriority根據(jù)pre和sub這兩個(gè)英文前綴也能看出來(lái)這兩個(gè)優(yōu)先級(jí)是什么意思不過(guò)我也不知道位啥要翻譯成響應(yīng)優(yōu)先級(jí)這么怪的名字也許學(xué)到后面就懂了吧。1.1.2 外部中斷與STM32F103ZET6NVIC是內(nèi)核控制中斷的外設(shè)所有具有cortex-M3內(nèi)核的芯片都大差不差而如何把中斷信息傳遞到NVIC就是片上外設(shè)的事情了在stm32f103xe.h這個(gè)頭文件中定義了一個(gè)如下圖所示的枚舉82行的注釋下面的部分總共有60個(gè)就是之前說(shuō)的可以把中斷信號(hào)傳遞到NVIC的60個(gè)外設(shè)其中第89到93行的EXTI就是根據(jù)外部的電平變化配置的中斷也是這次學(xué)習(xí)的主要內(nèi)容。EXTI是外部中斷和事件控制器根據(jù)上面的圖可以看出來(lái)這個(gè)芯片總共有五根EXTI線每根線之間相互獨(dú)立可以分別進(jìn)行不同的配置比如輸入類(lèi)型和觸發(fā)事件觸發(fā)事件指的是外部信號(hào)的上升沿觸發(fā)下降沿觸發(fā)或者雙沿觸發(fā)而輸入類(lèi)型這個(gè)名詞是從正點(diǎn)原子給的開(kāi)發(fā)指南上看到的我覺(jué)得更應(yīng)該叫做EXTI的輸出類(lèi)型EXTI接收的信號(hào)是外部的電平變化信號(hào)它對(duì)這些信號(hào)處理了以后再進(jìn)行產(chǎn)生脈沖或者傳遞信號(hào)至NVIC所以相比EXTI來(lái)說(shuō)它更應(yīng)該被叫做輸出類(lèi)型。來(lái)看參考手冊(cè)中的這個(gè)外部中斷/事件控制器框圖的意思是EXTI是由 20 個(gè)產(chǎn)生事件/中斷請(qǐng)求的邊沿檢測(cè)器組成總共20根并行的獨(dú)立的線右下角的輸入線三電平變化信號(hào)輸入的地方它可以是任何一個(gè)GPIO口或者是一些其他外設(shè)事件進(jìn)來(lái)之后經(jīng)過(guò)一個(gè)邊沿檢測(cè)電路這個(gè)邊沿檢測(cè)電路上面的那倆寄存器就是用來(lái)配置是上升沿下降沿還是雙沿觸發(fā)的通過(guò)配置對(duì)應(yīng)的寄存器就可以配置相應(yīng)的中斷源了然后經(jīng)過(guò)一個(gè)與軟件中斷事件寄存器組成的或門(mén)電路這個(gè)寄存器一般也用不到配置一直輸出0然后就到了EXTI的輸出岔路了一條是與事件屏蔽寄存器組成的與門(mén)電路另一條傳輸至請(qǐng)求掛起寄存器先看和事件屏蔽寄存器組成的這個(gè)與門(mén)電路這個(gè)與門(mén)電路的輸出是為了給其他外設(shè)電路產(chǎn)生脈沖信號(hào)比如定時(shí)器等通過(guò)配置事件屏蔽寄存器就可以屏蔽相應(yīng)的外設(shè)事件從而不產(chǎn)生對(duì)應(yīng)的脈沖信號(hào)起到屏蔽事件的作用另外一路傳到請(qǐng)求掛起寄存器當(dāng)信號(hào)傳到這個(gè)寄存器的時(shí)候?qū)?yīng)位就會(huì)被硬件置位然后告訴CPU現(xiàn)在來(lái)了個(gè)中斷能不能處理如果此時(shí)有更高優(yōu)先級(jí)的中斷正在執(zhí)行現(xiàn)在不能處理這個(gè)中斷的話CPU就會(huì)把NVIC的掛起寄存器也置位然后這個(gè)中斷就被掛起了如果可以執(zhí)行的話CPU就把這個(gè)中斷執(zhí)行了但是這個(gè)請(qǐng)求掛起寄存器的對(duì)應(yīng)位清零的工作就得通過(guò)代碼來(lái)實(shí)現(xiàn)了一般都會(huì)放到中斷服務(wù)函數(shù)里面來(lái)清零很多初學(xué)者一開(kāi)始看到中斷的代碼里清標(biāo)志位也許都會(huì)一頭霧水至少我有一頭霧水這里就解釋了這個(gè)標(biāo)志位的來(lái)源了然后信號(hào)經(jīng)過(guò)中斷屏蔽寄存器組成的與門(mén)之后進(jìn)入NVIC這里和剛才說(shuō)的事件屏蔽寄存器哪個(gè)與門(mén)意思差不多所以這里就是配置中斷屏蔽寄存器就是字面意思了就是為了屏蔽某一路中斷從而讓這個(gè)與門(mén)輸出0切斷它與NVIC的聯(lián)系。1.2 EXTI與GPIO配置剛才提到EXTI的輸入可以是任何一個(gè)GPIO口或者一些其他的外設(shè)事件總結(jié)如下EXTI線對(duì)應(yīng)連接0-15GPIO輸入16PVD輸出17RTC鬧鐘18USB喚醒19以太網(wǎng)喚醒除了前面的0-15是現(xiàn)在需要討論的后面的都暫時(shí)不看因?yàn)槲乙膊粫?huì)而且戰(zhàn)艦開(kāi)發(fā)板不是互聯(lián)型產(chǎn)品所以第19的以太網(wǎng)喚醒是沒(méi)有的。stm32f103系列總共有GPIOxx A - G7組每組16個(gè)總共7*16112個(gè)GPIO上面對(duì)應(yīng)的中斷線只有16根顯然不夠但是注意到GPIO每組的個(gè)數(shù)和中斷線的條數(shù)是一樣的所以芯片公司的設(shè)計(jì)也是這樣的第0根中斷線對(duì)應(yīng)的IO口可以選擇GPIOA.0、 GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0 和 GPIOG.0中的一個(gè)第1根可以選擇GPIOA.1、 GPIOB.1、GPIOC.1、GPIOD.1、GPIOE.1、GPIOF.1 和 GPIOG.1中的一個(gè)剩下的就同理了所以說(shuō)GPIO能同時(shí)啟用中斷的IO口最多只有16個(gè)其實(shí)也很顯然因?yàn)橹挥?6根線嘛配置這個(gè)對(duì)應(yīng)關(guān)系的寄存器是這些AFIO_EXTICR1 ~ AFIO_EXTICR4有沒(méi)有感覺(jué)亂七八糟的打開(kāi)芯片手冊(cè)找到描述這些寄存器的地方就看AFIO_EXTICR2吧它指的是第4到7根中斷線可選擇的IO口每四位組成一個(gè)四位整數(shù)以此來(lái)選擇GPIO的A-G其余的中斷配置方法就在這個(gè)同名的其他寄存器這個(gè)比較簡(jiǎn)單就到此為止了。1.2 外部中斷的配置整體來(lái)看外部中斷配置的過(guò)程分為三個(gè)部分第一部分是把GPIO映射到對(duì)應(yīng)的中斷上使得中斷信號(hào)能傳到EXTI第二部分是配置EXTI使得信號(hào)能傳遞到NVIC最后一部分就是NVIC的配置了接下來(lái)就來(lái)點(diǎn)個(gè)燈吧。1.2.1 IO口的配置首先要通過(guò)看原理圖來(lái)知道我們是從哪里獲取中斷源的下面這張圖的四個(gè)開(kāi)關(guān)就是獲取中斷源的當(dāng)開(kāi)關(guān)按下的時(shí)候MCU根據(jù)相應(yīng)的電平變化來(lái)進(jìn)入相應(yīng)的中斷服務(wù)。比如KEY_UP這個(gè)開(kāi)關(guān)按下以后MCU的PA0就會(huì)變成3.3V的高電平電平變化是由低到高所以我們要把它對(duì)應(yīng)的中斷配制成上升沿觸發(fā)其余的三個(gè)開(kāi)關(guān)按下的時(shí)候?qū)?yīng)的IO口就會(huì)變成低電平電平變化是由高到低所以要配制成下降沿觸發(fā)。它是要接收外部信號(hào)的所以肯定配制成輸入模式又因?yàn)橐杉娖阶兓员厝贿€得有一個(gè)上拉或者下拉來(lái)配制初始狀態(tài)以KEY_UP為例看一下。voidgpio_init(void){WKUP_GPIO_CLK_ENABLE();/* WKUP時(shí)鐘使能 */sys_gpio_set(WKUP_GPIO_PIN,SYS_GPIO_MODE_IN,SYS_GPIO_PUPD_PULLDOWN);/* WKUP引腳模式設(shè)置為下拉輸入 */}1.2.2 EXTI的配置EXTI的配置就兩步第一步就是配置AFIO來(lái)映射中斷配置之前先使能AFIO時(shí)鐘第二步是配置上升沿和下降沿。這個(gè)函數(shù)和正點(diǎn)原子會(huì)有點(diǎn)不同為了方便理解我把官方例程重新寫(xiě)了但是這樣的結(jié)果就是每次只能配置一個(gè)IO口而官方例程可以同時(shí)配置同一組GPIO的多個(gè)口不過(guò)也沒(méi)啥用即使官方可以同時(shí)配置多個(gè)它的例程也是寫(xiě)了多次而且這么多IO口在實(shí)際應(yīng)用中應(yīng)該也不會(huì)大量的使用同一組的GPIO的中斷。voidsys_nvic_ex_config(GPIO_TypeDef*p_gpiox,uint16_tpinx,uint8_ttmode){uint8_tgpio_num0;/* gpio編號(hào), 0~7, 代表GPIOA~GPIOG *//* 計(jì)算gpio編號(hào),因?yàn)槊恳唤MGPIO之間的地址偏移相差0x400所以要除以它 */gpio_num((uint32_t)p_gpiox-(uint32_t)GPIOA)/0X400;RCC-APB2ENR|10;/* AFIO 1,使能AFIO時(shí)鐘 *//* 配置AFIO寄存器*/switch(gpio_num){case0:case1:case2:case3:AFIO-EXTICR[0]~(0x000F(pinx%4));AFIO-EXTICR[0]|(gpio_num(pinx%4));break;case4:case5:case6:case7:AFIO-EXTICR[1]~(0x000F(pinx%4));AFIO-EXTICR[1]|(gpio_num(pinx%4));break;case8:case9:case10:case11:AFIO-EXTICR[2]~(0x000F(pinx%4));AFIO-EXTICR[2]|(gpio_num(pinx%4));break;case12:case13:case14:case15:AFIO-EXTICR[3]~(0x000F(pinx%4));AFIO-EXTICR[3]|(gpio_num(pinx%4));break;}/*配置上下拉觸發(fā)模式tmode遵循官方例程定義*/if(tmode0x01)EXTI-FTSR|1gpio_num;if(tmode0x02)EXTI-RTSR|1gpio_num;}最后就是EXTI的初始化了直接調(diào)用上面的函數(shù)就行了voidextix_init(void){sys_nvic_ex_config(KEY0_INT_GPIO_PORT,WKUP_GPIO_PIN,SYS_GPIO_RTIR);/* WKUP配置為上升沿觸發(fā)中斷 */}1.2.3 NVIC的配置NVIC的配置就是配置一些中斷優(yōu)先級(jí)分組以及配置中斷優(yōu)先級(jí)之類(lèi)的voidsys_nvic_init(uint8_tpprio,uint8_tsprio,uint8_tch,uint8_tgroup){uint32_ttemp;sys_nvic_priority_group_config(group);/* 設(shè)置分組不設(shè)置的話會(huì)使用默認(rèn)的分組0 */temppprio(4-group);/*把pprio放到表示搶占優(yōu)先級(jí)的高位*/temp|sprio(0x0fgroup);/*把pprio放到表示響應(yīng)優(yōu)先級(jí)的低位*/temp0xf;/* 取低四位 */NVIC-ISER[ch/32]|(1(ch%32));/* 使能中斷位(要清除的話,設(shè)置ICER對(duì)應(yīng)位為1即可) */NVIC-IP[ch]|temp4;/* 設(shè)置響應(yīng)優(yōu)先級(jí)和搶斷優(yōu)先級(jí) */}然后就是NVIC初始化其實(shí)是沒(méi)必要分這么細(xì)的就像官方例程那樣這些初始化的東西就應(yīng)該和上面的別的初始化內(nèi)容放在一起。voidnvic_init(void){sys_nvic_init(3,2,WKUP_INT_IRQn,2);/* 搶占3子優(yōu)先級(jí)2組2 */}最后就該寫(xiě)中斷服務(wù)函數(shù)了這個(gè)WK_UP開(kāi)關(guān)對(duì)應(yīng)的是MCU的PA0所以對(duì)應(yīng)第0根中斷線也就是需要寫(xiě)一個(gè)上面啟動(dòng)文件中和89行同名的函數(shù)不過(guò)就像一開(kāi)始說(shuō)的一般都使用宏定義起更好聽(tīng)的名字了。#defineWKUP_INT_IRQHandlerEXTI0_IRQHandlervoidWKUP_INT_IRQHandler(void){delay_ms(20);/* 消抖 */EXTI-PRWKUP_INT_GPIO_PIN;/* 清除WKUP所在中斷線 的中斷標(biāo)志位 */if(WK_UP1){BEEP_TOGGLE();/* LED2 狀態(tài)取反 */}}通過(guò)看Cortex-M3權(quán)威指南發(fā)現(xiàn)中斷遠(yuǎn)非上面寫(xiě)的這么簡(jiǎn)單不過(guò)對(duì)于日常的產(chǎn)品使用應(yīng)該也勉強(qiáng)夠用了來(lái)日方長(zhǎng)慢慢學(xué)。