{STM32} 江科大学习笔记-EXTI外部中断(下)

目录 

一、对射式红外线传感器

 接线图:

工作原理: 

对射式红外线传感器是一种常用的接近传感器,其工作原理如下:

传感器包含两个部分:发射器和接收器。发射器会发射红外线光束,而接收器会接收这些光束。

当没有物体遮挡时,发射器发射的红外线光束可以直接射向接收器。接收器会接收到这些光束,并转换为电信号。

当有物体遮挡时,发射器发射的光束会被物体遮挡,无法完全射向接收器。因此,接收器接收到的光信号会减弱或中断。

传感器会通过检测接收到的光信号的强弱来判断是否有物体靠近传感器。如果接收到的光信号强度低于设定的阈值,传感器会输出一个信号表明有物体遮挡。

通过这种方式,对射式红外线传感器可以用来检测物体的存在或接近程度,常用于自动门、机器人避障等应用场景中。

 对射式红外线传感器初始化函数:

 打通信号,配置外部中断:

 具体步骤:

①配置RCC时钟,将这里涉及的外设时钟都打开(不打开时钟外设无法工作) 

 配置RCC时钟参考下面文章:

江科大STM32-GPIO输出 点亮LED,LED闪烁,LED流水灯,蜂鸣器(学习笔记)_gpio点亮led灯 代码示例-CSDN博客

 注意:此次要配置两个外设时钟,分别是GPIOB和AFIO

EXTI和NVIC时钟这两个外设时钟一直是打开的,不需要再开启。 

NVIC也是内核外设,内核外设都不需要开启时钟。 

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);	//开启GPIOB的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);	//开启AFIO的时钟,外部中断必须开启AFIO的时钟

②配置GPIO,选择端口为输入模式 

使用外设不清楚配置什么模式时,可以参考数据手册

    GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode =GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Pin =GPIO_Pin_15;
	GPIO_InitStruct.GPIO_Speed =GPIO_Speed_50MHz;  
	GPIO_Init(GPIOB,&GPIO_InitStruct);  	//将PB15引脚初始化为上拉输入

③配置AFIO选择我们用的这一路GPIO,连接到后面的EXTI通道 

void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)

 作用:选择用作EXTI线的GPIO引脚。

参数说明
GPIO_PortSourceGPIO_PortSource:选择要用作EXTI线路源的GPIO端口。取值为GPIO_PortSourceGPIOx,其中x为(A..G)。
GPIO_PinSourceGPIO_PinSource:要配置的EXTI线路。该参数可以是GPIO_PinSourcex,其中x可以是(0..15)。

 

GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource15);//将外部中断的15号线映射到GPIOB,即选择PB15为外部中断引脚

④配置EXTI,选择边沿触发方式,比如下降沿触发,上升沿触发或者双边沿触发,还有选择触发响应方式可以选择中断响应或者事件响应,一般中断响应。

EXTI函数: 

四个函数都是对状态寄存器的读写,使用上有区分,最后两个只能读写与中断有关的标志位,对中断是否允许做出了判断。 上两个则是一般标志位读写。

ITStatus EXTI_GetITStatus(uint32_t EXTI_Line)  

作用:检查指定的EXTI行是否被断言。(查看中断标志位是否被置1) 

参数说明
EXTI_Line指定要检查的EXTI行。该参数可以是: EXTI_Linex:外部中断线x,其中x(0..19)

 返回值:EXTI_Line的新状态(SET或RESET)。

void EXTI_ClearITPendingBit(uint32_t EXTI_Line) 

作用:清除EXTI的行挂起位。 

参数说明
EXTI_Line指定要清除的EXTI行。该参数可以是EXTI_Linex的任意组合,其中x可以是(0..19)。

 无返回值。

 EXTI Init结构定义:
参数说明
EXTI_Line指定要启用或禁用的EXTI行
EXTI_Mode指定EXTI行的模式。
EXTI_Trigger指定EXTI线的触发信号活动边。
EXTI_LineCmd指定所选EXTI行的新状态。
可设置为“ENABLE”或“DISABLE”

 EXTI_Line配置

 EXTI_Mode配置

 EXTI_Trigger配置

具体配置 

    //将EXTI的第十五个线路配置成中断模式,下降沿触发,开启中断
	EXTI_InitTypeDef EXTI_InitStruct; 
	EXTI_InitStruct.EXTI_Line =EXTI_Line15;
	EXTI_InitStruct.EXTI_LineCmd =ENABLE;//开启中断
	EXTI_InitStruct.EXTI_Mode =EXTI_Mode_Interrupt;//中断模式
	EXTI_InitStruct.EXTI_Trigger =EXTI_Trigger_Falling;//下降沿触发
	EXTI_Init(&EXTI_InitStruct);

⑤配置NVIC,给我们中断选择一个合适的优先级 ,最后,通过NVIC外部中断信号就可以进入CPU了     

 NVIC 函数:

 

void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup); 

配置优先级分组:抢占优先级和子优先级。 

参数说明
NVIC_PriorityGroup优先级分组位长度

 

一般来说,中断不多,很难导致中断冲突,对优先级分组就比较随意哪个都行。 

注意:分组方式整个芯片只能用一种,所以按理说这个分组代码只需要执行一次就可以了。如果把他放在模块里进行分组,那要确保每个模块里都要选择同一个分组。或者选择把分组代码放在主函数里,那模块就不需要进行分组了。

NVIC Init结构定义: 

 

参数说明
NVIC_IRQChannel指定要启用或禁用的IRQ通道。
NVIC_IRQChannelPreemptionPriority指定IRQ通道的抢占优先级
在NVIC_IRQChannel中指定
NVIC_IRQChannelSubPriority指定IRQ通道的子优先级
NVIC_IRQChannelCmd是否为NVIC_IRQChannel中定义的IRQ通道将被启用或禁用。
可设置为“ENABLE”或“DISABLE”

NVIC_IRQChannel配置 

 

 NVIC_IRQChannelPreemptionPriority与NVIC_IRQChannelSubPriority配置

具体配置: 

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC优先级分组
	
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel =EXTI15_10_IRQn;//10-15线路都可以进
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority =1;中断只有一个,配置等级较随意
	NVIC_InitStruct.NVIC_IRQChannelSubPriority =1;
	NVIC_Init(&NVIC_InitStruct);

 中断函数:

 进启动文件复制中断函数,中断函数无参数无返回值,函数名不能写错,最好复制过来,写错了就进不了中断的。

 写好中断函数后,中断函数里面一般都是先进行中断标志位的判断,确保是我们想要的中断源。判断中断标志位函数查看上面EXTI函数

void EXTI15_10_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line15)== SET)//判断是否是外部中断15号线触发的中断
	{
		/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_15) == 0)
		{
			CountSensor_Count ++;					//计数值自增一次
		}
		//CountSensor_Count++;
		EXTI_ClearITPendingBit(EXTI_Line15);		//清除外部中断15号线的中断标志位
													//中断标志位必须清除
													//否则中断将连续不断地触发,导致主程序卡死
	}
}

检查程序是否能跳到中断函数:

点击调试按钮,找到中断函数,打一个断点 ,然后全速运行,回到硬件,用档光片遮挡一下红外传感器,看看程序有没有跳到中断函数

遮挡跳到中断函数: 

返回变量函数: 

uint16_t CountSensor_GetCount(void)
{
	return CountSensor_Count;
}

主函数: 

#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:");	//显示一个字符串
	
	while(1)
	{
		OLED_ShowNum(1,7,CountSensor_GetCount(),5);//OLED不断刷新显示CountSensor_Get的返回值
	}
}

 注意:遮挡传感器时,OLED数字跳动混乱有可能是硬件问题,有可能是最小系统板松动没接好,有问题可以适当按压调试看看

 修改EXTI初始化边沿触发方式(上升、下降,双边沿)可以改变档光片遮挡传感器触发OLED屏数字的变化方式。比如下降沿触发方式是遮挡后移开档光片OLED数字加一(移开档光片触发中断),而上升沿则是一遮挡OLED数字就加一(遮挡触发中断)。

 二、旋转编码器计次

 接线图:

工作原理: 

机械触点式旋转编码器是一种旋转位置传感器,其工作原理基于两个关键部分:旋转驱动和触点检测。当旋转编码器旋转时,旋转驱动会带动一个旋转轴,该轴连接了编码器的内部结构。在内部,有一组触点排列成圆形,这些触点会随着旋转而接触到外部固定的触点。

当旋转编码器旋转时,触点会通过触点检测器来检测接触点的状态(接触或未接触)。通过检测接触点的状态变化,编码器可以确定旋转方向和旋转角度。编码器内部通常会有一些逻辑电路用于处理这些信号,并将旋转信息转换成数字信号输出。

总的来说,机械触点式旋转编码器的工作原理是利用旋转驱动、触点检测和信号处理来实现对旋转位置信息的检测和输出。

旋转编码器初始化函数: 

void Encoder_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);	//开启GPIOB的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);	//开启AFIO的时钟,外部中断必须开启AFIO的时钟
	
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode =GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Pin =GPIO_Pin_0 | GPIO_Pin_1;
	GPIO_InitStruct.GPIO_Speed =GPIO_Speed_50MHz;  
	GPIO_Init(GPIOB,&GPIO_InitStruct);  	//将PB0、PB1引脚初始化为上拉输入
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0);//将外部中断的0号线映射到GPIOB,即选择PB0为外部中断引脚
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);
	
	//将EXTI的第0个和第1线路配置成中断模式,下降沿触发,开启中断
	EXTI_InitTypeDef EXTI_InitStruct; 
	EXTI_InitStruct.EXTI_Line =EXTI_Line0 | EXTI_Line1;
	EXTI_InitStruct.EXTI_LineCmd =ENABLE;//开启中断
	EXTI_InitStruct.EXTI_Mode =EXTI_Mode_Interrupt;//中断模式
	EXTI_InitStruct.EXTI_Trigger =EXTI_Trigger_Falling;//下降沿触发
	EXTI_Init(&EXTI_InitStruct);//将结构体变量交给EXTI_Init,配置EXTI外设
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //NVIC优先级分组
													//即抢占优先级范围:0~3,响应优先级范围:0~3
													//此分组配置在整个工程中仅需调用一次
													//若有多个中断,可以把此代码放在main函数内,while循环之前
													//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
	
	
	NVIC_InitTypeDef NVIC_InitStruct;//定义结构体变量,可重复使用
	NVIC_InitStruct.NVIC_IRQChannel =EXTI0_IRQn;//选择配置NVIC的EXTI0线
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;//指定NVIC线路使能
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority =1;//指定NVIC线路的抢占优先级为1
	NVIC_InitStruct.NVIC_IRQChannelSubPriority =1;//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStruct); //将结构体变量交给NVIC_Init,配置NVIC外设
	
	NVIC_InitStruct.NVIC_IRQChannel =EXTI1_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority =1;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority =2;
	NVIC_Init(&NVIC_InitStruct);
}

中断函数&中断对变量的加减函数

代码逻辑:反转 ,PB0(旋转编码器A相)下降沿触发中断,PB1(旋转编码器B相)低电平

                正转,PB1(旋转编码器B相)下降沿触发中断 ,PB0(旋转编码器A相)低电平

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);
	}
}

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);
	}
}

旋转编码器获取增量值函数: 

int16_t Encoder_Get(void)
{
	/*使用Temp变量作为中继,目的是返回Encoder_Count后将其清零*/
	/*在这里,也可以直接返回Encoder_Count
	  但这样就不是获取增量值的操作方法了
	  也可以实现功能,只是思路不一样*/
	int16_t Temp;
	Temp = Encoder_Count;
	Encoder_Count = 0;
	return Temp;
}

主程序: 

#include "stm32f10x.h"                  // Device header
#include "Delay.h" 
#include "OLED.h" 
#include "Encoder.h" 

int16_t Num;

int main(void)
{
	OLED_Init();
	Encoder_Init();
	
	OLED_ShowString(1,1,"Num:");	//显示一个字符串
	
	while(1)
	{
		// 调用 Encoder_Get 函数并将返回值累加到 Num 上
		Num +=Encoder_Get(); //Num = Num + Encoder_Get()
		OLED_ShowSignedNum(1,5,Num,5);//OLED显示数字(十进制,带符号数)Num
	}
}

 注意:①在中断函数里最好不要执行耗时过长的代码,中断函数要简短快速,不要进来中断就Delay多少毫秒这样的代码,因为中断是处理突发的事情,如果为了处理一个突发的事情呆在中断里面不出来了,那主程序会遭到严重的阻塞。

②不要在中断函数和主函数调用相同的函数或者操作同一个硬件,尤其是硬件相关的函数,比如OLED显示函数,如果你既在主程序调用OLED函数又在中断函数调用OLED函数,OLED就会显示错误。因为这样子主程序OLED刚显示一半,啪!就进来中断了,结果中断里还是OLED显示函数,那OLED就挪到其他地方显示了,这时还没有问题,但当中断结束之后,需要继续原来的显示,这是就出问题了,因为硬件的显示位置被挪到其他地方了,所以再回来的时候,继续显示的内容就会跟着跑到其他地方去了。这就会造成问题,虽然在中断进入和退出的时候会有保护现场和恢复现场,但这只能保证CPU程序能正常返回不出问题。对于外部硬件的话,并没有在进入中断时进行现场保护,所以中断返回后就出问题了。为了避免问题,就最好不要在主程序和中断程序里操作可能产生冲突的硬件。

编写不易,有错望指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值