【STM32】NVIC / EXTI / AFIO 介绍

文章目录

  • 中断系统
  • NVIC简介
  • NVIC基本结构
  • NVIC优先级分组
  • EXTI外部中断
  • EXIT基本结构
  • AFIO复用IO口
  • EXTI内部框图
  • AFIO / EXTI / NVIC 相关函数
  • AFIO相关函数
  • EXTI相关函数
  • NVIC相关函数
  • 旋转编码器简介
  • 对射式红外传感器计次
  • 接线图
  • CountSensor(传感器)驱动程序封装
  • main.c 源程序
  • 旋转编码器计次
  • 接线图
  • Encoder驱动程序封装
  • main.c 源程序
  • 中断系统

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

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

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

    中断执行流程

    NVIC简介

    在STM32中,NVIC(Nested Vectored Interrupt Controller)是一个中断控制器,负责管理和处理微控制器的中断

    NVIC是一个内核外设,是CPU的小助手

    它允许多个中断源以优先级方式响应,使得系统能够快速响应高优先级的中断请求,NVIC支持嵌套中断,即高优先级的中断可以打断低优先级的中断

    NVIC的主要功能包括:

    1. 中断向量表:存储所有中断的服务程序地址
    2. 中断优先级管理:允许为不同中断分配不同的优先级,以控制中断的响应顺序
    3. 中断使能和禁用:可以启用或禁用特定的中断
    4. 中断触发方式:支持多种中断触发方式,如上升沿、下降沿等

    通过NVIC,STM32能够高效地处理实时事件,提高系统的响应速度和性能

    一个形象的比喻:CPU是急诊室的医生,中断源 是需要就诊的病人,NVIC就是医生助手,负责安排病人就诊顺序,如果没有小助手那么医生就既需要看病又需要给病人排队,降低了看病效率,但是有小助手负责根据病人的优先程度排队,医生就只需要负责看病即可

    NVIC基本结构

    STM32有68个可屏蔽中断通道,包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设

    使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级

    NVIC优先级分组

  • NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级
  • 抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队
  • 分组方式 抢占优先级 响应优先级
    分组0 0位,取值为0 4位,取值为0~15
    分组1 1位,取值为0~1 3位,取值为0~7
    分组2 2位,取值为0~3 2位,取值为0~3
    分组3 3位,取值为0~7 1位,取值为0~1
    分组4 4位,取值为0~15 0位,取值为0

    EXTI外部中断

    EXTI(External Interrupt/Event Controller)是STM32微控制器中的一个模块,用于处理外部中断和事件,它允许外部信号(如按钮、传感器等)触发中断,从而实现对外部事件的快速响应

  • EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
  • 支持的触发方式:上升沿 / 下降沿 / 双边沿 / 软件触发
  • 支持的GPIO口:所有 GPIO 口,但相同的 Pin 不能同时触发中断(eg. PA1 和 PB1 不能同时触发中断)
  • 通道数:16 个 GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒
  • 触发响应方式:中断响应 / 事件响应
  • EXIT基本结构

    【AFIO说明】
    之前介绍过,EXTI模块只有16个GPIO的通道,但是每个GPIO外设都有16个引脚,所以在这里会有一个AFIO中断引脚选择的电路模块
    AFIO就是一个数据选择器,它可以在前面的GPIO外设的16个引脚里选择一个连接到后面的EXTI通道里,这就是 “所有的GPIO 口都能触发中断
    但是相同的Pin不能同时触发中断” 的原因

    AFIO复用IO口

    “复用”指的是将同一个输入/输出(I/O)引脚用于多种不同的功能或用途

  • AFIO主要用于引脚复用功能的选择和重定义(也就是数据选择器的作用)
  • 在STM32中,AFIO主要完成两个任务
    1. 复用功能引脚重映射
    2. 中断引脚选择
  • EXTI内部框图

    AFIO / EXTI / NVIC 相关函数

    AFIO相关函数

    //AFIO 没有专门的库函数文件,它的库函数和 GPIO 在一个文件中
    
    void GPIO_AFIODeInit(void); 	
    //用于复位 AFIO 外设,调用该图数后 AFIO 外设的配就会被全部清除
    	
    void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
    //用于锁定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开头,但是里面操作的AFIO寄存器)
    	第一个参数:GPIO_Portsource是选择某个GPIO外设作为外部中断源
    	第二个参数:GPIO_Pinsource是指定要配置的外部中断线
    */
    
    void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);
    //和以太网有关(我们的芯片没有以太网外设,所以用不到)
    

    EXTI相关函数

    void EXTI_DeInit(void);
    //用于清除EXTI配置,恢复成上电默认的状态
    	
    void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
    //调用这个函数,可以根据这个结构体里的参数配置EXTI外设
    //初始化EXTI主要用的就是这个函数
    	
    void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);
    //调用这个函数,可以把参数传递的结构体变量赋一个默认值
    	
    void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);
    //用于软件触发外部中断
    //调用这个函数,参数给一个指定的中断线,就能软件触发一次外部中断
    	
    FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);
    void EXTI_ClearFlag(uint32_t EXTI_Line);
    
    ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
    //用来获取EXTI的中断标志位状态,如果EXTI线有中断发生函数返回“SET”否则返回“RESET”
    
    void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
    //用于清除中断标志位,以便下一次中断能够触发(每次中断程序结束后都应该清除一下中断标志位)
    
    

    NVIC相关函数

    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);
    //系统低功耗配置
    

    旋转编码器简介

    旋转编码器:用来测量位置、速度或旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向

    类型:机械触点式/霍尔传感器式/光栅式

    对射式红外传感器计次

    接线图

    CountSensor(传感器)驱动程序封装

    CountSensor.c

    #include "stm32f10x.h"                  // Device header
    
    uint16_t CountSensor_Count;
    
    void CountSensor_Init(void){	
    	
    	//Step1:开启始时钟	
    	//RCC开启始时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);	
    	//开启 AFIO 时钟,AFIO 也是 APB2 的外设,
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);	
    	//EXTI 和 NVIC 两个外设的时钟一直是打开的,不需要再开启时钟了
    	
    	//Step2:配置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);
    	
    	//Step3:配置AFIO
    	//配置AFIO外部中断引脚选择
    	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
    	//执行完后AFIO的第14个数据选择器就拨好了
    	//这样PB14号引脚的电平信号就可以顺利通过AFIO进入到EXTI电路了
    	
    	//Step4:配置EXTI
    	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_Falling;
    	EXTI_Init(&EXTI_InitStructure);
    	
    	//Step5:配置NVIC
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_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);
    }
    
    //中断函数
    //在STM32中,中断函数的名字都是固定的,每个中断通道都对应一个中断函数
    //中断函数的名字可以参考启动文件
    void EXTI15_10_IRQHandler(void){
        //由于这个函数 EXTI10 到 EXTI15 都能进,所以要先判断一下是不是我们想要的 EXTI14
    	if (EXTI_GetITStatus(EXTI_Line14) == SET){
    		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0){
    			CountSensor_Count++;
    		}		
    		EXTI_ClearITPendingBit(EXTI_Line14);
    	}
    }
    
    uint16_t CountSensor_Get(void){
    	return CountSensor_Count;
    }
    

    CountSensor.h

    #ifndef __COUNT_SENSOR_H
    #define __COUNT_SENSOR_H
    
    void CountSensor_Init(void);
    uint16_t CountSensor_Get(void);
    
    #endif
    

    main.c 源程序

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "CountSensor.h"
    
    int main(void){
    	OLED_Init();
    	CountSensor_Init();
    	
    	OLED_ShowString(1, 1, "Count:");
    	
    	while(1){
    		OLED_ShowNum(1, 7, CountSensor_Get(), 5);
    	}
    }
    
    

    旋转编码器计次

    接线图

    Encoder驱动程序封装

    Encoder.c

    #include "stm32f10x.h"                  // Device header
    
    int16_t Encoder_Count;
    
    void Encoder_Init(void){
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);	
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);	
    	
    	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);
    	
    	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);
    	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);
    	
    	EXTI_InitTypeDef EXTI_InitStructure;
    	EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;
    	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    	EXTI_Init(&EXTI_InitStructure);
    	
    	
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    	
    	NVIC_InitTypeDef NVIC_InitStructure;
    	NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    	NVIC_Init(&NVIC_InitStructure);
    	
    	NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
    	NVIC_Init(&NVIC_InitStructure);
    }
    
    int16_t Encoder_Get(void){
    	int16_t Temp;
    	Temp = Encoder_Count;
    	Encoder_Count = 0;
    	return Temp;
    }
    
    //在A相下降沿和B相低电平时才判断为反转,Encoder_Count--
    void EXTI0_IRQHandler(void){
    	if(EXTI_GetITStatus(EXTI_Line0) == SET){
    		if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0){
    			Encoder_Count--;
    		}
    		EXTI_ClearITPendingBit(EXTI_Line0);
    	}
    	
    }
    
    //在B相下降沿和A相低电平时才判断为正转,Encoder_Count++
    void EXTI1_IRQHandler(void){
    	if(EXTI_GetITStatus(EXTI_Line1) == SET){
    		if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0){
    			Encoder_Count++;
    		}
    		EXTI_ClearITPendingBit(EXTI_Line1);
    	}
    }
    

    Encoder.h

    #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();
    	Encoder_Init();
    	
    	OLED_ShowString(1, 1, "Num:");	
    	
    	while(1){
    		Num += Encoder_Get();
    		OLED_ShowSignedNum(1, 5, Num, 5);
    	}
    }
    
    

    STM32 专栏文章均参考 《STM32入门教程-2023版 细致讲解 中文字幕》教程视频

    作者:YuCaiH

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【STM32】NVIC / EXTI / AFIO 介绍

    发表回复