#中断系统和EXTI外部中断#
中断系统(管理和执行中断的逻辑结构)
- 中断:在主程序运行过程中,出现了特定的中断触发条件(中断源)(外部中断—引脚发生电平跳变,定时器—到达定时的时间,串口通信—接收数据),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行
- 中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源
- 中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回
中断执行流程
STM32中断
- 68个可屏蔽中断通道(中断源),包含EXTI外部中断、TIM定时器、ADC模数转换器、USART串口、SPI通信、I2C通信、RTC实时时钟等多个外设
- 使用NVIC(在STM32中管理中断分配优先级)统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级
内核中断:
①复位中断,当产生复位事件时,程序就会自动执行复位中断函数(复位后程序开始执行的位置)。
②NMI,不可屏蔽中断。
③硬件失效
④存储管理
⑤总线错误
⑥错误应用
……
外设中断:
①窗口看门狗,监测程序运行状态的中断(程序卡死没有及时喂狗,窗口看门狗就会申请中断)
②PVD电源电压监测,如果供电电压不足就会申请中断。
③EXTI0~EXTI4,EXTI9_5,EXTI15_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简介(众多能产生中断的外设之一)
- EXTI(Extern Interrupt)外部中断
- EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
- 支持的触发方式:上升沿/下降沿/双边沿/软件触发
- 支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断(PA0和PB0不能同时用)
- 通道数20个:16个GPIO_Pin(0~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);
}
}