STM32

发布于:2025-07-02 ⋅ 阅读:(13) ⋅ 点赞:(0)

#中断系统和EXTI外部中断#

中断系统(管理和执行中断的逻辑结构)

  • 中断:在主程序运行过程中,出现了特定的中断触发条件(中断源)(外部中断—引脚发生电平跳变,定时器—到达定时的时间,串口通信—接收数据),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行
  • 中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源
  • 中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回

中断执行流程

STM32中断

  • 68个可屏蔽中断通道(中断源),包含EXTI外部中断、TIM定时器ADC模数转换器USART串口、SPI通信、I2C通信、RTC实时时钟等多个外设
  • 使用NVIC(在STM32中管理中断分配优先级)统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级

 

内核中断:

①复位中断,当产生复位事件时,程序就会自动执行复位中断函数(复位后程序开始执行的位置)。

②NMI,不可屏蔽中断。

③硬件失效

④存储管理

⑤总线错误

⑥错误应用

……

外设中断:

①窗口看门狗,监测程序运行状态的中断(程序卡死没有及时喂狗,窗口看门狗就会申请中断)

PVD电源电压监测,如果供电电压不足就会申请中断。

EXTI0~EXTI4,EXTI9_5EXTI15_10外部中断对应的中断源

……

程序中的中断函数地址,由编译器分配,不固定,但是中段跳转由于硬件的限制,只能跳到固定的地址执行程序,所以为了能让硬件跳转到一个不固定的中段函数里,就需要在内存中定义一个地址的列表,在这个固定位置有编译器,再加上一条跳转到中断函数的代码,这样中断跳转跳到任意位置可行,中段地址的列表叫中段向量表

 

NVIC基本结构(嵌套中断向量控制器)

STM32中,统一分配中断优先级和管理中断

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

NVIC有许多输入口,可以接许多中断线路

NVIC只有一个输出口,根据每个中断的优先级分配中断的先后顺序,通过一个输出口连接CPU,确定中断

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简介(众多能产生中断的外设之一)

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

(由于外部中断,可以从低功耗模式的停止模式下唤醒stm32,①pvd电源电压监测—电源从电压过低恢复时,PVD借助外部中断退出停止模式;②RTC闹钟—为了省电,RTC定闹钟后,stm32会进入停止模式,等到闹钟响的时候再唤醒,就需要借助外部中断;③USB唤醒和以太网唤醒都是类似作用)

  • 触发响应方式:中断响应/事件响应
  • (事件响应:Stm32对外部中断增加的一种额外功能,外部中断检测到引脚电平变化,正常流程是会选择触发中断,但也可以选择触发事件,那么外部中断的信号不会通向CPU,而是通向其他外设,触发其他外设的操作——触发ADC转换,触发DMA
  • 中断响应是正常流程引脚电平变化触发中断,事件响应不会触发中断,而是触发别的外设操作,属于外设之间的联合工作。

 

EXTI基本结构

AFIO复用IO

  • AFIO主要用于引脚复用功能的选择和重定义(选择器)
  • STM32中,AFIO主要完成两个任务:复用功能引脚重映射(将默认复用功能引脚换到重定义模块)、中断引脚选择

EXTI框图

配合外部中断原理模块

特征:想要获取信号是外部驱动的很快的突发信号(旋转编码器的输出信号/红外遥控接收头的输出)

旋转编码器简介

  • 旋转编码器:用来测量位置、速度或旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向
  • 类型:机械触点式/霍尔传感器式/光栅式

硬件电路

未旋转→(触点未导通,上拉电阻)A端口高电平

旋转→(内部触点导通)A端口低电平

外部中断流程: 

//初始化

//配置外部中断流程

//看EXTI基本结构图,将外设配置好,线路串通即可

//GPIO-AFIO——EXTI——NVIC

//配置RCC,打开时钟——使外设能够工作

//配置GPIO,选择端口为输入模式


AFIO相关函数

//配置AFIO,库函数与GPIO同一个文件,选择我们用的一路GPIO,连接到后面的EXIT

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的数据选择器,选择我们想要的中断引脚

void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);

//与以太网有关,芯片没有配置,用不到


EXIT相关函数

//配置EXTI,选择边沿触发方式(上升沿/下降沿/双边沿),选择触发响应方式(中断响应/事件响应)

void EXTI_DeInit(void);

//将EXTI配置清除,恢复上电默认状态

void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);

//可根据结构体的参数配置EXTI外设,初始化EXTI函数

EXIT在c文件中代码举例

EXTI_InitTypeDef EXTI_InitStructure;

EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//中断模式

EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling //下降沿触发

EXTI_InitStructure.EXTI_Line=EXTI_Line14 ;//PB14

EXTI_InitStructure.EXTI_LineCmd=ENABLE;//开启中断

EXTI_Init(&EXTI_InitStructure); 

EXTI_InitStructure.EXTI_Trigger=

EXTI_Trigger_Rising// 上升沿触发

EXTI_Trigger_Falling //下降沿触发

EXTI_Trigger_Rising_Falling //双沿触发

void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);

//可以把参数传递的结构体变量赋一个默认值

void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);

//用来软件触发外部中断,调用函数,需要参数给定一个中断线,就能软件触发一次外部中断

//如果只需要外部引脚触发外部中断,就不需要使用此函数


//在外设运行中,会产生一些状态标志位

//比如外部中断,有一个挂起寄存器置一个标志位,串口收到数据,会置标志位

//标志位都是存放在状态寄存器,当程序想要看到这些标志位,就会用到这四个函数读写状态寄存器

①FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);

//获取指定标志位是否被置1

②void EXTI_ClearFlag(uint32_t EXTI_Line);

//对置1的标志位进行清除

/想查看与中断有关的标志位和清除标志位下面俩个函数

③ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);

//获取中断标志位是否被置1

④void EXTI_ClearITPendingBit(uint32_t EXTI_Line);

//清除中断挂起标志位

在主程序查看和清除标志位,用①②函数

在中断函数查看和清除标志位,用③④函数


//配置NVIC,给中断选择一个何使的优先级,通过NVIC,外部中断信号就能进入CPU,CPU接收到中断信号,跳转到中断函数执行中断程序

NVIC为内核外设,库函数在杂项

在配置中断之前,先置定中断分组

再使用NVIC_Init初始化NVIC

void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);

//中断分组,参数使中断分组的方式

NVIC在c文件中代码举例

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//分组,整个工程执行一次

NVIC_InitTypeDef NVIC_InitStructure;

NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;

NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;

NVIC_InitStructure.NVIC_IRQChannel=EXTI15_10_IRQn;

NVIC_Init(&NVIC_InitStructure);

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);

//系统低功耗配置

void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource);


对射式红外传感器计数

1.count Sensor.c

#include "stm32f10x.h"                  // Device header

uint16_t count;



//初始化
void CountSensor_Init(void)
{
//配置外部中断流程
//看EXTI基本结构图,将外设配置好,线路串通即可
//GPIO-AFIO——EXTI——NVIC
	
	
//1.配置RCC管理外设,打开时钟——使外设能够工作
	RCC_APB2PeriphResetCmd(RCC_APB2Periph_GPIOB,ENABLE);//PB14
	//配置AFIO时钟,也是APB2外设
	//EXTI和NVIC外设一直都是打开状态,不需要配置
	RCC_APB2PeriphResetCmd(RCC_APB2Periph_AFIO,ENABLE);//PB14
	
	
//2.配置GPIO,选择端口为输入模式
	GPIO_InitTypeDef GPIO_InitStructure;
	//选择模式,对于外部中断来说,上拉输入,下拉输入,浮空输入
	//不知道如何配置模式,在外设配置手册查看外设GPIO配置
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//上拉输入,默认高电平输入方式
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_14;//PB14
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;//常规
	GPIO_Init(GPIOB,&GPIO_InitStructure);

//3.配置AFIO,库函数与GPIO同一个文件,选择我们用的一路GPIO,连接到后面的EXIT
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);//PB14


//4.配置EXTI,选择边沿触发方式(上升沿/下降沿/双边沿),选择触发响应方式(中断响应/事件响应)
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//中断模式
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;//上升沿触发 
	EXTI_InitStructure.EXTI_Line=EXTI_Line14 ;//PB14
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;//开启中断
	
	EXTI_Init(&EXTI_InitStructure);

//5.配置NVIC,给中断选择一个何使的优先级,通过NVIC,外部中断信号就能进入CPU,CPU接收到中断信号,跳转到中断函数执行中断程序
//	在配置中断之前,先置定中断分组
//  再使用NVIC_Init初始化NVIC
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//分组,整个工程执行一次
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	NVIC_InitStructure.NVIC_IRQChannel=EXTI15_10_IRQn;
	NVIC_Init(&NVIC_InitStructure);
}



//中断程序
void EXTI15_10_IRQHandler(void)
{
	//中断标志位判断,外部中断10~15都能进,判断是否是14
   if(EXTI_GetITStatus(EXTI_Line14)==SET)
	 {
		 //执行中断程序
		 count++;
		 //清除中断标志位函数,只有中断标志位置1,程序就会跳到中断函数
	   EXTI_ClearITPendingBit(EXTI_Line14);
	 }
}

 count Sensor.h

#ifndef __COUNTSENSOR_H_
#define __COUNTSENSOR_H_

void CountSensor_Init(void);
extern uint16_t count;

#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(2,7,count,5);
	}

}

<这个代码有问题,我的OLED无法显示,除非把红外传感器初始化删除,根up编写对比了,还没找到原因@_@>


旋转编码器计次

1.encounter.c

#include "stm32f10x.h"                  // Device header

int16_t count;				
//B相下降沿和A相低电平,判断为正转
//A相下降沿和B相低电平,判断为正转
void Encoder_Init(void)
{
	//RCC开启时钟
	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;//PB0和PB1
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);						
	
	//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
	//对俩个通道分别设置优先级														
	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 = count;
	count = 0;
	return Temp;//返回count的变化值,用于外部加减变量
}

void EXTI0_IRQHandler(void)
{
	//检查中断标志位
	if (EXTI_GetITStatus(EXTI_Line0) == SET)		
	{
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)//反转
			//判断另一个引脚
		{
				count--;					
		}
		EXTI_ClearITPendingBit(EXTI_Line0);			//清除外部中断标志位
	}
}
//在中断不要执行耗时的代码
//不要在主函数和制度函数调用同一个函数或操作同一个硬件,会显示错误
void EXTI1_IRQHandler(void)
{
	//检查外部中断标志
	if (EXTI_GetITStatus(EXTI_Line1) == SET)		
	{
		/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)//正转
			{
				count ++;					
			}
		EXTI_ClearITPendingBit(EXTI_Line1);			//清除外部标志位
	}
}

2.encounter.h

#ifndef __ENCODER_H_
#define __ENCODER_H_
void Encoder_Init(void);
int16_t Encoder_Get(void);

#endif

3.main.c

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "encoder.h"
uint16_t number;
int main(void)
{	
	OLED_Init();
	OLED_ShowString(1,1,"number");
	Encoder_Init();
	while(1)
	{
		number +=Encoder_Get();
		OLED_ShowSignedNum(1,8,number,5);
	}

}


网站公告

今日签到

点亮在社区的每一天
去签到