传感器 · 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。

OLED 显示 SHT30 @0x44 温度 28.8 C 湿度 49.7%
正常显示:湿度 49.7%,温度 28.8 °C
OLED 显示 SHT30 @0x44 湿度 70.8%,哈气后上升
哈气后峰值:湿度 70.8%,明显上升
27.4 — 28.8实测温度范围(°C)
51% → 72%哈气后湿度变化
1 秒刷新一次
0x44实测识别地址

实测温湿度变化

下表是现场观察到的大致范围,属于现象记录,不是标定数据。不同房间、季节、通风情况差别很大。

测试场景温度湿度
室温正常显示约 27.4 — 28.8 °C约 51%
对着传感器哈气小幅变化快速上升到约 72%
松手后回到室温逐步回落到约 51%

编译烧录记录

PlatformIO 终端显示 Verified OK 与 SUCCESS
PlatformIO 上传输出:** 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
未验证的环境不当成支持。本项目没有在 Arduino IDE、Keil、STM32CubeIDE 下编译或验证过,所以不提供这些环境的支持说明。下载区的 .ino 文件同理,已标注「未实测」。代码里的 SHT40 分支没有换 SHT40 实物复测过,本次实拍全程识别为 SHT30。

What it does

这个模块干什么

SHT30 / SHT40 就是给单片机装一支"体温计 + 湿度计",专门看现在多热、多潮。对着它哈一口气,湿度数值立刻涨上去;松手之后,又慢慢降回原来的值。单片机拿到这两个数,就能自己决定要不要开风扇、除湿、报警。

它和 DHT11 这类老式温湿度的区别是:出厂就带 I2C 数字输出和 CRC 校验,读回来的每个数据都自带校验,传输错了会被挡掉,不会把一个坏数当真的显示出来。

SHT30 / SHT40DHT11(对照)
接口I2C,两根线,可多器件共用单总线,时序要求严
校验每个数据带 CRC-8 校验只有简单的校验和
精度±0.2~0.3 °C,±2% RH 左右±2 °C,±5% RH
量程-40~125 °C,0~100% RH0~50 °C,20~90% RH
响应快,湿度 τ63% 约几秒慢,湿度要十几秒

对照数据来自两家官方数据手册的典型值,由我们整理成表。资料参考

Wiring

接线

本项目实测 SHT30/SHT40 和 OLED 共用同一组 I2C,一共只要 4 根线。下图接法实机跑通。

STM32F103C8T6 + SHT30/SHT40 + 0.96 OLED 简化接线图
信号SHT30/SHT40STM32F103C8T6OLED
电源VCC3.3VVCC / VDD
地GNDGNDGND
时钟SCLPB8SCL / SCK
数据SDAPB9SDA
地址选择ADDRGND(模块板一般自带下拉,排针不接等效接地)—
按模块丝印接线,不要照着照片数孔位。市面上 SHT30/SHT40 模块板芯片相同但排针顺序、上拉、稳压并不统一。有的板子顺序是 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。

PlatformIO 工程 ZIP 含 platformio.ini、src/main.cpp、README、接线图。已去掉本机编译路径,解压即可用。4 KB
下载 ZIP
已编译固件 bin 本页代码编译出的 firmware.bin,可直接烧录到 STM32F103C8T6。46 KB
下载 BIN
Arduino IDE 版 .ino 未实测 与上面同一份代码,只改了扩展名。未在 Arduino IDE 下实际编译验证,供参考。用它需要先装 STM32 官方核心和两个 Adafruit 库。5 KB
下载 INO

bin 怎么烧进去

这块板子不能用网页烧录器。网页烧录(WebSerial)目前主要面向 ESP32 / ESP8266 这类自带 USB 串口下载的芯片。STM32F103C8T6 本项目用的是 ST-Link + SWD,浏览器接触不到 SWD 接口,所以本页不提供在线烧录入口——提供了也烧不进去。

用下面任一方式烧录 bin:

方式工具说明
ST-Link(本项目用的)STM32CubeProgrammer选 ST-LINK,起始地址填 0x08000000,选 bin 文件后 Download
ST-Link 命令行st-flashst-flash write firmware.bin 0x08000000
USB 转串口STM32CubeProgrammer / FlyMcuBOOT0 拉高复位进 ISP 模式,走串口下载
不要照抄 COM 号。视频和截图里出现的串口号是我们这台电脑的,你的电脑多半不一样。查当前串口:pio device list,找 CP2102 / CP210x / CH340 对应的那个。

第一次使用

  1. 按上面接线图接好 4 根线:3.3V、GND、PB8、PB9
  2. 下载 ZIP,解压,用 VS Code + PlatformIO 打开这个文件夹
  3. 等依赖库自动装完,点 Build,看到 SUCCESS
  4. 接上 ST-Link,点 Upload,看到 ** Verified OK **
  5. 看 OLED:显示温度湿度。对着传感器哈一口气,湿度明显上升,松手慢慢回落

Demo

视频演示

本项目实测 约 46 秒,全部是实机画面,没有演示动画。

时间内容
0:00实机结果,OLED 正常显示,湿度约 51%
0:13对着传感器哈气,湿度快速上升
0:16湿度峰值区间,最高约 72%
0:23松手后湿度逐步回落
0:35回到正常显示,收尾

Reference

实用参数与资料

资料参考 这一节来自芯片数据手册和公开资料,不是我们实测的结论。只留会影响你怎么接线、选型、写代码的内容。

SHT30 和 SHT40 到底差在哪

两个都是 Sensirion 的数字温湿度传感器,但不是简单换代,命令集和公式都不一样,所以代码里要写两个分支。

项目SHT30SHT40来源
测量命令0x2C06(单次·高重复性·时钟延展)0xFD(高精度 T&RH)数据手册
典型精度±0.3 °C / ±2 %RH±0.2 °C / ±1.8 %RH数据手册
供电2.4 — 5.5 V1.08 — 3.6 V数据手册
湿度换算RH = 100 × S/65535RH = -6 + 125 × S/65535数据手册
测量等待时钟延展,主控等 ACK无时钟延展,主控要 delay数据手册

最需要提前知道的一条:自加热

连续高频率测量会让芯片自己发热,导致温度读数偏高。数据手册明确提醒测量间隔不要低于 1 Hz(一秒最多测一次),否则自加热的影响会叠加。本页代码主循环是 delay(1000),正好一秒一次,落在安全范围内。如果你要加快刷新,别低于一秒。

安装位置对温度的影响

问题说明
离热源远一点MCU、稳压芯片、OLED 屏都会发热,传感器贴太近会被"烘热",温度读数偏高
进外壳要通风密封外壳把板子热量闷住,累积起来温度就飘,装壳时给传感器位置留透气孔
避开结露长时间泡在 100% 结露或有机挥发物环境里,湿度会慢慢漂移

以上为温湿度传感器安装的通用工程实践,本项目在面包板上裸测,没有做过外壳实验。资料参考

响应时间和湿度迟滞

湿度不是瞬间跟上的。数据手册给 SHT30 的湿度响应时间 τ63% 约 8 秒——环境湿度突变后,读数要花几秒才跟到新值的 63%,完全稳定更慢。

这正好解释了视频里的现象:哈气之后湿度不是"啪"一下到顶,而是要等一两秒才冲到峰值;松手后也不是立刻掉回来,而是慢慢回落。这是传感器的物理特性,不是程序卡了。

基本参数

参数SHT30SHT40来源
供电范围2.4 — 5.5 V1.08 — 3.6 V数据手册
温度量程-40 — +125 °C-40 — +125 °C数据手册
湿度量程0 — 100 %RH0 — 100 %RH数据手册
I2C 地址0x44 / 0x45
由 ADDR 引脚电平决定
0x44 / 0x45 / 0x46
由订购型号决定,见下
数据手册
CRC 校验CRC-8,多项式 0x31,初始值 0xFF数据手册
注意量程是芯片本身,不是模块板。市面上很多 SHT30 模块板自带稳压,标称可接 5V;但和 3.3V 的 STM32 一起用时直接 3.3V 最省事最安全。本项目就是 3.3V。

地址怎么来的:SHT30 和 SHT40 不是一回事

这两颗芯片决定地址的方式完全不同,混着记就会扫不到。

扫到 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,扫描不到传感器

按这个顺序查:

  1. 先看有没有共地。SHT30/SHT40、OLED、STM32 的 GND 必须接在一起,只接电源正极不接地,I2C 一定不通。
  2. 确认 SCL 接 PB8、SDA 接 PB9。这两根接反是最常见的错误,程序不会报错,就是读不到。
  3. 看模块丝印,不要照着照片数孔位。不同批次模块的引脚顺序不一样,插反了供电就错了。
  4. 确认地址。SHT30 的 ADDR 脚接到 3.3V 地址会变成 0x45;SHT40 的地址是出厂型号定死的,SHT40-BD1B 就是 0x46,接线改不了。本页代码会把 0x44 / 0x45 / 0x46 都试一遍,但很多第三方库只写死 0x44,就会报找不到——这不是模块坏了。先用 I2C Scanner 扫一下,扫到几就用几。
  5. 确认供电是 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待补充

本页所有「资料参考」内容均由我们重新整理表述,未直接复制原文。参数以你手上模块的实际批次和卖家说明为准。