前題:
閉門造車,兩周了,經過各種的思考和求問,反復閱讀了<<M3權威指南>>和<<stm32不完全手冊>>的相關章節,以及開發板廠商的實驗例程,對stm32這塊中斷終有所悟,是以記之。
至於中斷的什么優先級,什么優先級分組,使能之類的原理,就不再贅述。這里主要是記載以下如何使用中斷,以及中斷配置函數的實現過程,其中並敘述我曾經的疑惑和感悟。
我的開發板里的中斷例程是用按鍵控制一個燈亮和滅的兩個狀態。
這個例程的實現過程如下描述:
第一步,將一個I/O口配置成中斷輸入模式。
這里需要注意的是,GPIO本身是沒有中斷功能神馬的。如果硬要使他產生中斷輸入方式,就得將相應的端口映射到相應的外部事件上去。而其他外設是有中斷功能的,直接使能/失能其中斷即可,比如USART,直接開啟其發送/接收中斷,那么USART也就相應的采取中斷方式進行工作了。
而這一點,是我開始很疑惑的:為啥GPIO口使用中斷方式進行工作的時候就必須要映射到外部事件上去,而其他就不呢?百度網友的解惑是:比如USART產生的中斷,是沒有經過EXTI,而是直接將中斷放入了NVIC;但是GPIO它作為中斷源,是要經過EXTI的。仔細參看下面兩個圖,其實就會恍然大悟:
這第一步就是作為輸入中斷源的I/O口的相關配置,例程庫函數如下:
1 void BUTTON_Configuration(void) 2 { 3 GPIO_InitTypeDef GPIO_InitStructure; 4 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; 5 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12; 6 GPIO_Init(GPIOD, &GPIO_InitStructure); 7 8 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO,ENABLE); 9 GPIO_EXTILineConfig(GPIO_PortSourceGPIOD , GPIO_PinSource11); 10 GPIO_EXTILineConfig(GPIO_PortSourceGPIOD , GPIO_PinSource12); 11 }
因為我板子上的例程是按鍵輸入中斷,所以函數名字就寫的按鍵配置吧;
3~5行,就是gpio口的普通配置,學習單片機開天辟地,就先是gpio口,這個沒啥稀奇的了,沒啥可說的了;我的板子上是PD^11,PD^12兩個端口作為中斷輸入的。
8行,注意這個時候,要使能GPIO口的復用時鍾功能。
9~10行,就是將PD^11,PD^12映射到外部事件線上去。在keil中跳轉到其函數實現中:
1 void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource) 2 { 3 uint32_t tmp = 0x00; 4 /* Check the parameters */ 5 assert_param(IS_GPIO_EXTI_PORT_SOURCE(GPIO_PortSource)); 6 assert_param(IS_GPIO_PIN_SOURCE(GPIO_PinSource)); 7 8 tmp = ((uint32_t)0x0F) << (0x04 * (GPIO_PinSource & (uint8_t)0x03)); 9 AFIO->EXTICR[GPIO_PinSource >> 0x02] &= ~tmp; 10 AFIO->EXTICR[GPIO_PinSource >> 0x02] |= (((uint32_t)GPIO_PortSource) << (0x04 * (GPIO_PinSource & (uint8_t)0x03))); 11 }
5~6行,用庫函數的都知道,就是兩個宏定義,起到的作用是對相關的數據神馬的進行正確性檢查。
8行,GPIO_PinSource這個是外面BUTTON_Configuration()調用GPIO_EXTILineConfig()時傳的參數。可能都不知道8行這個式子為啥要這么寫。先看看我例程中是如何傳的參:
GPIO_EXTILineConfig(GPIO_PortSourceGPIOD , GPIO_PinSource11);
也即是GPIO_PinSource <==>GPIO_PinSource11;那么GPIO_PinSource11是個什么東西呢:官方庫已經這樣定義了:
#define GPIO_PinSource11 ((uint8_t)0x0B)
如果按照我的例程,8行這個式子中,tmp == 0x0F << (0x04 *(0x0B & 0x03)==>tmp = 0x0F000;先不管這個數字是個啥意思,反正它就是個數字,它其實是為了第9行寄存器的值服務的-->既然如此,如果用寄存器寫的話,我可以直接給寄存器某個值,何必要山路十八彎呢?當然,庫函數,是有通用性的,就像一個數學公式的作用。
9行:AFIO_EXTICR:外部事件控制<配置>寄存器。在數據手冊中顯示,有4個,它們分別對應的是各個外部事件exit_x<x = 0~15>。每個寄存器對應4個外部事件,於是4x4 = 16。,注意數據手冊中的編號是從1開始的,而不是0開始的;但是,MDK中是0~3的。於是這里把相關值帶進來一看,第九行其實就變成了如下式子:
AFIO->EXTICR[GPIO_PinSource >> 0x02] &= ~tmp;
AFIO->EXTICR[2] &= ~0xF000;==>AFIO->EXTICR[2] &= 0x0FFF;
什么意思?不就是將這個寄存器的第12~15清零嗎?不就是將數據手冊中第AFIO_EXTICR3寄存器的12~15清零么?再次注意:該寄存器在MDK中是0~3的,數據手冊中的編號是從1開始的,而不是0開始的;
這個樣,9行以前的一切的操作,就是為了給該寄存器的某個位進行清零嘛,至於具體清哪一位,還得看你映射到哪一位。
10行:引腳選擇了,現在就選擇這個引腳是哪個端口的,我的是D端口,那么按照官方對D端口的定義如下:
#define GPIO_PortSourceGPIOD ((uint8_t)0x03)
AFIO->EXTICR[2] |= 0x03 << 12;查看數據手冊,恰好是設置成了D口的第11號端子上嘛。
於是第一步總結是:
1)外部事件寄存器相關位清零;
2)設置輸入端子的編號
3)設置端口編號
注:一共有A~G個端口嘛,而每個端口上又有N多端子,這個參看GPIo那章數據手冊。
悟出:庫函數確實方便,具有公式效應,但山路十八彎,在這里,該例程中,要實現該功能,用寄存器,就兩條語句嘛:
1 AFIO->EXTICR[2] &= 0x0FFF; 2 AFIO->EXTICR[2] |= 0x03 << 12;
1行:12~15清零
2行:0x03:表示是PD端口 ;0x03 << 12:表示在AFIO_EXTICR寄存器中的第12位開始寫入0x03這個值,而括號中的2,說明是第三個寄存器,這樣一組合,恰好就是PD^11了.描述起來一長串,如果看對照個看數據手冊的話,就一目了然了。而這里唯一會讓人凌亂的是:這個寄存器在數據手冊上的編號和MDK中的編號不一致。自己在細讀了<<stm32不完全手冊>>才發現這個問題,開始可是百思不得其解呀。
總結下第一步要做的事情:
1)初始化I/O口為輸入;
2)開啟I/O復用時鍾,並設置外部事件映射關聯。
接下來是第二步:
第一步是將外部GPIO口映射到某外部事件上去。那么接下來,就該對該外部事件進行配置了,包括外部事件線路的選擇、觸發條件、使能。這里需要理解清楚的是,GPIO口和外部事件是各自獨立的,它們並不是一體的---詳細理解第一步,將GPIO口映射到某外部事件,可以看出GPIO和外部事件這個東西是兩個不同的東西,在這里,GPIO的映射,無非就是GPIO口搭了外部事件的一趟順風車。也所以,外部事件依然是要配置和使能的,不能說,將GPIO口映射到外部事件就可以產生中斷了。
接下來看看例程中外部事件的配置函數:
1 void EXTI_Configuration(void) 2 { 3 EXTI_InitTypeDef EXTI_InitStructure; 4 /*PD11外部中斷輸入*/ 5 EXTI_InitStructure.EXTI_Line = EXTI_Line11; 6 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; 7 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; 8 EXTI_InitStructure.EXTI_LineCmd = ENABLE; 9 EXTI_Init(&EXTI_InitStructure); 10 11 /*PD12外部中斷輸入*/ 12 EXTI_InitStructure.EXTI_Line = EXTI_Line12; 13 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; 14 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; 15 EXTI_InitStructure.EXTI_LineCmd = ENABLE; 16 EXTI_Init(&EXTI_InitStructure); 17 }
可以看出,5~8;12~15行,無非就是在填充一個接頭體。而真正實在的是9、16行:它們是外部事件初始化函數,把前面填充的結構體地址作為參數,傳進EXTI_INit()。
在MDK中右鍵EXTI_Init()跳轉到該函數的實現中去,代碼如下:
1 void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct) 2 { 3 uint32_t tmp = 0; 4 5 /* Check the parameters */ 6 assert_param(IS_EXTI_MODE(EXTI_InitStruct->EXTI_Mode)); 7 assert_param(IS_EXTI_TRIGGER(EXTI_InitStruct->EXTI_Trigger)); 8 assert_param(IS_EXTI_LINE(EXTI_InitStruct->EXTI_Line)); 9 assert_param(IS_FUNCTIONAL_STATE(EXTI_InitStruct->EXTI_LineCmd)); 10 11 tmp = (uint32_t)EXTI_BASE; 12 13 if (EXTI_InitStruct->EXTI_LineCmd != DISABLE) 14 { 15 /* Clear EXTI line configuration */ 16 EXTI->IMR &= ~EXTI_InitStruct->EXTI_Line; 17 EXTI->EMR &= ~EXTI_InitStruct->EXTI_Line; 18 19 tmp += EXTI_InitStruct->EXTI_Mode; 20 21 *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line; 22 23 /* Clear Rising Falling edge configuration */ 24 EXTI->RTSR &= ~EXTI_InitStruct->EXTI_Line; 25 EXTI->FTSR &= ~EXTI_InitStruct->EXTI_Line; 26 27 /* Select the trigger for the selected external interrupts */ 28 if (EXTI_InitStruct->EXTI_Trigger == EXTI_Trigger_Rising_Falling) 29 { 30 /* Rising Falling edge */ 31 EXTI->RTSR |= EXTI_InitStruct->EXTI_Line; 32 EXTI->FTSR |= EXTI_InitStruct->EXTI_Line; 33 } 34 else 35 { 36 tmp = (uint32_t)EXTI_BASE; 37 tmp += EXTI_InitStruct->EXTI_Trigger; 38 39 *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line; 40 } 41 } 42 else 43 { 44 tmp += EXTI_InitStruct->EXTI_Mode; 45 46 /* Disable the selected external lines */ 47 *(__IO uint32_t *) tmp &= ~EXTI_InitStruct->EXTI_Line; 48 } 49 }
第11行,就是存儲了一個地址值。可以先看看它們是怎么定義的:
1 #define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */ 2 #define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) 3 #define EXTI_BASE (APB2PERIPH_BASE + 0x0400)
如果對stm32框架掌握的足夠熟悉,這里一眼便知端倪:GPIO口是掛在APB2上的,APB2是連在AHB總線上的,AHB再連到總線矩陣上的,環環相套,牽一發而動全身。附圖如下:
第16行、17行,分別是EXTI_IMR中斷事件屏蔽寄存器和外部事件屏蔽寄存器,相應的位為0的時候,它們屏蔽相應線上的中斷/事件請求。帶入例程中的值算算
1 #define EXTI_Line11 ((uint32_t)0x00800) /*!< External interrupt line 11 */ 2 #define EXTI_Line12 ((uint32_t)0x01000) /*!< External interrupt line 12 */
先將事件11的值帶入下面兩個式子,換算如下:
1 EXTI->IMR &= ~0x00800; 2 EXTI->EMR &= ~0x00800;
表示,將寄存器EXTI_IMR的11位清零,將EXTI_EMR的11位清零<下標從0開始>--意思是,將先關閉11號線上的中斷/事件請求功能:有個原則是,在配置某一線上的中斷或者事件之前,先將該線上的中斷/事件清零,官方庫寫的比較嚴謹,所以這里先清零。那意思是,接下來就該對該線上的中斷/事件進行配置了。
且看上面外部事件初始化函數中的31行和32行,EXTI_RTSR是上升沿邊沿觸發寄存器,EXTI_FTSR是下降沿邊沿觸發器,這里一看便知,是在配置邊沿觸發模式:邊沿觸發分3類--上、下、隨便。當然,在配置邊沿觸發的時候,邊沿觸發器相應的位也應該清零,這在該函數的24、25行已經體現出來了。
另外注意模式的配置,看該函數中的19行:
1 tmp += EXTI_InitStruct->EXTI_Mode;
這個式子的表達的意思,接上文是:
tmp = (uint32_t)EXTI_BASE + EXTI_InitStruct->EXTI_Mode;
司馬昭知心,路人皆知:就是某個地址 加上一個值后,這個地址也就變成了另外一個地址了。但為啥要這么做呢?
而外部事件中斷模式的值定義為如下:
1 typedef enum 2 { 3 EXTI_Mode_Interrupt = 0x00, 4 EXTI_Mode_Event = 0x04 5 }EXTIMode_TypeDef;
可能不熟悉的<包括開始的我>,會發現,tmp在開始就只是對寄存器清零使用了下,就沒再使用了啊?可是在該函數中,為啥后面還有一串關於tmp的代碼呢?
如果細讀開始處賦值的意思就該明白了:是將某一個地址寫入了該變量,那么在一定的條件下,該變量就相當於內存地址了嘛。注意是在一定的條件下,而函數中,也給出了這個條件,那就是類型強制轉換。
請看39行或者47行:
1 *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line; 2 3 4 ----------------- 5 *(__IO uint32_t *) tmp &= ~EXTI_InitStruct->EXTI_Line;
__IO 表示volatile關鍵字;*(volatile unsigned int *) 表示了什么?指針的前面加*號,表示一個具體的值,也即是某個地址上具體的數據。這里有個鏈接:
<http://blog.sina.com.cn/s/blog_6b9f38c60100nv7i.html>
於是這里表示的是對某個內存地址進行操作,也就是向某個內存空間放入某個值,放入的是什么值呢:中斷時間線的值;放入什么地址呢?
tmp = (uint32_t)EXTI_BASE + EXTI_InitStruct->EXTI_Mode;
注意,如果邊沿觸發不是隨機的話,還要加上邊沿觸發寄存器的值。
由於以上幾步開始沒有理解,特別是我最后沒有把中斷事件線的值寫入內存空間,導致我的第一次按鍵中斷實驗失敗了,而且還不知道錯在哪,在分析了官方代碼后,方才知曉。
到這里,外部事件配置就完成了,可能細心的人會發現並沒有使能外部事件/中斷,是怎么回事呢?請看看上面tmp所代表的的內存空間地址是多少,然后對照着數據手冊上查看下事件/中斷屏蔽寄存器的地址是多少,再看看下面這句代碼最后的值是多少,就一目了然了。
1 *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
總結下第二步:就是配置外部事件的模式、觸發條件、使能外部觸發。而在這個過程中,注意先將某功能寄存器相關位清零后再寫入值。
這個過程用不寫成通用的庫函數的話,那么可以用下面代碼代替:
1 #define tmp (*(volatile unsigned int*))0x40010400 2 3 EXTI->IMR &= ~0x00800; 4 EXTI->EMR &= ~0x00800; 5 EXTI->RTSR &= ~0x00800; 6 EXTI->FTSR &= ~0x00800; 7 EXTI->RTSR |= 0x00800;//如果配置成上升沿觸發的話 8 tmp |= 0x00800;//使能中斷/事件
第三步,現在就該配置中斷了。也即是配置中斷分組,以及中斷優先級。當然,這並不是最后的工作。
中斷配置函數如下:
1 void NVIC_Configuration(void) 2 { 3 NVIC_InitTypeDef NVIC_InitStructure; 4 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 5 6 /*外部中斷線*/ 7 NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn ; 8 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0 ; 9 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; 10 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE ; 11 NVIC_Init(&NVIC_InitStructure); 12 }
4行,是一個中斷分組函數,跳轉,看實現:
1 void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup) 2 { 3 /* Check the parameters */ 4 assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup)); 5 6 /* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */ 7 SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup; 8 }
要查的SCB_AIRCR應用程序及復位控制寄存器的話,手頭就必須備有<<xxxM3編程手冊>>,<<xxxstm32數據手冊>>中是沒有這個寄存器的,當然還有許多都沒有,比如滴答定時器等等。
其中 AIRCR_VECTKEY_MASK 就是一個鑰匙,在改寫SCB_AIRCR中的值的時候,必須填入這個值,否則修改無效。AIRCR_VECTKEY_MASK = 0x05FA0000;
而后面就跟着組的編號,注意這個編號不是簡單的就是0,1,2,3,4.不能就這么簡單的寫入這個寄存器了。
注意這個寄存器的名字,它本身並不叫中斷分組寄存器,而是借用了這個寄存器的某幾位來進行中斷分組--換句話說,這個寄存器可能要實現多種控制功能,而中斷分組功能是在其中的某幾位:當然,編程手冊上說明了,是該寄存器的8~10位來進行中斷分組控制;所以,組號需要進行位移到8~10位上來。如圖:
<存疑部分>
當然,該函數中的值,也就影響到下面中斷優先級的配置。在MDK中跳入NVIC_Init()中,看其實現過程:
1 void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct) 2 { 3 uint32_t tmppriority = 0x00, tmppre = 0x00, tmpsub = 0x0F; 4 5 /* Check the parameters */ 6 assert_param(IS_FUNCTIONAL_STATE(NVIC_InitStruct->NVIC_IRQChannelCmd)); 7 assert_param(IS_NVIC_PREEMPTION_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority)); 8 assert_param(IS_NVIC_SUB_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelSubPriority)); 9 10 if (NVIC_InitStruct->NVIC_IRQChannelCmd != DISABLE) 11 { 12 /* Compute the Corresponding IRQ Priority --------------------------------*/ 13 tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700))>> 0x08; 14 tmppre = (0x4 - tmppriority); 15 tmpsub = tmpsub >> tmppriority; 16 17 tmppriority = (uint32_t)NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority << tmppre; 18 tmppriority |= NVIC_InitStruct->NVIC_IRQChannelSubPriority & tmpsub; 19 tmppriority = tmppriority << 0x04; 20 21 NVIC->IP[NVIC_InitStruct->NVIC_IRQChannel] = tmppriority; 22 23 /* Enable the Selected IRQ Channels --------------------------------------*/ 24 NVIC->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] = 25 (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F); 26 } 27 else 28 { 29 /* Disable the Selected IRQ Channels -------------------------------------*/ 30 NVIC->ICER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] = 31 (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F); 32 } 33 }
該函數中的第3行,有3個臨時變量,分別是:優先級組--這個組的意思就是中斷分組的那個意思,至於是否是那個值,看下文解釋;搶斷優先級;亞優先級。注意亞優先級初始值是0x0F--具體是啥原因呢?且看下面代碼。
第13行,先取出中斷控制復位神馬神馬寄存器的8~10 這3個位上的值< SCB->AIRCR& 0x07>;然后經過一定的算法得出中斷組號:假設SCB_AIRCR的8~10位為0x07,按照上面圖來說,也就是第0組,那么按照它里面的這個式子來算,恰好結果tmppriority = 0;至於具體為啥會ST會想到用這么個式子來得出組號,我現在只可意會。
那么,假設是第0組,也就是tmppriority = 0;那么按照中斷分組中,第0組的搶斷優先級和亞優先級的規定來說,是全4位亞優先級。那么14和15行就很容易明白了,它就是在設置搶斷優先級和亞優先級的比例位數。
那么17行,就是像臨時變量中寫入搶斷優先級的值;
18行,就是向臨時變量中寫入亞優先級的值;注意搶斷優先級和亞優先級一個在先一個在后,一旦分組一定,那么它們是會乖乖呆在自己位置上,不會去占用別人的位置的。
21行,就是把優先級配置值寫入NVIC_IP寄存器,它是NVIC中斷優先級配置寄存器,其定義在<<xxx編程手冊>>中,同時可參看<<xx不完全手冊>>。
在我例程版本庫中,對該寄存器的定義方法是:
1 __IO uint8_t IP[240]; /*!< Offset: 0x300 Interrupt Priority Register (8Bit wide) */
如果帶入例程中的值來算:EXTI15_10_IRQn 的中斷編號為40,即是:
NVIC->IP[40] = tmppriority;
意思就是向相應的中斷上賦予你的優先級配置值,這里特別要注意19行,優先級的值還向左移動了4位,這個是為啥呢?無法回答,帶入例程中的值算算:我例程中是,分在中斷2組,搶斷為0,亞優先級有1,則帶入:
1 tmppriority = 0x10; 2 NVIC->IP[40] =0x10;
也即是IP[40]的第5位置1,可能還是無法理解,再次參看正點原子的書,得知,中斷占IP寄存器的8位,但是只是用到了其高4位,而我們在設值的時候,也即是設它高4位的值。那么就符合這里的情況了:搶斷優先級為0,亞優先級為1,組號為2。 也退出,亞優先級和搶斷優先級一共4位,搶斷處於高位。這4位中,它倆怎么分,就是一個此消彼長的情況,視不同的組而定了。至於組合優先級之間的對應關系,上圖已經清楚的解釋了。也即是SCB_AIRCR 這個寄存器處理的事情了。當然,這些寄存器,在<<xxx數據手冊>>中也許是找不到的,而在編程手冊中去找。
最后,補充一點就是怎么查看EXTI15_10_IRQn 的中斷編號,這個數據手冊上有,當然,官方也在庫中給定義了。數據手冊部分截圖如下:
其中EXTI15_10中斷的位置在該向量表的第40號位置,所以剛才上面的優先級寄存器引腳的值就是40,至於具體為啥要寫成IP[40],而不是直接IP,這里有個小小編程技巧,是數組的妙用,屬於C語言范疇了。
接下來的第24行和30行,是第一對相反作用的寄存器,即中斷使能/失能,注意,別以為寫0就可以失能,stm32不認為那樣有效,必須是向失能寄存器中寫1才可以的。同時注意一點是:這個寄存器定義成了數組,那么數組中就應該有N個元素。它這里就是某個元素相應的位,管理一個區域的中斷。這里說復雜了,編程手冊上已經講的非常詳細了。另外,它的定義方式如同<<xx不完全手冊>>上所講解的那樣,但是定義的模式並不一定就完全相同:世界是變動的。
好吧,中斷也使能了,總結下第三步:
1)中斷分組:注意是在SCB_AIRCR寄存器的8~10.分組的同時,也就影響到了后面優先級的分配。
2) 配置優先級:其實質還是在SCB_AIRCR寄存器的4~7位,前面8~10位是什么值,那么這里4~7位就該怎么分了;當然,最后的配置值是寫入了NVIC_IP的寄存器中了;
3)使能中斷:在NVIC_ISER寄存器中,注意該寄存器定義的方式是數組,而非普通的變量,理解的時候,要理解一個數組是由N個變量組成即可<非嚴謹>。
第四步:中斷服務函數:
九九歸一,終於來到了最后一步,也是所有中斷必須要經歷的一步。
這里有個重點必須注意:所有中斷服務函數的名字,ST官方已經取好了,而且還放在了中斷向量表中了<也即是啟動文件里>,如果你不自己寫啟動文件的話,那么你的中斷服務函數的名字必須和ST官方的一樣,不然,一個中斷來了,找不到負責任的函數,它就只有悲劇去了。
看看例程吧:
1 void EXTI15_10_IRQHandler(void) 2 { 3 if(EXTI_GetITStatus(EXTI_Line11)!= RESET) 4 { 5 EXTI_ClearITPendingBit(EXTI_Line11); 6 Flag = 0x01; 7 } 8 9 if(EXTI_GetITStatus(EXTI_Line12)!= RESET) 10 { 11 EXTI_ClearITPendingBit(EXTI_Line12); 12 Flag = 0x02; 13 } 14 }
1 int main(void) 2 { 3 /* Add your application code here 4 */ 5 SystemInit(); /*系統初始化*/ 6 LED_Configuration(); 7 BUTTON_Configuration(); 8 NVIC_Configuration(); 9 EXTI_Configuration(); 10 /* Infinite loop */ 11 while (1) 12 { 13 switch(Flag) 14 { 15 case 0x01: 16 { 17 LED2(1); 18 Delay(); 19 LED2(0); 20 Delay(); 21 break; 22 } 23 case 0x02: 24 { 25 LED3(1); 26 Delay(); 27 LED3(0); 28 Delay(); 29 break; 30 } 31 default : 32 { 33 LED1(1); 34 Delay(); 35 LED1(0); 36 Delay(); 37 break; 38 } 39 } 40 } 41 }
沒啥說的,就是定義了一個全局變量Flag,每次中斷,都影響Flag的值,然后main函數判斷該值,就這么簡單。完了。
最后的總結:
寫了3個晚上,由於白天在公司太累了。但是又想深入理解下stm32的中斷過程並加以整理,就只有一點一點的啃下來。個人的感覺是:stm32中,用的最多,也是最起碼的,便是中斷和時鍾這兩個模塊。當然中斷還可以自己修改向量表,這個我在s3c2440中就試過,由於時間有限,這里不再贅述。也由於自己被庫函數給弄暈了頭,在入手stm3的一個月內自己居然搭不起一個工程框架,這個是一個悲哀。用庫函數也許簡單、快捷。但我更傾向於寄存器:簡潔。在網上看見有的老師說:只有高手才玩寄存器。但我師傅和我一致認為:新手就應該從寄存器開始,一點一點,知其然,更知其所以然。等到了隨心所欲的時候,可以適當的用下庫函數。當然,這僅是我們個人看法。從中斷的庫函數+寄存器走了一遭,中斷並不是想象的那么難,明白了許多,理解了許多,心里非常的開心;如果有人能看到這些,並能對他有所幫助的話,那我就非常開心了。(over)