stm32 中斷幾個庫函數實現過程分析。


前題:

  閉門造車,兩周了,經過各種的思考和求問,反復閱讀了<<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)

 

 

 





 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM