STM32 CubeMax TCRT5000L光电对管巡线 原理与实现
1. 光电对管的原理
电子制作中使用的光电对管一般为TCRT5000系列,该系列分为TCRT5000和TCRT5000L,两者的区别仅在于针脚的长度不同

在每一个管中,包含一个红外光电二极管和一个光敏三极管,红外光电二极管不断向外发射红外线,红外线经外部环境反射后被光敏三极管吸收,光敏三极管导通的程度和吸收的红外线强度成正比。所以只要检测光敏三极管导通的程度就知道反射红外线材料的性质。一般颜色深的材质吸收红外线多,颜色浅的材质反射红外线多,这就是我们检测黑线的基本原理

由原理图可知,光敏三极管导通后输出的值是一个连续的模拟值,所以我们可以通过ADC读取模拟值送入单片机进行处理,这样可以得到灵敏度非常高的结果。这种做法的缺点算法复杂,如果管子数量多了也会对ADC提出高要求。所以简便起见,我们一般把这个模拟值送到一个电压比较器中,这种情况下,输出值只有0和1两种情况,单片机的负担就会小很多,也足以应付大部分场合

使用比较器的典型电路长这样(自己画的,仅供参考),使用多个光电对管排成一排对黑线进行检测是目前的主流做法


图中的三角形就是电压比较器的符号了,型号是LM393

典型的实物像这样,这是某宝上买的模块

如果要买现成的模块,最好看一下上面有没有电位器(直插的贴片的都行),有电位器的一般都可以调节比较器的阈值,可以调整探测黑线的灵敏度(上面这个就没有电位器,说不定得自己换电阻…)

这时的算法编写就比较简单,只要用GPIO读取高、低电平即可

2. CubeMax配置
以上面发的那个实物模块为例,一共有5个光电对管,我们就需要5个输入引脚。这里最好使用外部中断输入而不是GPIO输入,使用中断输入能够避免一直轮询检测,节约一些GPIO资源

所以我们需要配置5个GPIO外部中断输入引脚,在配置引脚时,千万不要配置数字相同的引脚作为中断输入,比如说PA1和PB1,因为数字相同的引脚是共用中断线的,字母靠后的引脚会覆盖字母靠前的引脚(PB会覆盖PA),字母靠前的引脚的中断就不起作用了(详见数据手册)

因为这一款模块是探测到黑线输出低,所以我们需要设置为内部上拉,上升\下降沿触发中断

最后,记得将外部中断的优先级设低一点,否则可能会出现中断冲突导致卡死,比如说在外部输入中断函数中调用HAL_Delay时,就会与Time base中断冲突导致卡死

其他基础配置不再赘述

  1. 接线
    模块与STM32的接线如下

4. 代码编写
eletube.h内容如下

#ifndef _ELETUBE_H_

#define _ELETUBE_H_



#include "stm32f1xx.h"

#include <stdio.h>



//光电对管中断输入引脚

#define ETUBE_PIN_1  GPIO_PIN_0

#define ETUBE_PIN_2  GPIO_PIN_1

#define ETUBE_PIN_3  GPIO_PIN_10

#define ETUBE_PIN_4  GPIO_PIN_11

#define ETUBE_PIN_5  GPIO_PIN_12



#define ETUBE_PORT_1 GPIOB

#define ETUBE_PORT_2 GPIOB

#define ETUBE_PORT_3 GPIOB

#define ETUBE_PORT_4 GPIOB

#define ETUBE_PORT_5 GPIOB



void Etube_Check(void);//将探测情况输出



#endif

eletube.c内容如下

#include "eletube.h"



uint8_t etubeCkeck[5] = {1,1,1,1,1};//存放每个对管探测到黑线的状态,1没探测到,0探测到



void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)//输入中断处理函数

{

    if(GPIO_Pin == ETUBE_PIN_1)//1号引脚

    {

        if(HAL_GPIO_ReadPin(ETUBE_PORT_1, ETUBE_PIN_1))//上升沿

        {

            etubeCkeck[0] = 1;

        }

        if(!HAL_GPIO_ReadPin(ETUBE_PORT_1, ETUBE_PIN_1))//下降沿

        {

            etubeCkeck[0] = 0;

        }

    }



    else if(GPIO_Pin == ETUBE_PIN_2)//2号引脚

    {

        if(HAL_GPIO_ReadPin(ETUBE_PORT_2, ETUBE_PIN_2))//上升沿

        {

            etubeCkeck[1] = 1;

        }

        if(!HAL_GPIO_ReadPin(ETUBE_PORT_2, ETUBE_PIN_2))//下降沿

        {

            etubeCkeck[1] = 0;

        }

    }



    else if(GPIO_Pin == ETUBE_PIN_3)//3号引脚

    {

        if(HAL_GPIO_ReadPin(ETUBE_PORT_3, ETUBE_PIN_3))//上升沿

        {

            etubeCkeck[2] = 1;

        }

        if(!HAL_GPIO_ReadPin(ETUBE_PORT_3, ETUBE_PIN_3))//下降沿

        {

            etubeCkeck[2] = 0;

        }

    }

    else if(GPIO_Pin == ETUBE_PIN_4)//4号引脚

    {

        if(HAL_GPIO_ReadPin(ETUBE_PORT_4, ETUBE_PIN_4))//上升沿

        {

            etubeCkeck[3] = 1;

        }

        if(!HAL_GPIO_ReadPin(ETUBE_PORT_4, ETUBE_PIN_4))//下降沿

        {

            etubeCkeck[3] = 0;

        }

    }

    else if(GPIO_Pin == ETUBE_PIN_5)//4号引脚

    {

        if(HAL_GPIO_ReadPin(ETUBE_PORT_5, ETUBE_PIN_5))//上升沿

        {

            etubeCkeck[4] = 1;

        }

        if(!HAL_GPIO_ReadPin(ETUBE_PORT_5, ETUBE_PIN_5))//下降沿

        {

            etubeCkeck[4] = 0;

        }

    }

}



void Etube_Check(void)//将探测情况输出

{
    uint8_t i = 0;

    for(i = 0;i < 5;i++)

    {

        if(etubeCkeck[i] == 0) printf("0  ");

        else if(etubeCkeck[i] == 1) printf("1  ");

        else {};



        printf("\r\n");

    }

}



while (1)
  {
    /* USER CODE END WHILE */



    /* USER CODE BEGIN 3 */

    Etube_Check();

    HAL_Delay(500);

  }

(记得自己将printf重定义)

5. 结果
输出正常,很ok