目录

一、外部中断配置流程

1.开启GPIO和AFIO时钟

2.配置GPIO

3.AFIO选择外部中断引脚

4.配置外部中断EXTI

5.配置中断分组NVIC(注意事项多)

6.主函数执行中断函数代码 

二、对射式红外传感器计次代码

三、旋转编码器计次代码

四、补充介绍引脚复用AFIO

AFIO的作用

1. 引脚重映射

2. 配置外部中断线

3. 调试接口IO配置(慎用)

库函数小结

五、注意事项

1.在NVIC配置时需要注意的问题(重要!)

2.多个中断源共用一个中断函数的问题

3.中断函数标志位需要手动清除

4.多选择的简便初始化方法

5.中断控制函数的标志性特点

6.中断函数的标志性特点


一、外部中断配置流程

想要使用外部中断只需根据下图打通基本结构涉及的外设即可

1.开启GPIO和AFIO时钟

EXTI和NVIC的时钟一直是打开的

2.配置GPIO

设为输入模式

3.AFIO选择外部中断引脚

AFIO可以用来引脚重映射或是配置中断线的,在本文中用于配置中断线

只需要调用通道选择的函数GPIO_EXTILineConfig()即可

4.配置外部中断EXTI

结构体参数包括触发方式、哪个触发线(需与GPIO保持一致)、触发模式等

5.配置中断分组NVIC(注意事项多)

①调用分组函数NVIC_PriorityGroupConfig()(整个工程只需要调用一次,为防止多个c文件混乱分组建议将其放在主函数内)

②使用结构体初始化(结构体参数包括中断通道选择、响应优先级和抢占优先级(取值范围需要在分组函数设置的范围内)

6.主函数执行中断函数代码 

①中断函数在启动文件内查找

②中断函数的标志是以IRQHandler结尾

③在执行具体的操作前先查看中断标志位是个好习惯

④对于多个中断源共用一个中断通道(中断函数)的,需要在中断函数内部判断中断信号的来源

⑤中断函数标志位需要及时手动清除

二、对射式红外传感器计次代码

#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号线的中断标志位
													//中断标志位必须清除
													//否则中断将连续不断地触发,导致主程序卡死
	}
}

三、旋转编码器计次代码

旋转编码器有两根线,可通过同时判断两根线的电平判断是正转还是反转

#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号线的中断标志位
													//中断标志位必须清除
													//否则中断将连续不断地触发,导致主程序卡死
	}
}

四、补充介绍引脚复用AFIO

AFIO 的全称是 Alternate Function I/O,即复用功能I/O。它的核心职责是管理和配置引脚除了默认的GPIO功能之外的“第二职业”

AFIO的作用

1. 引脚重映射

这是AFIO最核心的功能。

问题:芯片设计时,一个外设(如USART1、SPI1、定时器2等)的信号线默认是固定连接到某些特定引脚上的。例如,USART1的TX/RX默认在PA9PA10上。

矛盾:如果你的PCB板子上把PA9PA10用作其他用途了,但又想用USART1,怎么办?

解决方案引脚重映射。很多外设都有备用的连接引脚。通过配置AFIO的重映射寄存器,你可以把外设“搬”到另一组引脚上去。

2. 配置外部中断线

我们在讲中断系统时提到,EXTI控制器只有有限的线路(0-15),但每个GPIO端口的同名引脚(如PA0,PB0,PC0...)都共享同一条EXTI线(EXTI0)。

问题:当你想使用 PC0 作为外部中断触发源时,如何告诉EXTI控制器,你现在想监听的是 PC0 ,而不是 PA0 或 PB0 ?

解决方案:通过AFIO的EXTI配置寄存器来选择EXTI线对应的源GPIO端口。

3. 调试接口IO配置(慎用)

这个功能用于当你想使用JTAG/SWD接口来调试程序时,可以释放被调试接口占用的引脚,用作普通GPIO。

问题:STM32默认上电后,PA13(SWDIO),PA14(SWCLK),PB3(JTDO),PB4(JNTRST)等引脚是被调试器占用的,你无法直接当作普通IO使用。

解决方案:通过AFIO的调试端口控制寄存器来解除它们的“调试身份”。

库函数小结

功能 解决的问题 关键标准库函数
引脚重映射 将外设从默认引脚“搬家”到备用引脚 GPIO_PinRemapConfig
外部中断线配置 选择EXTI线连接到的具体GPIO端口 GPIO_EXTILineConfig
调试IO配置 释放JTAG/SWD引脚用作普通GPIO GPIO_DebugPortConfig

五、注意事项

1.在NVIC配置时需要注意的问题(重要!)

调用分组函数整个工程只需要调用一次,为防止多个c文件混乱分组建议将其放在主函数内

初始化结构体参数中的响应优先级和抢占优先级取值范围需要在分组函数设置的范围内

2.多个中断源共用一个中断函数的问题

对于多个中断源共用一个中断通道(中断函数)的,需要在中断函数内部判断中断信号的来源,由于C语言没有同名函数,所以要想使用一个中断通道的多个中断源使用 if 进行判断即可

如下

void EXTI15_10_IRQHandler(void)
{
	if (EXTI_GetITStatus(EXTI_Line14) == SET)		//判断是否是外部中断14号线触发的中断
	{

		EXTI_ClearITPendingBit(EXTI_Line14);														
	}

	if (EXTI_GetITStatus(EXTI_Line13) == SET)		//判断是否是外部中断13号线触发的中断
	{

		EXTI_ClearITPendingBit(EXTI_Line13);														
	}

    ……

}

3.中断函数标志位需要手动清除

4.多选择的简便初始化方法

对于一个结构体内的多个通道都需要初始化,且只是通道选择的不同其余参数全都一致,那么不必多次调用初始化函数,可以用或运算符( | )将多个通道或起来

如果是结构体多个参数都不一致,那么才需要重新调用初始化函数

5.中断控制函数的标志性特点

STM32多数外设的中断函数的标志性特点是 XX_ITConfiig()

外部中断的控制是用使用AFIO选择中断引脚的函数GPIO_EXTILineConfig()

6.中断函数的标志性特点

STM32中断函数的特点是XX_IRQHandler()

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐