stm32 外部中断库函数实现全程分析

2016-10-09来源: eefocus关键字: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。每个寄存器对应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.配置对应IO口为上(下)拉输入

 

 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);

2.使能相应IO端口和端口复用时钟

 

 8     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO,ENABLE);

3.外部事件寄存器相关位清零;

 

 8   tmp = ((uint32_t)0x0F) << (0x04 * (GPIO_PinSource & (uint8_t)0x03));
 9   AFIO->EXTICR[GPIO_PinSource >> 0x02] &= ~tmp;

4.设置输入端子、端口编号

 

10   AFIO->EXTICR[GPIO_PinSource >> 0x02] |= (((uint32_t)GPIO_PortSource) << (0x04 * (GPIO_PinSource & (uint8_t)0x03)));

 

 
步骤3-4:(库函数)
9      GPIO_EXTILineConfig(GPIO_PortSourceGPIOD , GPIO_PinSource11);
10     GPIO_EXTILineConfig(GPIO_PortSourceGPIOD , GPIO_PinSource12);

注:一共有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 中断几个库函数实现过程分析。(转,原作者历时三天,呕心沥血,剖析的很细致) - 蓝眼泪 - 蓝眼泪的博客
 
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 *) 表示了什么?指针的前面加*号,表示一个具体的值,也即是某个地址上具体的数据。这里有个链接:

于是这里表示的是对某个内存地址进行操作,也就是向某个内存空间放入某个值,放入的是什么值呢:中断时间线的值;放入什么地址呢?

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应用程序及复位控制寄存器的话,手头就必须备有<>,<>中是没有这个寄存器的,当然还有许多都没有,比如滴答定时器等等。

其中 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中断优先级配置寄存器,其定义在<>中,同时可参看<>。

在我例程版本库中,对该寄存器的定义方法是:

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 这个寄存器处理的事情了。当然,这些寄存器,在<>中也许是找不到的,而在编程手册中去找。

最后,补充一点就是怎么查看EXTI15_10_IRQn 的中断编号,这个数据手册上有,当然,官方也在库中给定义了。数据手册部分截图如下:

其中EXTI15_10中断的位置在该向量表的第40号位置,所以刚才上面的优先级寄存器引脚的值就是40,至于具体为啥要写成IP[40],而不是直接IP,这里有个小小编程技巧,是数组的妙用,属于C语言范畴了。

接下来的第24行和30行,是第一对相反作用的寄存器,即中断使能/失能,注意,别以为写0就可以失能,stm32不认为那样有效,必须是向失能寄存器中写1才可以的。同时注意一点是:这个寄存器定义成了数组,那么数组中就应该有N个元素。它这里就是某个元素相应的位,管理一个区域的中断。这里说复杂了,编程手册上已经讲的非常详细了。另外,它的定义方式如同<>上所讲解的那样,但是定义的模式并不一定就完全相同:世界是变动的。

好吧,中断也使能了,总结下第三步:

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)

关键字:stm32  外部中断  库函数

编辑:什么鱼 引用地址:http://news.eeworld.com.cn/mcu/article_2016100930243.html
本网站转载的所有的文章、图片、音频视频文件等资料的版权归版权所有人所有,本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如果本网所选内容的文章作者及编辑认为其作品不宜公开自由传播,或不应无偿使用,请及时通过电子邮件或电话通知我们,以迅速采取适当措施,避免给双方造成不必要的经济损失。

上一篇:STM32之ADC配置
下一篇:STM32 RAM 调试

关注eeworld公众号 快捷获取更多信息
关注eeworld公众号
快捷获取更多信息
关注eeworld服务号 享受更多官方福利
关注eeworld服务号
享受更多官方福利

推荐阅读

STM32解决:st-link连接下载程序的问题

STM32解决:Error: Flash Download failed - "Cortex-M3"本人由于使用普中科技的stm32 的开发板的 USB的下载的地方坏了,所以不得不使用arm仿真器 st-link 进行下载。鼓捣了半天下面总结一下几个问题:1、st-link的驱动下载首先你插上st-link的时候,电脑的设备管理器这个地方是有感叹号的,说明还没有装好驱动,所以我就在网上找啊找。终于根据:win8【笔者没这个系统,无法测试,请大家测试后报告】:http://pan.baidu.com/s/1sjJQxZn(转载来自:https://blog.csdn.net/imxiangzi/article
发表于 2019-07-19
STM32解决:st-link连接下载程序的问题

解决stm32f103通过stlink不能烧录程序问题

问题:   stm32(stm32f103c8T6)开发板只能通过串口烧录程序,而st—link居然不行描述:解决:st-link固件升级用stm32cubemx快速开发时没有配置好调试模式重新生成代码就可以了如果还是不行的话,就得升级一下stlink固件了,具体升级方法可百度
发表于 2019-07-19
解决stm32f103通过stlink不能烧录程序问题

STM32下载不成功问题汇总

在某宝上买了五个最小系统核心板是STM32F103C8T6的芯片,刚拿到手准备下载程序调试,上电后板子自带LED闪烁,这是商家自己下载的示例程序,说明芯片工作着,用KEIL4进行下载自己程序,把自己编译好的程序下载。用的JLINK的四线下载调试下载口,SW的调试接口,点击下载后发现擦除成功,下载失败,提示:Load "..\Output\STM32-DEMO.axf" Set JLink Project File to "F:文件RFID程序电机USERJLinkSettings.ini"* JLink Info: Device "STM32
发表于 2019-07-19
STM32下载不成功问题汇总

STM32高级开发(11)-使用GDB调试你的工程

/scripts/target/stm32f4x_stlink.cfg在执行完此条指令后该终端就会一直执行OpenOCD的程序了,不要关闭它,我们再打开一个终端界面,进入我们的工程目录,比如我这里进入的就是我的libopencm3样例工程下的blink子工程目录。$ cd '/home/yangliu/workspace/libopencm3-my-example/blink'然后我们使用指令输入调试文件并打开GDB程序。$ arm-none-eabi-gdb blink.elf 然后我们在GDB的指令界面中,输入连接指令,连接本地的3333端口。(gdb)target remote localhost:3333此时
发表于 2019-07-19
STM32高级开发(11)-使用GDB调试你的工程

STM32F4标准外设库模板工程建立与使用

SW4STM32安装其实固件库安装过程很简单,在第一次新建工程时会提示选择使用Stdperiph 驱动还是Cube HAL,由于Stm32官方大力推行Cube HAL固件库,所以Cube HAL的固件库直接可以从网上直接一键下载安装。然而对于老的StdPeriph固件库不能一键式下载安装,会提示出错。所以,我们需要自己下载一个.zip固件包,放在C:UsersLYAppDataRoamingAc6SW4STM32firmwares文件夹下,其中的LY就是计算机的用户名。然后新建工程时在选择Stdperiph固件时会自动解压缩,这样就能使用该库进行编译了。界面如下所示:工程配置器件与时钟或者,修改晶振与时钟,根据注释可以算得
发表于 2019-07-19
STM32F4标准外设库模板工程建立与使用

基于STM32的外设的GPIO外设设置总结

1、背景外设驱动的寄存器设置对于外设功能正常运行异常重要。现在对GPIO的配置进行总结。2、GPIO的配置总结复用GPIO配置GPIO设置为输出或者是复用模式时,需要设置输出速度;而无论设置为什么模式,都要对GPIO的内部上下拉进行设置。注意:在输入模式(普通输入/模拟输入)下,OTYPE和OSPEED参数无效!!
发表于 2019-07-19
基于STM32的外设的GPIO外设设置总结

小广播

何立民专栏

单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号 电信业务审批[2006]字第258号函 京公海网安备110108001534 Copyright © 2005-2019 EEWORLD.com.cn, Inc. All rights reserved