前言
上一篇文章已经介绍了如何用按键点灯,使用的是按键扫描的方式,实现了点灯的第二步。这一篇则介绍如何用外部中断的方式实现按键点灯的过程。
一、实验原理
1.外部中断的定义
中断想必大家都知道,通俗来说也就是你现在在做事情1,被打扰了先去做了事情2,事情2做完了回去继续做事情1。
而STM32的外部中断也是如此,只是它做的事情跟你做的不太一样罢了,而且对处理事情有更为严格的优先级关系。举个通俗的例子,它在做事情1,但是被事情2和事情3同时打扰了,事情2的优先级大于事情3,那么它会先去做事情2再去做事情3,都做完了才回归主线事情1。所以有利于提高cpu的利用率。
2.外部中断的介绍
嵌入式要学好必然要把图看好,那么先上个外部中断的流程框图:
2.1 STM32的外部中断线
每个输入线可以独立地配置输入类型(脉冲或挂起)和对应的触发事件(上升沿或下降沿或者双边沿触发)。每个输入线都可以独立地被屏蔽。挂起寄存器保持着状态线的中断请求。
STM32的中断控制器支持19个外部中断/事件请求:
- 线0~15:对应外部IO口的输入中断。
- 线16:连接到PVD输出。
- 线17:连接到RTC闹钟事件。
- 线18:连接到USB唤醒事件
2.2 外部中断线与IO引脚对应关系
每个IO口都可以对应作为中断,几个IO口为一组映射到一个中断线上,如GPIOx.0映射到EXTI0,GPIOx.1映射到EXTI1,...,GPIOx.15映射到EXTI15.
外部中断通用I/O映像
2.3外部中断所需寄存器
STM32外部中断需要用到以下几个寄存器:
- 中断屏蔽寄存器(EXTI_IMR)
- 事件屏蔽寄存器(EXTI_EMR)
- 上升沿触发选择寄存器(EXTI_RTSR)
- 下降沿触发选择寄存器(EXTI_FTSR)
- 软件中断事件寄存器(EXTI_SWIER)
- 挂起寄存器(EXTI_PR)
2.4外部中断与中断服务函数
IO口外部中断只有7个中断服务函数
- EXTI0~EXTI4 分别对应一个中断服务函数
- EXTI5~EXTI9 对应一个中断服务函数
- EXTI10~EXTI15 对应一个中断服务函数
ps:这边值得注意的是EXTI5~EXTI9与EXTI10~EXTI15对应的都只有一个中断服务函数,所以要是出现在这范围出现多个线程的中断会有点问题,但在后面对这问题进行了解决。而且还有多个IO口出现同一线程该如何区分开的问题,也对其进行解决。
二、实验步骤
1.NVIC配置
1. 对NVIC初始化,用到 NVIC_InitTypeDef 结构体,其中四个成员:
- NVIC_IRQChannel参数来选择将要配置的中断向量;
- NVIC_IRQChannelCmd参数来进行使能(ENABLE)或关闭(DISABLE)该中断;
- NVIC_IRQChannelPreemptionPriority成员要配置中断向量的抢占优先级;
- NVIC_IRQChannelSubPriority需要配置中断向量的子优先级(响应优先级);
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);
2.EXTI配置
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给其提供时钟非常合适。
参考:AFIO时钟误区及其用法解析-CSDN博客
3.中断函数
/* 中断服务函数,用于产生中断动作 */
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)
{
//什么都不加
}
}
四、实验效果
外部中断-点灯文章来源:https://www.toymoban.com/news/detail-857368.html
结束语
本文以STM32VET6为例讲解了用外部中断控制按键点灯的实现方法,并解决了几个可能会在实际中遇到的问题,并在代码中也给出相应注释。希望对大家有所帮助!如果还有什么问题,欢迎评论区留言,谢谢!文章来源地址https://www.toymoban.com/news/detail-857368.html
到了这里,关于STM32(三):外部中断 (标准库函数)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!