赞
踩
上一篇文章已经介绍了如何用按键点灯,使用的是按键扫描的方式,实现了点灯的第二步。这一篇则介绍如何用外部中断的方式实现按键点灯的过程。
中断想必大家都知道,通俗来说也就是你现在在做事情1,被打扰了先去做了事情2,事情2做完了回去继续做事情1。
而STM32的外部中断也是如此,只是它做的事情跟你做的不太一样罢了,而且对处理事情有更为严格的优先级关系。举个通俗的例子,它在做事情1,但是被事情2和事情3同时打扰了,事情2的优先级大于事情3,那么它会先去做事情2再去做事情3,都做完了才回归主线事情1。所以有利于提高cpu的利用率。
嵌入式要学好必然要把图看好,那么先上个外部中断的流程框图:
每个输入线可以独立地配置输入类型(脉冲或挂起)和对应的触发事件(上升沿或下降沿或者双边沿触发)。每个输入线都可以独立地被屏蔽。挂起寄存器保持着状态线的中断请求。
STM32的中断控制器支持19个外部中断/事件请求:
- 线0~15:对应外部IO口的输入中断。
- 线16:连接到PVD输出。
- 线17:连接到RTC闹钟事件。
- 线18:连接到USB唤醒事件
每个IO口都可以对应作为中断,几个IO口为一组映射到一个中断线上,如GPIOx.0映射到EXTI0,GPIOx.1映射到EXTI1,...,GPIOx.15映射到EXTI15.
外部中断通用I/O映像
STM32外部中断需要用到以下几个寄存器:
- 中断屏蔽寄存器(EXTI_IMR)
- 事件屏蔽寄存器(EXTI_EMR)
- 上升沿触发选择寄存器(EXTI_RTSR)
- 下降沿触发选择寄存器(EXTI_FTSR)
- 软件中断事件寄存器(EXTI_SWIER)
- 挂起寄存器(EXTI_PR)
IO口外部中断只有7个中断服务函数
- EXTI0~EXTI4 分别对应一个中断服务函数
- EXTI5~EXTI9 对应一个中断服务函数
- EXTI10~EXTI15 对应一个中断服务函数
ps:这边值得注意的是EXTI5~EXTI9与EXTI10~EXTI15对应的都只有一个中断服务函数,所以要是出现在这范围出现多个线程的中断会有点问题,但在后面对这问题进行了解决。而且还有多个IO口出现同一线程该如何区分开的问题,也对其进行解决。
1. 对NVIC初始化,用到 NVIC_InitTypeDef 结构体,其中四个成员:
2. NVIC只可配置16种中断向量的优先级:编号越小,优先级别越高;
3. 抢占优先级:是指打断其它中断,会出现嵌套中断;
4. 子优先级(响应优先级):先处理响应优先级高的中断;
- /* 选择中断优先级配置组为4个抢占式优先级和4个子优先级,可以参考misc.h文件了解相关设置 */ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
- /* 使能KEY1所在的外部中断通道 */
- NVIC_InitStructure.NVIC_IRQChannel = KEY_IRQCHANNEL;
- /* 设置抢占式优先级为2 */
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
- /* 设置子优先级为3 */
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03;
- /* 使能外部中断通道 */
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
- /* 初始化配置嵌套向量中断控制器 */
- NVIC_Init(&NVIC_InitStructure);
EXTI的配置步骤如下所示:
①使能EXTIx线的IO时钟和复用时钟(AFIO)
②配置EXTI中断线与I/O的映射关系
③EXTI的I/O口线引脚和工作模式的配置
④配置EXTIx线的中断优先级
⑤EXTI 中断线工作模式配置
- /* 为启用IO引脚中断功能需要使能复用功能时钟 */
- KEY1_RCC_CLOCKCMD(KEY1_RCC_CLOCKGPIO | KEY1_RCC_CLOCKAFIO,ENABLE);
- /* 选择PE7作为中断输入源 */
- GPIO_EXTILineConfig(KEY1_GPIO_PORTSOURCE,KEY1_GPIO_PINSOURCE);
- /* KEY1对应的断线 */
- EXTI_InitStructure.EXTI_Line=KEY1_EXITLINE;
- /* 外部中断模式 */
- EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
- /* 上升沿触发方式 */
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
- /* 使能中断 */
- EXTI_InitStructure.EXTI_LineCmd = ENABLE;
- /* 根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器 */
- EXTI_Init(&EXTI_InitStructure);
其中AFIO的作用是信号传输媒介或者说是信号搬运工,EXTI的时钟是由AFIO提供的,所以需要开启。而AFIO时钟在APB2总线上的原因是端口重映射之后,映射的新引脚都是GPIO的引脚,GPIOx又是APB2的外设,使用APB2给其提供时钟非常合适。
- /* 中断服务函数,用于产生中断动作 */
- EXTIx_IRQHandler();
- /* 中断判断函数,用于判断中断动作 */
- EXTI_GetITStatus()
- /* 中断清除函数,用于清除中断动作 */
- EXTI_ClearITPendingBit()
上面说过由于EXTI5~EXTI9与EXTI10~EXTI15分别对于中断服务函数EXTI9_5_IRQHandler和EXTI15_10_IRQHandler,所以要是出现在这范围出现多个线程的中断,可能会出现问题,则需要进行对线程判断的处理,以及对IO口判断的处理。
- void KEY_IRQHANDLER(void)
- {
- /* 处理Line7的中断 */
- if(EXTI_GetITStatus(EXTI_Line7) != RESET)
- {
- /* 处理GPIOA的PIN7的中断,若没有多个IO占用Line7可以注释 */
- if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_7) == Bit_SET) // 检查引脚状态
- {
- //事件
- }
-
- /* 处理EXTI_Line7的中断 */
- EXTI_ClearITPendingBit(EXTI_Line7);
- }
-
- // 重复上述逻辑,检查其他线(如EXTI_Line5, EXTI_Line6,...)
- }

程序分为3个文件:bsp_key.c、bsp_key.h、main.c
1.bsp_key.c
这边值得注意的是KEY的引脚线序,对下方代码进行选取,我都为大家列好情况了,看下注释即可。
- /* 包含头文件 ----------------------------------------------------------------*/
- #include "bsp/key/bsp_key.h"
-
-
- /**
- * 函数功能: 配置KEY作为中断引脚并使能中断
- * 输入参数:无
- * 返 回 值: 无
- * 说 明:配置KEY为上升沿中断,当按下按键时就有一个从低电平变为高
- * 电平过程。
- */
- void KEY1_EXIT_Config(void)
- {
- /* 定义IO硬件初始化结构体变量 */
- GPIO_InitTypeDef GPIO_InitStructure;
- /* 定义外部中断线初始化结构体变量 */
- EXTI_InitTypeDef EXTI_InitStructure;
- /* 定义嵌套向量中断控制器初始化结构体变量 */
- NVIC_InitTypeDef NVIC_InitStructure;
-
- /* 为启用IO引脚中断功能需要使能复用功能时钟 */
- KEY1_RCC_CLOCKCMD(KEY1_RCC_CLOCKGPIO | KEY1_RCC_CLOCKAFIO,ENABLE);
-
- /* 设定KEY1对应引脚IO编号 */
- GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN;
- /* 设定KEY1对应引脚IO最大操作速度 :GPIO_Speed_50MHz */
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- /* 设定KEY1对应引脚IO为浮空输入模式 */
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
- /* 初始化KEY1对应引脚IO */
- GPIO_Init(KEY1_GPIO, &GPIO_InitStructure);
-
- /* 选择PE7作为中断输入源 */
- GPIO_EXTILineConfig(KEY1_GPIO_PORTSOURCE,KEY1_GPIO_PINSOURCE);
-
- /* KEY1对应的断线 */
- EXTI_InitStructure.EXTI_Line=KEY1_EXITLINE;
- /* 外部中断模式 */
- EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
- /* 上升沿触发方式 */
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
- /* 使能中断 */
- EXTI_InitStructure.EXTI_LineCmd = ENABLE;
- /* 根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器 */
- EXTI_Init(&EXTI_InitStructure);
-
- /* 选择中断优先级配置组为4个抢占式优先级和4个子优先级,可以参考misc.h文件了解相关设置 */
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
-
- /* 使能KEY1所在的外部中断通道 */
- NVIC_InitStructure.NVIC_IRQChannel = KEY_IRQCHANNEL;
- /* 设置抢占式优先级为2 */
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
- /* 设置子优先级为3 */
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03;
- /* 使能外部中断通道 */
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
- /* 初始化配置嵌套向量中断控制器 */
- NVIC_Init(&NVIC_InitStructure);
- }
-
- void KEY2_EXIT_Config(void)
- {
- /* 定义IO硬件初始化结构体变量 */
- GPIO_InitTypeDef GPIO_InitStructure;
- /* 定义外部中断线初始化结构体变量 */
- EXTI_InitTypeDef EXTI_InitStructure;
- /* 定义嵌套向量中断控制器初始化结构体变量 */
- NVIC_InitTypeDef NVIC_InitStructure;
-
- /* 为启用IO引脚中断功能需要使能复用功能时钟 */
- KEY1_RCC_CLOCKCMD(KEY2_RCC_CLOCKGPIO | KEY2_RCC_CLOCKAFIO,ENABLE);
-
- /* 设定KEY2对应引脚IO编号 */
- GPIO_InitStructure.GPIO_Pin = KEY2_GPIO_PIN;
- /* 设定KEY2对应引脚IO最大操作速度 :GPIO_Speed_50MHz */
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- /* 设定KEY2对应引脚IO为浮空输入模式 */
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
- /* 初始化KEY2对应引脚IO */
- GPIO_Init(KEY2_GPIO, &GPIO_InitStructure);
-
- /* 选择PE8作为中断输入源 */
- GPIO_EXTILineConfig(KEY2_GPIO_PORTSOURCE,KEY2_GPIO_PINSOURCE);
-
- /* KEY2对应的断线 */
- EXTI_InitStructure.EXTI_Line=KEY2_EXITLINE;
- /* 外部中断模式 */
- EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
- /* 上升沿触发方式 */
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
- /* 使能中断 */
- EXTI_InitStructure.EXTI_LineCmd = ENABLE;
- /* 根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器 */
- EXTI_Init(&EXTI_InitStructure);
-
- /* 选择中断优先级配置组为4个抢占式优先级和4个子优先级,可以参考misc.h文件了解相关设置 */
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
-
- /* 使能KEY2所在的外部中断通道 */
- NVIC_InitStructure.NVIC_IRQChannel = KEY_IRQCHANNEL;
- /* 设置抢占式优先级为0 */
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;
- /* 设置子优先级为0 */
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
- /* 使能外部中断通道 */
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
- /* 初始化配置嵌套向量中断控制器 */
- NVIC_Init(&NVIC_InitStructure);
- }
-
- void KEY3_EXIT_Config(void)
- {
- /* 定义IO硬件初始化结构体变量 */
- GPIO_InitTypeDef GPIO_InitStructure;
- /* 定义外部中断线初始化结构体变量 */
- EXTI_InitTypeDef EXTI_InitStructure;
- /* 定义嵌套向量中断控制器初始化结构体变量 */
- NVIC_InitTypeDef NVIC_InitStructure;
-
- /* 为启用IO引脚中断功能需要使能复用功能时钟 */
- KEY1_RCC_CLOCKCMD(KEY3_RCC_CLOCKGPIO | KEY3_RCC_CLOCKAFIO,ENABLE);
-
- /* 设定KEY3对应引脚IO编号 */
- GPIO_InitStructure.GPIO_Pin = KEY3_GPIO_PIN;
- /* 设定KEY3对应引脚IO最大操作速度 :GPIO_Speed_50MHz */
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- /* 设定KEY3对应引脚IO为浮空输入模式 */
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
- /* 初始化KEY3对应引脚IO */
- GPIO_Init(KEY1_GPIO, &GPIO_InitStructure);
-
- /* 选择PE9作为中断输入源 */
- GPIO_EXTILineConfig(KEY3_GPIO_PORTSOURCE,KEY3_GPIO_PINSOURCE);
-
- /* KEY3对应的断线 */
- EXTI_InitStructure.EXTI_Line=KEY3_EXITLINE;
- /* 外部中断模式 */
- EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
- /* 上升沿触发方式 */
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
- /* 使能中断 */
- EXTI_InitStructure.EXTI_LineCmd = ENABLE;
- /* 根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器 */
- EXTI_Init(&EXTI_InitStructure);
-
- /* 选择中断优先级配置组为4个抢占式优先级和4个子优先级,可以参考misc.h文件了解相关设置 */
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
-
- /* 使能KEY3所在的外部中断通道 */
- NVIC_InitStructure.NVIC_IRQChannel = KEY_IRQCHANNEL;
- /* 设置抢占式优先级为1 */
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
- /* 设置子优先级为1 */
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
- /* 使能外部中断通道 */
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
- /* 初始化配置嵌套向量中断控制器 */
- NVIC_Init(&NVIC_InitStructure);
- }
-
- /**
- * 函数功能: 针对KEY多根线序为5-9的中断服务函数
- * 输入参数:无
- * 返 回 值: 无
- * 说 明:在stm32f103检测到上升沿信号后会自动进入对应的中断服务函数,我们可以在
- * 服务函数内实现一些处理。
- * 多根线序为5-9的线
- */
- void KEY_IRQHANDLER(void)
- {
-
- if(EXTI_GetITStatus(EXTI_Line7) != RESET)
- {
- /* 延时一小段时间,消除抖动 */
- Delay(10);
- LED1_TOGGLE;
- /* 处理EXTI_Line7的中断 */
- EXTI_ClearITPendingBit(EXTI_Line7);
- }
-
- if(EXTI_GetITStatus(EXTI_Line8) != RESET)
- {
- Delay(10);
- LED2_TOGGLE;
- /* 处理EXTI_Line8的中断 */
- EXTI_ClearITPendingBit(EXTI_Line8);
- }
- if(EXTI_GetITStatus(EXTI_Line9) != RESET)
- {
- Delay(10);
- LED3_TOGGLE;
- /* 处理EXTI_Line9的中断 */
- EXTI_ClearITPendingBit(EXTI_Line9);
- }
- // 重复上述逻辑,检查其他线(如EXTI_Line5, EXTI_Line6)
- }
-
- /**
- * 函数功能: 针对KEY线序单独分开的中断服务函数
- * 输入参数:无
- * 返 回 值: 无
- * 说 明:在stm32f103检测到上升沿信号后会自动进入对应的中断服务函数,我们可以在
- * 服务函数内实现一些处理。
- * 线序单独分开的线
- */
- //void KEY1_IRQHANDLER(void)
- //{
- // /* 确保是否产生了EXTI Line中断 */
- // if(EXTI_GetITStatus(KEY1_EXITLINE) != RESET)
- // {
- // Delay(10);
- // /* LED1灯翻转 */
- // LED1_TOGGLE;
- // /* 清除中断标志位 */
- // EXTI_ClearITPendingBit(KEY1_EXITLINE);
- // }
- //}
-
- //void KEY2_IRQHANDLER(void)
- //{
- // if(EXTI_GetITStatus(KEY2_EXITLINE) != RESET)
- // {
- // Delay(10);
- // LED2_TOGGLE;
- // EXTI_ClearITPendingBit(KEY2_EXITLINE);
- // }
- //}
-
- //void KEY3_IRQHANDLER(void)
- //{
- // if(EXTI_GetITStatus(KEY3_EXITLINE) != RESET)
- // {
- // Delay(10);
- // LED3_TOGGLE;
- // EXTI_ClearITPendingBit(KEY3_EXITLINE);
- // }
- //}

2.bsp_key.h
这边值得注意的是KEY的引脚线序,对下方代码进行选取,我都为大家列好情况了,看下注释即可。
- #ifndef __BSP_KEY_H__
- #define __BSP_KEY_H__
-
- /* 包含头文件 ----------------------------------------------------------------*/
- #include <stm32f10x.h>
- #include "bsp/delay/delay.h"
- #include "bsp/led/bsp_led.h"
-
- /* 类型定义 --------------------------------------------------------------*/
- typedef enum
- {
- KEY_UP = 0,
- KEY_DOWN = 1,
- KEY_DOWN_LONG = 2,
- }KEYState_TypeDef;
-
- /* 宏定义 --------------------------------------------------------------------*/
- #define KEY1 (uint8_t)0x01
- #define KEY2 (uint8_t)0x02
- #define KEY3 (uint8_t)0x04
- #define IS_KEY_TYPEDEF(KEY) (((KEY) == KEY1) || ((KEY) == KEY2) || ((KEY) == KEY3))
-
- /* 宏定义 --------------------------------------------------------------------*/
- #define KEY1_RCC_CLOCKCMD RCC_APB2PeriphClockCmd
- #define KEY1_RCC_CLOCKGPIO RCC_APB2Periph_GPIOE
- #define KEY1_GPIO_PIN GPIO_Pin_7
- #define KEY1_GPIO GPIOE
- #define KEY1_DOWN_LEVEL 0 /* 根据原理图设计,KEY1按下时引脚为低电平,所以这里设置为0 */
- #define KEY1_RCC_CLOCKAFIO RCC_APB2Periph_AFIO
- #define KEY1_GPIO_PORTSOURCE GPIO_PortSourceGPIOE
- #define KEY1_GPIO_PINSOURCE GPIO_PinSource7
- #define KEY1_EXITLINE EXTI_Line7
-
- /* 根据线序来,需要用到则取消注释并修改*/
- //#define KEY1_IRQCHANNEL EXTI1_IRQn
- //#define KEY1_IRQHANDLER EXTI1_IRQHandler
-
- #define KEY2_RCC_CLOCKCMD RCC_APB2PeriphClockCmd
- #define KEY2_RCC_CLOCKGPIO RCC_APB2Periph_GPIOE
- #define KEY2_GPIO_PIN GPIO_Pin_8
- #define KEY2_GPIO GPIOE
- #define KEY2_DOWN_LEVEL 0 /* 根据原理图设计,KEY2按下时引脚为低电平,所以这里设置为0 */
- #define KEY2_RCC_CLOCKAFIO RCC_APB2Periph_AFIO
- #define KEY2_GPIO_PORTSOURCE GPIO_PortSourceGPIOE
- #define KEY2_GPIO_PINSOURCE GPIO_PinSource8
- #define KEY2_EXITLINE EXTI_Line8
-
- /* 根据线序来,需要用到则取消注释并修改*/
- //#define KEY2_IRQCHANNEL EXTI2_IRQn
- //#define KEY2_IRQHANDLER EXTI2_IRQHandler
-
- #define KEY3_RCC_CLOCKCMD RCC_APB2PeriphClockCmd
- #define KEY3_RCC_CLOCKGPIO RCC_APB2Periph_GPIOE
- #define KEY3_GPIO_PIN GPIO_Pin_9
- #define KEY3_GPIO GPIOE
- #define KEY3_DOWN_LEVEL 0 /* 根据原理图设计,KEY3按下时引脚为低电平,所以这里设置为0 */
- #define KEY3_RCC_CLOCKAFIO RCC_APB2Periph_AFIO
- #define KEY3_GPIO_PORTSOURCE GPIO_PortSourceGPIOE
- #define KEY3_GPIO_PINSOURCE GPIO_PinSource9
- #define KEY3_EXITLINE EXTI_Line9
-
- /* 根据线序来,需要用到则取消注释并修改*/
- //#define KEY3_IRQCHANNEL EXTI3_IRQn
- //#define KEY3_IRQHANDLER EXTI3_IRQHandler
-
- /* KEY多根线序为5-9,不用则注释 */
- #define KEY_IRQCHANNEL EXTI9_5_IRQn
- #define KEY_IRQHANDLER EXTI9_5_IRQHandler
-
-
- #define IRQ_DISABLE __set_PRIMASK(1) /* 关闭总中断 */
- #define IRQ_ENABLE __set_PRIMASK(0) /* 开放总中断 */
-
- /* 扩展变量 ------------------------------------------------------------------*/
- /* 函数声明 ------------------------------------------------------------------*/
- void KEY_GPIO_Init(void);
- KEYState_TypeDef KEYx_StateSet(GPIO_TypeDef* KEYx_GPIO, uint16_t KEYx_GPIO_PIN, uint8_t KEYx_DOWN_LEVEL);
- KEYState_TypeDef KEYx_Choice(int KEYIndex);
- void KEY_LED(void);
-
- void KEY1_EXIT_Config(void);
- void KEY2_EXIT_Config(void);
- void KEY3_EXIT_Config(void);
- #endif // __BSP_KEY_H__
-

3.main.c
这边值得注意的while函数里面什么都不用加,较为简单,运行就完事儿了!
-
- /* 包含头文件 ----------------------------------------------------------------*/
- #include "stm32f10x.h"
- #include "bsp/led/bsp_led.h"
- #include "bsp/key/bsp_key.h"
- #include "bsp/delay/delay.h"
-
-
- /* 函数体 --------------------------------------------------------------------*/
-
- /**
- * 函数功能: 主函数.
- * 输入参数: 无
- * 返 回 值: 无
- * 说 明: 无
- */
- int main(void)
- {
- LED_GPIO_Init();
- KEY1_EXIT_Config();
- KEY2_EXIT_Config();
- KEY3_EXIT_Config();
-
- while (1)
- {
- //什么都不加
- }
- }
-

外部中断-点灯
本文以STM32VET6为例讲解了用外部中断控制按键点灯的实现方法,并解决了几个可能会在实际中遇到的问题,并在代码中也给出相应注释。希望对大家有所帮助!如果还有什么问题,欢迎评论区留言,谢谢!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。