传感器 · SHT30/SHT40 · I2C 温湿度
SHT30/SHT40 温湿度传感器 STM32F103 接线与代码
让单片机知道"现在多热、多潮"。本页是 STM32F103C8T6 + SHT30/SHT40 + 0.96 OLED 的完整实机验证记录,含接线图、完整代码、源码 ZIP 和已编译固件下载,全部实测通过。
Quick Facts
- 芯片:SHT30 / SHT40(I2C 接口,数字温湿度)
- 地址:0x44(本项目实测;SHT40 部分型号是 0x45/0x46)
- 供电:3.3V,与主控共地
- 接线:SCL→PB8,SDA→PB9,共 4 根线
- 配套:STM32F103C8T6 + 0.96 OLED(0x3C)
- 实测:温度 27.4 — 28.8 °C,湿度 51% → 72%
Verified
实机验证结果
本项目实测 下面的照片全部是我们自己拍的实机画面,不是渲染图。更新于 2026-08-14。
实测温湿度变化
下表是现场观察到的大致范围,属于现象记录,不是标定数据。不同房间、季节、通风情况差别很大。
| 测试场景 | 温度 | 湿度 |
|---|---|---|
| 室温正常显示 | 约 27.4 — 28.8 °C | 约 51% |
| 对着传感器哈气 | 小幅变化 | 快速上升到约 72% |
| 松手后 | 回到室温 | 逐步回落到约 51% |
编译烧录记录
** Verified OK ** → [SUCCESS]| 项目 | 本项目实际使用 |
|---|---|
| 主控 | STM32F103C8T6(BluePill) |
| 传感器 | SHT30/SHT40 温湿度模块,实测识别为 SHT30 |
| 显示 | 0.96 寸 OLED,SSD1306,128×64,地址 0x3C |
| 开发工具 | VS Code + PlatformIO |
| 框架 | Arduino / STM32duino |
| 烧录 | ST-Link |
| 串口 | 115200 |
| 依赖库 | Adafruit SSD1306、Adafruit GFX Library |
.ino 文件同理,已标注「未实测」。代码里的 SHT40 分支没有换 SHT40 实物复测过,本次实拍全程识别为 SHT30。
What it does
这个模块干什么
SHT30 / SHT40 就是给单片机装一支"体温计 + 湿度计",专门看现在多热、多潮。对着它哈一口气,湿度数值立刻涨上去;松手之后,又慢慢降回原来的值。单片机拿到这两个数,就能自己决定要不要开风扇、除湿、报警。
它和 DHT11 这类老式温湿度的区别是:出厂就带 I2C 数字输出和 CRC 校验,读回来的每个数据都自带校验,传输错了会被挡掉,不会把一个坏数当真的显示出来。
| SHT30 / SHT40 | DHT11(对照) | |
|---|---|---|
| 接口 | I2C,两根线,可多器件共用 | 单总线,时序要求严 |
| 校验 | 每个数据带 CRC-8 校验 | 只有简单的校验和 |
| 精度 | ±0.2~0.3 °C,±2% RH 左右 | ±2 °C,±5% RH |
| 量程 | -40~125 °C,0~100% RH | 0~50 °C,20~90% RH |
| 响应 | 快,湿度 τ63% 约几秒 | 慢,湿度要十几秒 |
对照数据来自两家官方数据手册的典型值,由我们整理成表。资料参考
Wiring
接线
本项目实测 SHT30/SHT40 和 OLED 共用同一组 I2C,一共只要 4 根线。下图接法实机跑通。
| 信号 | SHT30/SHT40 | STM32F103C8T6 | OLED |
|---|---|---|---|
| 电源 | VCC | 3.3V | VCC / VDD |
| 地 | GND | GND | GND |
| 时钟 | SCL | PB8 | SCL / SCK |
| 数据 | SDA | PB9 | SDA |
| 地址选择 | ADDR | GND(模块板一般自带下拉,排针不接等效接地) | — |
VCC GND SCL SDA ADDR,有的是 GND VCC SCL SDA ADDR。同样插在第 1 脚,一个接的是 VCC,另一个接的是 GND。本项目实测的模块地址为
0x44(该模块 ADDR 自带下拉电阻,排针不接即等效接地)。
| 要点 | 说明 |
|---|---|
| 供电 3.3V | 本项目实测用 3.3V。多数 SHT30 模块板带稳压可接 5V,但和 3.3V 的 STM32 一起用时直接 3.3V 最省事 |
| 必须共地 | 所有模块 GND 接在一起,否则 I2C 会莫名失败 |
| 地址不冲突 | SHT30/SHT40 是 0x44,OLED 是 0x3C,可以共用一组 I2C。ADDR 接 3.3V 会变成 0x45 |
| PA13 / PA14 | 留给 ST-Link 调试,不要占用 |
Code
完整代码
本项目实测 下面是实际烧录进板子、拍摄视频时运行的那一份,一字未改。环境:VS Code + PlatformIO + Arduino(STM32duino)。
关键处说明
1. 把 I2C 指到 PB8 / PB9。STM32duino 默认的 I2C 引脚不是 PB8/PB9,不手动指定就读不到任何东西。
2. 发测量命令。SHT30 发 0x2C06(单次·高重复性·时钟延展),SHT40 发 0xFD(高精度)。命令是 16 位,要拆成两个字节发。SHT40 没有时钟延展,所以后面统一 delay(10),对 SHT30 是多余的但无害。
3. 读 6 字节并校验 CRC。读回来 温度2字节 + CRC + 湿度2字节 + CRC,两个 CRC 都用 Sensirion 的 CRC-8(多项式 0x31、初始值 0xFF)校验,任何一个对不上就整条丢弃。
4. 换算:温度 -45 + 175 × S/65535(两者相同);湿度 SHT30 是 100 × S/65535,SHT40 是 -6 + 125 × S/65535——SHT40 多了 -6 和 125,拿错公式湿度会整体算错。
#include <Arduino.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h>
constexpr uint8_t OLED_ADDR = 0x3C;
constexpr int SCREEN_WIDTH = 128;
constexpr int SCREEN_HEIGHT = 64;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
enum ShtType : uint8_t { SHT_NONE = 0, SHT30, SHT40 };
// Sensirion CRC-8, polynomial 0x31, init 0xFF.
static bool shtCrcOk(const uint8_t *data, uint8_t len, uint8_t crc) {
uint8_t c = 0xFF;
for (uint8_t i = 0; i < len; i++) {
c ^= data[i];
for (uint8_t b = 0; b < 8; b++) {
c = (c & 0x80) ? (uint8_t)((c << 1) ^ 0x31) : (uint8_t)(c << 1);
}
}
return c == crc;
}
// Read one temperature + humidity sample from an SHT30 or SHT40.
bool shtRead(uint8_t addr, ShtType type, float &temp, float &rh) {
// SHT30: 0x2C06 = single shot, high repeatability, clock stretching.
// SHT40: 0xFD = high precision T & RH.
uint16_t cmd = (type == SHT40) ? 0xFD : 0x2C06;
float rhOffset = (type == SHT40) ? -6.0f : 0.0f;
float rhScale = (type == SHT40) ? 125.0f : 100.0f;
Wire.beginTransmission(addr);
Wire.write((uint8_t)(cmd >> 8));
Wire.write((uint8_t)(cmd & 0xFF));
if (Wire.endTransmission() != 0) {
return false;
}
// SHT40 has no clock stretching on the measure command: wait for it.
// For SHT30 this wait is harmless (0x2C06 stretches the clock anyway).
delay(10);
uint8_t buf[6] = {0};
if (Wire.requestFrom(addr, (uint8_t)6) < 6) {
return false;
}
for (uint8_t i = 0; i < 6; i++) {
buf[i] = (uint8_t)Wire.read();
}
// 6 bytes: T_MSB, T_LSB, T_CRC, RH_MSB, RH_LSB, RH_CRC.
if (!shtCrcOk(buf, 2, buf[2]) || !shtCrcOk(buf + 3, 2, buf[5])) {
return false;
}
uint16_t st = (uint16_t)((buf[0] << 8) | buf[1]);
uint16_t sr = (uint16_t)((buf[3] << 8) | buf[4]);
temp = -45.0f + 175.0f * (float)st / 65535.0f;
rh = rhOffset + rhScale * (float)sr / 65535.0f;
return true;
}
void showStatus(const char *line1, const char *line2) {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("02_SHT30_SHT40");
display.println();
display.println(line1);
display.println(line2);
display.display();
}
void showReadout(const char *name, uint8_t addr, float temp, float rh) {
char buf[24];
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.print("02_SHT30_SHT40");
display.setCursor(0, 16);
snprintf(buf, sizeof(buf), "%s @0x%02X", name, addr);
display.print(buf);
display.setCursor(0, 32);
snprintf(buf, sizeof(buf), "T: %.1f C", temp);
display.print(buf);
display.setCursor(0, 48);
snprintf(buf, sizeof(buf), "RH: %.1f %%", rh);
display.print(buf);
display.display();
}
uint8_t g_shtAddr = 0;
ShtType g_shtType = SHT_NONE;
void detectSht() {
float t = 0.0f, rh = 0.0f;
// 0x44/0x45 是 SHT30 的 ADDR 引脚两种接法;0x46 是 SHT40-BD1B 出厂固定地址,
// 漏掉 0x46 会让那个型号报"找不到传感器",被误判成坏件
for (uint8_t addr : {0x44, 0x45, 0x46}) {
if (shtRead(addr, SHT40, t, rh)) {
g_shtAddr = addr;
g_shtType = SHT40;
return;
}
if (shtRead(addr, SHT30, t, rh)) {
g_shtAddr = addr;
g_shtType = SHT30;
return;
}
}
g_shtAddr = 0;
g_shtType = SHT_NONE;
}
void setup() {
Serial.begin(115200);
delay(300);
Wire.setSCL(PB8);
Wire.setSDA(PB9);
Wire.begin();
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println("OLED init failed");
while (true) {
delay(1000);
}
}
showStatus("OLED OK", "SHT detect...");
Serial.println("OLED OK");
detectSht();
}
void loop() {
if (g_shtType == SHT_NONE) {
detectSht();
}
if (g_shtType == SHT_NONE) {
showStatus("SHT not found", "Check wiring");
Serial.println("SHT30/SHT40 not found");
} else {
float temp = 0.0f, rh = 0.0f;
const char *name = (g_shtType == SHT40) ? "SHT40" : "SHT30";
if (shtRead(g_shtAddr, g_shtType, temp, rh)) {
char buf[48];
snprintf(buf, sizeof(buf), "%s T=%.2f C RH=%.2f %%", name, temp, rh);
showReadout(name, g_shtAddr, temp, rh);
Serial.println(buf);
} else {
showStatus("Read failed", "Check wiring");
Serial.println("SHT read failed");
}
}
delay(1000);
}
库依赖:Adafruit SSD1306 和 Adafruit GFX Library,用 PlatformIO 打开工程会按 platformio.ini 自动下载,不用手动安装。
必须保留的编译开关:platformio.ini 里有一行 -Wl,-u,_printf_float。STM32duino 默认的精简版 newlib 不支持浮点格式化,去掉这行 OLED 上 T: / RH: 后面会空白。详见下方「常见问题排查」。
Download
下载
下面三个文件都可以直接点击下载。推荐用第一个 ZIP,解压后 VS Code 打开就能 Build。
bin 怎么烧进去
用下面任一方式烧录 bin:
| 方式 | 工具 | 说明 |
|---|---|---|
| ST-Link(本项目用的) | STM32CubeProgrammer | 选 ST-LINK,起始地址填 0x08000000,选 bin 文件后 Download |
| ST-Link 命令行 | st-flash | st-flash write firmware.bin 0x08000000 |
| USB 转串口 | STM32CubeProgrammer / FlyMcu | BOOT0 拉高复位进 ISP 模式,走串口下载 |
pio device list,找 CP2102 / CP210x / CH340 对应的那个。
第一次使用
- 按上面接线图接好 4 根线:3.3V、GND、PB8、PB9
- 下载 ZIP,解压,用 VS Code + PlatformIO 打开这个文件夹
- 等依赖库自动装完,点 Build,看到 SUCCESS
- 接上 ST-Link,点 Upload,看到
** Verified OK ** - 看 OLED:显示温度湿度。对着传感器哈一口气,湿度明显上升,松手慢慢回落
Demo
视频演示
本项目实测 约 46 秒,全部是实机画面,没有演示动画。
| 时间 | 内容 |
|---|---|
| 0:00 | 实机结果,OLED 正常显示,湿度约 51% |
| 0:13 | 对着传感器哈气,湿度快速上升 |
| 0:16 | 湿度峰值区间,最高约 72% |
| 0:23 | 松手后湿度逐步回落 |
| 0:35 | 回到正常显示,收尾 |
Reference
实用参数与资料
资料参考 这一节来自芯片数据手册和公开资料,不是我们实测的结论。只留会影响你怎么接线、选型、写代码的内容。
SHT30 和 SHT40 到底差在哪
两个都是 Sensirion 的数字温湿度传感器,但不是简单换代,命令集和公式都不一样,所以代码里要写两个分支。
| 项目 | SHT30 | SHT40 | 来源 |
|---|---|---|---|
| 测量命令 | 0x2C06(单次·高重复性·时钟延展) | 0xFD(高精度 T&RH) | 数据手册 |
| 典型精度 | ±0.3 °C / ±2 %RH | ±0.2 °C / ±1.8 %RH | 数据手册 |
| 供电 | 2.4 — 5.5 V | 1.08 — 3.6 V | 数据手册 |
| 湿度换算 | RH = 100 × S/65535 | RH = -6 + 125 × S/65535 | 数据手册 |
| 测量等待 | 时钟延展,主控等 ACK | 无时钟延展,主控要 delay | 数据手册 |
最需要提前知道的一条:自加热
delay(1000),正好一秒一次,落在安全范围内。如果你要加快刷新,别低于一秒。
安装位置对温度的影响
| 问题 | 说明 |
|---|---|
| 离热源远一点 | MCU、稳压芯片、OLED 屏都会发热,传感器贴太近会被"烘热",温度读数偏高 |
| 进外壳要通风 | 密封外壳把板子热量闷住,累积起来温度就飘,装壳时给传感器位置留透气孔 |
| 避开结露 | 长时间泡在 100% 结露或有机挥发物环境里,湿度会慢慢漂移 |
以上为温湿度传感器安装的通用工程实践,本项目在面包板上裸测,没有做过外壳实验。资料参考
响应时间和湿度迟滞
湿度不是瞬间跟上的。数据手册给 SHT30 的湿度响应时间 τ63% 约 8 秒——环境湿度突变后,读数要花几秒才跟到新值的 63%,完全稳定更慢。
基本参数
| 参数 | SHT30 | SHT40 | 来源 |
|---|---|---|---|
| 供电范围 | 2.4 — 5.5 V | 1.08 — 3.6 V | 数据手册 |
| 温度量程 | -40 — +125 °C | -40 — +125 °C | 数据手册 |
| 湿度量程 | 0 — 100 %RH | 0 — 100 %RH | 数据手册 |
| I2C 地址 | 0x44 / 0x45由 ADDR 引脚电平决定 | 0x44 / 0x45 / 0x46由订购型号决定,见下 | 数据手册 |
| CRC 校验 | CRC-8,多项式 0x31,初始值 0xFF | 数据手册 | |
地址怎么来的:SHT30 和 SHT40 不是一回事
这两颗芯片决定地址的方式完全不同,混着记就会扫不到。
- SHT30:地址由 ADDR 引脚电平决定——接 GND 是
0x44,接 VDD 是0x45。接线时能改。 - SHT40:没有 ADDR 引脚,地址在出厂订购型号里就定死了,接线改不了。数据手册的型号编码表列了三种:
…1结尾 =0x44、C=0x45、B=0x46。常见的 SHT40-AD1B 是0x44,但 SHT40-BD1B 是0x46。
0x46 不是坏件。如果客户手里是 SHT40-BD1B,I2C Scanner 扫出来就是 0x46,这是正常的。很多第三方库和例程只写死 0x44,就会报"找不到传感器",很容易被误判成模块损坏。本页代码已经把 0x44 / 0x45 / 0x46 三个地址都扫一遍。
本项目实测的模块是 0x44。
ADDR 引脚不要悬空
SHT30 的 ADDR 引脚不能悬空——Sensirion SHT3x 数据手册原文写得很明确:"The ADDR pin must not be left floating." 悬空时电平不确定,可能出现时好时坏、扫得到但读数出错的现象。
实际用模块板一般不用担心:绝大多数 SHT30 模块板在 ADDR 上已经焊了下拉电阻,所以排针上"不接"等效于接地,地址就是 0x44——本项目实测的模块就是这样。但这是模块板给的保障,不是芯片本身允许悬空。自己画板或直接用裸芯片时,ADDR 必须明确接 GND 或 VDD。
读数据为什么要做 CRC 校验
每次读回来 6 个字节,不是简单的"温度 2 字节 + 湿度 2 字节",中间夹着两个 CRC 校验字节:温度高8位 · 温度低8位 · 温度CRC · 湿度高8位 · 湿度低8位 · 湿度CRC。
I2C 只有两根线,走线长了、干扰大了就可能传错位。廉价传感器一般不校验,错了就错了;Sensirion 器件自带 CRC,读错的数据会被挡掉,保证显示的是可信值。这也是我们用 CRC 判断"传感器回没回对数据"、进而识别型号的依据。
Troubleshooting
常见问题排查
下面是根据手册工作原理整理的常见原因。标注「实测」的是我们自己遇到过的,其余是资料推断。
OLED 显示 SHT not found,扫描不到传感器
按这个顺序查:
- 先看有没有共地。SHT30/SHT40、OLED、STM32 的 GND 必须接在一起,只接电源正极不接地,I2C 一定不通。
- 确认 SCL 接 PB8、SDA 接 PB9。这两根接反是最常见的错误,程序不会报错,就是读不到。
- 看模块丝印,不要照着照片数孔位。不同批次模块的引脚顺序不一样,插反了供电就错了。
- 确认地址。SHT30 的 ADDR 脚接到 3.3V 地址会变成
0x45;SHT40 的地址是出厂型号定死的,SHT40-BD1B 就是0x46,接线改不了。本页代码会把 0x44 / 0x45 / 0x46 都试一遍,但很多第三方库只写死 0x44,就会报找不到——这不是模块坏了。先用 I2C Scanner 扫一下,扫到几就用几。 - 确认供电是 3.3V。本项目实测用 3.3V。
OLED 上温度和湿度显示空白,只有标题和型号正常
实测 这是我们真实踩过的坑。现象很典型:标题、型号这些纯文字正常,唯独温度和湿度两行是空的。
原因:STM32duino 默认用精简版 newlib,printf/snprintf 的浮点功能默认没链接,%.1f 输出空串。
解决:platformio.ini 里必须保留这一行:
build_flags =
-Wl,-u,_printf_float
去掉这行,浮点格式化就不起作用。加上后固件大约多占 12KB Flash,完全在容量内。
温度读数偏高
先看是不是自加热。连续高频率测量会让芯片自己发热,读数偏高。把测量间隔放到 1 秒以上再看。
再看安装位置。传感器离 MCU、稳压芯片、OLED 太近会被烘热。把它挪远一点、留点通风,读数通常会降下来。
湿度一直不动,或者哈气也没反应
先确认读数在变。湿度响应慢(τ63% 约 8 秒),哈气后要等一两秒才升,松手后慢慢回落。如果完全不动,查两件事:
- 传感器表面是不是被覆盖了。结露、胶水、贴纸盖住感应区,湿度就读不进来。
- 是不是读到了没校验的坏数据。如果代码没做 CRC 校验,读到坏数据可能被当成常量显示。本页代码已经带校验。
读数乱跳,或者 CRC 一直失败
大概率是总线问题。走线过长、上拉电阻缺失或阻值不对、总线被其它设备干扰,都会导致传输出错。
本页代码遇到 CRC 校验失败会直接丢弃这组数据并显示 Read failed,不会显示错值。如果频繁看到 Read failed,检查上拉电阻和走线长度。
数值和手机 App / 别的温湿度计对不上
这是正常的,不是坏了。几个原因:
- SHT30/SHT40 本身有 ±0.2~0.3 °C、±2% RH 的典型误差
- 温度跟位置强相关——离热源、门窗、人体的距离不同,读数就是不一样
- 湿度响应有迟滞,两个设备放在一起也可能因为通风不同而差几个点
SHT30/SHT40 给的是稳定、可重复、带校验的相对量,做趋势观察和阈值判断完全够用;要精确标定必须用标准仪器。
References
资料来源
本页信息来源声明
| 内容 | 来源 |
|---|---|
| 接线、地址、代码、温湿度实测值、编译烧录记录、视频 | 本项目实测 2026-08-14 实机测试 |
| 芯片参数、命令字、CRC-8、转换公式、自加热、响应时间、地址选择 | 资料参考 Sensirion 数据手册 |
| 模块板引脚顺序、上拉、稳压差异 | 资料参考 厂家与经销商模块资料 |
| 与 DHT11 的对照、安装位置注意 | 资料参考 数据手册与常规工程实践 |
| SKU | 待补充 |
本页所有「资料参考」内容均由我们重新整理表述,未直接复制原文。参数以你手上模块的实际批次和卖家说明为准。