【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个外部中断,如图:
但是这种也会有一些缺陷,中断的判断逻辑较为复杂,并且容错率降低,一个地方出现断电,会导致同行同列出问题。
欢迎各位留言交流,文中有错误请及时指正!
作者:刘岳霖