STM32-Day10(中断-红外&旋转计次)
5.6 硬件介绍
5.6.1 使用外部中断模块的特性
对于STM32来说,想要获取的信号是外部驱动的很快的突发信号,比如旋转编码器的输出信号,可能很长时间不会触发,这时不需要STM32做任何事情,一触发就会有很多脉冲波形需要STM32接收,信号是突发的,STM32不知道什么时候会来,同时是外部驱动,只能被动读取,并且很快速,以上情况就可以使用外部中断去读取。
5.6.2旋转编码器
旋转编码器:用来测量位置、速度或旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向
类型:机械触点式、霍尔传感器式、光栅式

硬件电路:

左图是模块的电路图
中间圆形的是旋转轴,旋转轴旋转时,下方的两个触点(四个小圆点)以相位相差90度的方式交替导通,接10K的上拉电阻,默认没有旋转的时候交点(电阻之间红色小圆点)被上拉为高电平,通过R3输出到A端口的就也是高电平。
当旋转时,内部触点导通,交点被拉低到GND.再通过R3输出,A端口就是低电平
C1的作用是滤波电容
5.7 外部中断实战
5.7.1 对射式红外传感器计次
接线图:

效果:当挡光片或编码盘在对射式红外传感器中间经过时,DO就会触发电平跳变,电平跳变的信号触发STM32 PB14号口的中断,在中断函数中,执行变量++的程序,然后主循环里调用OLED显示这个变量
中断函数的配置流程:
启动时钟后,先配置GPIO输入输出,然后配置AFIO进行选择,再配置EXIT对触发方式进行设置,最后设置NVIC,选择响应优先级,最后进入CPU
时钟只需要打开GPIO和AFIO即可,EXIT和NVIC(内核的外设不需要开启时钟)是一直打开的状态
配置AFIO用到的库函数:
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
可以配置AFIO的数据选择器,来选择我们想要的中断引脚
配置EXTI需要用到的库函数:
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);
EXTI_DeInit:恢复成默认上电状态
EXTI_Init:配置EXIT外设,初始化用的就是该函数,使用方法和GPIO_Init相同
EXTI_StructInit:把参数传递的结构体变量赋一个默认的值
以上三个函数基本上每个外设都有
EXTI_GenerateSWInterrupt:软件触发外部中断
EXTI_GetFlagStatus:获取指定的标志位是否被置1
EXTI_ClearFlag:对置1的标志位进行清除
EXTI_GetITStatus:获取中断标志位是否被置1
EXTI_ClearITPendingBit:清除中断挂起标志位
以上四个是查看和清除标志位的函数,在主程序中进行用前两个,中断中用后两个
配置NVIC需要用到的库函数:
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);
NVIC_PriorityGroupConfig:进行中断分组,参数是中断分组的方式
NVIC_Init:根据结构体里面指定的参数初始化NVIC
NVIC_SetVectorTable:设置中断向量表
NVIC_SystemLPConfig:系统低功耗配置
中断分组函数定义中:pre-emption priority抢占优先级,subpriority响应优先级
中断函数配置完整代码:
#include "stm32f10x.h" // Device header
void CountSensor_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_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);//配置AFIO
EXTI_InitTypeDef EXTI_InitStruct;//配置EXIt
EXTI_InitStruct.EXTI_Line = EXTI_Line14;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStruct);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//分组的方式整个芯片只能使用一组,要保证每个模块的分组都是同一个
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruct);
}
中断程序应该防止在中断函数中,在STM32中中断函数名字是固定的,每个中断通道都对应一个中断函数。函数名在Start文件夹中的startup文件中,以IRQHandler结尾的就是中断函数名,中断函数写在CountSensor.c文件夹中
在中断函数里一般都是先进行一个中断标志位的判断,确保是正确的中断源触发的函数。因为这个函数EXIT10~15都可以进入,所以要先判断一下是不是EXIT14进入
中断函数一般不需要声明,因为其不需要主动调用,在中断产生的时候进行调用即可
在写完中断函数之后,可以使用调试模式进行查看,中断是否可以成功跳转,使用Debug按钮,在中断函数地方打一个断点,查看中断之后是否会进入中断函数。

这样就是已经进入中断函数,然后用一个变量统计中断触发的次数即可,本次采用无符号整型变量-unint16_t,在CountSensor文件中定义变量,然后再在文件中定义一个返回这个变量的函数即可。
完整文件代码:
#include "stm32f10x.h" // Device header
uint16_t CountSensor_Count;
void CountSensor_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_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);//配置AFIO
EXTI_InitTypeDef EXTI_InitStruct;//配置EXIt
EXTI_InitStruct.EXTI_Line = EXTI_Line14;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_Init(&EXTI_InitStruct);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//分组的方式整个芯片只能使用一组,要保证每个模块的分组都是同一个
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
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);
}
}
主函数代码:
#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(1,7,CountSensor_Get(),5);
}
}
5.7.2 旋转编码器计次
接线图:


上面是正转,下面是反转,上下两条线分别是A相和B相,正转的时候保证A相在低电平同时B相下降沿的时候计次,反转的时候反之,这样保证了转到位才计次,所以需要用到PB0和PB1两个口。因此此Demo需要用到两个中断的初始化
在设计中断函数的具体内容时,参考5.6.2中的硬件电路进行设计代码
完整模块代码:
#include "stm32f10x.h" // Device header
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);//配置AFIO
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);
EXTI_InitTypeDef EXTI_InitStruct;//配置EXIt
EXTI_InitStruct.EXTI_Line = EXTI_Line0|EXTI_Line1;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStruct);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//分组的方式整个芯片只能使用一组,要保证每个模块的分组都是同一个
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStruct);
}
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)
{
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0)
{
Encoder_Count--;
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
void EXTI1_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line1) == SET)
{
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0)==0)
{
Encoder_Count++;
}
EXTI_ClearITPendingBit(EXTI_Line1);
}
}
注意下面两个中断(PB0和PB1)的优先级不可以搞反,否则就会相互抵消
而且注意EXTI要调整为下降沿触发,否则会导致正反搞错
主函数代码:
#include "stm32f10x.h" // Device header
#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);
}
}
更多推荐



所有评论(0)