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);
    }
}
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐