1. 对射式红外传感器的工作原理与硬件连接

对射式红外传感器是嵌入式项目中非常实用的检测元件,特别适合用于测速、计数和位置检测。它由一个红外发射管和一个接收管组成,当两者之间没有遮挡时,红外光能够顺利到达接收管,输出低电平;当有物体遮挡时,接收管无法接收到红外光,输出高电平。

这种传感器有3个引脚:VCC接3.3V-5V电源,GND接地,DO是数字输出引脚。在实际使用中,我们通常将DO引脚连接到STM32的GPIO口,并通过外部中断来检测电平变化。

我刚开始用这种传感器时,犯过一个低级错误:把AO引脚当成了数字输出。其实AO是模拟输出,用于检测红外信号的强度,而DO才是我们需要的数字开关信号。这个坑希望大家能避开。

接线很简单:VCC接3.3V,GND接地,DO接STM32的任意GPIO口(比如PB14)。接好后,用手遮挡传感器,上面的指示灯会亮灭变化,这就是它在正常工作。

2. STM32外部中断系统深度解析

STM32的外部中断系统是个很精巧的设计,它由三个主要部分组成:GPIO、EXTI和NVIC。

GPIO是我们要检测的引脚,它负责读取外部信号的电平状态。STM32的所有GPIO口都可以配置为外部中断源,但有个限制:相同编号的引脚不能同时用作中断源。比如PA0和PB0都用EXTI0,只能选其中一个。

EXTI是外部中断/事件控制器,它负责监测GPIO的电平变化。EXTI有20条中断线,前16条对应GPIO的0-15引脚,后4条用于特定外设(如RTC闹钟、USB唤醒等)。

EXTI支持四种触发方式:上升沿、下降沿、双边沿和软件触发。在对射式红外传感器应用中,我们通常选择下降沿或上升沿触发,具体取决于传感器的输出特性和我们的检测需求。

NVIC是嵌套向量中断控制器,它管理所有中断的优先级和响应。当EXTI检测到电平变化时,会向NVIC发出中断请求,NVIC根据优先级决定是否让CPU处理这个中断。

这里有个重要概念:中断嵌套。意思是高优先级的中断可以打断正在处理的低优先级中断。这在复杂的系统中很有用,但在简单的计数应用中,我们通常不需要这么复杂的配置。

3. 实战配置:GPIO与EXTI初始化

现在我们来实际配置一个对射式红外传感器的计数应用。我以STM32F103系列为例,使用PB14引脚。

首先需要开启相关时钟。EXTI和NVIC是内核外设,不需要额外开启时钟,但GPIO和AFIO需要:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

接下来配置GPIO为上拉输入模式。上拉输入的意思是引脚内部有个电阻拉到高电平,当外部没有信号时,引脚会保持高电平状态:

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

然后配置AFIO,将PB14映射到EXTI14:

GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);

这一步很关键,它告诉芯片:EXTI14线要监测的是PB14引脚,而不是其他端口的14号引脚。

最后配置EXTI参数。我们选择下降沿触发,因为对射式传感器在从无遮挡变为有遮挡时,输出会从低电平跳变到高电平,但我们实际想要检测的是从有遮挡变为无遮挡的瞬间(下降沿):

EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);

在实际项目中,我建议先用简单代码测试传感器实际输出的电平变化,再决定使用哪种触发方式,避免因理解错误导致配置不对。

4. NVIC中断优先级配置技巧

NVIC的配置相对复杂一些,因为它涉及到中断优先级的管理。STM32F103系列支持4种优先级分组方式:

  • 第0组:0位抢占优先级,4位响应优先级
  • 第1组:1位抢占优先级,3位响应优先级
  • 第2组:2位抢占优先级,2位响应优先级
  • 第3组:3位抢占优先级,1位响应优先级

我通常使用第2组,这样有4个抢占优先级和4个响应优先级,足够大多数应用使用:

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

然后配置EXTI15_10中断通道(EXTI10到EXTI15共享一个中断向量):

NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);

这里有个实用建议:对于简单的传感器计数应用,不需要设置太高的优先级。如果设置过高,可能会影响其他重要功能(如通信)的实时性。

5. 中断服务函数的编写与防抖动处理

中断服务函数是实际处理传感器信号的地方,这里有几个关键点需要注意。

首先,函数名必须完全正确。EXTI10到EXTI15的中断服务函数名为EXTI15_10_IRQHandler,这个名字是固定的,写错就无法进入中断。

在函数内部,首先要检查是否是我们的中断线触发了中断:

void EXTI15_10_IRQHandler(void)
{
    if(EXTI_GetITStatus(EXTI_Line14) == SET)
    {
        // 这里是中断处理代码
    }
}

防抖动处理是非常重要的一步。机械传感器或物理遮挡往往会产生信号抖动,导致一次遮挡触发多次中断。我在早期项目中就吃过这个亏,计数总是比实际多好几倍。

解决方法是在中断中再次检测引脚电平:

if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
{
    CountSensor_Count++;
}

这样即使因为抖动多次进入中断,只有真正为低电平时才会议计数。

最后一定要清除中断标志位,否则会连续触发中断:

EXTI_ClearITPendingBit(EXTI_Line14);

6. 测速算法与距离计算实现

有了计数功能,我们就可以实现测速和距离测量了。这对智能小车、编码器应用非常有用。

测速原理:在一定时间内统计中断次数,计算速度。比如码盘有20个缝隙,1秒钟内产生了200次中断,那么轮子转了10圈(200/20)。如果轮子周长是10厘米,那么速度就是100厘米/秒。

代码实现可以这样:

uint32_t last_time = 0;
uint32_t current_count = 0;
float speed = 0;

void calculate_speed(void)
{
    uint32_t current_time = get_system_time();  // 获取当前时间
    uint32_t count_diff = CountSensor_Get() - current_count;
    
    if(current_time - last_time >= 1000)  // 每1秒计算一次速度
    {
        speed = (count_diff / 20.0) * wheel_circumference;
        current_count = CountSensor_Get();
        last_time = current_time;
    }
}

距离计算更简单,只需要知道总中断次数和码盘缝隙数:

float distance = (CountSensor_Get() / 20.0) * wheel_circumference;

在实际项目中,我建议使用32位变量来存储计数值,因为16位变量最多只能计数65535次,对于长时间运行的应用可能不够用。

7. 常见问题排查与优化建议

在实际使用中,可能会遇到各种问题。我总结了一些常见问题及解决方法:

问题1:完全无法进入中断 检查时钟是否开启(GPIO和AFIO),检查引脚映射是否正确,检查中断线配置是否正确,检查NVIC配置是否使能。

问题2:计数不准确,多了很多 这通常是信号抖动导致的,需要添加防抖动处理。可以在中断中再次检测电平,或者添加简单的延时去抖动。

问题3:偶尔漏计数 可能是中断处理时间太长,导致错过了某些快速变化的中断。优化中断服务函数,只做最必要的操作,其他处理放到主循环中。

优化建议:

  1. 使用硬件去抖动:有些高级型号的STM32支持硬件消抖功能,可以在EXTI中配置
  2. 使用定时器输入捕获功能:对于高频信号,使用定时器的输入捕获模式可能比外部中断更准确
  3. 使用DMA:对于极高频率的信号,可以考虑使用DMA来记录计数,减轻CPU负担

我在一个工业计数项目中就遇到过高速计数的问题,最后是结合外部中断和定时器才完美解决。当信号频率超过1kHz时,单纯的外部中断就可能因为处理不过来而漏计数。

8. 进阶应用:旋转编码器与光电传感器

对射式红外传感器的原理可以扩展到其他类型的传感器,如旋转编码器和光电传感器。

旋转编码器用于测量旋转速度和方向。它通常有两路输出,产生相位差90度的正交信号。通过检测两路信号的相位关系,可以判断旋转方向。

// 在中断中判断旋转方向
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_A) == 0)
{
    if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_B) == 0)
    {
        // 正转
        encoder_count++;
    }
    else
    {
        // 反转
        encoder_count--;
    }
}

光电传感器与对射式传感器类似,但它是反射式的:发射和接收在同一侧,通过检测反射光来判断物体是否存在。这种传感器在自动门、智能家居中应用广泛。

在实际选择传感器时,要根据具体需求来决定。对射式传感器检测距离远、抗干扰能力强,但需要两边安装;反射式传感器安装简单,但受物体表面特性影响大。

我在一个智能仓储项目中就同时使用了这两种传感器:对射式用于检测传送带上是否有物体通过,反射式用于检测物体是否到位。每种传感器都有其适用场景,关键是要理解其特性并正确配置。

更多推荐