stm32 中断几个库函数实现过程分析【转】

[复制链接]
6815|39
 楼主| sunmeat 发表于 2014-11-28 09:02 | 显示全部楼层
而外部事件中断模式的值定义为如下:
  1. 1 typedef enum
  2. 2 {
  3. 3   EXTI_Mode_Interrupt = 0x00,
  4. 4   EXTI_Mode_Event = 0x04
  5. 5 }EXTIMode_TypeDef;
 楼主| sunmeat 发表于 2014-11-28 09:03 | 显示全部楼层
可能不熟悉的<包括开始的我>,会发现,tmp在开始就只是对寄存器清零使用了下,就没再使用了啊?可是在该函数中,为啥后面还有一串关于tmp的代码呢?
如果细读开始处赋值的意思就该明白了:是将某一个地址写入了该变量,那么在一定的条件下,该变量就相当于内存地址了嘛。注意是在一定的条件下,而函数中,也给出了这个条件,那就是类型强制转换。
请看39行或者47行:
  1. 1 *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
  2. 2
  3. 3
  4. 4 -----------------
  5. 5 *(__IO uint32_t *) tmp &= ~EXTI_InitStruct->EXTI_Line;
__IO 表示volatile关键字;*(volatile unsigned int *) 表示了什么?指针的前面加*号,表示一个具体的值,也即是某个地址上具体的数据。这里有个链接:

<http://blog.sina.com.cn/s/blog_6b9f38c60100nv7i.html>

于是这里表示的是对某个内存地址进行操作,也就是向某个内存空间放入某个值,放入的是什么值呢:中断时间线的值;放入什么地址呢?
  1. tmp = (uint32_t)EXTI_BASE + EXTI_InitStruct->EXTI_Mode;
注意,如果边沿触发不是随机的话,还要加上边沿触发寄存器的值。
 楼主| sunmeat 发表于 2014-11-28 09:04 | 显示全部楼层
由于以上几步开始没有理解,特别是我最后没有把中断事件线的值写入内存空间,导致我的第一次按键中断实验失败了,而且还不知道错在哪,在分析了官方代码后,方才知晓。

到这里,外部事件配置就完成了,可能细心的人会发现并没有使能外部事件/中断,是怎么回事呢?请看看上面tmp所代表的的内存空间地址是多少,然后对照着数据手册上查看下事件/中断屏蔽寄存器的地址是多少,再看看下面这句代码最后的值是多少,就一目了然了。
  1. 1 *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
 楼主| sunmeat 发表于 2014-11-28 09:04 | 显示全部楼层
总结下第二步:就是配置外部事件的模式、触发条件、使能外部触发。而在这个过程中,注意先将某功能寄存器相关位清零后再写入值。

这个过程用不写成通用的库函数的话,那么可以用下面代码代替:
  1. #define tmp  (*(volatile unsigned int*))0x40010400

  2. EXTI->IMR &= ~0x00800;
  3. EXTI->EMR &= ~0x00800;
  4. EXTI->RTSR &= ~0x00800;
  5. EXTI->FTSR &= ~0x00800;
  6. EXTI->RTSR |= 0x00800;//如果配置成上升沿触发的话
  7. tmp |= 0x00800;//使能中断/事件
 楼主| sunmeat 发表于 2014-11-28 09:19 | 显示全部楼层
第三步,现在就该配置中断了。也即是配置中断分组,以及中断优先级。当然,这并不是最后的工作。

中断配置函数如下:
  1. void NVIC_Configuration(void)
  2. {
  3.     NVIC_InitTypeDef NVIC_InitStructure;
  4.     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

  5.     /*外部中断线*/
  6.     NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn ;      
  7.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0 ;
  8.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  9.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE ;
  10.     NVIC_Init(&NVIC_InitStructure);
  11. }
 楼主| sunmeat 发表于 2014-11-28 09:21 | 显示全部楼层
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. }
 楼主| sunmeat 发表于 2014-11-28 09:21 | 显示全部楼层
要查的SCB_AIRCR应用程序及复位控制寄存器的话,手头就必须备有<<xxxM3编程手册>>,<<xxxstm32数据手册>>中是没有这个寄存器的,当然还有许多都没有,比如滴答定时器等等。

其中 AIRCR_VECTKEY_MASK 就是一个钥匙,在改写SCB_AIRCR中的值的时候,必须填入这个值,否则修改无效。AIRCR_VECTKEY_MASK = 0x05FA0000;

而后面就跟着组的编号,注意这个编号不是简单的就是0,1,2,3,4.不能就这么简单的写入这个寄存器了。

注意这个寄存器的名字,它本身并不叫中断分组寄存器,而是借用了这个寄存器的某几位来进行中断分组--换句话说,这个寄存器可能要实现多种控制功能,而中断分组功能是在其中的某几位:当然,编程手册上说明了,是该寄存器的8~10位来进行中断分组控制;所以,组号需要进行位移到8~10位上来。如图:
图像 059.png
 楼主| sunmeat 发表于 2014-11-28 09:23 | 显示全部楼层
<存疑部分>

当然,该函数中的值,也就影响到下面中断优先级的配置。在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.     tmppriority = (uint32_t)NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority << tmppre;
  17.     tmppriority |=  NVIC_InitStruct->NVIC_IRQChannelSubPriority & tmpsub;
  18.     tmppriority = tmppriority << 0x04;
  19.         
  20.     NVIC->IP[NVIC_InitStruct->NVIC_IRQChannel] = tmppriority;
  21.    
  22.     /* Enable the Selected IRQ Channels --------------------------------------*/
  23.     NVIC->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
  24.       (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
  25.   }
  26.   else
  27.   {
  28.     /* Disable the Selected IRQ Channels -------------------------------------*/
  29.     NVIC->ICER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
  30.       (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
  31.   }
  32. }
 楼主| sunmeat 发表于 2014-11-28 09:23 | 显示全部楼层
该函数中的第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不完全手册>>。
 楼主| sunmeat 发表于 2014-11-28 09:24 | 显示全部楼层
在我例程版本库中,对该寄存器的定义方法是:
  1. 1  __IO uint8_t  IP[240];                      /*!< Offset: 0x300  Interrupt Priority Register (8Bit wide) */
如果带入例程中的值来算:EXTI15_10_IRQn 的中断编号为40,即是:
  1. NVIC->IP[40] = tmppriority;
  1. 意思就是向相应的中断上赋予你的优先级配置值,这里特别要注意19行,优先级的值还向左移动了4位,这个是为啥呢?无法回答,带入例程中的值算算:我例程中是,分在中断2组,抢断为0,亚优先级有1,则带入:
  1. 1  tmppriority = 0x10;
  2. 2  NVIC->IP[40] =0x10;
也即是IP[40]的第5位置1,可能还是无法理解,再次参看正点原子的书,得知,中断占IP寄存器的8位,但是只是用到了其高4位,而我们在设值的时候,也即是设它高4位的值。那么就符合这里的情况了:抢断优先级为0,亚优先级为1,组号为2。 也退出,亚优先级和抢断优先级一共4位,抢断处于高位。这4位中,它俩怎么分,就是一个此消彼长的情况,视不同的组而定了。至于组合优先级之间的对应关系,上图已经清楚的解释了。也即是SCB_AIRCR 这个寄存器处理的事情了。当然,这些寄存器,在<<xxx数据手册>>中也许是找不到的,而在编程手册中去找。

最后,补充一点就是怎么查看EXTI15_10_IRQn 的中断编号,这个数据手册上有,当然,官方也在库中给定义了。数据手册部分截图如下:
 楼主| sunmeat 发表于 2014-11-28 09:25 | 显示全部楼层
 楼主| sunmeat 发表于 2014-11-28 09:25 | 显示全部楼层
其中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个变量组成即可<非严谨>。
 楼主| sunmeat 发表于 2014-11-28 09:33 | 显示全部楼层
第四步:中断服务函数:

  九九归一,终于来到了最后一步,也是所有中断必须要经历的一步。

  这里有个重点必须注意:所有中断服务函数的名字,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.     if(EXTI_GetITStatus(EXTI_Line12)!= RESET)  
  9.     {  
  10.         EXTI_ClearITPendingBit(EXTI_Line12);
  11.         Flag = 0x02;
  12.     }   
  13. }
 楼主| sunmeat 发表于 2014-11-28 09:33 | 显示全部楼层
  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. }
 楼主| sunmeat 发表于 2014-11-28 09:34 | 显示全部楼层
没啥说的,就是定义了一个全局变量Flag,每次中断,都影响Flag的值,然后main函数判断该值,就这么简单。完了。
z_weijun518 发表于 2014-11-28 21:17 | 显示全部楼层
呵呵,楼主辛苦了!
pmp 发表于 2014-11-29 23:53 | 显示全部楼层
不错说的好
329547875 发表于 2015-7-6 14:48 | 显示全部楼层
号贴呀!!!我的串口怎么进不了中断了
comeon201208 发表于 2015-7-11 16:44 | 显示全部楼层
USART产生的中断,是没有经过EXTI,而是直接将中断放入了NVIC;但是GPIO它作为中断源,是要经过EXTI的。
comeon201208 发表于 2015-7-11 16:45 | 显示全部楼层
将外部GPIO口映射到某外部事件上去。那么接下来,就该对该外部事件进行配置了,包括外部事件线路的选择、触发条件、使能。这里需要理解清楚的是,GPIO口和外部事件是各自独立的,它们并不是一体的---详细理解第一步,将GPIO口映射到某外部事件,可以看出GPIO和外部事件这个东西是两个不同的东西,在这里,GPIO的映射,无非就是GPIO口搭了外部事件的一趟顺风车。

这个是很重要的步骤的。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 在线客服 返回列表 返回顶部