
1. 为什么做这个项目AMG8833能干什么不能干什么先说结论AMG8833是一颗8x8分辨率的红外热阵列传感器价格便宜、上手快非常适合做有没有人哪里热这类粗粒度感知但别指望它拍出热像仪级别的画面。我在实际项目里把它当作一个64像素的体温雷达而不是微型热成像相机。这颗芯片的核心价值在于它不依赖可见光能在完全黑暗的环境里感知物体表面温度检测范围大概在-20°C到80°C之间具体取决于模块版本出厂校准后的精度标称在±2.5°C左右。这个精度做工业级测温当然不够但要判断面前有没有人“某个区域是不是异常发热”完全够用。做这个项目的动机很直接我想验证一个低成本人体感应方案替代红外热释电传感器PIR。PIR的问题在于只能感知动了没有对静止不动的人完全没有办法容易被热源、气流干扰。而AMG8833直接读温度场哪怕人站着不动只要体温和环境温度有差异就能稳定识别。所以本项目定下的目标是用STM32F103通过I2C驱动AMG8833周期性读取8x8共64个像素点的温度数据。基于温度场数据做一个简易人体感应算法有人靠近蜂鸣器短鸣持续报警。通过OLED实时显示温度网格直观看到热点移动。温度超过设定阈值时触发报警阈值可以按键调整。适合谁参考对I2C传感器驱动、嵌入式状态机设计、低成本人体感应方案感兴趣的朋友。哪怕你之前没碰过AMG8833只要用过STM32的HAL库跟着这篇文章走一遍基本能跑通完整流程。2. AMG8833的核心工作机制寄存器、数据格式和读取流程2.1 8x8温度阵列到底怎么读出来的AMG8833内部集成了64个热电堆传感器排列成8x8的网格。每个像素点都有一个对应的温度寄存器范围从0x80到0xFF每两个字节代表一个像素点的温度数据。实际上64个像素占128个字节读取的时候I2C地址会自增可以连续读一大块。每个像素的温度数据有意思的地方在于它不是标准的补码整数而是12位有符号数低12位有效符号位在最高位。换算公式是实际温度 寄存器值 × 0.25°C也就是说寄存器读到0x0100实际就是64.0°C读到0xFFF0实际是-4.0°C。这个0.25的分辨率决定了AMG8833能分辨的最小温差。对于人体感应场景体温和环境温差通常在好几度以上0.25°C的分辨率绰绰有余。2.2 关键寄存器不是很多但初始化顺序有讲究AMG8833的寄存器空间不大操作上最常用的是下面这几个寄存器地址作用PCTL0x00电源控制0x00休眠0x01正常模式RST0x01软件复位写0x3F复位写0x30是标志位清除FPSC0x02帧率设置默认10fps可改1fpsTTHL/TTHH0x0E/0x0F芯片内部热敏电阻温度用于环境温度参考T01L~T64H0x80~0xFF64个像素点的温度数据初始化顺序建议是先软件复位等待一段时间再设置电源模式最后配置帧率。如果顺序反了偶尔会有初始化不成功的现象。我怀疑和芯片内部的上电时序有关但官方数据手册没明确提实际调试中按这个顺序来最稳。2.3 I2C读取的实操细节地址、连续读和超时处理AMG8833的I2C地址是7位地址默认为0x68也有0x69的版本取决于模块上的地址引脚。7位地址0x68对应的8位写地址是0xD0读地址是0xD1。在STM32的HAL库里HAL_I2C_Mem_Read函数的第一个地址参数填的是7位地址左移一位后的值吗不是这里容易踩坑。HAL库的I2C地址参数跟设备地址模式有关。如果初始化时选择了I2C_ADDRESSINGMODE_7BIT那么填的地址就是7位地址本身也就是0x68。HAL库内部会自动左移。如果你习惯填0xD0反而会因为地址不匹配导致通信失败。我在这上面浪费过半天时间切记。读取温度数据推荐用连续读函数uint8_t temp_raw[128]; HAL_I2C_Mem_Read(hi2c1, AMG8833_ADDR, 0x80, I2C_MEMADD_SIZE_8BIT, temp_raw, 128, 100);一次把128个字节全部读出来再在本地解析比每像素单独读要快得多。默认帧率10fps也就是每帧间隔100ms128字节的传输在100kHz标准模式下大约需要15ms左右配合DMA可以做到不阻塞主循环。3. STM32F103侧的硬件准备与基础工程搭建3.1 接线方案和BOM清单我用的是STM32F103C8T6最小系统板也就是大家常说的蓝板。AMG8833模块用常见的Gravity兼容模块OLED是0.96寸SSD1306蜂鸣器是有源蜂鸣器按键用一个轻触开关就够了。整体物料成本大概几十块大部分元器件手边应该都有。接线表如下模块信号STM32F103引脚AMG8833VIN3.3VAMG8833GNDGNDAMG8833SCLPB6I2C1_SCLAMG8833SDAPB7I2C1_SDAAMG8833INT悬空或PA0外部中断OLEDSCLPB6与AMG8833共用I2C1OLEDSDAPB7与AMG8833共用I2C1蜂鸣器I/OPA1LED指示灯I/OPA2按键I/OPA0AMG8833模块板载了电平转换电路可以直接用3.3V供电。如果用的是纯裸芯片传感器需要自己搭上拉电阻SDA和SCL各接一个4.7kΩ电阻到3.3V。这里要特别提醒STM32F103的PB6和PB7内部已经有上拉但外部模块的走线长度和寄生电容会影响上升沿建议在I2C总线上外挂4.7kΩ上拉电阻实测能明显减少通信错误。3.2 CubeMX配置里容易被忽略的三个选项用STM32CubeMX生成工程的话配置I2C1时有几个地方需要注意I2C速度模式选择Standard Mode100kHz。AMG8833支持400kHz快速模式但和OLED共用总线时OLED对时序的容忍度差一些激进上400kHz偶尔会出现花屏或坐标错乱100kHz最稳。I2C时钟配置里Clock No Stretch Mode保持默认的Disabled。如果开启这个选项相当于告诉从机我不能拉伸时钟但AMG8833在某些状态下比如内部ADC转换期间确实需要时钟拉伸开了反而容易超时。打开I2C1的中断并在回调函数里加调试日志。很多初学者只在主循环里轮询HAL_I2C_Mem_Read的返回值一旦出现HAL_BUSY异常就死等。打开中断后配合HAL_I2C_ErrorCallback可以快速定位是ACK失败还是超时失败。sysTick中断、串口1用于日志输出PA9/PA10系统时钟用内部8MHz晶振倍频到72MHz这些基础配置就不展开了。3.3 HAL库I2C通信的坑为什么有时候读出来的数据全是一样的STM32F103的硬件I2C在HAL库下有个老问题偶尔会出现总线忙BUSY状态无法释放导致后续所有通信都超时。网上众说纷纭有人说必须用软件模拟I2C但我实测下来硬件I2C只要做好超时处理和总线恢复完全能稳定跑。我的做法是在每次I2C通信前检查总线状态while (HAL_I2C_GetState(hi2c1) ! HAL_I2C_STATE_READY) { if (HAL_I2C_DeInit(hi2c1) ! HAL_OK) { Error_Handler(); } HAL_I2C_Init(hi2c1); }同时确保每次通信的超时时间设置合理。100kHz模式下128字节连续读的耗时接近15ms我给的是100ms超时既不会因微小的时序抖动而误报超时也不会让系统卡死太久。另外一个非常隐蔽的问题是如果模块上电时序不对AMG8833可能处于一种假活状态——I2C能应答但读回来的温度数据永远是同一个值。解决方法是上电后软件复位一次然后延时50ms以上再初始化。这个我在第一版固件里没处理好导致有时候开机读到一堆恒定25.00°C的数据排查了很久才发现是模块没真正完成复位。4. 解析温度数据与人体感应算法从64个温度值到有人/没人4.1 把原始寄存器值转换成可读温度读取的128字节原始数据按顺序每两字节组成一个像素点的温度值。需要注意的是字节序AMG8833是小端存储低字节在前。转换代码如下float pixels[64]; for (int i 0; i 64; i) { uint16_t raw temp_raw[i * 2] | (temp_raw[i * 2 1] 8); int16_t signed_raw (int16_t)(raw 4) 4; // 取12位有符号数 pixels[i] signed_raw * 0.25f; }这段代码的(int16_t)(raw 4) 4是关键。因为温度值是12位有符号数符号位在第11位直接当成16位有符号数来转换会把高4位的无用位当作数据位导致负数温度计算错误。先左移4位把符号位顶到最高位再算术右移4位就得到了正确的12位有符号数值。4.2 环境温度参考热敏电阻寄存器为什么重要AMG8833内部还有一个测温热敏电阻读取的寄存器是0x0E低字节和0x0F高字节同样按照12位有符号数解析。这个值代表传感器自身的温度一般接近环境温度。它在算法里有什么用做人体感应时我不关心像素的绝对温度而是关心像素与环境的温差。因为绝对温度会受到环境温度的影响比如夏天30°C的房间里人脸可能是34°C温差只有4°C冬天18°C的房间里人脸可能是30°C温差有12°C。如果不做背景扣除直接用绝对温度阈值很难找到一个在所有季节都合适的判断值。我的处理方式是先读取热敏电阻温度作为环境温度参考然后计算每个像素相对环境温度的差值float ambient read_thermistor_temp(); float delta[64]; for (int i 0; i 64; i) { delta[i] pixels[i] - ambient; }这样处理后无论环境温度怎么变只要有人出现在传感器视野内对应的像素温差就会明显高于背景像素。这个思路和热释电传感器检测变化的原理完全不同AMG8833能捕捉静止人体靠的就是这种相对温差。4.3 人体感应算法的三个核心策略有了温差数组接下来的问题是怎么判断有人第一扣除背景。传感器视野内如果有热水杯、发热设备这类静态热源它们对应的像素温差也会很高。最简单有效的策略是维护一个背景温度图用低通滤波持续更新。当某像素的当前温度与背景温度差值大于阈值时判定为有效目标。背景更新公式background[i] background[i] * 0.9f pixels[i] * 0.1f;这个公式只对不存在目标的像素生效。实际做法是先计算当前帧所有像素的温度如果没有任何像素超过阈值就把整帧作为背景更新如果有像素超过阈值就只更新那些未超过阈值的像素。这样既能适应环境缓慢变化又不会把人体吸收进背景。第二空间聚类。人体不是一个像素而是相邻的一片像素。如果有人出现在视野边缘可能只有一个像素的温度偏高。如果要求至少2个相邻像素同时超阈值才判定有人就能过滤掉大部分噪声尖峰。简单实现是int hot_count 0; for (int i 0; i 64; i) { if (delta[i] HOT_THRESHOLD) { // 检查上下左右四个邻居是否有超阈值的 if (has_hot_neighbor(i, delta)) { hot_count; } } } if (hot_count 2) { // 判定有人 }第三时间消抖。温度数据本身有噪声单帧超阈值不能立即判定有人。我实测帧率10fps的情况下连续3帧即300ms都满足条件才触发报警能过滤掉绝大部分随机噪声。离开判定也做了消抖连续30帧3秒不满足条件才判为离开避免人稍微动一下或者呼吸时呼出的热气短暂影响判断。4.4 通讯录为什么需要用滑动平均而不是单帧裸数据如果你直接看单帧温度数据会发现即使背景静止不动每个像素的温度值也有±0.5°C左右的波动。这不全是传感器噪声和供电稳定性、模块摆放位置都有关系。我的做法是在算法前端做一个3帧滑动平均float filtered[64]; for (int i 0; i 64; i) { filtered[i] (frame_history[0][i] frame_history[1][i] frame_history[2][i]) / 3.0f; }每次新的数据帧过来把最旧的一帧丢掉加入最新的。滑动平均的作用是让温度场变得平滑减少因噪声导致的误判。代价是响应延迟增加一帧100ms对于人体感应场景完全可以接受。5. 温度报警与状态机设计怎么让设备不乱叫5.1 报警状态的三种模式切换这个项目的报警逻辑需要区分人体存在报警和温度超限报警两个场景。前者是目标温度与环境温差过大后者是某个像素绝对温度超过了设定值比如60°C。两者可以独立触发也可以同时存在。我设计了一个简单的状态机总共4个状态状态含义行为STANDBY待机OLED显示温度网格蜂鸣器关闭TRIGGERED触发鸣叫蜂鸣器响LED亮持续2秒ALARM持续报警蜂鸣器间歇响LED闪烁COOLDOWN冷却恢复蜂鸣器停保持静默5秒状态跳转逻辑如下从STANDBY如果检测到人体或温度超过阈值进入TRIGGERED。TRIGGERED 持续2秒后如果触发条件仍然满足进入ALARM如果条件消失回到STANDBY。ALARM状态下每5秒检查一次触发条件满足则继续响不满足则进入COOLDOWN。COOLDOWN结束后无论如何都回到STANDBY。状态机的好处在于它把蜂鸣器怎么叫和检测到什么人解耦了。检测逻辑只需改变触发标志状态机负责何时响、何时停代码结构清晰很多。后续要加延时报警、间歇报警甚至远程通知都只需要改状态机不需要动算法层。5.2 蜂鸣器响铃策略间歇比持续更有效人体感应报警和温度报警的响铃策略不一样。人体感应报警面向有人闯入场景用了急促的短鸣蜂鸣器响200ms、停200ms循环。温度超限报警面向设备异常过热场景用了长鸣持续响1秒、停1秒。两种声音模式通过同一个GPIO控制只是控制时序不同这比用两个蜂鸣器便宜得多。用有源蜂鸣器就别用PWM控制声音频率了有源蜂鸣器内部已经集成了振荡电路只要给它高电平就会发出固定频率的声音PWM控制反而可能产生刺耳的混响。如果是无源蜂鸣器才需要用定时器输出不同频率的方波来改变音调。5.3 人机交互设计按键调阈值、OLED显示状态加了一个按键短按切换查看环境温差最大值和查看绝对温度最大值两种显示模式长按则调整报警阈值。阈值调整的步进是1°C用OLED显示当前阈值简单直观。OLED的显示内容比普通温度计丰富一些我做了一个8x8的热力网格图每个格子根据温度相对于环境温差的颜色等级填充。有库直接支持色块填充如果没有也可以自己做简单的映射表温差0-1°C用空心格1-3°C用半实心格3°C以上用全实心格。在0.96寸屏幕上这种显示方式比纯数字直观得多一眼就能看到热点在哪里。不过OLED的刷新是个问题SSD1306的I2C写整屏数据要占用不少总线时间。加上和AMG8833共用I2C1如果每次刷新OLED都阻塞式写屏AMG8833的读取帧率会受到很大影响。我的做法是用DMA方式刷新OLED同时把OLED刷新率降到5HzAMG8833保持10Hz读取实测两边互不干扰。6. 实测数据与调试避坑记录6.1 环境温度变化下的稳定性测试我在三种场景下做了测试空调房25°C、室内自然通风30°C、户外阴凉处22°C。结果如下场景环境温度人体表面温度温差范围静态热源误报空调房25°C32~35°C6~10°C稳定室内自然通风30°C33~36°C3~6°C稳定户外阴凉处22°C28~31°C6~9°C稳定测试中发现当环境温度接近人体表面温度时比如夏天室内34°C温差只有1~3°C这时单纯用固定阈值就比较容易误判或者漏判。所以我给算法加了一个自适应阈值逻辑当环境温度超过30°C时判定阈值从3°C动态降低到2°C。虽然牺牲了一点抗噪声能力但综合考虑人体表面温度也会相应升高整体检测可靠性反而提升了。6.2 模块放在什么位置影响非常大这个看起来是常识但实际调试中很容易忽略。模块放在桌面上对着人的角度不同读出来的热点分布完全不同。传感器的视场角大约60°8x8像素均匀分布在这个视场里。如果传感器垂直朝上放人站在侧面只能扫到半边身体热点被压低容易漏检。我的建议是传感器朝向需要覆盖的区域中心略微向下倾斜15~30°。安装高度大约1.5米对应用户坐在工位上的上半身位置这样既能看到头部也能看到肩部热点范围更明确。另外传感器正前方尽量不要有金属或者玻璃等低发射率物体它们的表面温度读数偏低而且容易反射周围环境的热辐射造成读数漂移。我一开始把模块放在笔记本电脑旁边屏幕上反射的热量导致靠近屏幕一侧的像素总是偏高排查了很久才定位到是反射干扰。6.3 I2C数据偶尔错乱的排查链路项目调通后遇到过一个偶发性问题AMG8833读回来的数据帧里偶尔会有某一行像素全部为-273°C这表示寄存器值读到0x0000。不是每次都出现但一旦出现就会导致一帧数据异常。排查过程是这样的先怀疑是I2C通信超时导致的读取失败加了错误回调日志但没有捕获到任何NACK或超时。然后怀疑是DMA搬运和算法读取的竞争问题。我用DMA读取后直接放到缓冲区解析函数在另一个任务里跑。如果把DMA读取的结果复制到另一个缓冲区后再解析问题依旧。最后定位到是I2C时钟延展导致的下一个字节数据错位。AMG8833在内部ADC转换完成前访问温度寄存器会拉低SCL等待如果HAL库的读取时序在此时发生了细微偏差后续字节就会错位。解决方案是每次读取温度前先读一次状态寄存器0x00的PCTL确认芯片处于正常模式再读取温度数据。同时每次读取后把数据帧的特征值比如64个像素的和做校验如果和值超过合理范围丢弃当前帧重新读取一次。加上这两种保护后再没有出现过异常数据帧。6.4 报警器的防误报设计一个容易被忽略的细节人体感应报警器的痛点是误报。除了算法层面的消抖还有一个容易被忽略的细节报警触发后蜂鸣器的声音会让周围空气产生微弱振动如果传感器距离蜂鸣器太近这种振动会导致温度数据产生微小波动极端情况下会形成自激——蜂鸣器响导致数据抖动数据抖动导致蜂鸣器继续响。我的解决方法是物理层面的蜂鸣器与传感器保持至少20cm距离中间放一层薄的隔音棉。同时在软件上加了COOLDOWN状态触发报警后强制静默5秒。这5秒足够让数据恢复稳定也避免越响越判、越判越响的恶性循环。7. 还能往哪里扩展这个项目的下一步方向7.1 更精准的测温标定AMG8833出厂精度±2.5°C如果想提高绝对温度精度可以做一个两点标定用黑体或者精密温度计做参考分别记录低温点和高温点的偏差然后做一个线性修正。实测下来做好标定后可以把误差缩小到±1°C以内。代码里只需要在解析温度后加一个斜率修正和偏移修正pixels[i] pixels[i] * 1.03f - 0.8f;具体的斜率和偏移值需要根据你手头模块的实测数据计算。7.2 与FreeRTOS结合做任务拆分如果项目继续复杂化建议引入FreeRTOS把I2C读取、算法处理、OLED显示、报警控制拆成独立任务。I2C读取任务有最高优先级算法处理次之OLED显示最低。通过队列传递温度数据帧算法层和应用层彻底解耦。我之前在另一个项目里用这种架构同时挂AMG8833和多个传感器稳定性提升非常明显。7.3 无线报警上报在这个基础上加一个ESP8266或者HC-05蓝牙模块通过UART把报警事件和温度数据上报到手机或者局域网服务器就能变成一个简易的无线温度监控节点。报警不再局限于本地蜂鸣器而是能推送到手机App或者微信小程序。我建议后期加这个功能时重点关注断线重连机制和数据上报失败的重试策略否则无线模块一旦卡住容易拖垮整个主控的时序。7.4 多点阵列组网单颗AMG8833的视场角有限如果想监控更大空间可以把多颗传感器通过I2C多路选择器比如TCA9548A挂载到同一路I2C总线上每颗负责一个方向的监控。主控周期轮询所有传感器把多个8x8网格拼接成一个更大范围的热力图。这样做的最大难点是传感器之间的重叠区域校准不同传感器的温度响应可能有偏差需要在拼接处做插值平滑不然热力图上会出现明显的接缝。我的建议是先把这个单传感器版本跑稳定再考虑多点组网。人体感应逻辑和状态机在单点和多点之间是通用的后面的扩展更多的是系统集成的问题而不是算法问题。