你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32实例-外部中断实验

[复制链接]
STMCU小助手 发布时间:2022-6-29 17:00
   之前我们介绍了 STM32F10x 的中断,本文我们就来学**外部中断。要实现的功能与按键实验一样,即通过按键控制 LED,只不过这里采用外部中断方式进行控制。

EXTI 简介
    STM32F10x 外部中断/事件控制器(EXTI)包含多达 20 个用于产生事件/中断请求的边沿检测器。EXTI 的每根输入线都可单独进行配置,以选择类型(中断或事件)和相应的触发事件(上升沿触发、下降沿触发或边沿触发),还可独立地被屏蔽。

EXTI 结构框图
    EXTI 框图包含了 EXTI 最核心内容,掌握了此框图,对 EXTI 就有一个全局的把握,在编程的时候思路就非常清晰,EXTI 框图如图。

微信图片_20220628172423.jpg

    从上图中可以看到,有很多信号线上都有标号 9 样的“20”字样,这个表示在控制器内部类似的信号线路有 20 个,这与 STM32F10x 的 EXTI 总共有
20 个中断/事件线是吻合的。因此我们只需要理解其中一个的原理,其他的 19个线路原理都是一样的。
    EXTI 分为两大部分功能,一个产生中断,另一个产生事件,这两个功能从
硬件上就有所差别,这个在框图中也有体现。从图中标号 3 的位置处就分出了两条线路,一条是 3-4-5 用于产生中断,另一条是 3-6-7-8 用于产生事件。
    下面我们就来介绍下这两条线路:
(1)首先看下产生中断的这条线路(1-2-3-4-5)
1.标号 1 为输入线,EXTI 控制器有 20 个中断/事件输入线,这些输入线可以通过寄存器设置为任意一个 GPIO,也可以是一些外设的事件,这部分内容我们会在后面专门讲解。输入线一般是存在电平变化的信号。
2.边沿检测电路,EXTI 可以对触发方式进行选择,通过上升沿触发选择寄存器和下降沿触发选择寄存器对应位的设置来控制信号触发。边沿检测电路以输入线作为信号输入端,如果检测到有边沿跳变就输出有效信号 1 给红色框3 电路,否则输出无效信号 0。而上升沿和下降沿触发选择这两个寄存器可以控制需要检测哪些类型的电平跳变过程,可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发。
3.其实就是一个或门电路,一端输入信号线由标号 2 提供,一端由软件中断事件寄存器提供,只要有一个为有效信号 1,标号 3 电路则输出有效信号 1,否则为无效信号 0。软件中断事件寄存器允许我们使用软件来启动中断/事件线,这个在某些地方非常有用。
4.其实就是一个与门电路,一端输入信号线由标号 3 电路输出提供,一端由
中断屏蔽寄存器提供,只有当两者都为有效信号 1,标号 4 电路才会输出有效信号 1,否则输出无效。这样我们就可以简单的控制中断屏蔽寄存器来实现是否产生中断的目的。当我们把中断屏蔽寄存器设置为 1 时,标号 4 输出就取决于标号3 电路的输出。标号 3 电路输出的信号会被保存到挂起寄存器内,如果确定标号3 电路输出为 1 就会把挂起寄存器对应位置 1。
5.将挂起寄存器内容输入到NVIC 内,从而实现系统中断事件的控制。
(2)最后我们再来看下产生事件这条线路(1-2-3-6-7-8),前面 1-2-3
都是一样的,只是在 3 的输出后产生分歧。
6.其实就是一个与门电路,一端来至标号 3 电路的输出信号,一端来至事件屏蔽寄存器,只有两者都为有效电平 1,标号 6 输出才有效。当事件屏蔽寄存器设置为 0 时,不管标号3 电路输出为 1 还是 0,标号 6 电路输出均为 0。当事件屏蔽寄存器设置为 1 时,标号 6 电路输出取决于标号 3 电路输出,这样就可以简单的控制事件屏蔽寄存器来实现是否产生事件的目的。
7.脉冲发生器电路,其输入端只与标号 6 电路输出有关,标号6输出有效,
脉冲发生器才会输出一个脉冲信号。
8.脉冲信号,由标号 7 脉冲发生器产生,是事件线路的终端,此脉冲信号可
供其他外设电路使用,比如定时器、ADC 等。这样的脉冲信号通常用来触发定时器、ADC 等开始转换。
    从上面 EXTI 框图可以看出,中断线路最终会输入到 NVIC 控制器中,从而会运行中断服务函数,实现中断内功能,这个是软件级的。而事件线路最后产生的脉冲信号会流向其他的外设电路,是硬件级的。在 EXTI 框图最顶端可以看到,其外设接口时钟是由 PCLK2,即 APB2 提供,所以在后面使能 EXTI 时钟的时候一定要注意。

外部中断/事件线映射
    STM32F10x 的 EXTI 具有 20 个中断/事件线,对应连接的外设说明如下表。

微信图片_20220628172419.jpg

    从上表可知,STM32F10x 的 EXTI 供外部 IO 口使用的中断线有 16 根,但是我们使用的 STM32F103 芯片却远远不止 16 个 IO 口, 那么 STM32F103 芯片怎么解决这个问题的呢?因为 STM32F103 芯片每个 GPIO 端口均有 16 个管脚, 因此把每个 端 口 的 16 个 IO 对 应 那 16 根 中 断 线 EXTI0-EXTI15 。比 如 :GPIOx.0-GPIOx.15(x=A,B,C,D,E,F,G)分别对应中断线 EXTI0-EXTI15,这样一来每个中断线就对应了最多 7 个 IO 口,比如:GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0。但是中断线每次只能连接一个在 IO 口上,这样就需要通过 AFIO 的外部中断配置寄存器 1 的 EXTIx[3:0]位来决定对应的中断线映射到哪个 GPIO端口上, 对于中断线映射到 GPIO 端口上的配置函数在stm32f10x_gpio.c 和 stm32f10x_gpio.h 中,所以使用到外部中断时要把这个文件加入到工程中,在创建库函数模板的时候我们默认已经添加。EXTI 的GPIO 映射图如下图所示。

微信图片_20220628172412.jpg

EXTI 配置步骤
    接下来我们介绍下如何使用库函数对外部中断进行配置。这个也是在编写程序中必须要了解的。具体步骤如下:(EXTI 相关库函数在 stm32f10x_exti.c 和stm32f10x_exti.h 文件中)
(1)使能 IO 口时钟,配置IO 口模式为输入
    由于本章使用开发板上 4 个按键 IO 口作为外部中断输入线,因此需要使能对应的IO 口时钟及配置 IO口模式,在按键实验章节中,我们就介绍过要把对应IO 口设置为输入模式,这部分配置与按键实验一样。
(2)开启 AFIO 时钟,设置 IO 口与中断线的映射关系
    接下来我们需要将GPIO 映射到对应的中断线上,只要使用到外部中断,就
必须先使能 AFIO 时钟, 前面已经说了它是挂接在 APB2 总线上的, 所以使能AFIO时钟库函数为:
  1. RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
复制代码

    然后,我们就可以把 GPIO 映射到对应的中断线上,配置 GPIO 与中断线映射的库函数如下:
  1. void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_tGPIO_PinSource);
复制代码

    比如我们将中断线 0映射到GPIOA 端口,那么就需要如下配置:
  1. GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
复制代码

    这时候 GPIOA.0 管脚就与中断线 0 连接起来,其他端口中断线的映射类似。
(3)配置中断分组(NVIC),使能中断
    我们知道 EXTI 产生中断线路最终是流向 NVIC 控制器的,由 NVIC 调用中断服务函数,因此我们需要对 NVIC 进行配置。NVIC 的配置在上一章介绍 STM32中断的时候已经讲过,这里就不重复,不清楚的朋友可以回过头看下。配置 NVIC范例如下:
  1. NVIC_InitTypeDef NVIC_InitStructure;
  2. NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;//EXTI0中断通道
  3. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
  4. NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; // 子 优 先级
  5. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道使能
  6. NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化 VIC 寄存器
复制代码

(4)初始化 EXTI,选择触发方式
    配置好 NVIC 后,我们还需要对中断线上的中断初始化,EXTI 初始化库函数如下:
  1. void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
复制代码

    函 数 形 参 是 有 一 个 结 构 体 EXTI_InitTypeDef 类 型 的 指 针 变 量 ,
EXTI_InitTypeDef 结构体成员变量如下:
  1. typedef struct
  2. {
  3.   uint32_t EXTI_Line; //中断/事件线
  4.   EXTIMode_TypeDef EXTI_Mode; //EXTI 模式
  5.   EXTITrigger_TypeDef EXTI_Trigger; //EXTI 触发方式
  6.   FunctionalState EXTI_LineCmd; //中断线使能或失能
  7. }EXTI_InitTypeDef;
复制代码

    下面就来介绍下结构体内成员的意义:
EXTI_Line:EXTI 中断/事件线选择,可配置参数为 EXTI0-EXTI20,可参考上表。
EXTI_Mode:EXTI 模式选择,可以配置为中断模式 EXTI_Mode_Interrupt和事件模式 EXTI_Mode_Event。
EXTI_Trigger :触 发 方 式 选 择 , 可 以 配 置 为 上 升 沿 触 发
EXTI_Trigger_Rising、下降沿触发 EXTI_Trigger_Falling、上升沿和下降沿触发 EXTI_Trigger_Rising_Falling。
EXTI_LineCmd:中断线使能或者失能,配置 ENABLE 为使能,DISABLE 为失能,我们这里要使用外部中断,所以需使能。
(5)编写 EXTI 中断服务函数
    所有中断函数都在 STM32F1启动文件中, 不知道中断函数名的可以打开启动文件查找。这里我们使用到的是外部中断,其函数名如下:
  1. EXTI0_IRQHandler
  2. EXTI1_IRQHandler
  3. EXTI2_IRQHandler
  4. EXTI3_IRQHandler
  5. EXTI4_IRQHandler
  6. EXTI9_5_IRQHandler
  7. EXTI15_10_IRQHandler
复制代码

    从函数名可以看到,前面 0-4 个中断线都是独立的函数,中断线 5-9共用一个 函 数 EXTI9_5_IRQHandler , 中 断 线 10-15 也 共 用 一 个 函 数EXTI15_10_IRQHandler,所以要在编写对应中断服务函数时要注意。

硬件设计
    本章硬件电路依然使用开发板上的 4 个按键,其电路如下图所示。

微信图片_20220628172358.png

微信图片_20220628172746.png

微信图片_20220628172353.jpg

    当按键按下时,对应的 IO 口电平会发声变化,这时只要配置好对应端口的外部中断触发方式就可以触发中断。
    要实现外部中断方式控制LED,程序框架如下:
(1)初始化对应端口的EXTI
(2)编写 EXTI 中断函数
(3)编写主函数
    在前面介绍 EXTI 配置步骤时,就已经讲解如何初始化 EXTI。下面我们打开“ 外部中断实验”工程,在 APP 工程组中可以看到添加exti.c 文件,在 StdPeriph_Driver 工程组中添加 stm32f10x_exti.c库文件。外部中断操作的库函数都放在 stm32f10x_exti.c 和 stm32f10x_exti.h文件中,所以使用到外部中断就必须加入 stm32f10x_exti.c 文件,同时要包含对应的头文件路径。

EXTI 初始化函数
    要使用外部中断,我们必须先对它进行配置。EXTI 初始化代码如下:
  1. /****************************************************************
  2. * 函 数 名 : My_EXTI_Init
  3. * 函数功能 : 外部中断初始化
  4. * 输 入 : 无
  5. * 输 出 : 无
  6. *****************************************************************/
  7. void My_EXTI_Init(void)
  8. {
  9. NVIC_InitTypeDef NVIC_InitStructure;
  10. EXTI_InitTypeDef EXTI_InitStructure;
  11. RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
  12. GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource2);//选择 GPIO 管脚用作外部中断线路
  13. GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource3);//选择 GPIO 管脚用作外部中断线路
  14. GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource4);//选择 GPIO 管脚用作外部中断线路
  15. GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);//选择 GPIO 管脚用作外部中断线路
  16. //EXTI0 NVIC 配置
  17. NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;//EXTI0 中断通道
  18. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;// 抢 占优先级
  19. NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级
  20. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
  21. NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化 VIC 寄存器
  22. //EXTI2 NVIC 配置
  23. NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;//EXTI2 中断通道
  24. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;// 抢 占优先级
  25. NVIC_InitStructure.NVIC_IRQChannelSubPriority =2; //子优先级
  26. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
  27. NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化 VIC 寄存器
  28. //EXTI3 NVIC 配置
  29. NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;//EXTI3 中断通道
  30. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;// 抢 占优先级
  31. NVIC_InitStructure.NVIC_IRQChannelSubPriority =1; //子优先级
  32. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
  33. NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化 VIC 寄存器
  34. //EXTI4 NVIC 配置
  35. NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;//EXTI4 中断通道
  36. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;// 抢 占优先级
  37. NVIC_InitStructure.NVIC_IRQChannelSubPriority =0; //子优先级
  38. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
  39. NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化 VIC 寄存器
  40. EXTI_InitStructure.EXTI_Line=EXTI_Line0;
  41. EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
  42. EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;
  43. EXTI_InitStructure.EXTI_LineCmd=ENABLE;
  44. EXTI_Init(&EXTI_InitStructure);
  45. EXTI_InitStructure.EXTI_Line=EXTI_Line2|EXTI_Line3|EXTI_Line4;
  46. EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
  47. EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;
  48. EXTI_InitStructure.EXTI_LineCmd=ENABLE;
  49. EXTI_Init(&EXTI_InitStructure);
  50. }
复制代码

    在My_EXTI_Init()函数中,首先使能 AFIO 时钟,并将 4 个按键端口映射到对应中断线上,4 个按键连接端口是 PA0、PE2、PE3、PE4。然后配置相应的NVIC并使能对应中断通道,由于 4 个按键的 IO 口占用了 4 个中断线,属于不同的中断通道,需要分别对其配置,从 NVIC 配置代码中可以看到 4 个中断通道的响应优先级为 0-3,所以 NVIC 被分成了 2 组,分组代码放在了主函数,只有一条语句:
  1. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组分 2组。
复制代码

    最后就是对 EXTI 初始化, 通过配置 EXTI_InitStructure 结构体成员值实现EXTI的配置,从代码中可以看到,中断线 0(EXTI_Line0)被配置为上升沿触发,中断线 2-4 被配置为下降沿触发,这是因为我们的按键 K_UP 是高电平有效,而其他 3个按键是低电平有效,这个在按键实验中已经介绍过。
    其实如果你会配置一个中断线的 EXTI,那么其他中断线都是类似的。

EXTI 中断函数
    初始化 EXTI 后,中断就已经开启了,当任意按键按下后会触发一次中断,这时程序就会进入中断服务函数执行,所以我们还需要编写对应的 EXTI 中断函数,这里我们以 PA0 管脚的 K_UP 按键进行讲解,其他的按键的中断函数类似,具体代码如下:
  1. /****************************************************************
  2. * 函 数 名 : EXTI0_IRQHandler
  3. * 函数功能 : 外部中断 0 函数
  4. * 输 入 : 无
  5. * 输 出 : 无
  6. *****************************************************************/
  7. void EXTI0_IRQHandler(void)
  8. {
  9. if(EXTI_GetITStatus(EXTI_Line0)==1)
  10.   {
  11.     delay_ms(10);
  12.     if(K_UP==1)
  13.     {
  14.       led2=0;
  15.     }
  16.   }
  17.   EXTI_ClearITPendingBit(EXTI_Line0);
  18. }
复制代码

    因为PA0管脚对应的中断线是EXTI0, 所以其中断函数为EXTI0_IRQHandler,这个从名字来看就很好理解。进入中断函数后,为了确保中断是否真的发生,我们还会对其中断标志位状态进行判断,获取 EXTI 中断标志位状态函数如下:
  1. EXTI_GetITStatus(EXTI_Line0);
复制代码

    函 数 参 数 EXTI_Line0 是 所 要 判 断 的 中 断 线 , 可 以 为EXTI_Line0-EXTI_Line20。
    如果 EXTI 中断线有中断发生,函数返回SET,否则返回RESET。SET也可以用 1表示,RESET 可以用0表示。
    在结束中断服务函数前,我们还会清除中断标志位,函数如下:
  1. EXTI_ClearITPendingBit(EXTI_Line0);
复制代码

    在库函数内, 还提供了两个函数用来判断外部中断状态以及清除外部状态标志位的函数 EXTI_GetFlagStatus 和 EXTI_ClearFlag,他们的作用和前面两个函数的作用类似。只是在 EXTI_GetITStatus 函数中会先判断这种中断是否使能,使能了才去判断中断标志位,而 EXTI_GetFlagStatus 直接用来判断状态标志位。
    在中断函数内,还调用了 delay_ms 函数,用于软件消抖,如果 K_UP确实按下了,就点亮 D2 指示灯。

主函数
编写好 EXTI 初始化和中断服务函数后,接下来就可以编写主函数了,代码
如下:
  1. /****************************************************************
  2. * 函 数 名 : main
  3. * 函数功能 : 主函数
  4. * 输 入 : 无
  5. * 输 出 : 无
  6. *****************************************************************/
  7. int main()
  8. {
  9. u8 i;
  10. SysTick_Init(72);
  11. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级
  12. 分组 分2 组
  13. LED_Init();
  14. KEY_Init();
  15. My_EXTI_Init(); //外部中断初始化
  16. while(1)
  17. {
  18. i++;
  19. if(i%20==0)
  20. {
  21. led1=!led1;
  22. }
  23. delay_ms(10);
  24. }
  25. }
复制代码

    主函数实现的功能很简单,首先对 NVIC 进行分组,这里我们调用NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2)函数,将 NVIC 分为 2 组即抢占优先级和响应优先级都占 2位,本套教程所有中断分组都采用这种设置,后面就不做重复。再对使用到的硬件端口时钟和 IO 口初始化,然后调用我们前面编写的EXTI的初始化函数, 最后进入while循环语句, 不断让D1指示间隔200ms闪烁。
    有的朋友就会问, 在主函数中怎么没有看到按键对 LED 的控制呢?因为我们在 My_EXTI_Init()函数内就已经把按键管脚映射到中断线上,并配置了相应的触发方式, 当有按键按下, 即会进入对应中断服务函数执行相应的功能程序, LED的控制就在中断函数内完成的。

实例现象
    将工程程序编译后下载到开发板内,可以看到 D1 指示灯不断闪烁,表示程序正常运行。当按下 K_UP 键,D2 指示灯点亮;当按下 K_DOWN 键,D2 指示灯熄灭;当按下 K_LEFT 键,D3指示灯熄灭,当按下 K_RIGHT 键,D3指示灯点亮。

收藏 评论0 发布时间:2022-6-29 17:00

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版