【STM32-外部中断】解决外部中断线冲突的方法(中断复用)

【STM32-外部中断】外部中断数过多导致中断线冲突解决办法(中断复用)

说明

项目中用到多个电机控制,行程两端限位用的都是微动开关。轮询的方法可以解决问题,但是一旦异常整个程序就会死等在那。使用的是STM32F4ZGT6芯片,有16个中断线(EXTI),而需要用到近20个外部中断,硬件上没法大改,只好思考如何软件解决。一通搜索下,只有提出这个问题的帖子,没找到解决方法,特写此文。

解决思路

1.如果一个开发板中串口数量不够用了该怎么办呢,大概率是去搞串口复用,那么复用的思路是否能放在这个问题上呢?
2.既然考虑复用的可行性,那么就要去了解中断线是如何配置并生效的,于是查找资料,这部分有很多大牛解释的很明白了,就不赘述,上链接:

STM32-外部中断详解
STM32-外部中断(此篇有寄存器说明,推荐阅读)
stm32共用外部中断线问题(此篇为中断线共用问题解释)

一番阅读后了解到,中断线是配置 中断屏蔽寄存器(EXTI_IMR) 来生效的:

那么既然是寄存器操作,我在启动电机之前重新配置一下中断的配置,对寄存器重新写入中断线配置,然后再初始化使其生效,每次启动电机之前重复操作,是不是就可以复用的目的?

适用的范围

思考到这里,就想到了这个方法的有一定的适用范围:
1.需要配置的外部中断大于芯片可提供的、可同时生效的外部中断线数量,才会用到这种方法,正常小于其提供的数量不用每次使用之前都初始化。
2.局限性:需要有上位机控制、有其他外部条件控制程序运行,或是程序逻辑中所有任务循环进行。换句话说,程序需要知道下一步需要做什么,才能去配置相应的中断线。

编程实现

既然有了思路,那就开始实践测试。
首先我使用的是CUBEMX生成的项目代码,HAL库,查看GPIO初始化的代码(例如K17 K8这些都是微动开关名,不必在意):

void MX_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    // 启用GPIO端口时钟
    __HAL_RCC_GPIOA_CLK_ENABLE(); 

    // 配置GPIO引脚输出电平
    HAL_GPIO_WritePin(GPIOA, LED1_GPIO_O_Pin, GPIO_PIN_RESET); // 以LED1的引脚为例

    // 配置GPIO引脚参数
    GPIO_InitStruct.Pin = K16_GPIO_EXIT14_Pin;//这里是我自定义的引脚名称
    GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(K16_GPIO_EXIT14_GPIO_Port, &GPIO_InitStruct);

    // 配置外部中断(EXTI)的优先级和使能,如果有的话
    // 示例如下,以EXTI1为例
    HAL_NVIC_SetPriority(EXTI14_IRQn, 2, 0);
    HAL_NVIC_EnableIRQ(EXTI14_IRQn);
}

了解了函数结构后,我们就可以套用这个结构来写需要复用的代码,由于其是在寄存器内生效,同一时间一个中断线只能生效一个配置,所以我中断复用代码放在了电机驱动函数的开头:

void motor_active(MotorCommand_t *cmd)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0}; // 定义GPIO初始化结构体并清零

    HAL_NVIC_DisableIRQ(EXTI9_5_IRQn); // 禁用EXTI9到EXTI5的中断

    // 配置GPIO引脚为外部中断模式
    GPIO_InitStruct.Pin = K14_GPIO_EXTI9_Pin; // 设置引脚号(这里是K14,连接到EXTI9)
    GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; // 设置中断模式为上升沿触发
    GPIO_InitStruct.Pull = GPIO_NOPULL; // 设置无上拉或下拉
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化GPIOA端口的K14引脚

    // 设置EXTI9_5中断的优先级并使能
    HAL_NVIC_SetPriority(EXTI9_5_IRQn, 2, 0); // 设置中断优先级
    HAL_NVIC_EnableIRQ(EXTI9_5_IRQn); // 使能EXTI9到EXTI5的中断

    HAL_NVIC_DisableIRQ(EXTI9_5_IRQn); // 再次禁用EXTI9到EXTI5的中断(为了重新配置)

    // 配置另一个GPIO引脚为外部中断模式
    GPIO_InitStruct.Pin = K17_GPIO_EXTI8_Pin; // 设置引脚号(这里是K17,连接到EXTI8)
    GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; // 设置中断模式为上升沿触发
    GPIO_InitStruct.Pull = GPIO_NOPULL; // 设置无上拉或下拉
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); // 初始化GPIOD端口的K17引脚

    // 再次设置EXTI9_5中断的优先级并使能
    HAL_NVIC_SetPriority(EXTI9_5_IRQn, 2, 0); // 设置中断优先级
    HAL_NVIC_EnableIRQ(EXTI9_5_IRQn); // 使能EXTI9到EXTI5的中断

    // 下面是电机驱动代码,此处省略
}

中断复用实现部分的代码就可以了,下面还需要把对应的define加上,不然编译的时候编译器不知道这些陌生的名称是什么:

#define K14_GPIO_EXTI9_Pin GPIO_PIN_9
#define K14_GPIO_EXTI9_GPIO_Port GPIOA
#define K14_GPIO_EXTI9_EXTI_IRQn EXTI9_5_IRQn

当然CUBEMX已经帮我生成过的define定义就不用再定义一遍啦。编译通过,烧录测试,符合预期就OK了。

其他可能的解决方案

暂时只想到一种硬件的解决方法,那就是使用类似矩阵按键的电路,如此可以使用2N个引脚数,来达到N^2个外部中断,如图:

但是这种也会有一些缺陷,中断的判断逻辑较为复杂,并且容错率降低,一个地方出现断电,会导致同行同列出问题。
欢迎各位留言交流,文中有错误请及时指正!

作者:刘岳霖

物联沃分享整理
物联沃-IOTWORD物联网 » 【STM32-外部中断】解决外部中断线冲突的方法(中断复用)

发表评论