STM32
目录
#中断系统和EXTI外部中断#
中断系统(管理和执行中断的逻辑结构)
中断执行流程
STM32中断
内核中断(深色区域):
外设中断(白色区域):
NVIC基本结构(嵌套中断向量控制器)
NVIC优先级分组
EXTI简介(众多能产生中断的外设之一)
EXTI基本结构
AFIO复用IO口
EXTI框图
配合外部中断原理模块
旋转编码器简介
硬件电路
外部中断流程:
AFIO相关函数
EXIT相关函数
对射式红外传感器计数
1.count Sensor.c
注意:
2.count Sensor.h
3.main.c
旋转编码器计次
1.encounter.c
2.encounter.h
3.main.c
#up俩个方法都有误差,数字都不稳定,就去搜寻消抖方法#
4.其他方法:
5.注意事项
#中断系统和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,选择端口为输入模式(查看手册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 ;//PB14EXTI_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
set为置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);
设置SysTick定时器的时钟源(高速内核时钟或低速 8 分频时钟)
对射式红外传感器计数
1.count Sensor.c
#include \"stm32f10x.h\" // Device headeruint16_t count;//初始化void CountSensor_Init(void){//配置外部中断流程//看EXTI基本结构图,将外设配置好,线路串通即可//GPIO-AFIO——EXTI——NVIC//1.配置RCC管理外设,打开时钟——使外设能够工作RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//PB12//配置AFIO时钟,也是APB2外设RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//PB12//EXTI和NVIC外设一直都是打开状态,不需要配置//2.配置GPIO,选择端口为输入模式init初始化GPIO_InitTypeDef GPIO_InitStructure;//选择模式,对于外部中断来说,上拉输入,下拉输入,浮空输入//不知道如何配置模式,在外设配置手册查看外设GPIO配置GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//上拉输入,默认高电平输入方式GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12;//PB12GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;//常规//********GPIO_Init(GPIOB,&GPIO_InitStructure);//3.配置AFIO,库函数与GPIO同一个文件,选择我们用的一路GPIO,连接到后面的EXITGPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource12);//PB12//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_Line12 ;//PB12EXTI_InitStructure.EXTI_LineCmd=ENABLE;//开启中断//*******EXTI_Init(&EXTI_InitStructure);//5.配置NVIC,给中断选择一个合适的优先级,通过NVIC,外部中断信号就能进入CPU,CPU接收到中断信号,跳转到中断函数执行中断程序//在配置中断之前,先置定中断分组// 再使用NVIC_Init初始化NVICNVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//分组,整个工程执行一次//配置NVIC为分组2//即抢占优先级范围:0~3,响应优先级范围:0~3//此分组配置在整个工程中仅需调用一次//若有多个中断,可以把此代码放在main函数内,while循环之前//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置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的EXTI15_10线// EXTI15_10_IRQn = 40, /*!< External Line[15:10] Interrupts*///选择的是B12引脚,为外部中断12号线NVIC_Init(&NVIC_InitStructure);}//中断程序void EXTI15_10_IRQHandler(void){//中断标志位判断,外部中断10~15都能进,判断是否是12 if(EXTI_GetITStatus(EXTI_Line12)==SET) { //执行中断程序 count++; //清除中断标志位函数,只有中断标志位置1,程序就会跳到中断函数 EXTI_ClearITPendingBit(EXTI_Line12); }}
注意:
NVIC_InitStructure.NVIC_IRQChannel=EXTI15_10_IRQn;
F12跳转到stm32f10x.
根据芯片型号选择(芯片中8为64kflash)
MD
中容量产品
64~128K
STM32F101/102/103
#ifdef STM32F10X_MD在这个模块里寻找相对应的 外部中断通道
EXTI15_10_IRQn = 40, /*!< External Line[15:10] Interrupts */
根据选择的引脚选择外部中断线通道
我选的是PB12,为12号线,而12号线在根10号线通用同一个外部中断线
每个中断通道都有对应的外部中断函数,参考启动文件,以IRQhander结尾的就是中断函数的名字
EXTI15_10_IRQHandler ; EXTI Line 15..10
这是中断线10~15的外部中断函数名字
外部中断函数无返回值,无参数
首先进行中断标志位判断,外部中断10~15都能进,判断是否是12
后执行中断程序
最后注意清除中断标志位函数,只有中断标志位置1,程序就会跳到中断函数
数值跳太快,可以在count++前面加延时函数
2.count Sensor.h
#ifndef __COUNTSENSOR_H_#define __COUNTSENSOR_H_void CountSensor_Init(void);extern uint16_t count;#endif
3.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);}}
旋转编码器计次
正转反转区分:
1.encounter.c
#include \"stm32f10x.h\" // Device headerint16_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和PB1GPIO_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);}}
#up俩个方法都有误差,数字都不稳定,就去搜寻消抖方法#
#之前提的在中断函数内延时,占用内存#
4.其他方法:
这个是一位博主写的方法,我引用分享:
(江协科技STM32——旋转编码器计次(软件消抖)_旋转编码器消抖-CSDN博客)
增加判断正、反转的条件,读取一个周期内的电平变化再进行判断。首先将最小系统板的PB0引脚与A相连接,触发方式选择上升/下降沿触发,用A相的输出信号来触发中断,然后在A相下降沿触发第一次中断后读取B相电平,紧接着A相上升沿触发第二次中断后读取B相电平,结合两次读取到的电平来判断是正转还是反转,从A相的下降沿触发到上升沿触发期间,若B相电平发生了变化,则判定编码器转动,反之未转动,波形抖动时B相的电平保持不变,能够实现消抖。
全部代码就放在下面了,注意要改的几个点:
①只需要写EXTI0的中断函数②AFIO选择中断引脚只需要PB0作为中断引脚
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);将外部中断的0号线映射到GPIOB,即选择PB0为外部中断引脚
③EXTI初始化,只需选择配置外部中断的0号线
EXTI_InitStructure.EXTI_Line = EXTI_Line0 ;
④选择上升下降沿触发
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
⑤NVIC通道只需选择PB0-中断0通道
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
在启动项h文件中找到对应的
#include \"stm32f10x.h\" // Device header#include \"Delay.h\"int16_t count,B_level;//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和PB1GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);//AFIO选择中断引脚GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);//将外部中断的0号线映射到GPIOB,即选择PB0为外部中断引脚//EXTI初始化EXTI_InitTypeDef EXTI_InitStructure;EXTI_InitStructure.EXTI_Line = EXTI_Line0 ;EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; EXTI_Init (&EXTI_InitStructure);//NVIC中断分组NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置NVIC为分组2//对俩个通道分别设置优先级NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量可以重复使用NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;//在启动项h文件中找到对应的 // EXTI0_IRQn = 6,/*!< EXTI Line0 Interrupt */ NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStructure);}int16_t Encoder_Get(void){int16_t Temp;Temp = count;count = 0;return Temp;//返回count的变化值,用于外部加减变量}void EXTI0_IRQHandler(void){if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)//A相下降沿触发第一次中断{B_level=0;//读取B相电平,若为高电平则B_level置1,反之保持0if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 1){B_level=1;}}if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 1)//A相上升沿触发第二次中断{if(B_level==1 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //第一次B相高电平,第二次B相低电平,根据编码器正反转特征得出正转{count++;//正转}if(B_level==0 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 1) //第一次B相低电平,第二次B相高电平,根据编码器正反转特征得出反转{count--;//反转} }EXTI_ClearITPendingBit(EXTI_Line0);}
5.注意事项
此方法中,中断服务函数中的if语句顺序只能是先检测A相的下降沿,再检测上升沿。如果是先检测A相的上升沿再检测下降沿,则转动编码器时就会出现单片机上电后第一次转动没反应,以及每次反方向旋转的第一次转动没反应的问题。这是因为旋转编码器转动一次,只会产生“一个波形”,而单片机上电后A、B相默认输出高电平,第一次转动后A相先产生下降沿(触发中断后不会进入if语句),再产生上升沿(触发中断后只进入第一个if语句,同时给Cnt和B_level赋值),这样产生的后果是,之后每次转动编码器,都是第一次下降沿触发中断后进入第二个if语句,然后第二次上升沿触发中断后进入第一个if语句,这样虽然也能计次,但程序的逻辑不合理,会出现转动编码器但不计次的问题。