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

[复制链接]
6815|39
 楼主| sunmeat 发表于 2014-11-28 08:24 | 显示全部楼层 |阅读模式
前题:
  闭门造车,两周了,经过各种的思考和求问,反复阅读了<<M3权威指南>>和<<stm32不完全手册>>的相关章节,以及开发板厂商的实验例程,对stm32这块中断终有所悟,是以记之。
  至于中断的什么优先级,什么优先级分组,使能之类的原理,就不再赘述。这里主要是记载以下如何使用中断,以及中断配置函数的实现过程,其中并叙述我曾经的疑惑和感悟。
  我的开发板里的中断例程是用按键控制一个灯亮和灭的两个状态。
  这个例程的实现过程如下描述:

 楼主| sunmeat 发表于 2014-11-28 08:27 | 显示全部楼层
第一步,将一个I/O口配置成中断输入模式。
  这里需要注意的是,GPIO本身是没有中断功能神马的。如果硬要使他产生中断输入方式,就得将相应的端口映射到相应的外部事件上去。而其他外设是有中断功能的,直接使能/失能其中断即可,比如USART,直接开启其发送/接收中断,那么USART也就相应的采取中断方式进行工作了。
  而这一点,是我开始很疑惑的:为啥GPIO口使用中断方式进行工作的时候就必须要映射到外部事件上去,而其他就不呢?百度网友的解惑是:比如USART产生的中断,是没有经过EXTI,而是直接将中断放入了NVIC;但是GPIO它作为中断源,是要经过EXTI的。仔细参看下面两个图,其实就会恍然大悟:
 楼主| sunmeat 发表于 2014-11-28 08:28 | 显示全部楼层
 楼主| sunmeat 发表于 2014-11-28 08:28 | 显示全部楼层
 楼主| sunmeat 发表于 2014-11-28 08:31 | 显示全部楼层
这第一步就是作为输入中断源的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.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO,ENABLE);
  8.     GPIO_EXTILineConfig(GPIO_PortSourceGPIOD , GPIO_PinSource11);
  9.     GPIO_EXTILineConfig(GPIO_PortSourceGPIOD , GPIO_PinSource12);
  10. }
 楼主| sunmeat 发表于 2014-11-28 08:31 | 显示全部楼层
因为我板子上的例程是按键输入中断,所以函数名字就写的按键配置吧;

3~5行,就是gpio口的普通配置,学习单片机开天辟地,就先是gpio口,这个没啥稀奇的了,没啥可说的了;我的板子上是PD^11,PD^12两个端口作为中断输入的。

8行,注意这个时候,要使能GPIO口的复用时钟功能。

9~10行,就是将PD^11,PD^12映射到外部事件线上去。在keil中跳转到其函数实现中:
 楼主| sunmeat 发表于 2014-11-28 08:31 | 显示全部楼层
  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. }
 楼主| sunmeat 发表于 2014-11-28 08:32 | 显示全部楼层
5~6行,用库函数的都知道,就是两个宏定义,起到的作用是对相关的数据神马的进行正确性检查。

8行,GPIO_PinSource这个是外面BUTTON_Configuration()调用GPIO_EXTILineConfig()时传的参数。可能都不知道8行这个式子为啥要这么写。先看看我例程中是如何传的参:
  1. GPIO_EXTILineConfig(GPIO_PortSourceGPIOD , GPIO_PinSource11);
也即是GPIO_PinSource <==>GPIO_PinSource11;那么GPIO_PinSource11是个什么东西呢:官方库已经这样定义了:
  1. #define GPIO_PinSource11           ((uint8_t)0x0B)
 楼主| sunmeat 发表于 2014-11-28 08:33 | 显示全部楼层


如果按照我的例程,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端口的定义如下:
  1. #define GPIO_PortSourceGPIOD       ((uint8_t)0x03)
AFIO->EXTICR[2]  |= 0x03 << 12;查看数据手册,恰好是设置成了D口的第11号端子上嘛。
 楼主| sunmeat 发表于 2014-11-28 08:33 | 显示全部楼层
于是第一步总结是:

1)外部事件寄存器相关位清零;

2)设置输入端子的编号

3)设置端口编号

注:一共有A~G个端口嘛,而每个端口上又有N多端子,这个参看GPIo那章数据手册。
 楼主| sunmeat 发表于 2014-11-28 08:34 | 显示全部楼层
悟出:库函数确实方便,具有公式效应,但山路十八弯,在这里,该例程中,要实现该功能,用寄存器,就两条语句嘛:
  1. 1 AFIO->EXTICR[2] &= 0x0FFF;
  2. 2 AFIO->EXTICR[2] |= 0x03 << 12;
1行:12~15清零

2行:0x03:表示是PD端口 ;0x03 << 12:表示在AFIO_EXTICR寄存器中的第12位开始写入0x03这个值,而括号中的2,说明是第三个寄存器,这样一组合,恰好就是PD^11了.描述起来一长串,如果看对照个看数据手册的话,就一目了然了。而这里唯一会让人凌乱的是:这个寄存器在数据手册上的编号和MDK中的编号不一致。自己在细读了<<stm32不完全手册>>才发现这个问题,开始可是百思不得其解呀。
 楼主| sunmeat 发表于 2014-11-28 08:34 | 显示全部楼层
总结下第一步要做的事情:

1)初始化I/O口为输入;

2)开启I/O复用时钟,并设置外部事件映射关联。
 楼主| sunmeat 发表于 2014-11-28 08:34 | 显示全部楼层
接下来是第二步:

  第一步是将外部GPIO口映射到某外部事件上去。那么接下来,就该对该外部事件进行配置了,包括外部事件线路的选择、触发条件、使能。这里需要理解清楚的是,GPIO口和外部事件是各自独立的,它们并不是一体的---详细理解第一步,将GPIO口映射到某外部事件,可以看出GPIO和外部事件这个东西是两个不同的东西,在这里,GPIO的映射,无非就是GPIO口搭了外部事件的一趟顺风车。也所以,外部事件依然是要配置和使能的,不能说,将GPIO口映射到外部事件就可以产生中断了。

  接下来看看例程中外部事件的配置函数:
 楼主| sunmeat 发表于 2014-11-28 08:35 | 显示全部楼层
  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. }
 楼主| sunmeat 发表于 2014-11-28 08:35 | 显示全部楼层
可以看出,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.   /* Check the parameters */
  5.   assert_param(IS_EXTI_MODE(EXTI_InitStruct->EXTI_Mode));
  6.   assert_param(IS_EXTI_TRIGGER(EXTI_InitStruct->EXTI_Trigger));
  7.   assert_param(IS_EXTI_LINE(EXTI_InitStruct->EXTI_Line));  
  8.   assert_param(IS_FUNCTIONAL_STATE(EXTI_InitStruct->EXTI_LineCmd));

  9.   tmp = (uint32_t)EXTI_BASE;
  10.      
  11.   if (EXTI_InitStruct->EXTI_LineCmd != DISABLE)
  12.   {
  13.     /* Clear EXTI line configuration */
  14.     EXTI->IMR &= ~EXTI_InitStruct->EXTI_Line;
  15.     EXTI->EMR &= ~EXTI_InitStruct->EXTI_Line;
  16.    
  17.     tmp += EXTI_InitStruct->EXTI_Mode;

  18.     *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;

  19.     /* Clear Rising Falling edge configuration */
  20.     EXTI->RTSR &= ~EXTI_InitStruct->EXTI_Line;
  21.     EXTI->FTSR &= ~EXTI_InitStruct->EXTI_Line;
  22.    
  23.     /* Select the trigger for the selected external interrupts */
  24.     if (EXTI_InitStruct->EXTI_Trigger == EXTI_Trigger_Rising_Falling)
  25.     {
  26.       /* Rising Falling edge */
  27.       EXTI->RTSR |= EXTI_InitStruct->EXTI_Line;
  28.       EXTI->FTSR |= EXTI_InitStruct->EXTI_Line;
  29.     }
  30.     else
  31.     {
  32.       tmp = (uint32_t)EXTI_BASE;
  33.       tmp += EXTI_InitStruct->EXTI_Trigger;

  34.       *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
  35.     }
  36.   }
  37.   else
  38.   {
  39.     tmp += EXTI_InitStruct->EXTI_Mode;

  40.     /* Disable the selected external lines */
  41.     *(__IO uint32_t *) tmp &= ~EXTI_InitStruct->EXTI_Line;
  42.   }
  43. }
 楼主| sunmeat 发表于 2014-11-28 08:36 | 显示全部楼层
第11行,就是存储了一个地址值。可以先看看它们是怎么定义的:
  1. 1 #define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
  2. 2 #define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
  3. 3 #define EXTI_BASE             (APB2PERIPH_BASE + 0x0400)
 楼主| sunmeat 发表于 2014-11-28 08:36 | 显示全部楼层
如果对stm32框架掌握的足够熟悉,这里一眼便知端倪:GPIO口是挂在APB2上的,APB2是连在AHB总线上的,AHB再连到总线矩阵上的,环环相套,牵一发而动全身。附图如下:
图像 057.png
 楼主| sunmeat 发表于 2014-11-28 09:01 | 显示全部楼层
第16行、17行,分别是EXTI_IMR中断事件屏蔽寄存器和外部事件屏蔽寄存器,相应的位为0的时候,它们屏蔽相应线上的中断/事件请求。带入例程中的值算算
  1. 1 #define EXTI_Line11      ((uint32_t)0x00800)  /*!< External interrupt line 11 */
  2. 2 #define EXTI_Line12      ((uint32_t)0x01000)  /*!< External interrupt line 12 */
先将事件11的值带入下面两个式子,换算如下:
  1. 1     EXTI->IMR &= ~0x00800;
  2. 2     EXTI->EMR &= ~0x00800;
 楼主| sunmeat 发表于 2014-11-28 09:01 | 显示全部楼层
表示,将寄存器EXTI_IMR的11位清零,将EXTI_EMR的11位清零<下标从0开始>--意思是,将先关闭11号线上的中断/事件请求功能:有个原则是,在配置某一线上的中断或者事件之前,先将该线上的中断/事件清零,官方库写的比较严谨,所以这里先清零。那意思是,接下来就该对该线上的中断/事件进行配置了。

且看上面外部事件初始化函数中的31行和32行,EXTI_RTSR是上升沿边沿触发寄存器,EXTI_FTSR是下降沿边沿触发器,这里一看便知,是在配置边沿触发模式:边沿触发分3类--上、下、随便。当然,在配置边沿触发的时候,边沿触发器相应的位也应该清零,这在该函数的24、25行已经体现出来了。
 楼主| sunmeat 发表于 2014-11-28 09:02 | 显示全部楼层
另外注意模式的配置,看该函数中的19行:
  1. 1 tmp += EXTI_InitStruct->EXTI_Mode;
这个式子的表达的意思,接上文是:
  1. tmp = (uint32_t)EXTI_BASE + EXTI_InitStruct->EXTI_Mode;
司马昭知心,路人皆知:就是某个地址 加上一个值后,这个地址也就变成了另外一个地址了。但为啥要这么做呢?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

208

主题

2132

帖子

13

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