大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是以i.MXRT的GPIO模块为例谈谈中断处理函数(IRQHandler)的标准流程

  在痞子衡旧文 《串口(UART)自动波特率识别程序设计与实现(中断)》里,我们利用了 GPIO 模块内部集成的 I/O 边沿检测功能完成了 RXD 信号下降沿的捕捉,这里涉及到了 GPIO 中断处理函数。中断处理函数 IRQHandler 是嵌入式里非常特殊的一类函数,它们是嵌入式系统能够实时完成任务的关键所在,任何一个中断处理函数都需要被谨慎对待。

  上面那篇旧文里,痞子衡写的 GPIO 中断处理函数其实是有一点瑕疵的,虽然不影响最终波特率识别功能,但其并不是标准流程写法。今天痞子衡就和大家聊一聊什么是中断处理函数的标准流程:

一、GPIO模块中断简介

  GPIO 基本上可以说是 MCU 里最入门级的外设了,我们先来简单看一下 i.MXRT1011 里 GPIO 模块功能。

1.1 GPIO 一般设计

  i.MXRT 里每组 GPIO 最大包含 32 个 Pin,正好对应 32bit 寄存器,下面是 GPIO 三大基础寄存器:

  1. GDIR[31:0] - 配置 Pin 的输入/输出方向(仅当 IOMUXC 里配置为 GPIO 模式)
  2. DR[31:0] - 设置 Pin 输出电平
  3. PSR[31:0] - 保存 Pin 输入电平(以 ipg_clk_s 时钟来采样)

  操作上述 GPIO 外设寄存器的前提条件是在 IOMUXC 模块里已将 Pin 功能模式配为 GPIO (因为每个 Pin 可能被多种外设UART/Timer等复用)。比如文章开头提及的那篇旧文里我们用于波特率检测的 GPIO_09 引脚,它有如下八种复用功能,其中 Alt5 功能是 GPIO。

  将 GPIO_09 引脚设为 GPIO 功能模式后,还需要根据应用场景进一步配置其 Pad 属性,下图是 Pad 内部电路结构,我们可以配置的属性有很多,比如驱动强度、速度等级、上下拉等,这些也是在 IOMUXC 模块里完成的。

  在串口波特率识别检测场景里,我们需要在 IOMUXC 模块里将 GPIO_09 引脚配置为 GPIO 模式,并且相应配置 Pad 属性(主要是使能内部上拉,因为串口信号 Idle 状态是高电平),示例代码如下:

  1. #include "fsl_iomuxc.h"
  2. void io_pin_config(void)
  3. {
  4. CLOCK_EnableClock(kCLOCK_Iomuxc); /* iomuxc clock (iomuxc_clk_enable): 0x03U */
  5. IOMUXC_SetPinMux(
  6. IOMUXC_GPIO_09_GPIOMUX_IO09, /* GPIO_09 is configured as GPIOMUX_IO09 */
  7. 0U); /* Software Input On Field: Input Path is determined by functionality */
  8. IOMUXC_SetPinConfig(
  9. IOMUXC_GPIO_09_GPIOMUX_IO09, /* GPIO_09 PAD functional properties : */
  10. 0x01B0A0U); /* Slew Rate Field: Slow Slew Rate
  11. Drive Strength Field: R0/4
  12. Speed Field: fast(150MHz)
  13. Open Drain Enable Field: Open Drain Disabled
  14. Pull / Keep Enable Field: Pull/Keeper Enabled
  15. Pull / Keep Select Field: Pull
  16. Pull Up / Down Config. Field: 100K Ohm Pull Up
  17. Hyst. Enable Field: Hysteresis Enabled */
  18. }

1.2 GPIO 中断设计

  如果仅仅是控制 I/O 输入输出电平,那 GPIO 外设功能也太简陋了。为了让 GPIO 外设具备更大的应用价值,IC 设计者往往会为其加入边沿检测功能,如下图蓝框标出的寄存器(这些寄存器仅在 Pin 方向被配置为输入时有效):

  1. EDGE_SEL[31:0] - 配置是否使能 Pin 双边沿检测
  2. ICRx[31:0] - 配置 Pin 低电平/高电平/上升沿/下降沿四种检测模式(仅当 EDGE_SEL 里没使能双边沿)
  3. IMR[31:0] - 配置是否使能 Pin 中断
  4. ISR[31:0] - 记录 Pin 中断状态

  边沿检测功能会涉及中断响应,在 i.MXRT 里为了节省中断号资源,将 16 个 Pin 编为一组,这 16 个 Pin 共享一个中断号。i.MXRT1011 里一共 37 个 GPIO(即GPIO1[31:0]、GPIO2[13:0]、GPIO5[0]),所以你在 MIMXRT1011.h 头文件里会看到如下中断号定义:

  1. typedef enum IRQn {
  2. /* Core interrupts */
  3. // ...省略
  4. /* Device specific interrupts */
  5. GPIO1_Combined_0_15_IRQn = 70,
  6. GPIO1_Combined_16_31_IRQn = 71,
  7. GPIO2_Combined_0_15_IRQn = 72, // 没用满
  8. GPIO5_Combined_0_15_IRQn = 73, // 没用满
  9. // ...省略
  10. } IRQn_Type;

  在串口波特率识别检测场景里,我们需要在 GPIO 模块里将 GPIO_09 引脚配置为输入模式,且开启下降沿捕获中断,示例代码如下:

  1. #include "fsl_gpio.h"
  2. void io_func_config(void)
  3. {
  4. // I/O 配置为输入,下降沿捕获模式
  5. gpio_pin_config_t sw_config = {
  6. kGPIO_DigitalInput,
  7. 0,
  8. kGPIO_IntFallingEdge,
  9. };
  10. // 初始化 GPIO1[9] 管脚
  11. GPIO_PinInit(GPIO1, 9, &sw_config);
  12. // 使能 GPIO1[9] 管脚中断
  13. GPIO_PortEnableInterrupts(GPIO1, 1U << 9);
  14. // 配置使能系统 GPIO1 中断
  15. NVIC_SetPriority(GPIO1_Combined_0_15_IRQn, 1);
  16. NVIC_EnableIRQ(GPIO1_Combined_0_15_IRQn);
  17. }

二、中断处理函数(IRQHandler)的标准流程

  上一节铺垫那么多,现在终于到了核心的中断处理函数了,我们在文章开头提及的那篇旧文关于串口波特率识别场景里继续聊(上位机设置发送波特率为115200)。

2.1 有问题的中断处理函数

2.1.1 无效中断执行

  如下代码即是我们之前的中断处理函数写法,串口波特率识别接头暗号是 0x5A、0XA6,从信号时序上看一共有 7 个下降沿,原理上这个中断处理函数应该被触发执行 7 次(也是 s_pin_irq_func 执行次数),我们额外加个赋值调试变量 s_irqCount,按理说识别结束后这个变量值应该等于 7,但实际上它的值是 12,即多进了 5 次中断,这显然不太合理。不过合理的是 s_pin_irq_func 确实只执行了 7 次。

  1. // 辅助调试变量1
  2. uint32_t s_irqCount = 0;
  3. void GPIO1_Combined_0_15_IRQHandler(void)
  4. {
  5. // ****辅助调试1:记录中断处理函数触发执行次数
  6. s_irqCount++;
  7. // ****辅助调试2:翻转 GPIO1[10]
  8. GPIO1->DR_TOGGLE = 1U << 10;
  9. uint32_t interrupt_flag = (1U << 9);
  10. // 仅当GPIO1[9]中断发生时
  11. if ((GPIO_GetPinsInterruptFlags(GPIO1) & interrupt_flag) && s_pin_irq_func)
  12. {
  13. // 执行一次回调函数
  14. s_pin_irq_func();
  15. // 清除GPIO1[9]中断标志
  16. GPIO_ClearPinsInterruptFlags(GPIO1, interrupt_flag);
  17. }
  18. }

  为了进一步定位问题,我们用另一个 GPIO1[10] 来辅助,将其配置为 GPIO 输出模式,初值为高,在中断处理函数里做一次翻转,然后用示波器同时抓取 GPIO1[10:9],波形如下,可以看到中间的每个下降沿均连续触发了两次中断处理函数的执行:

  这个问题其实跟 ARM Errata 838869 有关,在Cortex-M4/7 上,如果 CPU 执行速度(此处 i.MXRT1011 工作在 500MHz 主频下)远远高于 GPIO 外设寄存器写入速度(1/4 主频),中断处理函数代码里在退出前才清中断标志位 ISR[9] 的话,会导致中断标志位还没有真正被清除掉,CPU 立即又再次执行中断处理函数(只要 ISR 寄存器里标志位仍处于置位状态)。至于功能回调函数 s_pin_irq_func 没有被误执行,是因为中断处理函数里有中断状态位置起判断语句,恰好执行到这里的时候,状态位 ISR[9] 已经被清除了(但这样并不可靠)。

2.1.2 漏掉有效中断

  这个中断处理函数还有其他问题吗?其实还有,我们知道中断处理函数的一般原则是快进快出,即在函数里不要执行过多的代码,导致执行时间过长,影响在此期间发生的同类中断被响应。为了便于定位问题,我们给第一次下降沿中断(时间起点)响应执行里增加额外 40us 的延时,故意让其错过第二次下降沿中断(3bit * (1s/115200bit) = 26.04us)但不要错过第三次下降沿中断(6bit * (1s/115200bit) = 52.08us)。

  1. // 辅助调试变量1
  2. uint32_t s_irqCount = 0;
  3. // 辅助调试变量2
  4. uint32_t s_irqDelay = 40;
  5. void GPIO1_Combined_0_15_IRQHandler(void)
  6. {
  7. // 辅助调试1:记录中断处理函数触发执行次数
  8. s_irqCount++;
  9. // 辅助调试2:翻转 GPIO1[10]
  10. GPIO1->DR_TOGGLE = 1U << 10;
  11. uint32_t interrupt_flag = (1U << 9);
  12. // 仅当GPIO1[9]中断发生时
  13. if ((GPIO_GetPinsInterruptFlags(GPIO1) & interrupt_flag) && s_pin_irq_func)
  14. {
  15. // 执行一次回调函数
  16. s_pin_irq_func();
  17. // ****辅助调试3:增加一次 40us 的延时
  18. if (s_irqDelay)
  19. {
  20. microseconds_delay(s_irqDelay);
  21. s_irqDelay = 0;
  22. }
  23. // 清除GPIO1[9]中断标志
  24. GPIO_ClearPinsInterruptFlags(GPIO1, interrupt_flag);
  25. }
  26. }

  上述代码测试波形图如下,这种情况下波特率识别功能已经不正常,s_irqCount 值为 11,更关键的是 s_pin_irq_func 仅被执行了 6 次,漏掉了 1 次。因为这 40us 的延时,导致第二次下降沿中断没有被及时响应,可以理解为第一次中断处理函数执行退出前清除中断标志位操作一次性清除了两次中断状态位的置起行为。

2.2 解决中断处理函数里的问题

2.2.1 避免无效中断执行

  基于 2.1.1 节最后的分析,我们改进代码如下:

  1. void GPIO1_Combined_0_15_IRQHandler(void)
  2. {
  3. // 辅助调试2:翻转 GPIO1[10]
  4. GPIO1->DR_TOGGLE = 1U << 10;
  5. uint32_t interrupt_flag = (1U << 9);
  6. if ((GPIO_GetPinsInterruptFlags(GPIO1) & interrupt_flag) && s_pin_irq_func)
  7. {
  8. s_pin_irq_func();
  9. GPIO_ClearPinsInterruptFlags(GPIO1, interrupt_flag);
  10. // ****改进1:中断标志清除之后加 DSB 操作或者 poll 状态寄存器 ISR 确保标志位已被清除
  11. __DSB();
  12. }
  13. }

  中断处理函数代码改进之后再次用示波器抓取波形,测试结果就正常了:

2.2.2 避免漏掉有效中断

  基于 2.1.2 节最后的分析,我们改进代码如下:

  1. void GPIO1_Combined_0_15_IRQHandler(void)
  2. {
  3. // 辅助调试2:翻转 GPIO1[10]
  4. GPIO1->DR_TOGGLE = 1U << 10;
  5. uint32_t interrupt_flag = (1U << 9);
  6. if ((GPIO_GetPinsInterruptFlags(GPIO1) & interrupt_flag) && s_pin_irq_func)
  7. {
  8. // ****改进2:先清除中断标志,再执行回调函数
  9. GPIO_ClearPinsInterruptFlags(GPIO1, interrupt_flag);
  10. // 改进1:中断标志清除之后加 DSB 操作或者回读状态寄存器 ISR 确保标志位已被清除
  11. __DSB();
  12. s_pin_irq_func();
  13. // 辅助调试3:增加一次 40us 的延时
  14. if (s_irqDelay)
  15. {
  16. microseconds_delay(s_irqDelay);
  17. s_irqDelay = 0;
  18. }
  19. }
  20. }

  中断处理函数代码改进之后再次用示波器抓取波形,测试结果来看至少没有漏掉第二次下降沿中断,当然实时性还是没能保证(如果要严格记录第二次中断发生的时刻,显然无法做到),不过对于本文讨论的串口波特率识别应用场景来说倒并不影响功能。但这种解决方法并不是万能的,如果第一次中断处理函数执行期间发生两次及以上同类中断,那还是会存在漏掉有效中断的情况。

2.3 标准的中断处理函数流程

  结合上面的问题展示与分析解决,现在我们来认真探讨下什么是中断处理函数 IRQHandler 的标准流程,痞子衡认为主要分为如下四步:第一步是对中断状态位的置起做一次确认(可选项,有些外设不一定有状态位),第二步是立即清除状态标志,第三步是确保状态标志已被清除,第四步才是执行真正的中断处理任务(这个任务执行时间要越短越好,最好就是仅记录必要的信息,等中断退出后进入主循环时再具体展开任务),故中断处理函数标准模板如下:

  1. void xxx_IRQHandler(void)
  2. {
  3. // Step 1: 检查状态标志位是否有效
  4. if ((xxx_IsInterruptFlagSet() && s_irq_func)
  5. {
  6. // Step 2: 清除状态标志位
  7. xxx_ClearInterruptFlag();
  8. // Step 3: 确保状态标志位已被清除
  9. __DSB();
  10. // Step 4: 执行回调函数,时间越短越好
  11. s_irq_func();
  12. }
  13. }

三、番外篇 - 神奇的GPIO1[7:0]

  最后再提一下,在部分 i.MXRT 型号上,关于中断号资源,GPIO1[7:0] 地位与其他 GPIO 引脚不太一样,它们还会有专门的中断号,比如在 MIMXRT1062.h 头文件里你可以看到 GPIO1_INTx_IRQn:

  1. typedef enum IRQn {
  2. /* Core interrupts */
  3. // ...省略
  4. /* Device specific interrupts */
  5. GPIO1_INT0_IRQn = 72, /**< Active HIGH Interrupt from INT0 from GPIO */
  6. GPIO1_INT1_IRQn = 73, /**< Active HIGH Interrupt from INT1 from GPIO */
  7. GPIO1_INT2_IRQn = 74, /**< Active HIGH Interrupt from INT2 from GPIO */
  8. GPIO1_INT3_IRQn = 75, /**< Active HIGH Interrupt from INT3 from GPIO */
  9. GPIO1_INT4_IRQn = 76, /**< Active HIGH Interrupt from INT4 from GPIO */
  10. GPIO1_INT5_IRQn = 77, /**< Active HIGH Interrupt from INT5 from GPIO */
  11. GPIO1_INT6_IRQn = 78, /**< Active HIGH Interrupt from INT6 from GPIO */
  12. GPIO1_INT7_IRQn = 79, /**< Active HIGH Interrupt from INT7 from GPIO */
  13. GPIO1_Combined_0_15_IRQn = 80, /**< Combined interrupt indication for GPIO1 signal 0 throughout 15 */
  14. GPIO1_Combined_16_31_IRQn = 81, /**< Combined interrupt indication for GPIO1 signal 16 throughout 31 */
  15. GPIO2_Combined_0_15_IRQn = 82, /**< Combined interrupt indication for GPIO2 signal 0 throughout 15 */
  16. GPIO2_Combined_16_31_IRQn = 83, /**< Combined interrupt indication for GPIO2 signal 16 throughout 31 */
  17. GPIO3_Combined_0_15_IRQn = 84, /**< Combined interrupt indication for GPIO3 signal 0 throughout 15 */
  18. GPIO3_Combined_16_31_IRQn = 85, /**< Combined interrupt indication for GPIO3 signal 16 throughout 31 */
  19. GPIO4_Combined_0_15_IRQn = 86, /**< Combined interrupt indication for GPIO4 signal 0 throughout 15 */
  20. GPIO4_Combined_16_31_IRQn = 87, /**< Combined interrupt indication for GPIO4 signal 16 throughout 31 */
  21. GPIO5_Combined_0_15_IRQn = 88, /**< Combined interrupt indication for GPIO5 signal 0 throughout 15 */
  22. GPIO5_Combined_16_31_IRQn = 89, /**< Combined interrupt indication for GPIO5 signal 16 throughout 31 */
  23. // ...省略
  24. } IRQn_Type;

  至此,以i.MXRT的GPIO模块为例谈谈中断处理函数(IRQHandler)的标准流程痞子衡便介绍完毕了,掌声在哪里~~~

欢迎订阅

文章会同时发布到我的 博客园主页CSDN主页知乎主页微信公众号 平台上。

微信搜索"痞子衡嵌入式"或者扫描下面二维码,就可以在手机上第一时间看了哦。

痞子衡嵌入式:以i.MXRT1xxx的GPIO模块为例谈谈中断处理函数(IRQHandler)的标准流程的更多相关文章

  1. 痞子衡嵌入式:利用GPIO模块来测量i.MXRT1xxx的系统中断延迟时间

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT1xxx的系统中断延迟时间. 在 <Cortex-M系统中断延迟及其测量方法简介> 一文里,痞子衡介绍了 Cor ...

  2. 痞子衡嵌入式:聊聊i.MXRT1170双核下不同GPIO组的访问以及中断设计

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT1170双核下不同GPIO组的访问以及中断设计. 在双核 i.MXRT1170 下设计应用程序,有一个比较重要的考虑点就是外 ...

  3. 痞子衡嵌入式:实测i.MXRT1010上的普通GPIO与高速GPIO极限翻转频率

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT1010上的普通GPIO与高速GPIO极限翻转频率. 上一篇文章 <聊聊i.MXRT1xxx上的普通GPIO与高速GP ...

  4. 痞子衡嵌入式:在串口波特率识别实例里逐步展示i.MXRT上提升代码执行性能的十八般武艺

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是在串口波特率识别实例里逐步展示i.MXRT上提升代码执行性能的十八般武艺. 恩智浦 MCU SE 团队近期一直在加班加点赶 SBL 项目 ...

  5. 痞子衡嵌入式:嵌入式MCU中通用的三重中断控制设计

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是嵌入式MCU中通用的三重中断控制设计. 我们知道在 MCU 裸机中程序代码之所以能完成多任务并行实时处理功能,其实主要是靠中断来调度的, ...

  6. 痞子衡嵌入式:聊聊i.MXRT1xxx上的普通GPIO与高速GPIO差异及其用法

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT上的普通GPIO与高速GPIO差异. GPIO 可以说是 MCU 上最简单最常用的外设模块了,当一些原生功能外设接口模块不能 ...

  7. 痞子衡嵌入式:系统时钟配置不当会导致i.MXRT1xxx系列下OTFAD加密启动失败

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是系统时钟配置不当会导致i.MXRT1xxx系列下OTFAD加密启动失败问题. 我们知道,i.MXRT1xxx家族早期型号(RT1050/ ...

  8. 痞子衡嵌入式:利用i.MXRT1xxx系列ROM提供的FlexSPI driver API可轻松IAP

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT系列ROM中的FlexSPI驱动API实现IAP. 痞子衡的技术交流群里经常有群友提问: i.MXRT中的FlexSPI驱动 ...

  9. 痞子衡嵌入式:了解i.MXRTxxx系列ROM API及其与i.MXRT1xxx系列的差异

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRTxxx系列ROM API设计细节. 痞子衡之前写过两篇文章 <利用i.MXRT1xxx系列ROM提供的FlexSPI ...

随机推荐

  1. Pytorch系列:(六)自然语言处理NLP

    这篇文章主要介绍Pytorch中常用的几个循环神经网络模型,包括RNN,LSTM,GRU,以及其他相关知识点. nn.Embedding 在使用各种NLP模型之前,需要将单词进行向量化,其中,pyto ...

  2. [Python] 安装 & 常用命令

    安装 http://www.xue51.com/soft/2301.html 常用命令 #查看pip版本$ pip --version#安装pip$ sudo apt install python3- ...

  3. dpkg -S {file} #ubuntu 14.04 rpm -qf {file} #centos 7

    Linux查找命令或组件对应安装包的方法原创FJEagle 最后发布于2017-12-15 19:10:06 阅读数 4603 收藏展开Linux查找命令或组件对应安装包的方法当新搭建服务器或者维护不 ...

  4. 如何对你的Linux系统进行基准测试: 3开源基准测试工具

    如何对你的Linux系统进行基准测试: 3开源基准测试工具   0 赞0 评论 文章标签:SYS  Source  benchmark  tool  开源  基准  系统     linux实用程序的 ...

  5. 基于LNMP架构搭建wordpress个人博客

    搭建过程 注意防火墙和selinux的影响可以先关闭. 一.安装nginx # 1.更改nginx源安装nginx [root@web01 ~]# vi /etc/yum.repos.d/nginx. ...

  6. Linux shell脚本全面学习(一)

    1. Linux 脚本编写基础 1.1 语法基本介绍 1.1.1 开头 程序必须以下面的行开始(必须方在文件的第一行): #!/bin/sh 符号#!用来告诉系统它后面的参数是用来执行该文件的程序.在 ...

  7. 拖动登录框 HTML+CSS+js

    先上效果 代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="U ...

  8. 关于unity贴图压缩

    unity官方 https://docs.unity3d.com/Manual/class-TextureImporterOverride.html //后续填充内容

  9. 吐血整理!Python常用第三方库,码住!!!

    ​ Python作为一种编程语言近年来越来越受欢迎,它为什么这么火? 其中一个重要原因就是因为Python的库丰富--Python语言提供超过15万个第三方库,Python库之间广泛联系.逐层封装.几 ...

  10. Spring Cloud Alibaba(13)---Sleuth概述

    Sleuth概述 前言 在微服务架构中,众多的微服务之间互相调用,如何清晰地记录服务的调用链路是一个需要解决的问题.同时,由于各种原因,跨进程的服务调用失败时,运维人员希望能够通过 查看日志和查看服务 ...