ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于STM32与AMG8833的红外热成像人体感应温度报警器

基于STM32与AMG8833的红外热成像人体感应温度报警器 先交代一下背景。年初帮朋友做一款放在玄关的人体感应演示板要求就三条人走近能检测到、能显示体表温度、温度偏高时能报警。我第一反应是用MLX90614这种单点红外测温芯片但实际试下来发现一个挺尴尬的问题——MLX90614的视场角只有几度得让用户刻意凑近对准才能测准体验非常糟糕。后来换成AMG88338x8像素红外热成像阵列视场角60度人站在半米外就能在64个像素点里抓到热源体感直接上了一个台阶。这篇博文就把我这次完整的做法写下来STM32F103加AMG8833做一个简易人体感应与温度报警器涵盖CubeMX配置、I2C读取、温度矩阵换算、检测和报警逻辑以及调试时踩过的各种坑。无论你是刚想接触红外热成像传感器的新手还是想快速搭一个非接触测温演示板的工程师这篇都能给你一套能直接用起来的方案。1. 项目背景与整体思路1.1 为什么最终选了AMG8833而不是单点测温很多人一提到红外测温脑子里第一个蹦出来的就是MLX90614。这类单点红外测温芯片确实便宜、精度高但它有个致命的应用限制视场角太小。MLX90614的标准视场角是5度左右也就是说在50厘米外它只能感知到一个很小的圆形区域直径大概4厘米。拿来做耳温枪、测物体表面没问题但拿来做“走近就能感应到人体”这种场景根本不行。人稍微偏一点测到的就是墙壁温度。AMG8833是8x8像素的热成像阵列典型视场角60度在50厘米外能覆盖的区域直径超过50厘米左右基本能盖住一个人的头部或者上半身。它虽然只有64个像素分辨率低得可怜但用在“人体感应”这个场景里恰好合适。更关键的是它测的不是某个点而是整片区域的温度分布这意味着我可以在软件里做更灵活的判定比如找出最热点、计算热点面积、对比背景温度这些都是单点传感器做不到的。AMG8833还有一个比较友好的地方内部自带一个热敏电阻可以读出传感器自身的环境温度用于和环境温度做差分做人体检测时就少了一道额外采集环境温度的工作。模块版价格在几十块钱左右而且也是I2C接口直接和STM32F103对接不用额外硬件。1.2 项目功能拆解与检测逻辑这个项目的核心任务是在STM32F103上通过I2C定时读取AMG8833的64个像素温度值然后从这64个温度点里判断有没有人体热源出现以及热源的最高温度是否超过设定的报警阈值。听起来简单但落地时要把几个问题拆开第一温度数据的准确性。AMG8833的出厂精度是正负2.5度左右这个精度从绝对温度角度看并不高但用来判断“有没有热源”“热源和环境差多少”是足够用的。温度矩阵读取之后不能直接用中间要做换算、做滤波不然单个像素的跳变会带来很多误报。第二人体感应算法的设计。最简单的方式是“最高温度减去环境温度”超过差值就认为有人。但实际操作时环境温度本身也在漂移阳光直射、空调吹风等都会改变温度场所以我在算法里加了一个背景基线更新的逻辑让整个系统能自适应环境变化。第三报警逻辑需要防误报。温度报警不能读到一帧高温就立刻响否则就会出现人只是路过或者传感器偶发毛刺就疯狂报警的情况。我用了一个三帧确认的简单状态机连续多次检测到热源才触发报警这个在后文里会详细说。2. 硬件选型与接线2.1 核心元器件清单做这个项目需要准备的硬件不多基本都是手边常见的东西器件型号/规格说明主控板STM32F103C8T6最小系统板蓝色Pill板即可便宜好用红外热成像AMG8833模块8x8支持I2C板上带3.3V稳压显示屏0.96寸OLEDI2C接口SSD1306用于显示温度矩阵和最高温度蜂鸣器有源蜂鸣器模块低电平触发报警输出LED普通5mm LED加限流电阻状态指示调试工具USB转TTL串口模块打印调试信息AMG8833模块我买的是常见的那种一片PCB上直接集成了传感器、LDO稳压和I2C电平转换默认I2C地址是0x69也有的板子可以通过焊桥改成0x68。买的时候尽量选带稳压的版本这样5V供电也不会烧芯片。OLED选择I2C接口是为了和AMG8833共用一条总线省GPIO。2.2 接线方案与供电设计接线是整个项目最简单但也是最容易出问题的一步。STM32F103C8T6的I2C1在PB6SCL和PB7SDA上AMG8833和OLED都挂在同一条I2C总线上。两者地址不冲突AMG8833是0x69SSD1306一般是0x3C所以可以并联使用。具体的接线顺序AMG8833的VIN接5VGND接GNDSCL接PB6SDA接PB7。OLED的VCC接3.3V或5V看模块标注GND接GNDSCL和SDA分别并联到PB6和PB7。蜂鸣器模块的信号脚接PC13三极管驱动VCC和GND接对应电源。LED正极串一个330欧电阻后接PA1阴极接地。串口模块的RX接PA9USART1_TXTX接PA10USART1_RX方便在电脑上看打印数据。提示如果AMG8833和OLED共用一条I2C总线且引线比较长建议在SCL和SDA上各自加一个4.7k欧上拉电阻到3.3V。虽然很多模块板上已经有上拉但实际使用中总线长度超过20厘米后上拉电阻能明显减少通信毛刺。供电方面AMG8833模块如果自带稳压可以直接从STM32的5V引脚取电否则就必须接3.3V。我实际测试下来从板子的5V脚取电然后由模块上的稳压降到3.3V温度数据更稳定可能是避开板上3.3V LDO的噪声也可能是心理作用但既然模块支持5V输入我就没有额外纠结。蜂鸣器模块和LED直接用3.3V供电就行注意蜂鸣器如果是那种小有源模块3.3V也能驱动声音稍小一点但够用。整个板子焊完通电前先用万用表量一下I2C总线上SCL和SDA对地电压正常情况下应该在3.3V左右如果某个引脚电压明显偏低大概率是接线短路了。这一步花30秒能避免上电烧模块的惨剧。3. 基于STM32CubeMX的工程搭建3.1 CubeMX配置要点时钟和I2C参数我习惯用STM32CubeMX生成HAL库工程快速又不容易漏配置。在CubeMX里选好STM32F103C8T6后几个关键配置点说一下时钟树部分外部晶振HSE选择8MHz然后让PLL把系统时钟拉到72MHz。注意APB1总线时钟是36MHz因为I2C1是挂在APB1上的这个值后面配I2C时序时会用到。I2C配置部分I2C1选择Fast Mode时钟频率设为400kHz。AMG8833支持Fast Mode但有一版固件的模块在400kHz下偶发通信失败如果发现读取不稳定可以直接降回100kHz Standard Mode对10fps的帧率来说完全不影响性能。I2C地址这里不需要在CubeMX里设置后面代码里直接用HAL函数去读就行。串口USART1配置为1152008N1用于打印温度矩阵和人体验状态。OLED那块如果是I2C接口的SSD1306也是复用I2C1总线不需要额外外设如果你手里是SPI版的OLED那就需要顺手把SPI1也配置出来。GPIO部分很简单PC13配置为推挽输出驱动蜂鸣器PA1配置为推挽输出驱动LED。其他引脚默认即可。注意很多人配置I2C时直接用默认值结果读AMG8833全是0xFF。这个问题的原因多半是APB1时钟倍频系数没设对导致I2C外设时钟计算出来的波特率不准。建议在CubeMX里确认APB1是36MHz、I2C Speed Mode是Fast。3.2 I2C读取函数与温度换算数据的坑都在这AMG8833的寄存器地址从0x00到0xFF其中0x80到0xFF是64个像素的温度数据区。每个像素占2字节所以要一次性读取128字节才能拿到全部64个点的温度。这里有个非常反直觉的地方AMG8833的数据是大端模式也就是高字节在前低字节在后这和很多其他I2C传感器正好相反。实际代码里我用HAL库的Mem_Read回调来读#define AMG8833_ADDR 0x69 #define AMG8833_PIXEL_BASE 0x80 uint8_t raw_data[128]; int16_t pixel_tmp[64]; float pixel_temperature[64]; HAL_StatusTypeDef amg8833_read_pixels(void) { HAL_StatusTypeDef ret; ret HAL_I2C_Mem_Read(hi2c1, (AMG8833_ADDR 1), AMG8833_PIXEL_BASE, I2C_MEMADD_SIZE_8BIT, raw_data, 128, 200); if (ret ! HAL_OK) { return ret; } for (int i 0; i 64; i) { pixel_tmp[i] ((int16_t)raw_data[i * 2] 8) | raw_data[i * 2 1]; pixel_temperature[i] (float)pixel_tmp[i] * 0.25f / 16.0f; } return HAL_OK; }代码里的换算逻辑解释一下。AMG8833内部实际是12位带符号温度数据LSB是0.25度但寄存器里存的数被左移了4位相当于放大了16倍。所以读到int16_t值之后先乘以0.25再除以16也就是乘以0.25再除以16最终等价于直接除以64。比如你从寄存器读到一个值2000那对应温度就是2000/64约等于31.25度。如果某些像素读出来是-273度这样的明显异常值通常是像素坏点或者接触不良可以在算法里直接丢弃。正常工作时64个点的数据里偶发一个坏点不用太担心这样的小噪声我在后续滤波里会处理掉。环境温度寄存器在0x5E和0x5F同样是高字节在前换算公式一样float amg8833_read_thermistor(void) { uint8_t buf[2]; HAL_I2C_Mem_Read(hi2c1, (AMG8833_ADDR 1), 0x5E, I2C_MEMADD_SIZE_8BIT, buf, 2, 200); int16_t raw ((int16_t)buf[0] 8) | buf[1]; return (float)raw * 0.25f / 16.0f; }这个芯片自带热敏电阻返回的是传感器所在位置的温度和环境温度非常接近拿来做背景参考值非常方便省去了我单独挂一个温度传感器的步骤。3.3 温升滤波与像素平滑直接读出来的64个温度值在静止场景下也会有小幅抖动幅度大概在正负0.5度左右。如果不做任何处理单纯用最高温度去判定人来没来很容易出现那种“人明明没动报警却断断续续”的诡异情况。我在代码里做了一层简单的一阶低通滤波也就是每个像素的历史值和当前读数的加权平均float smooth_matrix[64]; #define FILTER_ALPHA 0.3f void update_smooth_matrix(void) { for (int i 0; i 64; i) { smooth_matrix[i] smooth_matrix[i] * (1.0f - FILTER_ALPHA) pixel_temperature[i] * FILTER_ALPHA; } }这个滤波的用意是让温度变化趋势更平滑。FILTER_ALPHA取0.3表示新的测量值占三成历史值占七成这样随机噪声被削掉不少但人体进入检测区域时热源信号依然能在零点几秒内反映出来不会觉得反应迟钝。如果取0.1图像会很“粘”人走过去了温度半天才掉下来如果取0.8到0.9噪声压不住。实测下来0.2到0.4之间比较舒服。4. 人体感应与温度报警的算法设计4.1 环境温度基线怎么定AMG8833读出来的64个像素如果场景里没有人温度分布基本是均匀的所有像素都接近环境温度。一旦有人出现热源所在的那个几个像素温度会明显拉高。所以判断有无人体最直接的指标就是最高温度减去环境温度。环境温度的来源有两种选择。一是直接用芯片自带热敏电阻读出的温度值二是用当前温度矩阵里所有像素的某个统计值比如最小值或者中位数。我实际对比过热敏电阻的问题是它反映的是传感器周围空气的温度和像素视野里背景物体表面的温度不完全一致而像素最小值则能反应视野里最冷物体的温度比如一面白墙在阴凉处可能会比空气温度低好几度。我的做法是两者结合初始化时采集前50帧约5秒把这些帧的每个像素做累计平均作为初始背景矩阵运行过程中如果当前帧判定为“无人”就用当前帧缓慢更新背景值。公式很简单void update_background(void) { for (int i 0; i 64; i) { background[i] background[i] * 0.8f smooth_matrix[i] * 0.2f; } }只有当无人状态时才更新有人时冻结背景这样就能避免人站在那里时间久了被“吸收”进背景里导致检测失效。4.2 热源检测与报警判定流程检测算法我用的是一个三层判定流程复杂度不高但非常稳第一步找出当前滤波后矩阵里的最高温度max_temp以及最高温度像素的坐标。第二步计算最高温度和背景矩阵中对应像素的差值delta_temp。第三步根据delta_temp和max_temp两个条件一起判断status NO_PERSON; if (delta_temp 5.0f max_temp 25.0f) { status PERSON_DETECTED; } if (max_temp ALARM_TEMP_THRESHOLD status PERSON_DETECTED) { status TEMP_ALARM; }差值的5度和绝对温度的25度这两个阈值需要根据实际安装环境和距离微调。为什么是两个条件而不是一个因为如果只比绝对温度夏天场景环境温度本来就35度手一伸出来就误报如果只比差值一个发热的充电器插头也可能触发。两个条件同时满足误报概率低很多。4.3 三帧确认与报警冷却单帧判定为有人的时候先不要急着动作因为单个像素的滤波数据偶尔也会在几帧里产生一个小的波动尖峰。我加了一个简单的计数器连续3帧大约0.3秒判定有人才真正确认同时触发报警输出。反过来也一样取消报警时不要掉一帧就立即清除而是连续5帧检测不到人之后才恢复无状态。这样做的好处是人站在检测区域边缘或者姿态稍微动一下报警状态不会闪断。报警触发后就涉及报警冷却的问题。如果报警时蜂鸣器一直响对体验伤害很大。我设了一个90秒的报警确认时间首次报警响3秒然后停止90秒内如果人员还在区域内且温度依然超过阈值再次报警一次。这个逻辑用状态机来实现代码看起来是这样typedef enum { IDLE, CONFIRMING, ALARMING, COOLING } alarm_state_t; alarm_state_t alarm_state IDLE; uint8_t confirm_cnt 0; uint32_t cool_tick 0;状态迁移如下IDLE状态检测到有人进入CONFIRMING。CONFIRMING累计到连续3帧有热源进入ALARMING否则如果中间出现无人帧就退回IDLE。ALARMING响蜂鸣器3秒后进入COOLING并记录当前tick。COOLING状态下持续检测等90秒冷却时间到如果温度依然超标再回到ALARMING否则回IDLE。这套状态机逻辑不复杂但对减少无效报警帮助很大。否则在实际演示中经常出现这种情况人站在远处犹豫了一下门还没进报警器先响三下然后人走远了又来三下非常莫名。5. 报警执行与交互输出5.1 状态输出蜂鸣器、LED和串口报警状态确认后输出环节我用三路信号蜂鸣器输出、LED指示灯和串口打印。蜂鸣器接在PC13上低电平驱动报警时拉低持续3秒蜂鸣期间GPIO输出一个250ms间隔的方波让声音听起来是“滴-滴-滴”而不是长鸣现场提示效果要好不少void alarm_execute(uint8_t on) { if (on) { if ((HAL_GetTick() / 250) % 2 0) { HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_RESET); } else { HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_SET); } } else { HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_SET); } }LED的作用是提示当前状态。无人在场景里时灭检测到有人且未超温时亮报警时闪烁。这样在现场如果蜂鸣器关了也能一眼看出系统当前的状态。串口打印是调试阶段最依赖的出口。每一帧我打印一行信息内容包括最高温度值、最高温度位置坐标、温差和环境温度printf(max%.2fC pos(%d,%d) delta%.2fC env%.2fC\r\n, max_temp, max_x, max_y, delta_temp, env_temp);运行一段时间后在电脑上打开串口助手整个检测过程一目了然热源在视野里怎么移动、温度怎么变化、什么时候被判定为有人全部都能看明白。这个信息密度排查误报问题时特别好用。5.2 OLED显示8x8温度矩阵OLED显示我是参考了很多网友的做法直接用字符拼出8行8列的矩阵每个像素显示温度值太挤了所以我用的是色块方案——把64个像素映射成一个“文本灰度图”温度高于背景一定差值的用“#”相近的用“”空白用“.”。这样在OLED上就能直观看到热源在视野里的位置和大致大小。温度色块映射函数char map_thermal(float temp, float bg) { float diff temp - bg; if (diff 8.0f) return #; if (diff 4.0f) return ; if (diff 1.0f) return -; return .; }在有OLED的情况下显示区域第一行显示最高温度和报警状态下面8行显示热像字符矩阵。我第一次在OLED上看到一个人型热源在移动的时候确实有种“终于看到了”的感觉。6. 常见问题排查与调优记录6.1 读不到数据、全部是0xFF怎么排查这是群里问得最多的问题我看到过各种千奇百怪的原因做了一个排查顺序表给你参考现象可能原因排查方法I2C读到0xFF器件地址不对确认是0x69还是0x68用I2C扫描工具测读不到设备ACKSCL/SDA接反对调两根线再看供电不足导致复位模块用了5V但板子供电不稳单独对模块供电试试数据有规律性跳变总线没有上拉或线上干扰大加4.7k上拉电阻缩短杜邦线温度值全是零模块没有正确初始化检查是否执行了复位操作重新上电其中I2C地址这个坑特别容易踩。AMG8833模块上一般会有一个AD0跳线焊盘默认悬空时地址是0x69如果模块上把AD0拉到GND那地址就变成0x68。买回来的模块不同批次可能默认状态不一样所以拿到手第一件事就是写一个I2C扫描程序把所有地址都扫一遍确认是什么地址再继续往下做。还有一个我踩过的坑I2C总线上同时挂了AMG8833和OLEDAMG8833能读到但OLED偶尔花屏。原因是OLED上电瞬间电流尖峰把I2C总线上拉电压拉低了一下后续通信时序错乱。解决办法是OLED的电源引脚和I2C引脚分开走线尽量靠近模块供电端加一个10uF电解电容或者把OLED的VCC单独接在一个空余的3.3V引脚上。6.2 温度读数异常与精度校准AMG8833出厂精度正负2.5度左右这意味着你读到的31.0度可能是实际28.5度或33.5度。还好我们这个项目是拿来做人体热源检测和超温提示的不是医疗级测温所以精度影响不大。但如果你希望绝对温度尽量准一点有两个办法一个是软件校准。把传感器和一个已校准的温度计放在一起等热平衡后记录AMG8833读数和参考温度差的偏移量然后在代码里统一加上这个偏移量。这个方法简单粗暴但在整个测量范围内偏移量可能不一致所以只能近似。另一个是环境温度校准点补偿。AMG8833的自带热敏电阻精度相对好一些可以拿它和环境温度做拟合建立像素温度和热敏电阻温度的线性修正关系。实际操作时我在7个不同温度点采样做了最小二乘拟合把斜率修到1.0附近偏差可以从2.5度改善到1度以内。注意这个项目只适合做“提示型”测温也就是告诉用户“你的温度看起来偏高建议用医用设备复查”千万不要在文章或产品描述上说可以用于医学筛查这样会有合规风险。6.3 检测距离与灵敏度调整建议AMG8833只有8x8像素视场角60度检测距离是有限制的。我实测的数据是在0.3到0.5米距离上人脸或手掌能占到4到9个像素热点非常明显在1米左右人脸部区域只剩1到2个像素还能检测到但热点位置跳动变大在2米以上基本就只剩1个像素勉强高于背景很容易被噪声淹没。所以安装这个设备的时候建议装在人脸距离传感器0.5米左右的位置比如玄关柜顶部、门禁设备的侧面。不要装在正对阳光的位置也不要让电脑主机、路由器这类持续发热的设备出现在视野里否则背景温度会拉高人体温差反而变小。灵敏度调节主要通过两个旋钮操作delta_temp阈值和确认帧数。如果发现人还没走到感应区就报警把delta_temp从5度调到8度如果发现人站在面前没反应把5度降到3度。确认帧数我一般推荐3到5帧太低容易毛燥太高反应迟钝。这个项目做完之后我自己最大的体会是8x8像素的热成像阵列虽然在热成像圈子里看起来像是玩具级别但放到人体感应这个具体场景里它刚好处于“像素够用、成本可控、开发简单”的甜点位置。你用PIR红外传感器只能知道有人没人的二值信息无法进一步判断温度你用高分辨率热像仪性能过剩不说光开发成本就高出一个量级。AMG8833恰好填补了中间的空白。最后再分享一个实际调参的小技巧。如果你在现场调试时发现灵敏度忽高忽低先别急着改阈值把串口打印打开盯着环境温度和背景矩阵看几分钟。很多时候不是算法的问题而是安装位置有一台空调或者窗户漏进来的阳光把背景温度场搞得一团糟。把安装位置稍微挪一下避开这些干扰源比在代码里调任何参数都管用。后续如果想让这个项目再进一步可以考虑用这些温度数据做简单的热源追踪或者把温度矩阵通过Arduino等上位机画成伪彩图玩法还能更多。
返回列表