赞
踩
无人问津也好,技不如人也罢,都应静下心来,去做该做的事。
最近在学STM32,所以也开贴记录一下主要内容,省的过目即忘。视频教程为江科大(改名江协科技),网站jiangxiekeji.com
本期介绍外部中断和中断系统,中断就是主程序执行时,发生了中断触发条件,当这些紧急事件处理完后,CPU继续执行程序。
外部中断的使用场景:外部驱动很快的突变信号
相关实验有对射式红外传感器计次和旋转编码器计次
主程序里查看和清除标志位:Flagstatus EXTI GetFlagstatus (uint32 t EXTI Line);
void EXTI ClearFlag(uint32 t EXTI Line);
中断程序里查看和清除标志位 :ITstatus EXTI GetITstatus (uint32 t EXTI Line);
void EXTI ClearITPendingBit (uint32 t EXTI Line);
对射式红外传感器计次的DO为数字输出端,随意接一个GPIO,图上接到PB14。当挡光片或编码盘在对射式红外传感器的中间经过时,DO就会输出电平跳变的信号,进而触发PB14的中断,在中断函数里执行变量++的程序即可,然后在OLED上显示这个变量。
首先清楚外部中断信号的流向,从GPIO到AFIO,再到EXTI,再到NVIC,最终流向CPU,这样就可让CPU从主程序跳到中断程序执行。
EXTI和NVIC(内核外设)不需要开启时钟
- /*开启时钟*/
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,外部中断必须开启AFIO的时钟
选择端口为输入模式,具体选用哪种输入模式,需在STM32F10xxx参考手册-通用和复用功能I/O(GPIO和AFIO) 里查看
视频里采用输入上拉模式
- /*GPIO初始化*/
- GPIO_InitTypeDef GPIO_InitStructure;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB14引脚初始化为上拉输入
GPlO_PinRemapConfig(重映射的方式,新的状态):引脚重映射,本期不用此函数
GPIO_EXTILineConfig(GPIO PortSourceGPIOB,GPIO PinSource14):本期外部中断需用到的函数,调用此函数可配置AFIO的数据选择器,选择想要的中断引脚
- /*AFIO选择中断引脚*/
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//将外部中断的14号线映射到GPIOB,即选择PB14为外部中断引脚
在配置中断前,先指定中断的分组。然后用NVIC_Init函数初始化NVIC就行。
- /*EXTI初始化*/
- EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量
- EXTI_InitStructure.EXTI_Line = EXTI_Line14; //选择配置外部中断的14号线
- EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能
- EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中断线为中断模式
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //指定外部中断线为下降沿触发
- EXTI_Init(&EXTI_InitStructure); //将结构体变量交给EXTI_Init,配置EXTI外设
-
- /*NVIC中断分组*/
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
- //即抢占优先级范围:0~3,响应优先级范围:0~3
- //此分组配置在整个工程中仅需调用一次
- //若有多个中断,可以把此代码放在main函数内,while循环之前
- //若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
-
- /*NVIC配置*/
- NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
- NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //选择配置NVIC的EXTI15_10线
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
- NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设

中断向量表里以IRQHandler结尾的字符串就是中断函数的名字,中断函数的名字最好直接从启动文件复制过来。
- /**
- * 函 数:EXTI15_10外部中断函数
- * 参 数:无
- * 返 回 值:无
- * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
- * 函数名为预留的指定名称,可以从启动文件复制
- * 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
- */
- void EXTI15_10_IRQHandler(void)
- {
- if (EXTI_GetITStatus(EXTI_Line14) == SET) //判断是否是外部中断14号线触发的中断
- {
- /*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
- if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
- {
- CountSensor_Count ++; //计数值自增一次
- }
- EXTI_ClearITPendingBit(EXTI_Line14); //清除外部中断14号线的中断标志位
- //中断标志位必须清除
- //否则中断将连续不断地触发,导致主程序卡死
- }
- }

在模块前定义一个变量CountSensor_Count,在中断函数里写CountSensor_Count++,最后还需在模块里写一个get函数,返回这个变量CountSensor_Count
- uint16_t CountSensor_Count; //全局变量,用于计数
-
- /**
- * 函 数:获取计数传感器的计数值
- * 参 数:无
- * 返 回 值:计数值,范围:0~65535
- */
- uint16_t CountSensor_Get(void)
- {
- return CountSensor_Count;
- }
和之前的操作一样,先在Hardware下新建一个文件(Countsensor)的.c、.h文件,把对射式红外传感器的驱动函数封装起来。
- #include "stm32f10x.h" // Device header
- #include "Delay.h"
- #include "OLED.h"
- #include "CountSensor.h"
-
- int main(void)
- {
- /*模块初始化*/
- OLED_Init(); //OLED初始化
- CountSensor_Init(); //计数传感器初始化
-
- /*显示静态字符串*/
- OLED_ShowString(1, 1, "Count:"); //1行1列显示字符串Count:
-
- while (1)
- {
- OLED_ShowNum(1, 7, CountSensor_Get(), 5); //OLED不断刷新显示CountSensor_Get的返回值
- }
- }

- #ifndef __COUNT_SENSOR_H
- #define __COUNT_SENSOR_H
-
- void CountSensor_Init(void);
- uint16_t CountSensor_Get(void);
-
- #endif
CountSensor.s函数
- #include "stm32f10x.h" // Device header
-
- uint16_t CountSensor_Count; //全局变量,用于计数
-
- /**
- * 函 数:计数传感器初始化
- * 参 数:无
- * 返 回 值:无
- */
- void CountSensor_Init(void)
- {
- /*开启时钟*/
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,外部中断必须开启AFIO的时钟
-
- /*GPIO初始化*/
- GPIO_InitTypeDef GPIO_InitStructure;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB14引脚初始化为上拉输入
-
- /*AFIO选择中断引脚*/
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//将外部中断的14号线映射到GPIOB,即选择PB14为外部中断引脚
-
- /*EXTI初始化*/
- EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量
- EXTI_InitStructure.EXTI_Line = EXTI_Line14; //选择配置外部中断的14号线
- EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能
- EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中断线为中断模式
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //指定外部中断线为下降沿触发
- EXTI_Init(&EXTI_InitStructure); //将结构体变量交给EXTI_Init,配置EXTI外设
-
- /*NVIC中断分组*/
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
- //即抢占优先级范围:0~3,响应优先级范围:0~3
- //此分组配置在整个工程中仅需调用一次
- //若有多个中断,可以把此代码放在main函数内,while循环之前
- //若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
-
- /*NVIC配置*/
- NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
- NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //选择配置NVIC的EXTI15_10线
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
- NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
- }
-
- /**
- * 函 数:获取计数传感器的计数值
- * 参 数:无
- * 返 回 值:计数值,范围:0~65535
- */
- uint16_t CountSensor_Get(void)
- {
- return CountSensor_Count;
- }
-
- /**
- * 函 数:EXTI15_10外部中断函数
- * 参 数:无
- * 返 回 值:无
- * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
- * 函数名为预留的指定名称,可以从启动文件复制
- * 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
- */
- void EXTI15_10_IRQHandler(void)
- {
- if (EXTI_GetITStatus(EXTI_Line14) == SET) //判断是否是外部中断14号线触发的中断
- {
- /*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
- if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
- {
- CountSensor_Count ++; //计数值自增一次
- }
- EXTI_ClearITPendingBit(EXTI_Line14); //清除外部中断14号线的中断标志位
- //中断标志位必须清除
- //否则中断将连续不断地触发,导致主程序卡死
- }
- }

只有在B相下降沿和A相低电平时才识别为正转
首先清楚外部中断信号的流向,从GPIO到AFIO,再到EXTI,再到NVIC,最终流向CPU,这样就可让CPU从主程序跳到中断程序执行。
具体代码和对射式红外传感器大同小异,就直接展示完整代码
和之前的操作一样,先在Hardware下新建一个文件(Encoder)的.c、.h文件,把旋转编码器的驱动函数封装起来。
main函数
- #include "stm32f10x.h" // Device header
- #include "Delay.h"
- #include "OLED.h"
- #include "Encoder.h"
-
- int16_t Num; //定义待被旋转编码器调节的变量
-
- int main(void)
- {
- /*模块初始化*/
- OLED_Init(); //OLED初始化
- Encoder_Init(); //旋转编码器初始化
-
- /*显示静态字符串*/
- OLED_ShowString(1, 1, "Num:"); //1行1列显示字符串Num:
-
- while (1)
- {
- Num += Encoder_Get(); //获取自上此调用此函数后,旋转编码器的增量值,并将增量值加到Num上
- OLED_ShowSignedNum(1, 5, Num, 5); //显示Num
- }
- }

Encoder.h函数
- #ifndef __ENCODER_H
- #define __ENCODER_H
-
- void Encoder_Init(void);
- int16_t Encoder_Get(void);
-
- #endif
Encoder.c函数
- #include "stm32f10x.h" // Device header
-
- int16_t Encoder_Count; //全局变量,用于计数旋转编码器的增量值
-
- /**
- * 函 数:旋转编码器初始化
- * 参 数:无
- * 返 回 值:无
- */
- void Encoder_Init(void)
- {
- /*开启时钟*/
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,外部中断必须开启AFIO的时钟
-
- /*GPIO初始化*/
- GPIO_InitTypeDef GPIO_InitStructure;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB0和PB1引脚初始化为上拉输入
-
- /*AFIO选择中断引脚*/
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);//将外部中断的0号线映射到GPIOB,即选择PB0为外部中断引脚
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);//将外部中断的1号线映射到GPIOB,即选择PB1为外部中断引脚
-
- /*EXTI初始化*/
- EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量
- EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1; //选择配置外部中断的0号线和1号线
- EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能
- EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中断线为中断模式
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //指定外部中断线为下降沿触发
- EXTI_Init(&EXTI_InitStructure); //将结构体变量交给EXTI_Init,配置EXTI外设
-
- /*NVIC中断分组*/
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
- //即抢占优先级范围:0~3,响应优先级范围:0~3
- //此分组配置在整个工程中仅需调用一次
- //若有多个中断,可以把此代码放在main函数内,while循环之前
- //若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
-
- /*NVIC配置*/
- NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
- NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //选择配置NVIC的EXTI0线
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
- NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
-
- NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; //选择配置NVIC的EXTI1线
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //指定NVIC线路的响应优先级为2
- NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
- }
-
- /**
- * 函 数:旋转编码器获取增量值
- * 参 数:无
- * 返 回 值:自上此调用此函数后,旋转编码器的增量值
- */
- int16_t Encoder_Get(void)
- {
- /*使用Temp变量作为中继,目的是返回Encoder_Count后将其清零*/
- /*在这里,也可以直接返回Encoder_Count
- 但这样就不是获取增量值的操作方法了
- 也可以实现功能,只是思路不一样*/
- int16_t Temp;
- Temp = Encoder_Count;
- Encoder_Count = 0;
- return Temp;
- }
-
- /**
- * 函 数:EXTI0外部中断函数
- * 参 数:无
- * 返 回 值:无
- * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
- * 函数名为预留的指定名称,可以从启动文件复制
- * 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
- */
- void EXTI0_IRQHandler(void)
- {
- if (EXTI_GetITStatus(EXTI_Line0) == SET) //判断是否是外部中断0号线触发的中断
- {
- /*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
- if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
- {
- if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //PB0的下降沿触发中断,此时检测另一相PB1的电平,目的是判断旋转方向
- {
- Encoder_Count --; //此方向定义为反转,计数变量自减
- }
- }
- EXTI_ClearITPendingBit(EXTI_Line0); //清除外部中断0号线的中断标志位
- //中断标志位必须清除
- //否则中断将连续不断地触发,导致主程序卡死
- }
- }
-
- /**
- * 函 数:EXTI1外部中断函数
- * 参 数:无
- * 返 回 值:无
- * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
- * 函数名为预留的指定名称,可以从启动文件复制
- * 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
- */
- void EXTI1_IRQHandler(void)
- {
- if (EXTI_GetITStatus(EXTI_Line1) == SET) //判断是否是外部中断1号线触发的中断
- {
- /*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
- if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
- {
- if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) //PB1的下降沿触发中断,此时检测另一相PB0的电平,目的是判断旋转方向
- {
- Encoder_Count ++; //此方向定义为正转,计数变量自增
- }
- }
- EXTI_ClearITPendingBit(EXTI_Line1); //清除外部中断1号线的中断标志位
- //中断标志位必须清除
- //否则中断将连续不断地触发,导致主程序卡死
- }
- }

1、在中断里不要执行耗时过长的代码,比如在中断里执行delay函数,中断为了执行突发的事情。
2、最好不要在中断函数和主函数调用相同的函数或者操作同一个硬件。比如OLED函数,如果你既在主程序里调用OLED,在中断里调用OLED,OLED就会显示错误。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。