当前位置: 首页 > news >正文

嵌入式学习-EXTI外部中断

STM32 是一种基于 ARM Cortex-M 内核的微控制器系列,广泛应用于嵌入式系统开发。中断(Interrupt)是 STM32 中一个非常重要的功能,它允许微控制器在执行主程序的同时,响应外部事件或内部事件的请求,从而实现多任务处理和实时响应。

1. 中断的基本概念

  • 中断源(Interrupt Source):触发中断的事件来源,可以是外部信号(如按键按下)、内部事件(如定时器溢出)或其他硬件模块。

  • 中断请求(Interrupt Request, IRQ):中断源发出的信号,请求 CPU 暂停当前任务,转而执行中断服务程序。

  • 中断优先级(Interrupt Priority):STM32 支持多级中断优先级,允许根据中断的重要性分配优先级,高优先级的中断可以打断低优先级的中断。

  • 中断服务程序(Interrupt Service Routine, ISR):当中断发生时,CPU 跳转到对应的中断服务程序执行特定的任务,完成后返回主程序。

2. STM32 中断的配置步骤

配置 STM32 的中断通常需要以下步骤:

  1. 启用中断源:配置相关的硬件模块(如 GPIO、定时器、串口等)以允许其产生中断。

  2. 配置中断优先级:通过 NVIC(Nested Vectored Interrupt Controller)设置中断的优先级。

  3. 使能中断:通过 NVIC 使能对应的中断。

  4. 编写中断服务程序(ISR):在中断服务程序中处理中断事件。

3. NVIC(中断控制器)

NVIC 是 STM32 的中断控制器,用于管理中断的优先级和中断请求。NVIC 支持多个优先级级别(具体数量取决于芯片型号),可以通过编程配置中断的抢占优先级和子优先级。

  • 抢占优先级(Preemption Priority):高抢占优先级的中断可以打断低抢占优先级的中断。

  • 子优先级(Subpriority):在同一抢占优先级下,用于进一步区分中断的优先级。

EXTI的基本结构

  1. GPIO引脚与EXTI通道

    • 每个GPIO外设(如GPIOA、GPIOB等)有16个引脚,但EXTI模块只有16个通道。因此,需要通过AFIO(复用功能和中断引脚选择)模块进行引脚选择,AFIO模块的作用是从每个GPIO外设的16个引脚中选择一个引脚连接到EXTI通道

  • 中断和事件处理

    • EXTI模块支持20个输入信号(16个GPIO引脚 + 4个其他外设信号,如PVD、RTC闹钟等),这些信号可以触发中断或事件,中断信号通过NVIC(嵌套向量中断控制器)进行管理,而事件信号则用于触发其他外设的操作

  • 中断触发方式

    EXTI支持多种触发方式,包括上升沿、下降沿、双边沿(上升沿和下降沿)以及软件触发
  • 中断优先级

    • 通过NVIC配置中断的优先级,包括抢占优先级和子优先级

EXTI的工作原理

  1. GPIO引脚初始化

    • 将目标GPIO引脚配置为输入模式,并选择上拉或下拉输入

  • EXTI配置

    • 使用AFIO模块将GPIO引脚映射到EXTI通道

  • 配置EXTI的触发方式(上升沿、下降沿或双边沿)

  • NVIC配置

    • 设置中断优先级并使能对应的中断

  • 中断服务函数

    • 编写中断服务函数(ISR),在中断触发时处理逻辑

EXTI功能框图核心结构

  1. 输入信号(编号1)

    • EXTI控制器有20个中断/事件输入线,这些输入线可以配置为任意GPIO引脚,也可以是外设事件信号,每个输入信号通常来自GPIO引脚,通过AFIO(复用功能和中断引脚选择)模块映射到EXTI通道

  • 边沿检测电路(编号2)

    • 输入信号经过边沿检测电路,该电路根据上升沿触发选择寄存器(EXTI_RTSR)和下降沿触发选择寄存器(EXTI_FTSR)的设置来检测信号的上升沿或下降沿,如果检测到配置的边沿跳变,输出有效信号“1”到后续电路

  • 或门电路(编号3)

    • 或门电路的输入包括边沿检测电路的输出和软件中断事件寄存器(EXTI_SWIER)的输,或门的作用是只要有任意一个输入为“1”,输出即为“1”,用于支持硬件和软件触发

  • 中断信号处理(编号4)

    • 中断信号经过与门电路,另一个输入来自中断屏蔽寄存器(EXTI_IMR)。只有当EXTI_IMR使能对应位时,信号才会传递到中断挂起寄存器(EXTI_PR)

  • EXTI_PR用于保存中断请求信号,当信号为“1”时,表示有中断请求

  • 中断请求输出(编号5)

    • EXTI_PR的输出信号被送入NVIC(嵌套向量中断控制器),从而触发中断服务程序(ISR)

  • 事件信号处理(编号6)

    • 事件信号处理路径与中断路径在或门电路后分开,事件信号通过与门电路,输入来自事件屏蔽寄存器(EXTI_EMR),如果EXTI_EMR使能对应位,信号将传递到脉冲发生器

  • 脉冲发生器(编号7)

    • 脉冲发生器在输入信号为“1”时产生一个脉冲信号,用于触发其他外设(如定时器TIM、ADC等)

  • 事件输出信号(编号8)

    • 事件输出信号是一个脉冲信号,用于硬件级的外设触发

EXTI功能框图的特点

  • EXTI功能框图分为中断处理路径和事件处理路径,两者在硬件上有所不同

  • 中断路径通过NVIC触发中断服务程序,属于软件级处理;事件路径通过脉冲信号触发外设,属于硬件级处理

  • EXTI支持多种触发方式,包括上升沿、下降沿、双边沿以及软件触发

首先开启时钟,PIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//将外部中断的14号线映射到GPIOB,即选择PB14为外部中断引脚。中断向量表在启动文件文件查看。

  • #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
#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的返回值
	}
}

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

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

正转反转逻辑,A,B相都触发中断,B相下降沿,A相低电平正转;A相下降沿,B相低电平反转

相关文章:

  • Java UDP 通信:实现简单的 Echo 服务器与客户端
  • R JSON 文件
  • 私有化部署DeepSeek并SpringBoot集成使用(附UI界面使用教程-支持语音、图片)
  • 石基大商:OceanBase + Flink CDC,搭建连锁零售系统数据湖
  • IDEA 接入 Deepseek
  • comfyui使用ComfyUI-AnimateDiff-Evolved, ComfyUI-Advanced-ControlNet节点报错解决
  • 网络安全域的划分与隔离
  • 基于RKNN的嵌入式深度学习开发(1)
  • 青蛙跳杯子(BFS)
  • 如何将hf-mirror.com作为vllm默认的下载源? conda如何移除虚拟环境?conda 如何复制一份虚拟环境?
  • 15-YOLOV8OBB损失函数详解
  • Spring(三)容器-注入
  • 玩转大模型——Trae AI IDE国内版使用教程
  • 【我的Android进阶之旅】如何使用NanoHttpd在Android端快速部署一个HTTP服务器?
  • GPU、NPU与LPU:大语言模型(LLM)硬件加速器全面对比分析
  • 20241130 RocketMQ本机安装与SpringBoot整合
  • CSS2.1基础学习
  • STM32——DMA详解
  • 似然函数与极大似然估计
  • 表达式求值(后缀表达式)
  • 云南电商网站建设/象山seo外包服务优化
  • 常州广告公司排名/seo网络营销的技术
  • jsp网站建设/百度高级检索入口
  • 阿克苏网站建设公司/北京网站优化常识
  • 可信网站行业验证必须做吗/网络服务商在哪咨询
  • 如何做响应式布局网站/外链怎么做