STM32外部中断,对射式红外传感器计次/旋转编码计次

(声明:这时本人的个人归纳总结,为后期复习学习用)

一、中断系统

1、什么是中断

中断是在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行

2、中断优先级

当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源

3、中断嵌套

当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回

4、中断执行流程

中断的程序如图右边的C语言所示上面是主函数,while(1)死循环里就是主程序,正常情况下,程序就是在主程序里不断循环执行,当中断条件满足时,主程序就会暂停,然后自动跳转到中断程序里运行,中断顺序执行完之后,在返回主程序继续执行(一般中断程序都是在一个子函数里的,这个函数不需要我们调用,当中断来临时,由硬件自动调用这个函数)

二、STM32中断

68个可屏蔽的中断通道:68个中断源,这个是F1系列最多的中断数量对于一个具体的型号没有这么多中断,这个数量看看就行,具体以对应信号的数据手册为准

在表中的深灰色的部分是内核中的中断一般我们用不到,所以了解即可。剩下的部分就是STM32的外设中断,

1、NVIC

(1)基本结构

NVIC:(嵌套中断向量控制器),在STM32中,它是用来统一分配中断优先级和管理中断的,NVIC它是一个内核外设,是CPU的小组手

NVIC有很多输入口,你有多少个中断线路,都可以接过来,在NVIC左边的输入口有很多的不同类型的中断,每一个中断都有可能占用多个中断通道,所以这里有N条线;

NVIC只有一个输出端口,NVIC根据每个中断的优先级分配中断的先后顺序,然后通过右边的一个输出端口告诉CPU该处理哪个中断,对于中断先后分配的任务,CPU不需要知道

(2)优先级分组

响应优先级:类比:医生在病房正给一个病人在看病,病房外面等了很多的病人,当上一个病人看完后,紧急的病人即使是后来的,也会最先进去看病,这种类似于插队的优先级,叫做响应优先级;

抢占优先级:同样类比,如果在外面等候的病人更加紧急,并且此时已经有人在看病了,他可以不等上个人看完,直接冲到医生的屋里,让上一个病人靠边站等着,先给他看病,等他看完了,然后上一个病人在继续,上一个病人结束了,叫号系统在看有没有人来,这种形式的优先就是之前讲到的中断嵌套,这种决定是不是可以中断嵌套的优先级,就叫抢占优先级。

2、EXTI外部中断 

(1)基本结构

EXTI:可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序

支持的触发方式:上升沿/下降沿/双边沿/软件触发支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断

通道数:16GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒

触发响应方式:中断响应/事件响应

APIO:中断引脚选择的电路模块,就是一个数据选择器,可以在前面3个GPIO外设的16个引脚里选择其中一个连接到后面EXTI的通道里。

(2)AFIO基本结构

AFIO只要用于引脚复用功能的选择和重定义

在STM32中,AFIO主要完成两个任务,复用功能引脚映射,中断引脚选择

(3)EXTI内部结构

三、对射式红外传感器计次

1、接线图

2、原理分析

在上面的介绍中我们知道了中断的工作原理,也就是说在下面的这张图中我们只要把这个外部中断从GPIO到NVIC这一路中出现的外设模块都配置号,把这条信号电路给打通就行了。

3、实现步骤

(1)配置RCC

把我们这里涉及的外设的时钟都打开,这个别忘了不打开时钟外设是没法工作的。

(2)配置GPIO

选择我们的端口为输入模式。

(3)配置AFIO

选择我们用的这一路GPIO ,连接到后面的EXTI。

(4)配置EXTI

选择边沿触发方式,比如上升沿、下降沿或者双边沿,还有选择触发响应方式,可以选择中断响应和事件响应。当然我们一般都是中断响应。

(5)配置NVIC

给这个中断选择一个合适的优先级。通过NVIC,外部中断信号就能进入CPU。了。这样CPU才能收到中断信号,才能跳转到中断函数里执行中断程序。

这五步就是外部中断的配置流程,这里涉及的外设比较多。

CountSensor.c

(1)开启时钟

我们在使用外设的时候首先第一步就是开始时钟,不打开它外设是无法使用,这里我们开启了GPIO和AFIO时钟,因为NVIC是内核的外设不需要我们操作开启,EXTI是时钟都是开启的,

(2)GPIO的配置

//配置GPIO
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//如果不知道应该是什么输入方式可在参考手册中查找,EXTI输入模式下的GPIO的设置
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);

在GPIO输入模式中如果不知道要选定什么模式我们可以参考手册中查找

(3)AFIOP配置

配置完成,执行完这个函数后,AFIO的第14个数据选择器就拨好了;其中输入端被拨到了GPIOB外设上,对应的就是PB14号引脚,输出端固定连接的是EXTI的第十四个中断线路
这样PB14号引脚的电平信号就可以顺利通过AFIO,进入到后级EXTI电路了;

AFIO的其他函数:

配置AFIO,它没有分配专门的库函数文件而是和GPIO(gpio.h)在一个文件里,相关函数可以在那个文件中查看


 

void GPIO_AFIODeInit(void)

用来复位AFIO外设的,调用一下这个函数,FPIO外设的配置就会全部清除;


 void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

用来锁定GPIO配置的,调用这个函数,参数指定某个引脚,这个引脚就会被锁定,防止意外更改;也是GPIO的外设,用的时候不多。了解即可


void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
void GPIO_EventOutputCmd(FunctionalState NewState);

这两个函数用于配置AFIO事件输出功能的,用的不多,了解即可;


 void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);

  可以用来引脚重映射,第一个参数选择你要重映射的方式,第二个参数是新的状态;目前还没有学习到需要映射引脚的外设,所以实际的调用之后在学

 void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
   本节外部中断需要用到的函数,调用这个函数,就可以配置AFIO的数据选择器,来选择我们想要的中断引脚
   第一个参数GPIO_PortSource,选择某个GPIO外设作为外部中断源,参数可以是GPIO_PortSourceGPIOX,其中X可以是A到G;
   第二个参数GPIO_PinSource,指定要配置的外部中断线,参数可以是GPIO_PinSourceX,其中X可以是0到15;
  


 void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);

与以太网有关的,我们这个芯片没有以太网外设,所有用不到;

(4)EXTI配置

将EXTI的第14个线路配置为中断模式,下降沿触发,然后开启中断

EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line = EXTI_Line14 ;  		//指定要配置的中断线
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;			//指定中断的状态
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //选择什么状态,事件或是中断
	EXTI_InitStructure.EXTI_Trigger =  EXTI_Trigger_Rising_Falling;//选择上升沿还是下降沿触发,触发的模式
	EXTI_Init(&EXTI_InitStructure);

函数介绍:

//void EXTI_DeInit(void)

调用它就可以把 EXTI的配置都清除,恢复成上电默认的状态


//void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)

调用这个函数,就可以根据这个结构体里的参数配置EXTI外设,初始化EXTI只要用到的就是这个函数,使用方法和GPIO_Init也是一样的


//void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);

调用这个函数,可以把参数传递的结构体变量赋一个默认值


//void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);

软件触发外部中断的,调用这个函数,参数给一个指定的中断线,就能软件触发一次这个外部中断

//下面的函数,也是库函数的模板函数,很多模块都有这四个函数:在外设运行的过程中,会产生一些状态标志位,比如外部中断来了,会有一个挂起寄存器置了一个标志位,这些标志位都是存放在状态寄存器中的,当程序想要看这些标志为时,就可以用到这个函数
//如果想要在主程序里查看和清除标志位,就用下面前两个函数
//FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);可以获取指定的标志位是否被置1了
//void EXTI_ClearFlag(uint32_t EXTI_Line);可以对置1 的标志位进行清除;


//如果想在中断函数里查看和清除标志位,就用下面的这两个函数
//ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);获取中断标志位是否被置1了;
//void EXTI_ClearITPendingBit(uint32_t EXTI_Line);清除中断挂起标志位

(5)NVIC配置

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//这个分组的具体选择哪一个,根据我们的需求来选,抢占和优先,这里先选择2位抢占和2位优先
	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);

函数介绍

void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);用来中断分组,参数是中断分组的方式
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);根据结构体里面指定的参数初始化NVIC
    下面前两个函数用的不多,可以不看
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);设置中断向量表           void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);系统低功耗配置

(6)中断函数

//中断函数可以参考一个启动文件_md的文件,可以看到里面定义的中断向量表以IRQHandler结尾的就是中断函数的名字
void EXTI15_10_IRQHandler(void)//中断函数:中断函数都是无参无返回值
{
	//在中断函数里一般要进行一个中断标志位的判断,确保是我们想要的中断源发的这个函数,因为这个函数EXTI10---EXTI15都能进来,所以要判断一下是不是我们想要的EXTI14进来的
	if (EXTI_GetITStatus(EXTI_Line14) == SET)
	{
		Delay_ms(500);
		CountSensor_Count ++;
		EXTI_ClearITPendingBit(EXTI_Line14);//因为中有中断标志位置1了,程序就会跳转到中断函数,如果不清楚中断标志位,会一直申请中断,程序就会不断响应中断,执行中断函数,程序就会卡死在中断函数中
	}
}


在这里我说明一下延时函数因为传感器灵敏,和按键一样进行消抖,手抖的现象,以免出现遮挡一次会计数很多的现象。

(7)CountSensor.h

#ifndef __COUNT_SENSOR_H
#define __COUNT_SENSOR_H

void CountSensor_Init(void);
uint16_t CountSensor_Get(void);

#endif

(8)主函数

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "CountSensor.h"

int main(void)
{
	
	 OLED_Init();
	CountSensor_Init();
	OLED_ShowString(1,3, "Count:");
	
	while(1)
	{
		OLED_ShowNum(1,7,CountSensor_Get(),5);
	}
}

四、旋转编码器计次

这个旋转编码器原理大致是一样的,只是有些参数稍作修改
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号线的中断标志位
													//中断标志位必须清除
													//否则中断将连续不断地触发,导致主程序卡死
	}
}

Encoder.c

#ifndef __ENCODER_H
#define __ENCODER_H

void Encoder_Init(void);
int16_t Encoder_Get(void);

#endif

main.c

#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
	}
}

STM32单片机配合红外传感器进行计次原理通常涉及以下几个步骤: 1. **选择传感器**:首先,你需要一个红外(IR)传感器,比如常见的IR-recv模块,它能检测到红外信号的变化。 2. **连接硬件**:将IR传感器的中断引脚连接到STM32的输入引脚,以便在接收到红外脉冲时产生中断。例如,你可以连接到PA0或PB6这样的GPIO口。 3. **初始化中断**:在STM32的初始化阶段,配置中断控制器和GPIO为中断模,并设置适当的中断服务函数(ISR)来处理接收到的红外脉冲。 4. **中断服务函数(ISR)**:当红外信号变化时,ISR会被调用。在这个函数中,你需要检查传感器的状态(如接收到信号或未接收到),然后增加计数器或者设置标志位。 5. **计数或逻辑判断**:在中断服务函数中,根据红外脉冲的频率,你可以简单地计数,每接收到一个脉冲就递增计数器,或者根据特定的红外编码执行特定的操作,比如控制LED闪烁或发送数据。 6. **数据处理或显示**:在非中断上下文中,周期性地检查计数器或标志位的结果,可能的话,将计数值存储到存储器或通过串口发送出去。 7. **可能的错误处理**:确保有适当的错误处理机制,比如红外接收不连续或长时间没有接收到信号,防止误触发和计数溢出。 8. **电源管理**:考虑到电池寿命或功耗,确保在空闲时关闭不必要的功能,并使用低功耗模降低待机时的电流消耗。 **相关问题**: 1. STM32如何配置中断以响应红外传感器? 2. 如何在STM32中正确处理中断服务函数以计数红外脉冲? 3. 红外传感器计次时如何避免误触发和数据丢失?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值