链接:https://pan.baidu.com/s/1AhjlecDJmJ4MRV_pI4I4EQ    提取码:zczk
第四章:EXTI-使用按键点亮LED灯
在之前的几讲中,我讲过了如果通过编程来点亮LED灯,今天这节课,我要将另一种点亮LED灯的方法:通过独立按键来控制LED灯的亮灭。本质上,还是点亮LED灯,改变的只是触发方式,之前用的是程序触发从而点亮LED灯,咱们这次用的是按键了,当然也不仅仅是按键,你也可以使用其它的外设来控制LED灯亮灭,按键只是我们学习EXTI(外部中断/事件控制器来举的一个例子,仅此而已。

一、 神马是EXTI
STM32F10x-中文参考手册:
对于互联型产品,外部中断/事件控制器由20个产生事件/中断请求的边沿检测器组成,对于其它
产品,则有19个能产生事件/中断请求的边沿检测器。每个输入线可以独立地配置输入类型(脉冲
或挂起)和对应的触发事件(上升沿或下降沿或者双边沿都触发)。每个输入线都可以独立地被屏
蔽。挂起寄存器保持着状态线的中断请求。
         EXTIExternal interrupt/event controller外部中断/事件控制器,即外部设备产生中断或者事件,然后再通过编程来控制中断优先级和触发方式,每一个控制器都由一个边沿检测器构成,一共有19个,当然这是对于除了互联型产品而言,也就是说这样的触发可以由相关IO口的16个引脚(线0-15)来控制。(线16:连接到 PVD 输出。线 17:连接到 RTC 闹钟事件。线 18:连接到USB 唤醒事件。)
         边沿检测的含义实际上就高低电平的变化过程,当高电平转向低电平为下降沿,低电平转向高电平为上升沿,我们接下来要说的这个按键实际上就是利用了按下和松开就可以改变电平的变化来触发外部中断的。
STM32 (非互联型)供 IO 口使用的中断线只有 16 个,但是 STM32 的 IO 口却远远不止16 个,那么 STM32 是怎么把 16 个中断线和 IO 口一一对应起来的呢?于是 STM32 就这样设计,GPIO 的管教 GPIOx.0~GPIOx.15(x=A,B,C,D,E,F,G)分别对应中断线 15~0。这样每个中断线对应了最多 7 个 IO 口,以线 0 为例:它对应了 GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0。而中断线每次只能连接到 1 个 IO 口上,这样就需要通过配置来决定对应的中断线配置到哪个 GPIO 上了。下面我们看看 GPIO 跟中断线的映射关系图:
1.png
二、按键!
我们再来介绍一下按键。
2.png 3.png
指南者上面的按键是叫轻触开关(独立按键\微动按键),当按键按下,电路导通。我们再来看一下指南者这个板子上他的按键电路:
4.png
我们可以清晰地看到,指南者开发板具有两个按键(KEY1和KEY2),分别连到了单片机(STM32F103VET6)的PA0和PC13两个引脚上,当按键被按下,1、3处导通,相关单片机的引脚被拉高,而之前默认是接地的,为低电平,所以,按键按下有一个低电平到高电平的一个上升沿的跳变沿。

这个时候EXTI就会开始起作用了,通过他的控制,会触发一个中断或者事件。
三、  EXTI功能框图
5.png
上图画圈的部分,是20条线路,为了简写,所以有用那样的标号来代替,我们其实只需要了解一条线的运行过程就可以了。(具体与数字电路相关的知识这里先不讲,学过的,看图应该也可以理解)
四、  中断/事件线
EXTI 有 20 个中断/事件线,每个 GPIO 都可以被设置为输入线,占用 EXTI0 至EXTI15,还有另外七根用于特定的外设事件,见表 18-1。4 根特定外设中断/事件线由外设触发,具体用法参考《STM32F10X-中文参考手册》
中对外设的具体说明。
6.png
EXTI0 至 EXTI15 用于 GPIO,通过编程控制可以实现任意一个 GPIO 作为 EXTI 的输
入源。
按键挂载到哪个引脚上了,就要选择对应的输入线。比如我们指南者的按键KEY1是挂载到PA0这个口上了,那么相对应的,他的输入线就应该是EXTI0,我们配置EXTI0就可以了。
三、  中断配置:NVIC(嵌套向量中断控制器)
我们这个EXTI也属于中断的子集,所以要对中断的优先级进行配置,因为不可能同时触发多个中断,这涉及到优先级排队的问题。NVIC 是嵌套向量中断控制器,控制着整个芯片中断相关的功能,它跟内核紧密耦合,是内核里面的一个外设。(和NVIC相关的函数和结构体定义都在misc.h/c中)

为了方便起见,我们直接从代码简单讲一下NVIC,后面的章节会做详细补充:
  1. typedef struct
  2. {
  3.   uint8_t NVIC_IRQChannel;//配置中断源,在stm32f10x.h中,如果用的是EXTI0 就是EXTI0_IRQn(186行)
  4.   uint8_t NVIC_IRQChannelPreemptionPriority;//配置抢占优先级
  5.   uint8_t NVIC_IRQChannelSubPriority;//配置子优先级
  6.   FunctionalState NVIC_IRQChannelCmd; //中断源使能
  7. } NVIC_InitTypeDef;
注意:优先级配置的越小,优先级越高。

当然,也不要忘记对优先级进行分组(父子优先级的个数配置):
  1. void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
ST已经给了这个函数详细的注释:
  1. /**
  2.   * @brief  Configures the priority grouping: pre-emption priority and subpriority.
  3.   * @param  NVIC_PriorityGroup: specifies the priority grouping bits length.
  4.   *   This parameter can be one of the following values:
  5.   *     @arg NVIC_PriorityGroup_0: 0 bits for pre-emption priority
  6.   *                                4 bits for subpriority
  7.   *     @arg NVIC_PriorityGroup_1: 1 bits for pre-emption priority
  8.   *                                3 bits for subpriority
  9.   *     @arg NVIC_PriorityGroup_2: 2 bits for pre-emption priority
  10.   *                                2 bits for subpriority
  11.   *     @arg NVIC_PriorityGroup_3: 3 bits for pre-emption priority
  12.   *                                1 bits for subpriority
  13.   *     @arg NVIC_PriorityGroup_4: 4 bits for pre-emption priority
  14.   *                                0 bits for subpriority
  15.   * @retval None
  16.   */
三、  说了这么多,终于要开始编程啦~
还是要说一句:
在正式打代码之前,我们将外设ledEXTI都封装到user目录的一个文件夹中,并且创建c文件和头文件,并且记得将新建的外设文件夹添加到工程的头文件包含路径中,如图:
7.png
1、 初始化LEDGPIO
因为在前面的教程里面讲过如何点亮LED灯,所以这里不再次讲解,记住看原理图然后配置IO即可。
2、 中断优先级分组3、 初始化KEY1GPIO
记得GPIO的模式配置为浮空输入模式。

4、 初始化EXTI
8.png
  1. //选择EXTI的信号源和输入线,函数参数可以直接调到它的定义处,有详细说明。
  2.   GPIO_EXTILineConfig(KEY1_EXTI_PORTSOURCE, KEY1_EXTI_PINSOURCE);
  3.   EXTI_InitStructure.EXTI_Line = KEY1_EXTI_LINE;
  4. //配置为中断模式,还有一种是事件模式是传输一个脉冲信号给其他外设使用
  5.   EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  6. //上升沿中断,也就是按键被按下的时候才触发中断(看原理图),执行中断服务程序
  7.   EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
  8. //使能中断
  9.   EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  10.              EXTI_Init(&EXTI_InitStructure);
5、 编写中断服务函数
为了简单一点,不涉及到寄存器级别的编程,所以我直接用了一个保存led亮灭的标志i,来实现反转LED灯
  1. static int i=0;//定义一个静态变量,初始化为0
  2. void KEY1_IRQHandler(void)
  3. {
  4.   //确保是否产生了EXTI Line中断
  5. if(EXTI_GetITStatus(KEY1_EXTI_LINE) != RESET)
  6.         {
  7.                 if(i==0)
  8.                 {        
  9.                         GPIO_ResetBits(LED1_GPIO_PORT, LED1_GPIO_PIN);//点亮
  10.                         i=1;
  11.                 }
  12.                 else
  13.                 {        
  14.                         GPIO_SetBits(LED1_GPIO_PORT, LED1_GPIO_PIN);//熄灭
  15.                         i=0;
  16.                 }
  17.     //清除中断标志位
  18.                 EXTI_ClearITPendingBit(KEY1_EXTI_LINE);     
  19.         }  
  20. }
目录:
《stm32,我来了》小白教程之第一章:点亮LED灯
《stm32,我来了》小白教程之第二章:LED灯闪烁
《stm32,我来了》第三章:小项目大学问-1
《stm32,我来了》第三章:小项目大学问-2
《stm32,我来了》第四章:EXTI-使用按键点亮LED灯