深入了解STM32 EXTI中断功能
EXTI中断系统
EXTI中断获取的信号源是外部驱动的很快的突发信号,在主程序运行过程中,出现中断请求,编译器会保护主程序断点现场,跳出优先处理中断,中断程序运行完成后,跳回主程序继续运行断点后的主程序。
外部中断
中断响应:中断触发,主程序优先处理中断程序,中断程序结束后回到主程序保护点继续执行主程序。
EXTI触发方式
上升沿:电平从低电平变到高电平的瞬间触发中断
下降沿:电平从高电平变到低电平的瞬间触发中断
双边沿:上升沿和下降沿都可以触发中断
软件触发:程序触发
注意:Pin不能同时触发中断,例:PA1 , PB1 , PC1
外部中断基本结构
EXTI9_5为共用通道,表示EXTI5\6\7\8\9到NVIC使用同一个通道,外部中断的5~9会触发同一个中断函数,10~15也会触发同一个中断函数,可在函数中再根据标志位来区分到底是哪条中断。
AFIO
AFIO:中断引脚选择器,因为GPIO_Pin只有16个中断通道,所以同一个Pin,只有一个能接到通道内,例如PinA0 PinB0 PinC0 只有一个可以接到通道0上。
NVIC
1、中断通道就是中断源,STM32具体到某个型号不一定有68个中断这么全,基本上所有外设均可触发中断。一个外设可以有多个中断源,每个中断通道都有16个可编程的优先等级。
2、中断使用NVIC(嵌套中断向量控制器)统一管理(可以把它比作是医院里面的叫号系统,CPU是医生)。 管理中断、分配优先级都由它来控制。NVIC是一个内核外设,服务与CPU。图中n的意思是一个外设可能会占用多个中断通道。
中断排队规则:
中断是按照中断号来进行排队,并不是按照先来后到的顺序,按照优先级排队,值越小优先级越高。
响应优先级:
CPU目前处理的中断完成后,如果排队待处理的中断中有响应优先级的中断,即使排在末尾,也可以插队优先让CPU处理。
抢占优先级:
CPU目前正在处理中断的情况下,处于抢占优先级的中断源出现,可以让CPU暂停当前正在处理的中断,先处理抢占优先级的中断,处理完抢占优先级的中断后再处理刚才暂停的中断。
EXTI中断控制逻辑图
工程实施
GPIO到NVIC将所有外设配置
第一步:配置RCC,将涉及的的外设时钟都打开
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //开启APB2时钟外设GPIOB
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //开启APB2时钟外设AFIO
第二步:配置GPIO,将涉及端口设置为输入模式
GPIO_InitTypeDef GPIO_InitStructure; //创建结构体变量
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; //选择引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //输出速率50MHz(速率)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入(模式)
GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化
第三步:配置AFIO,选择用到的GPIO,连接到后面的EXTI
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14); //配置输入端GPIOB端口,PB14号引脚;输出端固定连接的是EXTI的第14个中断线路
第四步:配置EXTI,选择边沿触发方式,比如上升沿,下降沿,或者双边沿。可以选择响应方式,中断响应 或者 事件响应
EXTI_InitTypeDef EXTI_Initstructure; //创建结构变量
EXTI_Initstructure.EXTI_Line = EXTI_Line14; //配置的中断线,EXTI第14线路
EXTI_Initstructure.EXTI_LineCmd = ENABLE; //指定选择的中断线的新状态(开启中断)
EXTI_Initstructure.EXTI_Mode = EXTI_Mode_Interrupt; //中断模式(Event:事件模式)
EXTI_Initstructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
EXTI_Init(&EXTI_Initstructure); //初始化
第五步:配置NVIC,设置中断选择优先级
//选择中断分组,中断分组2,抢占优先级和响应优先级的取值范围都是0~3
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure; //创建结构体变量
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //指定中断通道来开启或关闭,EXTI_Line14(GPIO_Pin_14)对应EXTI15_10_IRQn中断通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //中断通道为ENABLE(使能)
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级设置为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //响应优先级设置为1
NVIC_Init(&NVIC_InitStructure); //初始化
配置完以上步骤就可以写中断函数了
中断函数
在启动文件中,已经定义好了中断函数的名字,每个中断通道都定义了一个中断函数。
我们设置的外部中断源是从 pin14 这个引脚进来的,所以函数名选择为:EXTI15_10_IRQHandler,pin10 到 pin15,六个通道的中断发生时,都会执行这个函数。
所以,当执行这个函数时,应该先判断是不是pin14发生的中断,如果是,则执行相应的命令。
注意:中断函数不需要在主函数中调用,中断触发时会自动执行中断函数
void EXTI15_10_IRQHandler(void){
//获取中断标志位, 判断EXTI14的中断标志位是不是为1(SET)(若发生中断,则标志位置1)
if (EXTI_GetITStatus(EXTI_Line14) == SET){
CountSensor_Count ++;
//中断程序结束后,清除中断标志位,否则陷入中断循环
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
常用函数
void EXTI_DeInit(void); //重置
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct); //初始化
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); //获取中断标志位
void EXTI_ClearITPendingBit(uint32_t EXTI_Line); //清除中断标志位
旋转编码器计次样例
接线图
旋转编码器正反判断
A相B相都触发中断,只有在B相下降沿和A相低电平时,才判断为正转,在A相下降沿和B相低电平时,才判断为反转,就可以保证正转反转都是转到位了,才执行数字加减的操作。
样例工程实施
代码
#include "stm32f10x.h"
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;
}
void EXTI0_IRQHandler(void){
//获取中断标志位
if (EXTI_GetITStatus(EXTI_Line0) == SET){ //判断EXTI1的中断标志位是不是为1(SET)
if (GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0){
Encoder_Count --; //计中断数
}
EXTI_ClearITPendingBit(EXTI_Line1); //清除中断标志位
}
}
void EXTI1_IRQHandler(void){
//获取中断标志位
if (EXTI_GetITStatus(EXTI_Line1) == SET){ //判断EXTI0的中断标志位是不是为1(SET)
if (GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0) == 0){
Encoder_Count ++; //计中断数
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除中断标志位
}
}
main代码
#include "stm32f10x.h"
#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);
}
}
作者:尘世中迷茫的小菜呀