5. 中断
本文来自于《STM32——江科大》的笔记整理。
中断系统
中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行
中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源
中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回
中断执行流程
STM32中断
- 68个可屏蔽中断通道,包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设
- 使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级
本节学习
- EXRI1——EXRI2——EXRI3——EXRI4——EXRI9_5——EXRI15_10
后面的地址是干什么的?
这是因为中断函数,它的地址是由编译器来分配,是不固定的,但是我们的中断跳转,由于硬件的限制,只能跳到固定的地址执行程序,所以为了能让硬件跳转到一个不固定的中断函数里
NVIC基本结构
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 |
5.1 EXTI外部中断
5.1.1 EXTI简介
- EXTI(Extern Interrupt)外部中断
- EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
- 支持的触发方式:上升沿/下降沿/双边沿/软件触发
- 支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断
- 通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒
- 触发响应方式:中断响应/事件响应
5.1.2 EXTI基本结构
5.1.2 AFIO复用IO口
- AFIO主要用于引脚复用功能的选择和重定义
- 在STM32中,AFIO主要完成两个任务:复用功能引脚重映射、中断引脚选择
5.1.3 EXTI框图
总结:
- 对应想要获取的信号是外部驱动的很快的突发信号,对应这种情况,可以考虑使用STM32的外部中断
5.1.4 旋转编码器介绍
- 旋转编码器:用来测量位置、速度或旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向
- 类型:机械触点式/霍尔传感器式/光栅式
机械触点一般不用于电机测速
硬件电路
我们只需要把这个外部中断的路线打通即可
5.1.5 对射式红外线传感器计次数
AFIO的库函数和GPIO在一个文件里
void GPIO_AFIODeInit(void);
void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
void GPIO_EventOutputCmd(FunctionalState NewState);
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
CountSensor.c
PB14号引脚的电平信号,就顺利通过了AFIO,进入到后级的EXTI电路了
#include "stm32f10x.h" // Device header
//定义一个全局变量计数
//默认0
uint16_t CountSensor_Count;
void CountSensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启时钟
//EXTI和NVIC两个外设的时钟一直都是打开的
//NVIC是内核的外设,是不需要开启时钟的
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
GPIO_Init(GPIOB, &GPIO_InitStructure);
//PB14号引脚的电平信号,就顺利通过了AFIO,进入到后级的EXTI电路了
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
//初始化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);
//配置NOIC
//分组方式整个芯片就只能用一组
//可以放到主函数的最开始
//放到模块里需要保证每个模块一致
//取值范围抢占优先级(0-3),响应优先级(0-3)
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStruct;
//需要在当前工程搜索IRQn_Type
//选择对应的中断通道
NVIC_InitStruct.NVIC_IRQChannel=EXTI15_10_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
//设置抢占优先级(0-3)
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
//设置响应优先级(0-3)
NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStruct);
}
uint16_t CountSensor_Get(void)
{
return CountSensor_Count;
}
//参考启动文件文件中的中断向量表
//中断函数的名字都是固定的(无参,无返回值)
void EXTI15_10_IRQHandler(void)
{
//先进行,中断标准位的判断(确保是我们想要的中断源触发的这个函数)
if(EXTI_GetITStatus(EXTI_Line14)==SET)
{
CountSensor_Count++;
//注意需要清除中断标志位,否则程序会不断的响应中断
//程序就卡死在了中断函数
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
CountSensor.h
#ifndef __COUNTSENOR_H
#define __COUNTSENOR_H
#include "stm32f10x.h" // Device header
uint16_t CountSensor_Get(void);
void CountSensor_Init(void);
#endif
main.c
OLED代码 -->见STM32——OLED显示屏
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"
int main()
{
OLED_Init();
CountSensor_Init();
OLED_ShowString(1,1,"Count:");//显示字符串
while(1)
{
OLED_ShowNum(1,7,CountSensor_Get(),5);
}
}
5.1.6 旋转编码器计次
连线图
中断代码思路
Encoder.c
#include "stm32f10x.h" // Device header
//定义一个全局变量计数
//默认0
int16_t Encoder_Count;
void Encoder_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启时钟
//EXTI和NVIC两个外设的时钟一直都是打开的
//NVIC是内核的外设,是不需要开启时钟的
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
GPIO_Init(GPIOB, &GPIO_InitStructure);
//PB14号引脚的电平信号,就顺利通过了AFIO,进入到后级的EXTI电路了
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);
//初始化EXTI
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line=EXTI_Line0 | EXTI_Line1;//0行
EXTI_InitStructure.EXTI_LineCmd=ENABLE;//开启
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//中断模式
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;//下降沿触发
EXTI_Init(&EXTI_InitStructure);
//配置NOIC
//分组方式整个芯片就只能用一组
//可以放到主函数的最开始
//放到模块里需要保证每个模块一致
//取值范围抢占优先级(0-3),响应优先级(0-3)
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStruct;
//需要在当前工程搜索IRQn_Type
//选择对应的中断通道(0)
NVIC_InitStruct.NVIC_IRQChannel=EXTI0_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
//设置抢占优先级(0-3)
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
//设置响应优先级(0-3)
NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStruct);
//需要在当前工程搜索IRQn_Type
//选择对应的中断通道(1)
NVIC_InitStruct.NVIC_IRQChannel=EXTI1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
//设置抢占优先级(0-3)
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
//设置响应优先级(0-3)
NVIC_InitStruct.NVIC_IRQChannelSubPriority=2;
NVIC_Init(&NVIC_InitStruct);
}
//返回变化值
uint16_t Encoder_Get(void)
{
int16_t Temp;
Temp=Encoder_Count;
return Temp;
}
//参考启动文件文件中的中断向量表
//中断函数的名字都是固定的(无参,无返回值)
void EXTI0_IRQHandler(void)
{
//先进行,中断标准位的判断(确保是我们想要的中断源触发的这个函数)
if(EXTI_GetITStatus(EXTI_Line0)==SET)
{
//读取PB1的电平,是不是低电平
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0)
{
Encoder_Count--;
}
//注意需要清除中断标志位,否则程序会不断的响应中断
//程序就卡死在了中断函数
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
void EXTI1_IRQHandler(void)
{
//先进行,中断标准位的判断(确保是我们想要的中断源触发的这个函数)
if(EXTI_GetITStatus(EXTI_Line1)==SET)
{
//读取PB1的电平,是不是低电平
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0)==0)
{
Encoder_Count++;
}
//注意需要清除中断标志位,否则程序会不断的响应中断
//程序就卡死在了中断函数
EXTI_ClearITPendingBit(EXTI_Line1);
}
}
Encoder.h
#ifndef __ENCODER_H
#define __ENCODER_H
#include "stm32f10x.h" // Device header
void Encoder_Init(void);
uint16_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()
{
OLED_Init();
Encoder_Init();
OLED_ShowString(1,1,"Num:");//显示字符串
while(1)
{
Num+=Encoder_Get();
OLED_ShowSignedNum(1,5,Num,5);
}
}
建议:在中断函数里不要执行耗时过长的代码
既然都看到最后了,可否给个呢,关注收藏不迷路
给那些看完的朋友,奖励一个 赤赤博客-后端+前端,觉得不错的话可以推荐给身边的朋友哟!