stm32 - 中断

stm32 - 中断

概念

stm32 支持的中断资源(都属于外设)

  • EXTI
  • TIM
  • ADC
  • USARt
  • SPI
  • I2C

stm32支持的中断

内核中断

外设中断

中断通道与优先级

一个外设可能占用多个中断通道(一个EXTI外设模块,可以有多个中断通道)

每个中断通道有16个优先级

中断向量表

自定义的中断服务函数,由编译器随机指定函数地址

stm32的中断,由于硬件的限制,只能跳到固定的地址执行程序

为了能让硬件跳转到一个不固定的中断函数中, 需要在内存中定义一个地址列表,这个列表的地址是固定的,中断发生后,先跳到这个固定位置,然后在这个固定位置,由编译器加上一条跳转到中断函数的代码,这样中断就可以跳转到任意位置了

NVIC 嵌套中断向量控制器

用于同一分配中断优先级和管理中断的,NVIC是一个内核外设
一个外设可能占用多个中断通道,所以有n条线

NVIC只有一个输出口,其根据每个中断的优先级分配中断的先后顺序

然后通过仅有的一个输出口,通知CPU应该处理哪个中断

优先级

  • 响应优先级:"插队看病"
  • 抢占优先级:"中断嵌套"

NVIC中断优先级由优先级寄存器的4位(0~15)决定,数值越小优先级越高

高n位为抢占优先级,4-n位为响应优先级

抢占优先级高的可以中断嵌套,响应优先高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队

中断

EXTI

检测指定GPIO的电平信号

电平信号发生变化时,EXTI申请中断

概念

触发方式

上升沿(低->高)触发中断

下降沿(高->低)触发中断

双边沿(上升沿和下降沿都可以触发中断)

软件触发:引脚电平未发生变化,通过在软件中调用执行代码触发中断

GPIO

任意的GPIO口都可以当做外部中断的引脚,但是相同的pin引脚不能同时触发中断(PA0/PB0; PA1/PB1/PC1)

通道数

16个GPIO_pin,外加PVD输出,RTC闹钟,USB唤醒,以太网唤醒

触发响应方式

中断响应:申请中断,让CPU执行中断函数

事件响应: 当外部中断检测到引脚电平变化时,正常的流程是选择触发中断,也可以选择触发事件,那么外部中断的信号就不会通向CPU了,而是通向其他外设,用来触发其他外设的操作

基本结构

EXTI模块支持20个中断通道数,16个pin_脚+PVD+RTC+USB+ETH
针对GPIO,利用AFIO进行中断选择(当使用GPIO实现外部中断时)

PA0/PB0/PC0... 通过AFIO选择器选择一个作为pin_0通道

PA1/PB1/PC1... 通过AFIO选择器选择一个作为pin_1通道

PA2/PB2/PC2... 通过AFIO选择器选择一个作为pin_2通道
每个通道有16可配置优先级,通过NVIC进行优先级配置

NVIC可以进行优先级分组(抢占/响应),每个分组都有取值范围,不同的取值范围,在CPU进行中断服务的时机不同
注意16个引脚输入通道,最终只有7个输入,其中9-5和15-10便成了路两路输出通道,因此需要中断标志位判断哪一个中断过来


PB14引脚的电平信号就可以通过AFIO进入到EXTI电路中

例子- 对射式红外传感器计次

main.c

c 复制代码
#include "stm32f10x.h"
#include "OLED.h"
#include "infrCountSensor.h"

int main()
{
	OLED_Init();
	OLED_ShowString(1,1,"helloworld");
	OLED_ShowString(2,1,"count: ");
	InfrCountSensor_Init(14);
	while (1) 
	{
		OLED_ShowNum(2,8,InfrCountSensor_GetCount(),8);
	}
}

infrCountSensor.h

c 复制代码
#ifndef __INFRCOUNTSENSOR_H__
#define __INFRCOUNTSENSOR_H__
#include "stm32f10x.h"
void InfrCountSensor_Init(unsigned char pin_num);
unsigned int InfrCountSensor_GetCount();
uint16_t GPIO_Pin_Num_Set(uint16_t pin_num);
uint8_t GPIO_AFIO_Pin_Num_Select(uint16_t pin_num);
uint32_t EXTI_Line_Set(unsigned char pin_num);
#endif

infrCountSensor.c

c 复制代码
#include "infrCountSensor.h"

static unsigned int infrCountSensor_count;

uint16_t GPIO_Pin_Num_Set(uint16_t pin_num)
{
	uint16_t GPIO_Pin_num=0;
	switch (pin_num)
	{
		case 0: {return GPIO_Pin_0;break;}
		case 1: {return GPIO_Pin_1;break;}
		case 2: {return GPIO_Pin_2;break;}
		case 3: {return GPIO_Pin_3;break;}
		case 4: {return GPIO_Pin_4;break;}
		case 5: {return GPIO_Pin_5;break;}
		case 6: {return GPIO_Pin_6;break;}
		case 7: {return GPIO_Pin_7;break;}
		case 8: {return GPIO_Pin_8;break;}
		case 9: {return GPIO_Pin_9;break;}
		case 10: {return GPIO_Pin_10;break;}
		case 11: {return GPIO_Pin_11;break;}
		case 12: {return GPIO_Pin_12;break;}
		case 13: {return GPIO_Pin_13;break;}
		case 14: {return GPIO_Pin_14;break;}
		case 15: {return GPIO_Pin_14;break;}
		default: return GPIO_Pin_num;
	}

}

uint8_t GPIO_AFIO_Pin_Num_Select(uint16_t pin_num)
{
	uint8_t AFIO_Pin_num=0;
	switch (pin_num)
	{
		case 0: {return GPIO_PinSource0;break;}
		case 1: {return GPIO_PinSource1;break;}
		case 2: {return GPIO_PinSource2;break;}
		case 3: {return GPIO_PinSource3;break;}
		case 4: {return GPIO_PinSource4;break;}
		case 5: {return GPIO_PinSource5;break;}
		case 6: {return GPIO_PinSource6;break;}
		case 7: {return GPIO_PinSource7;break;}
		case 8: {return GPIO_PinSource8;break;}
		case 9: {return GPIO_PinSource9;break;}
		case 10: {return GPIO_PinSource10;break;}
		case 11: {return GPIO_PinSource11;break;}
		case 12: {return GPIO_PinSource12;break;}
		case 13: {return GPIO_PinSource13;break;}
		case 14: {return GPIO_PinSource14;break;}
		case 15: {return GPIO_PinSource14;break;}
		default: return AFIO_Pin_num;
	}
}

uint32_t EXTI_Line_Set(unsigned char pin_num)
{
	uint32_t EXTI_Line_num=0;
	switch (pin_num)
	{
		case 0: {return EXTI_Line0;break;}
		case 1: {return EXTI_Line1;break;}
		case 2: {return EXTI_Line2;break;}
		case 3: {return EXTI_Line3;break;}
		case 4: {return EXTI_Line4;break;}
		case 5: {return EXTI_Line5;break;}
		case 6: {return EXTI_Line6;break;}
		case 7: {return EXTI_Line7;break;}
		case 8: {return EXTI_Line8;break;}
		case 9: {return EXTI_Line9;break;}
		case 10: {return EXTI_Line10;break;}
		case 11: {return EXTI_Line11;break;}
		case 12: {return EXTI_Line12;break;}
		case 13: {return EXTI_Line13;break;}
		case 14: {return EXTI_Line14;break;}
		case 15: {return EXTI_Line15;break;}
		default: return EXTI_Line_num;
	}
}

void InfrCountSensor_Init(unsigned char pin_num)
{
	// 打开GPIO/AFIO时钟
	// GPIO和AFIO是APB2总线的外设,需要手动开启时钟,RCC用于配置内核外的外设时钟
	// EXIT中断默认开始时钟,NVIC是内核外设(内核外设也无需开启时钟)
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
		 
	// 配置GPIO 输入模式
	uint16_t GPIO_Pin_num=0;
	GPIO_Pin_num=GPIO_Pin_Num_Set(pin_num);
	
	GPIO_InitTypeDef GPIO_InitStructure; 
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;		// 上拉输入
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_num;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	// 配置AFIO 选择器(中断引脚选择)
	// GPIO_AFIODeInit(); 		// 清空配置
	// GPIO_PinLockConfig(); 	// 锁定引脚,防止意外被更改
	// GPIO_PinRemapConfig(); 		// 引脚重映射
	// 选择指定的GPIO / pin 作为外部中断源
	uint8_t AFIO_Pin_num=0;
	AFIO_Pin_num=GPIO_AFIO_Pin_Num_Select(pin_num);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,AFIO_Pin_num); // 配置数据选择器,
															// AFIO_Pin_num 是AFIO的中断选择器号,这里是第14个数据选择器
	
	// 配置EXTI中断,触发方式,触发响应方式
	// EXTI_DeInit(); 		// 清除配置
	// EXTI_StructInit(); 	// 结构体赋值
	// EXTI_GenerateSWinterrupt(); // 软件触发
	// 外部中断的外部状态寄存器会设置对应的标志位
	// 在主程序中读写外部中断标志位,在中断服务函数中读写外部中断标志位
	uint32_t EXTI_Line_num=0;
	EXTI_Line_num=EXTI_Line_Set(pin_num);
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line=EXTI_Line_num;				// 指定要配置的中断线,中断EXTI输入通道
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;					// 中断线的新状态,开启中断
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt ;		// 中断模式interrupt或事件模式event
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;	//触发方式,下降沿
	EXTI_Init(&EXTI_InitStructure); 						// 初始化
	
	// 配置NVIC,中断优先级,进入CPU再执行中断程序
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 中断分组,两位抢占,两位响应
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=EXTI15_10_IRQn;		// 中断EXTI输出通道,15-10占用一个通道,在中断服务函数中需要判断中断标志位
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;	// 抢占优先级(在抢占中断中)
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;		// 响应优先级 (在响应中断中)
	NVIC_Init(&NVIC_InitStructure);
}

void EXTI15_10_IRQHandler(void) // 中断函数
{
	// 中断标志位的判断
	if (EXTI_GetITStatus(EXTI_Line14)==SET)
	{
		// 清楚中断标志位
		EXTI_ClearITPendingBit(EXTI_Line14);
		
		infrCountSensor_count++;
	}
}


unsigned int InfrCountSensor_GetCount()
{
	return infrCountSensor_count;
}

例子 - 旋转编码器

对于两个中断,AFIO、EXTI、NVIC的code方式

main.c

c 复制代码
#include "stm32f10x.h"
#include "Delay.h"
#include "LED.h"
#include "key.h"
#include "Buzzer.h"
#include "PhotoSensor.h"
#include "OLED.h"
#include "infrCountSensor.h"
#include "encoder.h"

int main()
{
	OLED_Init();
	OLED_ShowString(1,1,"helloworld");
	OLED_ShowString(2,1,"count: ");
	Encoder_Init();
	while (1) 
	{
		OLED_ShowSignedNum(2,8,Encoder_Get(),8);
	}
}

encoder.h

c 复制代码
#ifndef __ENCODER_H__
#define __ENCODER_H__

#include "stm32f10x.h"
void Encoder_Init();
int16_t Encoder_Get();
#endif

encoder.c

c 复制代码
#include "encoder.h"

int16_t encoder_rotate;

void Encoder_Init()
{
	// 打开GPIO/AFIO时钟
	// GPIO和AFIO是APB2总线的外设,需要手动开启时钟,RCC用于配置内核外的外设时钟
	// EXIT中断默认开始时钟,NVIC是内核外设(内核外设也无需开启时钟)
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
		 
	// 配置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);
	
	// 配置AFIO 选择器(中断引脚选择)
	// 选择指定的GPIO / pin 作为外部中断源
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0); // 配置数据选择器,
															// AFIO_Pin_num 是AFIO的中断选择器号,这里是第14个数据选择器
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1); // 配置数据选择器,
															// AFIO_Pin_num 是AFIO的中断选择器号,这里是第14个数据选择器


	// 配置EXTI中断,触发方式,触发响应方式
	// 外部中断的外部状态寄存器会设置对应的标志位
	// 在主程序中读写外部中断标志位,在中断服务函数中读写外部中断标志位;
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line=EXTI_Line0 | EXTI_Line1;				// 指定要配置的中断线,中断EXTI输入通道
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;					// 中断线的新状态,开启中断
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt ;		// 中断模式interrupt或事件模式event
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;	//触发方式,下降沿
	EXTI_Init(&EXTI_InitStructure); 						// 初始化
	
	// 对两个通道分别设置优先级
	// 配置NVIC,中断优先级,进入CPU再执行中断程序
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 中断分组,两位抢占,两位响应
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=EXTI0_IRQn;		// 中断EXTI输出通道
	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;		// 中断EXTI输出通道
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;	// 抢占优先级(在抢占中断中)
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;		// 响应优先级 (在响应中断中)
	NVIC_Init(&NVIC_InitStructure);
}

void EXTI0_IRQHandler()
{
	if (EXTI_GetITStatus(EXTI_Line0)==SET)
	{
		EXTI_ClearITPendingBit(EXTI_Line0);
		
		if (GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0)
		{
			encoder_rotate--;
		} 
	}
}

void EXTI1_IRQHandler()
{
	if (EXTI_GetITStatus(EXTI_Line1)==SET)
	{
		EXTI_ClearITPendingBit(EXTI_Line1);
		
		if (GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0)==0)
		{
			encoder_rotate++;
		} 
	}
}

int16_t Encoder_Get()
{
	return encoder_rotate;
}
相关推荐
杰克逊的日记9 天前
MCU编程
单片机·嵌入式硬件
Python小老六9 天前
单片机测ntc热敏电阻的几种方法(软件)
数据库·单片机·嵌入式硬件
懒惰的bit9 天前
STM32F103C8T6 学习笔记摘要(四)
笔记·stm32·学习
HX科技9 天前
STM32给FPGA的外挂FLASH进行升级
stm32·嵌入式硬件·fpga开发·flash·fpga升级
Suagrhaha9 天前
驱动入门的进一步深入
linux·嵌入式硬件·驱动
国科安芯9 天前
基于ASP4644多通道降压技术在电力监测系统中集成应用与发展前景
嵌入式硬件·硬件架构·硬件工程
Li Zi9 天前
STM32 ADC(DMA)双缓冲采集+串口USART(DMA)直接传输12位原始数据到上位机显示并保存WAV格式音频文件 收藏住绝对实用!!!
经验分享·stm32·单片机·嵌入式硬件
进击的程序汪9 天前
触摸屏(典型 I2C + Input 子系统设备)从设备树解析到触摸事件上报
linux·网络·嵌入式硬件
damo王10 天前
Zephyr 系统深入解析:SoC 支持包结构与中断调度器调优实践
单片机·嵌入式硬件·zephyr
逼子格10 天前
硬件工程师笔试面试高频考点汇总——(2025版)
单片机·嵌入式硬件·面试·硬件工程·硬件工程师·硬件工程师真题·硬件工程师面试