
简介基于STM32WB55与STHS34PF80的完整人体存在检测示例工程面向嵌入式开发者和智能家居、安防场景初学者解决如何通过STM32CubeMX配置I2C、UART等外设并读取传感器状态的核心问题。资源共178个文件压缩包约1.31MB以HAL库源码c/h、寄存器驱动文件、外设初始化代码为主同时包含Keil工程配置、I2C/UART/TIM实现文件以及编译生成的hex/axf固件和ioc工程描述便于直接打开工程或对照学习配置流程。已有204人学习浏览。通过该工程可快速掌握STHS34PF80的驱动移植方法理解TMOS红外传感与菲涅尔透镜在人体存在检测中的配合方式。基于提供的源码可直接编译下载获取人体静止/移动存在状态适合作为传感器应用开发的起步模板。1. 为什么STHS34PF80能“看见”静止的人而PIR不行传统PIR红外传感器只能感受移动产生的热释电脉冲人一坐下不动输出就会归零。STHS34PF80是ST推出的一款基于TMOS热敏晶体管的人体存在传感器它把人体与环境背景的温差当作直流分量来采集依靠片内硅红外滤波器和出厂校准算法能在没有菲涅尔透镜的情况下探测4米外的标准人体。更关键的是它的状态寄存器里同时给出“是否存在”和“是否运动”两个标志适合智能面板、安防这类长时间判别场景。下面用STM32WB55跑通sths34pf80_reg.c先把存在和运动状态读出来并给出CubeMX配置与排查建议。2. 硬件连接与CubeMX配置I2C通路是第一步拿到传感器先别急着调算法。STHS34PF80对外就是一个I2C从机所有输出和配置都映射在寄存器里。只要I2C能稳定读写后面所有状态读取都只是读一个字节的事情。我使用的评估板上传感器SCL和SDA接的是STM32WB55的P0.0/P0.1对应I2C1通道VDD为3.3VVDD_IO与I2C电平一致。上拉电阻板上已经放了4.7k如果你是自己画板记得在两条线上各加4.7k到3.3V否则数据线在快速模式下容易变形。2.1 TMOS为什么能把静止人体测出来传统PIR靠的是“运动切割视场”而TMOS的原理完全不同。STHS34PF80内部是热敏晶体管阵列红外辐射让晶体管结温升高集电极电流随之改变。芯片内部用斩波放大和ADC把这种微弱热电信号转为数字量再通过算法把目标像元和背景像元的差值提取出来。因此它输出的是热辐射的绝对值变化而不是时间微分。静止的人体在探测器上形成一个稳定的“热点”只要热点尺寸和温差满足阈值presence位就一直为1。这也是它不需要菲涅尔透镜的原因透镜的调制作用不是必须的芯片本身已经有硅红外滤波器来截止可见光和近红外。如果你想把探测距离拉得比4米更远可以在传感器前方加一个聚光型菲涅尔透镜但这时视场角会收窄同时需要重新标定阈值。2.2 CubeMX关键配置项使用STM32CubeMX生成工程时我一般这样选MCU选STM32WB55RGI2C1开成I2C模式速度选100kHz先调通后续再往上拉。USART1开在115200用于打印状态。时钟直接用内部HSI16再倍频到64MHz不依赖外部晶振这样在拿到板子前就可以先写好代码。下表是我每次开局都会核对一遍的配置配置项推荐值说明I2C1 Speed ModeStandard Mode 100kHz初期排除上拉不足的干扰I2C1 Address Size7-bit传感器是7位地址USART1 Baud Rate115200输出存在状态用Power RegulatorSMPS后续低功耗会用到DebugSerial Wire保留SWD释放PA13/PA14CubeMX生成的main.c里会初始化I2C1和USART1。此时不要急着写应用代码我会先用HAL_I2C_Mem_Read读WHO_AM_I寄存器确认从机真的在线。注意HAL库的地址参数需要的是8位总线地址也就是“7位地址左移一位并拼上读写位”。下面这段代码直接放在main函数的初始化之后。uint8_t id 0x00; HAL_StatusTypeDef ret HAL_I2C_Mem_Read(hi2c1, (STHS34PF80_I2C_ADDR 1) | 1, STHS34PF80_WHO_AM_I_REG, I2C_MEMADD_SIZE_8BIT, id, 1, 1000); if (ret HAL_OK id STHS34PF80_EXPECTED_ID) { printf(I2C OK, ID0x%02X\r\n, id); } else { printf(I2C ERROR\r\n); }HAL_I2C_Mem_Read的参数从左到右是I2C句柄、带读写位的设备地址、寄存器地址、寄存器地址的位宽、接收缓冲区、长度和超时时间。STHS34PF80_I2C_ADDR我定义的是0x35不同模块可能因SA0引脚电平不同变为0x36STHS34PF80_WHO_AM_I_REG是状态识别寄存器地址宏定义在sths34pf80_reg.h里。超时时间给1000ms只是为了避免卡死实际正常通讯一个字节不足1ms。如果返回HAL_ERROR先查地址左移是不是写成了(addr 1) | 0这是最常见的错误。2.3 让sths34pf80_reg.c的回调挂到HAL库上ST官方寄存器驱动并不是直接调用HAL_I2C_Mem_Read而是通过一个上下文结构体间接调用。我是这样把HAL和驱动接起来的static int32_t sths34_i2c_write(void *handle, uint8_t reg, const uint8_t *buf, uint16_t len) { HAL_StatusTypeDef ret HAL_I2C_Mem_Write((I2C_HandleTypeDef *)handle, (STHS34PF80_I2C_ADDR 1) | 0, reg, I2C_MEMADD_SIZE_8BIT, (uint8_t *)buf, len, 1000); return ret HAL_OK ? 0 : -1; } static int32_t sths34_i2c_read(void *handle, uint8_t reg, uint8_t *buf, uint16_t len) { HAL_StatusTypeDef ret HAL_I2C_Mem_Read((I2C_HandleTypeDef *)handle, (STHS34PF80_I2C_ADDR 1) | 1, reg, I2C_MEMADD_SIZE_8BIT, buf, len, 1000); return ret HAL_OK ? 0 : -1; } sths34pf80_ctx_t ctx { .write_reg sths34_i2c_write, .read_reg sths34_i2c_read, .handle hi2c1, };这里ctx的生命周期必须比传感器驱动长因此我习惯把它定义成全局变量。读写回调中handle由ctx.handle传入在回调里再转回I2C_HandleTypeDef*。驱动所有上层函数比如sths34pf80_presence_status_get最终都会调用这两个回调访问寄存器。如果回调返回非0驱动会认为通讯失败所以回调里不要吞掉HAL错误。补充一个注意CubeMX生成的I2C初始化函数可能会把I2C的时钟设置在400kHz调试阶段最好手动把hi2c1.Init.ClockSpeed改成100000因为面包板飞线和杜邦线在400kHz下容易出现毛刺导致偶发NACK。等确认读取稳定再回到400kHz。3. sths34pf80_reg.c驱动从复位到获取存在状态STHS34PF80的驱动启动序列不算复杂但顺序不能乱。我踩过的坑是跳过复位直接读状态结果返回0xFF另一个坑是使能后立刻读presence状态还没稳定导致第一个周期全是0。下面的流程是我在项目里验证过的最小序列。3.1 完整初始化序列初始化代码如下放到MX_I2C1_Init之后主循环之前uint8_t id 0; uint8_t boot 1; sths34pf80_device_id_get(ctx, id); if (id ! STHS34PF80_EXPECTED_ID) { printf(Device ID mismatch\r\n); return -1; } sths34pf80_reset_set(ctx, PROPERTY_ENABLE); while (boot) { HAL_Delay(2); sths34pf80_boot_get(ctx, boot); } sths34pf80_data_rate_set(ctx, STHS34PF80_ODR_15Hz); sths34pf80_enable_set(ctx, PROPERTY_ENABLE); HAL_Delay(100);第一步sths34pf80_device_id_get用来确认设备ID。如果ID不对后面所有寄存器操作都可能落到其他I2C从机上。第二步sths34pf80_reset_set写1触发芯片内部软件复位紧接着读取boot位复位完成后硬件会自动把该位清零。第三步sths34pf80_data_rate_set将输出数据率设为15Hz这个频率适合人存在检测响应约67ms功耗也可接受。最后sths34pf80_enable_set才真正把传感器主链路上电。最后延时100ms是为了让内部滤波器完成至少一个周期的收敛如果复位后马上读状态presence位可能是0。3.2 读取存在和运动状态初始化完成后主循环里只需要读一个状态结构体。代码很短sths34pf80_presence_status_t status; sths34pf80_presence_status_get(ctx, status); if (status.presence) { printf(presence1 ); } else { printf(presence0 ); } if (status.motion) { printf(motion1\r\n); } else { printf(motion0\r\n); }presence和motion是sths34pf80_presence_status_t里的两个布尔字段分别对应“有热特征存在”和“热特征发生移动”。静止站在传感器前方2米时presence应该为1motion会在刚开始站定时瞬间为1然后很快回到0。如果presence一直为0而motion有变化说明人和环境的温差太小或者阈值设置得过高这一部分在第4章处理。这里强调一点status.presence不是绝对人体存在它是“有移动前景或静止前景”所以宠物走过也可能触发。应用层要做二次判断比如连续多次读取都是presence1再认为有人。我一般维护一个长度为5的滑动窗口连续3次有效才置位“有人”状态。3.3 常用驱动函数与对应寄存器行为从sths34pf80_reg.c里可以看到所有操作都被封装成函数不推荐自己往寄存器里塞位定义。下面把这些函数分成三类方便对照函数名作用使用的寄存器位sths34pf80_device_id_get读取芯片IDWHO_AM_Isths34pf80_reset_set触发软件复位SOFT_RESETsths34pf80_boot_get查询复位完成标志BOOTsths34pf80_data_rate_set设置ODRODR[2:0]sths34pf80_presence_status_get读取存在/运动状态STATUS寄存器sths34pf80_enable_set传感器主开关ENABLE表中“使用的寄存器位”取自数据手册的寄存器映射具体偏移量在头文件里都有定义。我在调试时会打印status结构体的原始字节例如把sths34pf80_presence_status_get改成直接读寄存器地址再结合数据手册查看每一位。这样做带来的好处是如果驱动版本不同字段名有差异对照原始字节反而更容易排查。不过有一点值得注意sths34pf80_presence_status_get读取的状态是锁存寄存器读取后部分中断标志会被硬件清除。如果中断同时打开了顺序应该是“先读状态寄存器再清中断使能”。否则可能出现状态标志丢失。4. 参数调优与中断输出减少误报并让CPU睡下去现在我们已经把presence/motion读出来了但直接把裸状态接到报警系统肯定会被环境搞疯。比如窗外的热风、空调出风口、暖水瓶都可能产生虚假存在信号。STHS34PF80提供了阈值、平均次数、ODR和输出中断这几组参数必须配合起来用。4.1 存在阈值与平均次数怎么配合presence_threshold决定红外信号要被放大到什么程度才判定存在取值越小越灵敏。我通常从默认值开始先调低到让静止人体刚好能触发然后在此基础上加10%余量。average_presence则是对连续多次采样进行平均相当于数字低通滤波。理论上平均次数越高输出越平滑但响应时间也会变长。下面代码把阈值设为28平均次数设为64次sths34pf80_presence_threshold_set(ctx, 28); sths34pf80_average_presence_set(ctx, STHS34PF80_AVG_PRESENCE_64);阈值28是在我的测试环境中得到的环境温度26°C人体距离2米检测区域背景是白墙。如果你的环境有更强的热源直接把阈值拉高。STHS34PF80_AVG_PRESENCE_64表示对64个内部样本取平均输出周期仍然由ODR决定只是每个输出值内部计算更慢。平均次数增大后实测静止误报从每小时十几次降到几乎为零。需要注意阈值和平均值不是一个线性关系。阈值调高15%可能就把半米外的手掌检测丢掉了而平均值从64调到128只会让响应变慢不会减少强热源误报。我的经验是先用平均值把随机抖动压下去再用阈值去处理固定温差场景。4.2 ODR、功耗和响应速度的取舍ODR内部采样频率适合场景1Hz每秒一次输出门磁联动超低功耗15Hz每67ms一次输出标准人体存在检测30Hz或更高更快运动轨迹跟踪电池供电的设备我一般用1Hz配合中断输出CPU大部分时间在STOP2模式。如果只是用来判断房间有没有人15Hz已经足够再高只是浪费电流。传感器在不同ODR下的功耗差异很大1Hz时的功耗约几个微安级别15Hz时会上升到几十微安具体数值要参考你拿到的数据手册版本。反正不要盲目上高ODR高ODR除了功耗高对I2C频繁读取的压力也大。4.3 把存在状态映射到INT引脚STM32WB55在STOP2模式下主频停振但I2C外设仍然可以被唤醒。如果让CPU一直用轮询方式读状态功耗没法降下来。STHS34PF80提供可配置的INT引脚当presence或motion发生变化时输出脉冲。配置代码如下sths34pf80_pin_int_mode_set(ctx, STHS34PF80_INT_PULSED, STHS34PF80_INT_PIN_1); sths34pf80_pin_int_set(ctx, STHS34PF80_INT_PIN_1); sths34pf80_drdy_on_int_set(ctx, PROPERTY_ENABLE);INT_PULSED指定中断信号是脉冲形适合后面用边沿触发唤醒INT_PIN_1把路由到专用中断脚。启用drdy_on_int后INT脚不仅提示数据准备好也反映状态变化。在应用层我把INT引脚配置为EXTI下降沿唤醒触发后先读状态寄存器再决定是否上报。一个常见错误是只开中断使能没配置设备的中断极性。STHS34PF80默认高电平有效上升沿触发要看INT_MODE寄存器里是否设置了反相。如果单独用EXTI下降沿容易一次也进不去。我一般先把GPIO配置成上升沿下降沿都触发在回调里打印status原始值确认中断产生条件后再裁剪。5. 现场排查I2C读不到ID、状态一直为0、静止误报最后写写我实际调这块传感器时遇到的三个典型问题每一个都卡了我不少时间。5.1 WHO_AM_I都读不出来先排除地址错误。HAL库要求传8位地址如果你的板子上传感器地址是0x35那么函数里要传0x351|1。如果传0x35I2C总线上发出的是0x6B从机不可能响应。其次检查SCL/SDA上拉电阻HAL库的GPIO内部上拉虽然存在但在400kHz快速模式下只能给个底外部4.7k才可靠。最后用逻辑分析仪抓一下START后第一个ACK没有ACK就是地址或硬件问题有ACK就查寄存器地址。5.2 有动作没存在状态如果你在传感器前挥手motion有输出但静止站立时presence始终为0第一嫌疑是阈值太高。把阈值降到10以下等待两秒看状态字。第二个嫌疑是初始化后没有等滤波器收敛我遇到过使能后马上读状态100个周期内presence显示异常加100ms延时就正常。第三个原因是传感器放在窗户边人体和环境的温差被阳光拉平了这时需要把传感器换到更靠近目标的位置再考虑加菲涅尔透镜缩小视场。5.3 presence乱跳和误报检查周围有没有瞬态热源。暖风机、空调、冰箱散热会在几秒内改变背景温度导致presence位跟随变化。处理办法是提高average_presence到128并把presence阈值对应提高。还可以在应用层对presence做“持续确认”比如连续读到5次presence1才报警连续5次presence0才解除这个简单的防抖逻辑能过滤掉大部分毛刺。最后建议在最终产品的外壳开孔处加一层透红外滤光片避免可见光和气流扰动直接吹到传感器表面否则无论参数怎么调静止误报都很难根除。本文还有配套的精品资源点击获取