【STM32 学习笔记】EXTI外部中断
EXTI外部中断
表的详细内容在STM32F10xxx参考手册132页有,
然后右边这里还有个中断的地址,这个地址是干什么的呢?这个是因为我们程序中的中断函数,它的地址是由编译器来分配的,是不固定的。但是我们的中断跳转由于硬件的限制,只能跳到固定的地址执行程序,所以为了能让硬件跳转到一个不固定的中断函数里,这里就需要在内存中定义一个地址的列表。这个列表地址是固定的,中断发生后,就跳到这个固定位置,然后在这个固定位置由编译器,再加上一条跳转到中断函数的代码,这样中断跳转就可以跳转到任意位置。这个中断地址的列表,就叫中断向量表。
NVIC基本结构
这个NVIC的名字叫做嵌套中断向量控制器,在STM32中,它是用来统一分配中断优先级和管理中断的。
NVIC是一个内核外设,是CPU的小助手。STM32的中断非常多,如果把这些中断全都接到CPU上,那CPU还得引出很多线进行适配,设计上就很麻烦,并且如果很多中断同时申请,或者中断很多产生了拥堵,CPU也会很难处理,毕竟CPU主要是用来运算的,中断分配的任务就放到别的地方吧,所以NVIC就出现了。
NVIC有很多输入口,你有多少个中断线路,都可以接过来,比如这里可以接到EXTI、TIM、ADC、USART等等,这里线上画了个斜杠,上面写个n,这个意思是一个外设可能会同时占用多个中断通道,所以这里有n条线。然后NVIC只有一个输出口,NVIC根据每个中断的优先级分配中断的先后顺序,之后,通过右边这一个输出口就告诉CPU,你该处理哪个中断。对于中断先后顺序分配的任务,CPU不需要知道。
13:18~14:00举了例子 && 14:00讲了下面的NVIC中断分组
EXTI结构
但相同的Pin不能同时触发中断:这个意思就是,比如PA0和PB0不能同时用,或者,PA1、PB1、PC1这样的,端口GPIO_Pin一样的。
然后再看一下外部中断占用的通道,其中有16个GPIO_Pin,这就对应GPIO_Pin_0到GPIO_Pin_15,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒,这些加起来总共有20个中断线路。这里的16个GPIO_Pin是外部中断的主要功能,后面跟着的这四个东西其实是来“蹭网”的。因为这个外部中断有个功能,就是从低功耗模式的停止模式下唤醒STM32,那对于PVD电源电压监测,当从电源从电压过低恢复时,就需要PVD借助一下外部中断退出停止模式;对于RTC闹钟来说,有时候为了省电,RTC定一个闹钟之后,STM32会进入停止模式,等到闹钟响的时候再唤醒,这也需要借助外部中断;还有USB唤醒、以太网唤醒,也都是类似的作用。
中断响应,就是申请中断,让CPU执行中断函数;事件响应是STM32对外部中断增加的一种额外的功能。当外部中断检测到引脚电平变化时,正常的流程是选择触发中断,但是在STM32中,也可以选择触发一个事件,如果选择触发事件,那外部中断的信号就不会通向CPU了,而是通向其它外设,用来触发其它外设的操作,比如触发ADC转换、触发DMA等。所以总结一下:中断响应是正常的流程,引脚电平变化触发中断;事件响应不会触发中断,而是触发别的外设操作,属于外设之间的联合工作。
这里注意一下,本来20路输入,应该有20路中断的输出,但是可能ST公司觉得这20个输出太多了,比较占用NVIC的通道资源,所以就把其中外部中断的 9~5 和15 ~ 10给分到一个通道里。也就是说,外部中断的9~5会触发同一个中断函数,15~10也会触发同一个中断函数,在编程的时候,我们在这两个中断函数里,需要再根据标志位来区分到底是哪个中断进来的。
外部中断的使用场景:
就是对于STM32来说,想要获取的信号是外部驱动的很快的突发信号。比如旋转编码器的输出信号,你可能很久都不会拧它,这时不需要STM32做任何事,但是我一拧它,就会有很多脉冲波形需要STM32接收。这个信号是突发的,STM32不知道什么时候会来,同时它是外部驱动的,STM32只能被动读取,最后这个信号非常快,STM32稍微晚一点来读取,就会错过很多波形。那对于这种情况来说,就可以考虑使用STM32的外部中断了。有脉冲过来,STM32立即进入中断函数处理,没有脉冲的时候,STM32就专心做其它事情。
另外还有,比如红外遥控接收头的输出,接收到遥控数据之后,它会输出一段波形,这个波形转瞬即逝,并且不会等你,所以就需要我们用外部中断来读取。
最后还有按键,虽然它的动作也是外部驱动的突发事件,但我并不推荐用外部中断来读取按键。因为用外部中断不好处理按键抖动和松手检测的问题,对于按键来说,它的输出波形也不是转瞬即逝的。所以要求不高的话可以在主程序中循环读取,如果不想用主循环读取的话,可以考虑一下定时器中断读取的方式。这样既可以做到后台读取按键值、不阻塞主程序,也可以很好地处理按键抖动和松手检测的问题。
NVIC以及中断、事件手册讲解视频
代码实战2:如何使用中断和对射式红外传感器&旋转编码器
注意:我们这里是用到了PB14来做外部中断的
5-1 对射式红外传感器计次接线图
当挡光片在对射式红外传感器中间经过时,DO输出电平跳变信号,触发PB14号口的中断,在中断断数执行Num++
-
第一步,配置RCC,将程序涉及外设的时钟都打开
提示:有GPIOB和AFIO -
第二步,配置GPIO,选择端口为输入模式
-
第三步,配置AFIO,选择硬件所用用的那一路GPIO,连接到后面的EXTI
涉及函数如下:
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)
作用:选择用作EXTI线的GPIO引脚。
参数说明:
参数 | 说明 |
---|---|
GPIO_PortSource | 选择要用作EXTI线路源的GPIO端口。取值为GPIO_PortSourceGPIOx,其中x为(A…G)。 |
GPIO_PinSource | GPIO_PinSource:要配置的EXTI线路。该参数可以为GPIO_PinSourcex,其中x可以为(0…15)。 |
- 第四步,配置EXTI,选择边沿触发方式,比如上升沿、下降沿或者双边沿,还有选择触发响应方式,可以选择中断响应和事件响应
涉及函数如下:
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)
作用:根据EXTI InitStruct中的指定参数初始化EXTI外设。
参数说明:
参数 | 说明 |
---|---|
EXTI_InitStruct | 指向EXTI InitTypeDef结构体的指针包含ExTI外设的配置信息。 |
EXTI InitTypeDef结构体说明:
typedef struct
{uint32_t EXTI_Line; EXTIMode_TypeDef EXTI_Mode; EXTITrigger_TypeDef EXTI_Trigger; FunctionalState EXTI_LineCmd;
}EXTI_InitTypeDef;
参数说明以及举例
举例:
/* Enables external lines 12 and 14 interrupt generation on falling
edge */
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line12 | EXTI_Line14;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
- 第五步,配置NVIC,给我们这个中断选择一个合适的优先级
涉及函数如下:
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
作用:配置优先级分组:抢占优先级和子优先级。
参数说明:
参数 | 说明 |
---|---|
NVIC_PriorityGroup | 指定优先级分组位长度。 |
取值范围:
例如:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
- 最后,通过NVIC,外部中断信号就能进入CPU了,这样CPU才能收到中断信号,才能跳转到中断函数里执行中断程序
涉及函数如下:
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
作用:根据NVIC InitStruct中指定的参数初始化NVIC外设。
参数说明:
参数 | 说明 |
---|---|
NVIC_InitStruct | 指向NVIC InitTypeDef结构体的指针指定NVic外设的配置信息。 |
举例:
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
中断函数的格式:
根据中断向量表,找到所需中断函数,这里面以IRQHandler结尾的字符串就是中断函数的名字,再根据名字写中断函数。
例如:void EXTI15_10_IRQHandler(void){ }
这就是中断函数的格式,中断函数都是无参无返回值的,中断函数的名字不要写错了,写错了就进不了中断了,最好是直接从启动文件复制过来,这样就不会有问题了。
注:启动文件为
然后在中断函数里,一般都是先进行一个中断标志位的判断,确保是我们想要的中断源触发的这个函数,因为这个函数EXTI10到EXTI15都能进来,所以要先判断一下是不是我们想要的EXTI14进来的。所用函数:EXTI_GetITStatus(uint32_t EXTI_Line)
最后,中断程序结束后,一定要再调用一下清除中断标志位的函数,因为只有中断标志位置1了,程序就会跳转到中断函数。如果你不清除中断标志位,那它就会一直申请中断,这样程序就会不断响应中断,执行中断函数,那程序就卡死在中断函数里了。所用函数:EXTI_ClearITPendingBit(uint32_t EXTI_Line)
中断函数就不用声明了,因为中断函数不需要调用,它是自动执行的。
其它涉及函数:
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line)
作用:检查指定的 EXTI 线路触发请求发生与否(是不是我们想要的中断触发源)
返回值:(SET或RESET)
参数说明:
参数 | 说明 |
---|---|
EXTI_Line | EXTI_Line:要检查的EXTI行。EXTI_Linex:外部中断线x,其中x(0…19) |
void GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
作用:读取指定端口管脚的输入
参数说明:
参数 | 说明 |
---|---|
GPIOx | GPIOx:其中x可以为(A…G)选择GPIO外设。 |
GPIO_Pin | 指定要读取的端口位。该参数是GPIO_Pin_x,其中x可以是(0…15)。 |
void EXTI_ClearITPendingBit(uint32_t EXTI_Line)
作用:清除EXTI线路挂起位
参数说明:
参数 | 说明 |
---|---|
EXTI_Line | 指定要清除的EXTI行。该参数可以是ExTI Linex的任意组合,其中x可以是(0…19) |
EXTI和NVIC两个外设,这两个外设的时钟是一直都打开着的,不需要我们再开启时钟了。EXIT模块是由NVIC模块直接控制的,并不需要单独的外设时钟。NVIC也不需要开启时钟,是因为NVIC是内核的外设,内核的外设都是不需要开启时钟的。
代码如下:
蓝线部分是我自己需要注意的地方
5-2 旋转编码器计次37:30
在写中断函数的核心思想:
只有在B相下降沿和A相低电平时,才判断为正转
在A相下降沿和B相低电平时,才判断为反转
代码如下: