用一个反射式光电传感器,模拟"闯红灯"检测——STM32 物联网实验

一、写在前面

这学期的物联网实验课上,我们拿到了一个小小的传感器模块——反射式光电传感器。老师给它安排的实验是:模拟"闯红灯"检测

听起来好像很高大上,其实原理并不复杂:把传感器埋在"停止线"的位置,当有小车模型从它面前经过(相当于车辆越过停止线),传感器检测到遮挡,主控板就触发蜂鸣器报警,LCD 上显示"有遮挡",以此模拟一辆车在红灯时闯了过去。

这篇博客记录一下我做这个实验的完整过程。


二、反射式光电传感器是怎么工作的?

这个传感器(ST118,漫反射型)内部其实就两个核心元件:

  • 红外发射管:持续向外发射红外光
  • 接收管(光敏三极管):接收被物体反射回来的红外光

工作过程是这样的:

        红外光
  发射管 ──────►  物体(小车)
      ▲              │
      │              │ 反射
      │              ▼
      └────────  接收管
  • 没有物体时:红外光射出去没有东西反射回来,接收管收不到光,输出一个电平(本模块约定为 高电平 1,表示"无遮挡")。
  • 有物体经过时:红外光被物体表面反射回来,接收管接收到光后导通,输出电平跳变(低电平 0,表示"有遮挡")。

一句话总结:靠"有没有反射光"来判断前面有没有东西。

它和"对射式"光电传感器的区别在于:对射式是发射和接收分开放在两边,靠遮挡光路来检测;而反射式是发射接收做在一起,靠反射光来检测,安装更方便,适合做"有没有东西经过"这种检测。


三、硬件连接

实验用的是 STM32开发板,主控是 STM32L431VCT6。传感器信号脚接到 PD14,配置为 GPIO 输入模式。

整体连接关系如下:

模块引脚/接口作用
反射式光电传感器 ST118PD14(GPIO 输入)检测物体遮挡,输出数字电平
TFT-LCD 彩屏SPI 接口显示实验标题和"有/无遮挡"状态
有源蜂鸣器PE8有遮挡时报警
LED1PA4每秒翻转,作为运行指示
串口 UART2115200 波特率向上位机打印传感器状态

系统拓扑大概是:传感器把电平信号送给 STM32,STM32 每 1 秒轮询一次,然后根据结果驱动 LCD、蜂鸣器、LED,并通过串口输出数据。


四、代码实现

工程用 STM32 HAL 库开发,结构很清晰,主要分三层:

  • main.c:系统初始化 + 主循环
  • st118.c:传感器驱动(GPIO 初始化 + 状态读取)
  • handle.c:业务逻辑(检测、显示、报警、串口输出)

4.1 主函数:初始化 + 死循环

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    LCD_Display_Init();            // 屏幕初始化显示
    gpio_module_open(BEEP|LEDS);   // 打开蜂鸣器、LED
    uart_open(uart2, 115200);      // 串口初始化

    ST118_Init();                  // 初始化反射式光电传感器
    while(1)
    {
        ST118_Handle();            // 检测反射式光电传感器并显示
    }
}

主函数做的事情很标准:时钟、屏幕、蜂鸣器/LED、串口、传感器依次初始化,然后在 while(1) 里反复调用 ST118_Handle() 做检测。

4.2 传感器驱动:读一个 GPIO 电平

void ST118_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    ST118_Port_Clk_Enable;              // 使能 GPIOD 时钟
    GPIO_InitStruct.Pin = ST118_Pin;    // PD14
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;   // 输入模式
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(ST118_GPIO_Port, &GPIO_InitStruct);
}

uint8_t ST118_Read_State(void)
{
    // 返回传感器状态:1=无遮挡,0=有遮挡
    return HAL_GPIO_ReadPin(ST118_GPIO_Port, ST118_Pin);
}

驱动非常简单,就是把 PD14 配成输入,然后用 HAL_GPIO_ReadPin 读电平。返回值的约定写在注释里:1 表示无遮挡,0 表示有遮挡

4.3 业务逻辑:1 秒轮询 + 多外设响应

这是整个实验的核心:

void ST118_Handle(void)
{
    uint8_t st118_state = 0;
    static uint8_t st118_state_rev = 2;
    static uint32_t tickstart;

    if((HAL_GetTick() - tickstart) >= 1000)   // 1000ms 检测一次
    {
        tickstart = HAL_GetTick();
        st118_state = ST118_Read_State();

        if(st118_state == 1)         // 有遮挡
        {
            LCD_draw_bmp(96,120,48,gImage_guangdian);
            Show_Str(96,178,BLUE,WHITE,"有遮挡",16,1);  // 显示有遮挡
            beep_open();             // 蜂鸣器报警
        }
        else if(st118_state == 0)    // 无遮挡
        {
            LCD_draw_bmp(96,120,48,gImage_guangdianhui);
            Show_Str(96,178,BLUE,WHITE,"无遮挡",16,1);  // 显示无遮挡
            beep_close();            // 关闭蜂鸣器
        }

        if(st118_state != st118_state_rev)   // 状态变化时串口打印
        {
            printf("** 读取反射式光电传感器数据\r\n");
            printf("** st118_state:%d\r\n", st118_state);
            st118_state_rev = st118_state;
        }
        led_toggle(LED1_PORT);       // LED1 翻转指示
    }
}

几个设计细节值得说一下:

  1. HAL_GetTick() 做 1 秒定时轮询,而不是 HAL_Delay() 死等。这样主循环虽然一直在跑,但只有到 1 秒才真正做一次检测,不浪费 CPU,也方便以后加别的任务。
  2. 状态变化才打印串口。用一个静态变量 st118_state_rev 记录上一次的状态,只有和上次不一样时才 printf,避免串口被刷屏。
  3. LED1 每秒翻转一次,不管传感器状态如何,作为"程序在正常运行"的心跳指示。

五、实验现象

编译下载到开发板后,上电看到的现象:

  1. 启动:LCD 显示"反射式光电检测实验(车胤)"标题和检测图标,串口打印实验提示信息。
  2. 无遮挡:传感器前方没有东西时,LCD 显示"无遮挡",蜂鸣器不响,LED1 每秒闪一下。
  3. 有遮挡(模拟闯红灯):用手或小车模型从传感器正前方划过,LCD 立刻变成"有遮挡",蜂鸣器"嘀——"地响起来,模拟检测到车辆违章通过。
  4. 移开物体:恢复"无遮挡",蜂鸣器停止。
  5. 串口输出:每次状态变化,串口会打印类似下面的内容:
     反射式光电传感器数据检测实验
------------------------------
开始实验:

** 反射式光电传感器初始化
** 读取反射式光电传感器数据
** st118_state:0

** 读取反射式光电传感器数据
** st118_state:1

整体跑下来,"传感器感知 → 主控判断 → LCD/蜂鸣器/LED 响应"这条链路是通的,闯红灯的模拟效果也出来了。


六、踩坑记录:电平极性那点事

做这个实验的时候,我踩了一个不大不小的坑,值得记一下。

传感器驱动的注释里写得很清楚:

返回值:1 = 没有物体遮挡,0 = 有物体遮挡

但业务逻辑 ST118_Handle() 里的判断是:

if(st118_state == 1)         // 有遮挡
{
    Show_Str(...,"有遮挡",...);
    beep_open();
}
else if(st118_state == 0)    // 无遮挡
{
    Show_Str(...,"无遮挡",...);
    beep_close();
}

你看,代码里 state == 1 被当成了"有遮挡",和驱动注释的约定正好相反

实际烧录后,如果发现"手挡上去显示无遮挡、拿开反而显示有遮挡",那就是电平极性反了。解决办法有两种:

  • 软件取反:在判断条件里把 10 的分支对调,或者读的时候 !ST118_Read_State()
  • 硬件确认:查模块数据手册,看这个批次的模块到底是高电平有效还是低电平有效,有些模块板载比较器的输出极性可能和注释不一致。

这个坑让我意识到:嵌入式开发里,"注释说的"和"实际测的"不一定一致,一切以实测为准。 拿到一个新模块,先用万用表或串口打一下原始电平,确认极性再写逻辑,能省不少调试时间。


七、还能怎么玩?(扩展思考)

这个实验只是最基础的"检测到遮挡就报警",如果往真实的闯红灯检测系统靠,还有很多可以扩展的方向:

  1. 联动交通信号灯:现在的代码不管红灯绿灯,只要有车过就报警。真实场景应该加一个信号灯状态,只有红灯期间检测到车辆通过才判定为闯红灯,绿灯正常通行不报警。
  2. 双传感器测速 + 方向判断:沿道路方向放两个传感器,根据车辆先后遮挡的时间差可以算车速,还能判断行驶方向,比单传感器信息丰富得多。
  3. 数据上云:开发板上带了 BC35G NB-IoT 模块,可以把违章记录(时间、车道、状态)传到云端,做远程监管和统计。
  4. 图像取证:加个摄像头,闯红灯时抓拍一张,存上时间戳,形成完整的违章证据链。
  5. 多传感器融合:反射式光电 + 地磁 + 超声波组合用,复杂环境下检测更可靠,抗干扰能力更强。

八、总结

这次实验虽然小,但把物联网最基本的一条链路完整走了一遍:

传感器感知 → 主控处理 → 执行器响应 → 数据输出

具体收获:

  • 搞懂了反射式光电传感器"发射—反射—接收"的工作原理;
  • 练熟了 STM32 HAL 库下 GPIO 输入模式的配置和 HAL_GPIO_ReadPin 读取;
  • 学会了用 HAL_GetTick() 做非阻塞定时轮询,以及"状态变化才输出"的简单去抖/降频思路;
  • 踩了电平极性的坑,记住了"以实测为准"这条嵌入式铁律。

一个几块钱的小传感器,加上几十行代码,就能模拟出一个交通违章检测的雏形——这大概就是物联网实验的乐趣吧。


Logo

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

更多推荐