先别滤波,先定义哪些数据能用
扫描应答、CRC正确、数值在可用范围、当前仍新鲜是四个不同判断。一个低噪声均值可能由断线前旧点构成,曲线好看并不表示传感器还在工作。本次同时输出raw_T/raw_RH、quality、usable、trend、count、alarm和两种年龄。
已有固定版本真实构建和共享模型的主机输入检查;本站尚无该固件烧录、SHT40读数、板上故障fixture或断线恢复记录。物理测量精度也未验证,下面过程是可执行的验收步骤。
参考:[1] SHT4x Datasheet V6.4:温度工作范围、CRC、命令、换算与RH裁边[2] Sensirion SHT4x1.1.2:measureHighPrecision与返回码[3] Sensirion Core0.7.3 receiveFrame:数据长度和每字CRC
沿用S01的具体模块与四线接法
指定Adafruit PID4885带稳压、电平转换和上拉,VIN接主控3V3,GND共地,GPIO8/SDA、GPIO9/SCL,3Vo输出端留空。模块0x44不可在软件随意改成0x45;不能把裸芯片供电范围套到这块模块上。
完全断电后逐端追线,GPIO是信号号而非排针序号。用短线、单模块和100kHz;模块已有10k上拉,初次不再并联电阻。只从板USB-to-UART供电,日志115200。
| 主控端 | 模块端 | 核对 |
|---|---|---|
| 3V3 / J1-1 | VIN | 本例3.3V,不接5V |
| GND / J1-22 | GND | 共地 |
| GPIO8 / J1-12 | SDA | 显式指定 |
| GPIO9 / J1-15 | SCL | 100kHz |
| 不接 | 3Vo | 输出端留空 |
参考:[4] Adafruit PID4885模块引脚、固定0x44与上拉[6] 官方S3 DevKitC-1 v1.1:排针与模组[7] 官方DevKitC-1 v1.1固定原理图:J1排针与GPIO端点
先编译固定驱动,再连接传感器
使用Arduino-ESP32 3.3.12、Sensirion Core0.7.3和SHT4x1.1.2,sketch.yaml固定同一组合。先安装Core再用--no-deps安装驱动,避免公共依赖静默变版;完整core安装、上传和串口命令在README。
驱动measureHighPrecision发0xFD、等待10ms后读六字节;Core receiveFrame检查每个16位数据字的CRC,长度或CRC失败返回错误。Wire.setTimeOut(20)约束I2C操作等待,不保证整个loop总共≤20ms。应用先检查error,不能拿输出变量当成功。
arduino-cli lib install 'Sensirion Core@0.7.3'
arduino-cli lib install --no-deps 'Sensirion I2C SHT4x@1.1.2'
arduino-cli compile --fqbn 'esp32:esp32:esp32s3:FlashSize=8M,PSRAM=opi,FlashMode=qio,PartitionScheme=default_8MB,CDCOnBoot=default,USBMode=hwcdc' .参考:[2] Sensirion SHT4x1.1.2:measureHighPrecision与返回码[3] Sensirion Core0.7.3 receiveFrame:数据长度和每字CRC[5] Arduino-ESP32 3.3.12 Wire:setTimeOut与requestFrom
完整程序:真实与合成来源从日志就能分清
默认SENSOR每2000ms读取一次SHT40。f进入FIXTURE并清模型,此时不调用测量;r返回真实来源并再次清模型。每轮最多读8个串口字符,不让命令处理无限占用loop。
kind=SAMPLE才表示当轮采样输入及error;kind=STATUS不是新I2C数据,error=NA,raw仍是旧输入并由raw_age_ms说明。last_good_age_ms是最后好样本年龄;无好值=-1。nan表示不可用,不是0℃。uint32_t短差值支持millis回绕,假设主循环持续运行,不承诺跨一次完整49.7天不调用仍正确计龄。
// SPDX-License-Identifier: MIT
// S08: fixed SHT40 sensor path and explicitly labelled fault fixture.
#include <Arduino.h>
#include <Wire.h>
#include <SensirionI2cSht4x.h>
#include "quality_model.h"
SensirionI2cSht4x sensor;
quality::Model model;
constexpr uint8_t SDA_PIN=8,SCL_PIN=9;
constexpr uint32_t PERIOD_MS=2000;
bool busReady=false,fixture=false;uint32_t sampled=0,sequence=0,fixtureStep=0;
quality::Flag reported=quality::Flag::NO_DATA;
bool startBus(){bool ok=Wire.begin(SDA_PIN,SCL_PIN,100000);Wire.setTimeOut(20);sensor.begin(Wire,SHT40_I2C_ADDR_44);return ok;}
void report(uint32_t now,int error,bool acquired=true){
char errorText[16]="NA";if(acquired)snprintf(errorText,sizeof errorText,"%d",error);
Serial.printf("seq=%lu uptime_ms=%lu source=%s kind=%s error=%s raw_age_ms=%lld raw_T=%.2f raw_RH=%.2f quality=%s usable=%u trend_T=%.2f trend_RH=%.2f count=%u alarm=%s last_good_age_ms=%lld\n",
(unsigned long)++sequence,(unsigned long)now,fixture?"FIXTURE":"SENSOR",acquired?"SAMPLE":"STATUS",errorText,model.seen?(long long)uint32_t(now-model.last_seen):-1LL,model.raw_t,model.raw_rh,quality::name(model.flag),model.usable,model.filtered_t,model.filtered_rh,model.count,quality::name(model.alarm),(long long)model.age(now));
reported=model.flag;
}
void setup(){Serial.begin(115200);busReady=startBus();sampled=millis();Serial.println("S08 core=3.3.12 SHT4x=1.1.2 Core=0.7.3 SDA8 SCL9 0x44; f=fixture r=real; nan trend means unavailable");}
void loop(){
for(unsigned n=0;n<8 && Serial.available();++n){int c=Serial.read();if(c=='f'||c=='r'){fixture=c=='f';model=quality::Model{};fixtureStep=0;sampled=millis();reported=quality::Flag::NO_DATA;Serial.printf("MODE_RESET source=%s; no retained filter or alarm\n",fixture?"FIXTURE":"SENSOR");}}
uint32_t now=millis();model.tick(now);if(model.flag!=reported)report(now,0,false);
if(uint32_t(now-sampled)>=PERIOD_MS){
sampled=now;float t=NAN,rh=NAN;int16_t error=0;bool acquire=true;
if(fixture){
// 2 s slots. Four skipped slots exercise >4.5 s stale without inventing a sensor value.
const float temps[]={22,22.2f,NAN,22,22,22,22,22,22,22,40,40,29,28,31};
uint32_t k=fixtureStep++%15;rh=50;t=temps[k];if(k==3)error=-1;if(k>=5&&k<=8)acquire=false;
}else{if(!busReady)busReady=startBus();if(busReady)error=sensor.measureHighPrecision(t,rh);else error=-1;}
if(acquire){model.accept(now,t,rh,error);report(now,error);}
}
delay(1);
}
参考:[2] Sensirion SHT4x1.1.2:measureHighPrecision与返回码[3] Sensirion Core0.7.3 receiveFrame:数据长度和每字CRC[5] Arduino-ESP32 3.3.12 Wire:setTimeOut与requestFrom
先注入NaN、通信错、停采样和突变
FIXTURE每两秒一个slot、15slot循环:22、22.2、NaN、通信错、22、跳过四轮采样、22恢复、40突变、40、29、28、31。跳过时loop继续tick,超过4.5秒应STALE;恢复22应count1,随后40应STEP_CHANGE且trend40,不能仍显示旧均值为有效。
所有合成行标source=FIXTURE,不能当真实传感器通信。回到SENSOR等有效数据后,若做断线实验先完全断电再断SDA、再上电观察;恢复接线也先断电。不要热拔供电线造成回灌。按实际uptime与raw/quality/age检查,不只盯曲线或slot号。
| 输入 | 当前数据与趋势 | 告警 |
|---|---|---|
| NaN/通信错 | 立即无效,清窗口,trend=nan | UNKNOWN |
| >4500ms无输入 | STALE,保留旧raw但有raw_age | UNKNOWN |
| 恢复29℃ | 新窗口,不能猜旧告警 | UNKNOWN |
| 突变40℃ | 接受40,STEP_CHANGE,trend40 | ON |
| 28℃/30℃边界 | 按有效原始值判断 | OFF / ON |
参考:[1] SHT4x Datasheet V6.4:温度工作范围、CRC、命令、换算与RH裁边[2] Sensirion SHT4x1.1.2:measureHighPrecision与返回码[3] Sensirion Core0.7.3 receiveFrame:数据长度和每字CRC
资料来源与版本说明
按固定版本官方源码与具体板级核对;实际构建、主机输入检查和实板是分开记录的状态。没有把预期步骤写成本站已运行结果。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。