SHT43 DemoBoard 开袋即食,目前为止遇到最友善测评开发板,感觉真的不写一行代码就能完成任务,那不写代码玩点啥呢。 本文结合结合 Sensirion 开源板卡固件(sht43-demoboard-ble-firmware)把数据怎么解读这个关键问题搞清楚了,就不止得到一个摆设了。
我们来结合源码理解为什么能解、字节从哪来、公式在源码里长什么样整条数据线走一遍。
https://github.com/Sensirion/sht43-demoboard-ble-firmware
数据来源渠道
一块 SHT43 DemoBoard 上其实跑着四条互相独立的温湿度通路:
板载 LCD 直接显示
BLE 广播(空口,被动监听)—— 把原始 ticks 塞进厂自定义字段持续发
BLE GATT(需连接)—— 把换算后的 float32 温度值暴露成可读特征
MyAmbience App 数据日志(需连接)—— 后台攒帧导出
可以通过这四种方法获取数据,看你字节能有多折腾。


一、整条数据线:从传感器到空口字节
固件里数据是单向流水线,每一步都有源码实锤:
SHT4x 芯片 (I2C) │ 读 4 字节原始值: [T_hi T_lo CRC RH_hi RH_lo CRC] ▼ ExtractMeasurementValues() Sht4x.c:234 │ temperatureTicks = (data[0]<<8)|data[1] ← 高字节在前 = 大端组合成 uint16 │ humidityTicks = (data[3]<<8)|data[4] ▼ MessageBroker 发消息 (SHT4X_MESSAGE_ID_SENSOR_DATA) ▼ BleDefaultStateCb() BleContext.c:468 │ gCompleteAdvData.temperatureTicks = sensorMsg->...temperatureTicks │ gCompleteAdvData.humidityTicks = sensorMsg->...humidityTicks │ BleGap_AdvertiseRequest(...) ← 每次读到新值就重发广播 ▼ 广播 payload 里的 8 字节 (T 2B + RH 2B + 固定头 4B) ▼ 空口抓包 → 套 Sht4x 公式 → 真实温湿度
关键设计:BleContext.c:472 那行注释写得很直白——"this does not change the advertisement mode"。意思是每读到一次新传感器值,就把 ticks 原样塞进广播结构体、重新发一遍广播。设备像一个"活体信标":不等你连,它自己不停把最新读数往空口上甩。
二、广播帧逐字节拆解(packed 结构体分析)
空口抓到的一帧(完整 33 字节):
09 09 53 48 54 34 33 20 44 42 0b ff d5 06 00 06 b6 48 a1 65 ac 8d 02 0a 00
这 18 字节的厂自定义段(0b ff 之后)正好对应固件 BleTypes.h 里的一个 __attribute__((__packed__)) 结构体:
// BleTypes.h:129 字段顺序 = 空口字节序, packed 保证无对齐填充
typedef struct __attribute__((__packed__)) {
uint8_t adTypeSize; // 09
uint8_t adTypeFlag; // 09 (Complete Local Name)
uint8_t adTypeValue; // 06 (sample-type 占位)
uint8_t adTypeNameSize; // 09
uint8_t adTypeNameFlag; // 09
uint8_t name[8]; // "SHT43 DB"
uint8_t adTypeManufacturerSize;// 0b
uint8_t adTypeManufacturerFlag;// ff (Manufacturer Specific)
uint16_t companyIdentifier; // d5 06 = 0x06D5 小端 = Sensirion
uint8_t sAdvT; // 00 ← 注意: 这是 sAdvT, 不是 sampleType
uint8_t sampleType; // 06 ← 这才是 sample-type
uint8_t deviceIdMsb; // b6
uint8_t deviceIdLsb; // 48
uint16_t temperatureTicks; // a1 65 = 26017 (小端)
uint16_t humidityTicks; // ac 8d = 36268 (小端)
} BleTypes_CompleteAdvertisementData_t;两个极易混淆的字节
论坛帖把 00 06 整段写成"ad-type=0, sample-type=6",省写了——但结构体里它们是两个独立字段:
字节字段含义
| 00 | sAdvT | 广播子类型标志(advertisement sub-type) |
| 06 | sampleType | 数据格式标记(SHT4x 温湿度样本) |
两者数值一个 0 一个 6,不是一回事。BleContext.c:103-108 的初始化也分开写:.adTypeValue = 0x06(这是设备名段后面跟的值,不是 sampleType)和 .sampleType = 0x06(真正的格式标记)。读空口时别把 sAdvT 当 sampleType。
为什么 ticks 是小端
temperatureTicks 在结构体里是 uint16_t,而 STM32WB55(Cortex-M4,小端架构)往 packed 结构体写值时按小端落盘——所以空口上 a1 65 要读成 0x65a1 = 26017。这就是为什么帖子用 26017 而不是 0xa165。字节序由目标 MCU 的 ABI 决定,不是 BLE 规范规定的——这是逆向空口数据时最坑的一个点。
三、原始 ticks 怎么变成真实值
temperatureTicks/humidityTicks 是 SHT4x 芯片 I2C 输出的 14 位 ADC 原始值(0–16383),不是温度/湿度本身。换算公式是 Sensirion 官方 Datasheet 公开的标准线性公式,固件 Sht4x.c:155-165 里一行不差:
// Sht4x.c:155 单精度 float 运算 (注意 .F 后缀)
float Sht4x_TicksToTemperatureCelsius(uint16_t ticks) {
return (float)(ticks * (175.F / 65535.F)) - 45.F;
}
float Sht4x_TicksToHumidity(uint16_t ticks) {
return (float)(ticks * (125.F / 65535.F)) - 6.F;
}代入帖子那帧的 raw 值:
T = -45 + 175 × 26017 / 65535 = 24.47 ℃ RH = -6 + 125 × 36268 / 65535 = 63.18 %RH
有个小细节需要注意一下:
公式里 175.F / 65535.F 用的是 32-bit float 运算,不是整数除法。这意味着:
解算方用 float 算,结果和固件内部换算(喂给 GATT 的那份)完全一致
用 double 算会多出几个末位误差(无伤大雅,但和板端 GATT Read 对到第 2 位小数时可能差 0.01)
这就是为什么"四路对齐"里 GATT 和空口差不到 1℃ 却不会完全相等——它们经过不同数值通路(空口是 uint16 ticks 直接套 float 公式;GATT 是固件先算好 float 再按 IEEE754 小端 4 字节发出)
四、GATT 通路使用4 字节 float32
帖子另一条路——nRF Connect 连板读 Temperature 服务 0x2234 的特征 0x2235,返回 6E 88 CD 41。这不是 ticks,是固件已经换算好的 float32 温度值:
// TemperatureService.c:77 把 float 直接按 4 字节写进特征值
void TemperatureService_SetTemperature(float temperature) {
BleGatt_UpdateCharacteristic(..., (uint8_t*)&temperature, sizeof temperature);
}
// AddTemperatureCharacteristic: .maxValueLength = 4 ← 4 字节 = 1 个 float32把 6E 88 CD 41 按 IEEE 754 float32 小端 拆:
41 CD 88 6E → 0x41CD886E → 25.69 ℃
(注意 6E 88 CD 41 是小端字节序,重排成 41 CD 88 6E 才是 IEEE754 机器序。)
广播用 uint16 ticks(2 字节),GATT 用 float32(4 字节)——两条通路的"数据宽度"不同,这是 Sensirion 的取舍:广播要省 31 字节的 payload 预算,塞不下 4 字节浮点,只能压成 14-bit ticks;GATT 连接后带宽宽松,直接给换算好的高精度 float。
五、核心原理
广播是明文的。Manufacturer Specific Data(AD type 0xFF)是蓝牙规范明确留给厂商自由发挥的字段,BLE 协议本身不对它做任何加密/签名。Sensirion 把 ticks 原样塞进去,等于"裸奔"——任何 BLE 嗅探器都能偷听。
公式是公开的。ticks→物理量的换算写在 Sensirion 官方 Datasheet(HT_DS_Datasheet_SHT4x.pdf),线性公式,无密钥。
厂自定义段的结构是开源的。BleTypes.h 的 packed 结构体把每个字节的偏移、宽度、字节序全写死了,逆向后能精确对齐。
只要同时知道 ① Sensirion 的 SIG 公司 ID(0x06D5)② sample-type 标记(0x06)③ Datasheet 公式,剩下的就是"抓包 + 套公式",零固件依赖。
六、动手:自己写 30 行解码器
空口抓一帧后,按结构体偏移取字段(C 伪代码):
#include <stdint.h>
#include <stdio.h>
// 从一帧 BLE 广播里提取 0xFF 厂自定义段 (偏移见上表)
// raw: 0b ff d5 06 00 06 b6 48 a1 65 ac 8d 02
// ── ── ────── ── ── ────── ────── ────── ──
// L T company sAdvT sampleT deviceID Traw RHraw
float ticks_to_C(uint16_t t) { return (float)t * (175.f/65535.f) - 45.f; }
float ticks_to_RH(uint16_t h) { return (float)h * (125.f/65535.f) - 6.f; }
int main() {
// 按小端组 uint16: low byte first
uint16_t t_raw = 0x65a1; // a1 65 → 26017
uint16_t h_raw = 0x8dac; // ac 8d → 36268
printf("T = %.2f C\n", ticks_to_C(t_raw)); // 24.47
printf("RH = %.2f %%RH\n", ticks_to_RH(h_raw)); // 63.18
return 0;
}抓包工具:手机端 BleAnalyzer / nRF Sniffer,PC 端 BLE 嗅探器(任何能 dump 广播 payload 的)。把 0xFF 段抠出来,按上面偏移取值,套两个公式,就解完了。
七、可打包带走的经验
SHT43 DB 最值钱的不是那颗 ISO 认证的 SHT43,而是它把传感器读数做成"主动广播信标"的设计:
零成本扩展:家里摆 N 块,任何一台带 BLE 的中控(手机、树莓派、ESP32)空口监听就能聚合温湿度,不用给每块板配网络
被动可观测:不需要连接、不需要账号、不需要授权——纯空口旁路监听,适合做"读而不连"的传感网络
对照校准:同一片区域多块板互相广播,做空间温湿度场分布(哪面窗漏风、哪个角落回潮)
这也是 Sensirion 把这套固件开源的意图——给嵌入式开发者一个"BLE 传感节点"的完整参考:从 I2C 读传感器、到 MessageBroker 消息分发、到 GATT 服务 + 广播双通路暴露数据,全链路可读。
参考
固件源:github.com/Sensirion/sht43-demoboard-ble-firmware
关键文件:source/app_service/networking/ble/BleTypes.h(packed 结构体)、source/app/BleContext.c(广播填充 + 重发)、source/app_service/sensor/Sht4x.c(ticks→物理量公式、I2C 原始值解析)
GATT 服务:source/app_service/networking/ble/gatt_service/TemperatureService.c(float32 特征值)
传感器规格:Sensirion HT_DS_Datasheet_SHT4x.pdf(线性校准公式、τ63% 响应时间)

我要赚赏金
