ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

STM32F103与OpenMV实现无接触测温、口罩识别与门禁联动

STM32F103与OpenMV实现无接触测温、口罩识别与门禁联动

简介:一套基于STM32F103与OpenMV模组的无接触式红外体温监测与身份识别系统资料包,面向嵌入式、物联网及计算机视觉方向的开发者与课设/毕设学生,解决公共场所的非接触测温、人脸识别与口罩检测需求。包内共198个文件,约8.03MB,以Keil工程文件(uvprojx/uvoptx)、STM32标准外设库C/H源码、OpenMV端Python脚本与TensorFlow Lite神经网络模型为主,另含说明文档与调试辅助脚本。已有62人浏览/学习。STM32部分覆盖TIM、ADC、I2C、USART、CAN等外设驱动,配合GY906DCI红外测温模块实时采集体温;视觉侧集成人脸识别与口罩检测,算法涉及Adaboost与神经网络模型。系统还兼容OLE对接能力,便于融合到现有安防系统,适合希望从零复现完整功能、参考工程结构与代码实现的读者。

1. 无接触测温与身份识别:这套 STM32F103 + OpenMV 方案想解决什么问题

实验楼门口那台考勤测温一体机又坏了,返厂维修要两周,临时换成人工拿测温枪逐个测,早高峰排队能排到楼梯口。这不是个别现象,很多小型实验室、工厂车间、培训机构都卡在同一个地方:成品门禁测温一体机动辄两三千,装一台还行,多装几台预算直接超,而且封闭系统想改逻辑还得找厂家。于是有了这个经典替代方案——用一块 STM32F103 最小系统板做主控,一个 OpenMV 摄像头模组做视觉识别,一个 GY906DCI 红外测温模块做无接触测温,再配一块 OLED 屏显示结果,整套硬件成本也就是百元出头。这套系统跑起来后,人员走近,OpenMV 先检测人脸框,再判断口罩戴没戴,STM32 同时读红外测温数据,把身份结果和体温一并显示在 OLED 上,超温直接蜂鸣器报警。本文就把这套方案从选型、接线、模型部署到联调踩坑完整讲一遍,新手照着搭能跑通,熟手也能在这套经典架构里看到可复用的通信协议与标定方法。

2. 硬件选型与供电电路:STM32F103 最小系统、5V 转 3.3V 与 GY906DCI 的接线

2.1 STM32F103 最小系统的搭建要点

做这个项目之前,我先把 STM32F103 最小系统这块地基打好。很多第一次接触的朋友直接买一块成品开发板,板载 ST-Link、USB 转串口都有,省事,但如果你想把这套系统做成一个小体积的嵌入式产品,最好还是从最小系统开始。常见做法是买一块 STM32F103C8T6 蓝色药丸板,它本质上就是一块最小系统:8MHz 无源晶振、两个 22pF 负载电容、复位电路、BOOT0 下拉电阻、3.3V 稳压和一堆滤波电容。如果是自己画 PCB,注意晶振底下不要走信号线,复位电容用 100nF 加 10kΩ 电阻上拉到 3.3V,BOOT0 通过 10kΩ 电阻下拉到 GND,这样才能保证上电后从用户 Flash 启动而不是进入 Bootloader。

供电部分是这里最容易翻车的点。常见输入是 5V USB 或者 12V 适配器,我的做法是:5V 进来后先经过一个 AMS1117-3.3,输出给 STM32、OpenMV、OLED 供电;GY906DCI 则可以直接吃 5V,它在 3.3V 到 5V 都能工作,但注意它的 SCL/SDA 电平是跟随 VIN 的,如果 VIN 接 5V,那 I2C 总线的上拉电阻必须接到 5V,而 STM32 的 I2C 引脚是 5V 容忍的,这个没问题。OpenMV 模组内部有自己的电源管理,5V 输入即可,它的 UART 引脚输出是 3.3V 电平,对 STM32 来说直接接受没有压力。整机供电最忌讳的就是 USB 线又粗又长导致压降,OpenMV 在跑神经网络模型时瞬时电流能到 200mA 以上,线阻一大芯片就复位重启,这个我在 5.3 节还会专门讲。

2.2 5V 转 3.3V 电路设计:为什么 AMS1117 够用

说到 5V 转 3.3V 电路,最稳妥的方案就是 AMS1117-3.3。这个 LDO 的最大输入电压 12V,输出 3.3V,最大输出电流 1A,静态功耗低,外围只需要输入输出各接一个 10uF 电解电容和一个 100nF 陶瓷电容,再加一个 LED 指示灯就好。电路连接方式如下:5V 输入接到 AMS1117 的 VIN 引脚,GND 接地,VOUT 输出 3.3V,VOUT 与 GND 之间并联 10uF 和 100nF 电容,输入侧同样并联两个电容。注意 AMS1117 的最小压差约 1V,所以输入不能低于 4.3V,这就意味着如果你的输入是锂电池 3.7V,这个方案就行不通,得换 DC-DC 升压到 5V 再进去。

有人问为什么不直接用稳压二极管加三极管做一个简易稳压电路,我以前也这么干过,结果负载一加大输出电压直接掉到 2.8V,STM32 跑是能跑,但 USB 转串口芯片和 GY906DCI 就开始出现诡异故障,测温数据跳来跳去。原因是二极管稳压电路的内阻太大,动态响应差,根本扛不住 OpenMV 这种脉冲式负载。把 AMS1117 固定在这个项目里,后面所有 3.3V 负载都从 VOUT 取电,板上再用一个 0Ω 电阻做模拟地和数字地的单点连接,I2C 的 SCL/SDA 上拉电阻也统一接到 3.3V 或者 5V 侧,不要两端同时接,否则总线电平会在两个电压之间拉扯,出现 I2C 通信偶发超时。

2.3 GY906DCI 与 OpenMV 的物理接线和供电分配

GY906DCI 这个模块市面上有很多版本,DCI 后缀一般指的是贴片封装加 1m 引线版,传感器核心是 Melexis 的 MLX90614。模块的引脚定义很简单:VIN、GND、SCL、SDA,部分版本还引出 SDA、SCL 对应的 3.3V/5V 逻辑选择引脚,接法上我一般默认 VIN 接 5V,SCL/SDA 各接一个 4.7kΩ 上拉电阻到 5V。STM32F103C8T6 的 PB6 和 PB7 是 I2C1 的 SCL 和 SDA,PB6 接模块的 SCL,PB7 接模块的 SDA,两个引脚内部都有上拉,但内部上拉太弱不足以驱动总线,所以外部必须加上拉电阻。

下表是这套系统所有外设与 STM32 的引脚分配,我实际开过一版 PCB 后定下来就是这个接法:

外设接口类型STM32 引脚说明
GY906DCII2C1PB6 (SCL) / PB7 (SDA)地址 0x5A,外部上拉 4.7kΩ 到 5V
OpenMV M7USART2PA2 (TX) / PA3 (RX)交叉连接,GND 必须共地
OLED SSD1306软件 I2CPB8 (SCL) / PB9 (SDA)地址 0x3C,3.3V 供电
蜂鸣器GPIO 输出PA1超温拉高触发,NPN 三极管驱动
按键GPIO 输入PA0下拉电阻,用于阈值模式切换

OpenMV 的 UART 引脚是 P4(TX)和 P5(RX),与 STM32 的 PA3 和 PA2 交叉连接,即 OpenMV P4 接 STM32 PA3,OpenMV P5 接 STM32 PA2。这里有个坑:如果你的 OpenMV 是 H7 系列,它的 UART 引脚在扩展板上可能是 P1/P2,用之前先查原理图,别照搬 M7 的引脚。串口波特率我建议固定为 115200,这个速度下传输一帧 8 字节的识别结果只需要不到 0.7ms,OpenMV 的 UART 也能稳定输出,不会因为 CPU 忙于跑模型而丢字节。连线完成后用万用表测一遍所有 VCC 和 GND 之间有没有短路,再上电,这一步能避免很多后顾之忧。

3. OpenMV 端的视觉任务:Adaboost 人脸检测到神经网络口罩检测的模型迁移

3.1 Haar Cascade 与 Adaboost:OpenMV 内置人脸检测的第一步

OpenMV 里的内置人脸检测器属于传统机器学习,底层就是 Adaboost 算法训练的 Haar Cascade 级联分类器。Adaboost 的思路是把大量弱分类器(每个弱分类器只判断某个 Haar 特征是否成立,比如眼睛区域比脸颊区域暗)加权组合成一个强分类器,级联结构再一层层过滤掉非人脸区块。这个算法在 2001 年 Viola-Jones 论文里提出后统治人脸检测领域多年,到今天在 OpenMV 这种资源受限的嵌入设备上仍然有效,因为它的参数量小,运行时只需要做积分图和特征阈值判断,不涉及浮点卷积。第一次在 OpenMV 上跑通人脸检测,代码也就几行:

import sensor, image, time sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) # 320x240,人脸检测这个分辨率够用 sensor.set_vflip(True) sensor.skip_frames(30) cascade = image.HaarCascade("frontalface") # OpenMV 内置 Anaconda 型 Haar 级联文件 clock = time.clock() while True: clock.tick() img = sensor.snapshot() faces = img.find_features(cascade, threshold=0.5, scale_factor=1.5) for r in faces: img.draw_rectangle(r, color=(255, 0, 0)) print("face:", r)

find_features 返回的是包含人脸矩形框的列表,每个元素是 (x, y, w, h) 四元组。threshold 是检测阈值,0.5 是 Adaboost 级联分类器给出的置信度下限,调低到 0.3 能提升召回率但会引入误检,调高到 0.7 则漏检增多,这个参数跟光照强度关系非常大,在室内固定灯光下我一般用 0.4 到 0.5 之间。scale_factor 控制检测窗口的缩放步长,1.5 表示每轮检测窗口放大 1.5 倍,越小检测越慢但越精确。我在一个 320x240 QVGA 图上跑该检测器,OpenMV M7 大约能跑到 15 fps 左右,足够做近距离门禁场景,但如果你要求 1 米外多人同时检测,这个性能就不够用了,传统 Haar 对侧脸和遮挡也无能为力,所以我后面做口罩检测时换成了神经网络模型。

3.2 口罩检测的模型选型:卷积神经网络模型为什么比 Adaboost 更合适

戴口罩的人脸在 Haar 级联眼里是残缺的,因为嘴巴、鼻子区域的 Haar 特征被口罩掩盖了,检测置信度会掉到阈值以下,直接漏检。解决方案是引入基于卷积神经网络的检测模型。卷积神经网络模型在 OpenMV 上的落地形式主要有两种:一种是轻量级目标检测网络,比如 Tiny MobileNet SSD 或 FOMO,直接在整图上输出人脸的边界框和“戴口罩/未戴口罩”的类别;另一种是先用 Haar Cascade 找到人脸框,再把框内图像裁剪后送给一个图像分类网络判断是否有口罩,我选用后者,因为 OpenMV 的算力有限,全图做目标检测只能跑到 3-5 fps,而人脸框裁剪后的分类网络可以跑到 10 fps 以上,交互体验好很多。

分类网络的输入尺寸我设置为 96x96,三通道 RGB,输出两类:with_mask 和 without_mask。为什么选 96 而不是 128?因为 OpenMV 的 RAM 只有 256KB(H7 有 1MB),96x96x3 的输入张量在模型内部做卷积时中间层特征图不超过 12KB,SRAM 完全能装下;128x128 虽然精度略高,但模型体积和推理时间几乎翻倍,我实测在小样本数据集上准确率只提升不到 2%,不值得。模型结构采用 MobileNet v1 的深度可分离卷积模块加一个全局平均池化层和两个全连接层,参数量约 12 万。对 OpenMV 来说这个量级是安全的,推理一次 96x96 输入耗时约 80ms。

3.3 模型文件生成与部署:从数据集到 OpenMV 的落地路径

模型训练的第一步是准备数据集。我用了大约 8000 张图片,正负样本各半,主要来源是从摄像头连续采集的视频帧里自动裁剪人脸,配合公开人脸数据集的口罩变体。训练环境是 TensorFlow,训练完成后导出 TFLite 模型,注意量化的选择:OpenMV 的固件同时支持浮点和整数量化模型,但我实测整数量化模型的推理速度快了将近 40%,精度损失在 2% 以内,所以最终部署用的是 uint8 量化版本。导出后的 .tflite 文件约 480KB,放到 OpenMV 的 SD 卡根目录,然后用下面的代码加载预测:

import sensor, image, time, ml sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.skip_frames(30) net = ml.Model("mobilenet_mask.tflite") # 加载 TFLite 分类模型 labels = ["with_mask", "without_mask"] # 先用 Haar 裁人脸 cascade = image.HaarCascade("frontalface") while True: img = sensor.snapshot() faces = img.find_features(cascade, threshold=0.4) for r in faces: face_crop = img.to_rgb565().copy(r) # 裁剪人脸区域 result = net.classify(face_crop.resize(96, 96))[0] label = labels[result.max_index()] score = result.max() print("pred:", label, score) img.draw_rectangle(r) img.draw_string(r[0], r[1]-10, label + " " + str(round(score, 2)), color=(0, 255, 0))

ml.Model 在 OpenMV IDE 的固件中是加载 TFLite 文件的通用接口,返回一个网络对象。classify 方法返回的每个元素包含 output 张量,通过 max_index() 取置信度最高类别。如果你的固件版本较老,也可以换成 nn.load 加 net.predict 的写法,但新版固件已经统一到 ml.Model。部署时最容易犯的错是 SD 卡里模型文件和代码的路径不对,OpenMV 默认从当前目录查找文件,报错无法打开文件时先确认 IDE 的当前工作目录是根目录。模型推理期间,OpenMV 会暂时停止 UART 输出,这是正常的,因为 CPU 全部用于张量计算,所以设计上位机协议时一定要允许帧间隙不定长,不能假设每次串口发送间隔一致。

4. 体温读取与数据融合:MLX90614 的 I2C 协议、温度校准与 OLED 显示

4.1 STM32 通过 I2C 读取 GY906DCI 的温度数据

GY906DCI 模块的核心 MLX90614 是一个红外热电堆传感器,内部集成了 DSP 和 EEPROM。它通过 SMBus 协议(与 I2C 兼容)输出两个温度值:一个是热电堆探测到的物体表面温度,位于 RAM 地址 0x07;另一个是芯片自身的环境温度,位于 RAM 地址 0x06。我们做人体测温时读 0x07,数据格式是 16 位有符号整数,单位 0.02°C/bit,也就是说原始值乘以 0.02 就是开尔文温度,需要再减去 273.15 才得到摄氏度。下面这段代码是使用 STM32 HAL 库读取 MLX90614 的完整函数:

#define MLX90614_ADDR (0x5A << 1) // 7 位地址左移一位,I2C 需要 8 位地址 float MLX90614_ReadTemp(I2C_HandleTypeDef *hi2c) { uint8_t cmd = 0x07; // 读 RAM 地址 0x07(物体温度) uint8_t buf[3] = {0}; HAL_StatusTypeDef status; // 先写入要读取的 RAM 地址 status = HAL_I2C_Master_Transmit(hi2c, MLX90614_ADDR, &cmd, 1, 100); if (status != HAL_OK) return -273.15f; // 再读取 3 个字节:数据低字节、数据高字节、PEC 校验字节 status = HAL_I2C_Master_Receive(hi2c, MLX90614_ADDR, buf, 3, 100); if (status != HAL_OK) return -273.15f; int16_t raw = (int16_t)((buf[1] << 8) | buf[0]); float kelvin = raw * 0.02f; return kelvin - 273.15f; }

逻辑说明:HAL_I2C_Master_Transmit 发送 1 个字节的寄存器地址,之后调用 HAL_I2C_Master_Receive 读取 3 字节数据,第三字节 PEC 是循环冗余校验值,我们这里直接丢弃,不做校验,因为 I2C 通信距离短,误码率极低。参数上,超时时间设置成 100ms 足够应对总线繁忙,如果总线被其他设备占用可以适当提高到 200。需要注意的坑是 MLX90614 的地址左移:HAL 库的地址参数是 8 位格式,所以必须把 7 位地址 0x5A 左移一位变成 0xB4,否则 I2C 通信完全无响应。另外该传感器的 I2C 时钟频率最高 100kHz,在 CubeMX 里把 I2C1 的时钟速度设置为 Standard Mode 100kHz,不要用 400kHz 快速模式,我实测 400kHz 下读出的温度偶尔会出现 -270℃ 这种明显异常值。

4.2 体表温度到体温的线性校准:标定曲线的使用

MLX90614 测的是额头表面辐射温度,跟腋下体温或者耳温枪读数有系统偏差。这个偏差不是常数,在 33℃ 到 39℃ 范围内基本可以近似为线性,因此我的做法是用两点标定法求一条直线:y = kx + b,其中 x 是 GY906DCI 读到的表面温度,y 是参考体温计测出的真实体温。操作方法很简单:找两个人,一个体温正常约 36.5℃,另一个低烧约 37.8℃,用同一把医用电子测温枪同时测额头,记录两组数据,算出 k 和 b。如果没有医用测温枪,也可以用黑体辐射源做标定。

标定代码在 STM32 端就是两个宏定义的事:

#define CAL_K (1.05f) // 斜率补偿系数,由两点标定算出 #define CAL_B (0.80f) // 截距补偿,单位 ℃ float body_temp = CAL_K * surface_temp + CAL_B;

这两个参数的物理来源很直观:因为红外测温传感器接收到的辐射中混有环境反射成分,在 30℃ 环境中目标物体 36℃ 的辐射能量和黑体 35.2℃ 相当,所以斜率一般落在 1.0 到 1.1 之间,截距在 0.5 到 1.2 之间。温度校准后,还有一个重要参数是测量距离。MLX90614 的视场角是 35°,测量距离越远,传感器覆盖的皮肤面积越大,混入环境背景辐射的比例就越高,所以我系统设计时要求被测者额头与传感器距离在 3 到 5cm 之间,并在 OLED 上提示“请靠近”。如果距离太近,低于 2cm,热电堆反而会吸收额头热量的反射信号,读数偏高,这个在调试中需要反复实验确定最佳安装距离。

4.3 OLED 显示与超温告警:阈值判定与串口上报协议

OLED 我选用 0.96 寸 SSD1306,I2C 地址通常是 0x3C,用 PB8/PB9 软件模拟 I2C 驱动,这样可以避免和 GY906DCI 的硬件 I2C1 冲突。显示逻辑分三块:第一行显示“Temp: xx.x℃”,第二行显示 OpenMV 串口传过来的识别结果(戴口罩/未戴口罩 + 姓名或 ID),第三行显示系统状态,比如“请靠近”“正常”“超温”。当体温超过 37.3℃ 时,STM32 拉高 PA1 引脚驱动蜂鸣器响一声,同时把超温标记置位,OLED 屏幕上显示红色警告,这里颜色控制对单色屏来说就是反转显示模式。

身份识别结果通过串口从 OpenMV 传到 STM32,我制定了 8 字节的帧格式:帧头 0xAA、帧头 0x55、类别(0=正常,1=未戴口罩,2=超温,3=识别失败)、数据长度、姓名长度、姓名字节(最多 4 字节)、校验字节。校验用累加和——把第 1 到第 6 个字节累加取低 8 位,填到第 7 字节。STM32 端用中断接收加状态机解析,代码如下:

uint8_t rx_buf[8]; uint8_t rx_index = 0; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart == &huart2) { uint8_t d = rx_buf[rx_index]; // 从 DMA 缓冲区读到的字节 if (rx_index == 0 && d != 0xAA) { // 等待帧头1 return; } if (rx_index == 1 && d != 0x55) { // 等待帧头2 rx_index = 0; return; } rx_index++; if (rx_index >= 8) { // 校验通过后解析类别字段 uint8_t sum = 0; for (uint8_t i = 0; i < 7; i++) sum += rx_buf[i]; if (sum == rx_buf[7]) { uint8_t state = rx_buf[2]; if (state == 1) OLED_ShowString(0, 16, "No Mask!", 1); else if (state == 0) OLED_ShowString(0, 16, "Mask OK ", 1); } rx_index = 0; } } HAL_UART_Receive_IT(&huart2, &rx_buf[rx_index], 1); // 继续下一个字节 }

这段代码的要点是状态机从头开始匹配两个字节的帧头,任何一位不匹配就重置索引,保证接收同步健壮性。为什么帧头要用两个字节而不是一个?我最早只用 0xAA 做帧头,结果 OpenMV 偶尔跑模型卡顿导致串口多出一个空闲位,STM32 误把数据字节当作帧头,整个帧错位。加一个 0x55 的后缀帧头能让错帧概率降低两个数量级,代价仅仅是多一个字节的带宽。校验字段用的累加和,不是为了安全,是为了捕获传输中的单比特翻转;在无校验的情况下,1/256 的错帧概率在持续运行的设备上是无法接受的,一小时能出现 7、8 次误报。

5. 联调避坑指南:Keil MDK 工程、ST-Link 调试与常见故障排查

5.1 Keil MDK 工程搭建与 ST-Link 调试全流程

很多入行不久的朋友卡在第一步:拿到 STM32F103C8T6 不知道怎么建工程。在 CubeMX 里选芯片型号时输入 STM32F103C8,时钟配置选外部晶振 8MHz,PLL 倍频到 72MHz,然后勾选 I2C1、USART2、GPIO 输出等外设。生成的代码用 Keil MDK 打开,前提是已经下载过 STM32F1 系列的支持包,在 Keil 的 Pack Installer 里搜 STM32F1xx_DFP 安装即可,这就是常说的“stm32f103 pack 包下载”。工程搭建完成后,ST-Link 调试设置里有一个容易被忽略的选项:在 Debug 页选择 ST-Link Debugger,然后进入 Settings 的 Flash Download 页勾选 Reset and Run,否则程序下载后不会自动运行,按了复位键才跑。

我在调试中遇到最典型的故障是“No target connected”。明明 ST-Link 插在电脑上,SWD 的四根线(SWDIO、SWCLK、GND、3.3V)也接对了,就是连不上芯片。排查方法分为三步:第一步测 ST-Link 的 3.3V 输出是否正常;第二步确认目标板供电,如果目标板用 5V 供电,ST-Link 的 3.3V 可以不接,但不能让 ST-Link 和目标板的 GND 断开;第三步把 SWDIO 和 SWCLK 上的 100mil 排针重新拔插一下,接触不良的问题比芯片锁死更常见。如果还不行,按住目标板复位键,在 Keil 里点 Connect 的同时松开复位键,这个操作能救回把 SWD 引脚复用掉的板子,相当于卡在 bootloader 前的一瞬间抢回调试接口。

5.2 串口帧同步问题:OpenMV 和 STM32 的数据错乱

联调过程中最让我头疼的不是模型训练,而是 OpenMV 和 STM32 之间的串口数据错乱。现象是 OLED 上经常显示错误的口罩状态,或者干脆卡在“识别失败”。查到最后的原因有三个:第一个是波特率不一致,OpenMV 的 UART 初始化里写 115200,STM32 的 CubeMX 里设成 9600,两边差一个数量级,数据全是乱码,这属于低级错误,但对过一遍代码就能发现。第二个是共地问题,OpenMV 的 USB 供电和 STM32 的 5V 供电来自两个不同的适配器,GND 没有连在一起,结果 TX 引脚的电平参考点不同,接收端读到的波形是悬浮的。解决方法是把两个 GND 用导线直连,或者干脆用同一个 5V 适配器给整套系统供电。第三个是最隐蔽的:OpenMV 的 UART 缓冲区只有 64 字节,如果一帧数据长度超过缓冲区,且发送过程中被调度器打断,后半段字节直接丢弃,STM32 收到一半帧就校验失败。我的解决方法是把发送频率控制在模型每推理完一帧发一次,不加额外打印,并按 4.3 节的双帧头协议做同步。

如果你觉得调串口协议太麻烦,可以先用一条 USART 连接到电脑的 USB 转串口模块,分别看 OpenMV 发送的原始数据和 STM32 接收后的解析结果。我在 Keil 的 Watch 窗口里添加 rx_buf 数组,实时查看收到的字节,通过对比就能确认问题出在发送端还是接收端。还有一次遇到 OpenMV 发出来的字节每隔几个就少一个 0xAA 帧头,排查发现是 OpenMV 代码里 print 函数和 UART.write 同时使用,print 也占用串口输出,两条数据流互相穿插。把 print 注释掉后问题消失。经验就是:嵌入式系统里调试打印和业务数据一定要分开物理通道,OpenMV 的 print 走 IDE 的 USB 虚拟串口,UART.write 走物理串口,两者互不干扰。

5.3 GY906DCI 测温跳变与 OLED 白屏:三个必查点

GY906DCI 测温跳变是个经典玄学问题,表现是 OLED 上温度在 35℃ 到 40℃ 之间来回跳,一分钟能跳 0.8℃。按现象顺序排查,我的经验是三个必查点。第一查供电:MLX90614 内部有高精度 ADC,对电源纹波很敏感,如果 GY906DCI 的 VIN 和 OpenMV 共用一根长 USB 线供电,电机或者继电器动作时电压波动就会导致读数跳变。解决方法是 GY906DCI 的 VIN 不要直接从 USB 取电,而是从 AMS1117 之前接一个 LC 滤波电路,或者在模块 VIN 和 GND 之间并联一个 100uF 电解电容和一个 0.1uF 陶瓷电容。第二查距离:我把传感器装在支架上,被测者靠得太近时表面温度读数异常偏高,因为额头表面散发的热气在近距离形成了热对流影响传感探头,要求被测者站在固定距离标记线后,或者传感器凹进去 1cm 安装。第三查软件滤波:I2C 读取偶尔会受到干扰返回异常值,光靠 PEC 校验不够,我在代码里加了滑动平均滤波,取连续 5 次读数的中间 3 个值平均,每次读取间隔 50ms,这样即使出现一两次坏点也被滤掉了。

OLED 白屏则是另一类高发故障,现象是上电后屏幕亮但没有任何字符显示。必查点依次是:I2C 地址配置,SSD1306 的地址默认 0x3C,但某些国产屏是 0x3D,把地址改一下立马好;然后是复位引脚,如果 OLED 模块的 RES 引脚悬空,初始化时序会失败,我的做法是直接把 RES 接到 STM32 的一个空闲 GPIO,初始化时先拉低 10ms 再拉高;最后是 I2C 速率,SSD1306 支持到 400kHz,但如果你用软件模拟 I2C 且延时不够,或者硬件 I2C 配置时序有问题,屏幕就只有左上角出现一两个像素点的乱码。建议把软件 I2C 的延时调到 5us 以上,确保 SCL 高低电平宽度满足 2.5us 的最小要求。

6. 系统验证与调优技巧:测温标定怎么做,串口日志怎么留

整套系统跑通后,第一步验证不是直接拿真人测,而是用恒温热水袋做模拟热源。把热水袋表面温度用医用测温枪测好,放在 GY906DCI 正前方 4cm,看 10 次采集的波动范围,这个波动值应该小于 ±0.2℃。第二步再做 5 个人的真人对比标定,记录 GY906DCI 读数、测温枪读数、实际感知状态三列数据,用这些点重新拟合 4.2 节的 k 和 b 系数。表格式的标定记录我放在项目笔记里:

受测者GY906 原始温度 ℃测温枪体温 ℃误差 ℃
样本134.836.41.6
样本235.236.61.4
样本336.137.51.4
样本435.036.51.5

这组数据的误差非常稳定,用 y = 1.04x + 0.8 补偿后最大误差可以控制到 ±0.3℃ 范围内。验证完测温,再验证识别逻辑:打印 30 次口罩检测结果,统计准确率和单次检测耗时,如果准确率低于 90%,优先检查模型量化是否丢精度,以及 Haar 裁剪的人脸框是否完整包住面部。还有一个调整技巧值得提:OpenMV 的人脸框检测到后,在裁剪送入分类器之前先做一个 1.2 倍的扩展,把边缘的背景也包进来,这样分类器看到的上下文更完整,口罩边界不会被切掉,准确率能提升 3% 到 5%。

最后分享一个我的调试习惯:STM32 的 USART1 专门做调试日志,用 printf 重定向到串口助手,USART2 专门接 OpenMV,这样业务数据和调试日志各自独立。我的 send_debug 函数把当前的体温、校准后的 k/b 系数、OpenMV 帧的原始字节全部打印出来,出现任何异常时可以回看几十秒前的状态,而不是在黑匣子里瞎猜。这套系统我也前前后后改了三版,第一版卡在串口乱码,第二版卡在测温跳变,第三版才稳定运行起来。如果你也正在搭类似的方案,建议先把打印日志做成标配,再追求功能完整性。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表