【STM32】NVIC / EXTI / AFIO 介绍

时间:2024-11-10 08:25:58

文章目录

  • 中断系统
    • 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版 细致讲解 中文字幕》教程视频