The SHT40 is Sensirion’s small, accurate temperature and humidity sensor (±0.2 °C, ±1.8 % RH) and a step up from the SHT31 at a similar price. On a Pico 2 W it sits on I2C0 (GP4/GP5) at 3V3 and needs no library.
The script prints a CRC-checked reading every two seconds and uses the sensor’s own heater when monsoon humidity leaves it soaked.
Wiring
| Raspberry Pi Pico 2 W | SHT40 temperature and humidity sensor | Wire | Notes |
|---|---|---|---|
| 3V3 | VCC | red | 3V3 is pin 36. Plain 4-pin SHT40 boards have no regulator — 3.3 V only. |
| GND | GND | black | |
| GP5 | SCL | yellow | GP5 is physical pin 7 (I2C0 SCL). |
| GP4 | SDA | blue | GP4 is physical pin 6 (I2C0 SDA). |
3.3 V (boards with a regulator: 3.3–5 V) part, 3.3 V board. The chip is 1.08–3.6 V only. Boards with a regulator and level shifting (Adafruit-style, with a VIN pin) accept 5 V. Plain 4-pin clones often have neither — power them from 3.3 V and keep SDA/SCL at 3.3 V; on an Uno use a level shifter.
Board quirks
- The SHT40 takes one-byte commands:
0xFDmeasures with high precision in about 9 ms, and six bytes come back with two CRCs. - Humidity uses the SHT4x formula −6 + 125 × raw/65535, which can stray just outside 0–100 %; the script clamps it.
- After a minute at ~100 % RH the script fires the built-in heater (
0x39: 200 mW for 1 s) to drive off condensation, then waits for it to cool. Don’t run the heater continuously. - Clone boards without a regulator are 3.3 V-only. The Pico 2 W’s inputs tolerate 5 V while powered, but the sensor itself does not.
Code
# SHT40 temperature + humidity on a Raspberry Pi Pico 2 W (MicroPython, no library).
# Wiring: VCC -> 3V3 (pin 36), GND -> GND, SCL -> GP5 (pin 7), SDA -> GP4 (pin 6). I2C0.
# Address 0x44 (SHT40-AD1B, most boards) or 0x45 (SHT40-BD1B).
# In monsoon humidity the sensor can stay wet with condensation; the built-in heater dries it.
from machine import Pin, I2C
import time
i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=400_000)
found = i2c.scan()
addr = 0x44 if 0x44 in found else (0x45 if 0x45 in found else None)
if addr is None:
raise SystemExit("No SHT40 at 0x44/0x45 - found {}".format([hex(a) for a in found]))
def crc8(data): # Sensirion CRC-8: polynomial 0x31, init 0xFF
crc = 0xFF
for b in data:
crc ^= b
for _ in range(8):
crc = ((crc << 1) ^ 0x31) & 0xFF if crc & 0x80 else (crc << 1) & 0xFF
return crc
def measure(cmd=0xFD, wait_ms=10):
"""0xFD = high precision (~9 ms). 0x39 = heater 200 mW for 1 s, then measure."""
i2c.writeto(addr, bytes([cmd]))
time.sleep_ms(wait_ms)
d = i2c.readfrom(addr, 6)
if crc8(d[0:2]) != d[2] or crc8(d[3:5]) != d[5]:
raise OSError("CRC mismatch")
t = -45 + 175 * ((d[0] << 8) | d[1]) / 65535
rh = -6 + 125 * ((d[3] << 8) | d[4]) / 65535
return t, min(max(rh, 0.0), 100.0)
i2c.writeto(addr, b"\x94") # soft reset
time.sleep_ms(2)
i2c.writeto(addr, b"\x89") # read serial number
time.sleep_ms(2)
s = i2c.readfrom(addr, 6)
print("SHT40 at {} serial {:02X}{:02X}{:02X}{:02X}".format(hex(addr), s[0], s[1], s[3], s[4]))
wet_count = 0
while True:
try:
t, rh = measure()
print("Temp {:.2f} C Humidity {:.1f} %".format(t, rh))
wet_count = wet_count + 1 if rh > 95 else 0
if wet_count >= 30: # a minute of ~100 % RH: probably condensation on the sensor
print("Running the heater for 1 s to dry the sensor...")
measure(0x39, 1100)
time.sleep(10) # let it cool before trusting the next reading
wet_count = 0
except OSError as e:
print("Read failed:", e)
time.sleep(2)# SHT40 temperature + humidity on a Raspberry Pi Pico 2 W (MicroPython, no library).
# Wiring: VCC -> 3V3 (pin 36), GND -> GND, SCL -> GP5 (pin 7), SDA -> GP4 (pin 6). I2C0.
# Address 0x44 (SHT40-AD1B, most boards) or 0x45 (SHT40-BD1B).
# In monsoon humidity the sensor can stay wet with condensation; the built-in heater dries it.
from machine import Pin, I2C
import time
i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=400_000)
found = i2c.scan()
addr = 0x44 if 0x44 in found else (0x45 if 0x45 in found else None)
if addr is None:
raise SystemExit("No SHT40 at 0x44/0x45 - found {}".format([hex(a) for a in found]))
def crc8(data): # Sensirion CRC-8: polynomial 0x31, init 0xFF
crc = 0xFF
for b in data:
crc ^= b
for _ in range(8):
crc = ((crc << 1) ^ 0x31) & 0xFF if crc & 0x80 else (crc << 1) & 0xFF
return crc
def measure(cmd=0xFD, wait_ms=10):
"""0xFD = high precision (~9 ms). 0x39 = heater 200 mW for 1 s, then measure."""
i2c.writeto(addr, bytes([cmd]))
time.sleep_ms(wait_ms)
d = i2c.readfrom(addr, 6)
if crc8(d[0:2]) != d[2] or crc8(d[3:5]) != d[5]:
raise OSError("CRC mismatch")
t = -45 + 175 * ((d[0] << 8) | d[1]) / 65535
rh = -6 + 125 * ((d[3] << 8) | d[4]) / 65535
return t, min(max(rh, 0.0), 100.0)
i2c.writeto(addr, b"\x94") # soft reset
time.sleep_ms(2)
i2c.writeto(addr, b"\x89") # read serial number
time.sleep_ms(2)
s = i2c.readfrom(addr, 6)
print("SHT40 at {} serial {:02X}{:02X}{:02X}{:02X}".format(hex(addr), s[0], s[1], s[3], s[4]))
wet_count = 0
while True:
try:
t, rh = measure()
print("Temp {:.2f} C Humidity {:.1f} %".format(t, rh))
wet_count = wet_count + 1 if rh > 95 else 0
if wet_count >= 30: # a minute of ~100 % RH: probably condensation on the sensor
print("Running the heater for 1 s to dry the sensor...")
measure(0x39, 1100)
time.sleep(10) # let it cool before trusting the next reading
wet_count = 0
except OSError as e:
print("Read failed:", e)
time.sleep(2)Expected serial output
SHT40 at 0x44 serial 0F3A9C12
Temp 27.86 C Humidity 71.4 %
Temp 27.85 C Humidity 71.3 %SHT40 at 0x44 serial 0F3A9C12 Temp 27.86 C Humidity 71.4 % Temp 27.85 C Humidity 71.3 %
Common errors
No SHT40 at 0x44/0x45
Check the wiring and 3V3. Some boards mark SDA/SCL in the opposite order to the photo — follow the silkscreen.
Humidity stuck at 100 %
Water on the sensor. Let the heater cycle run, and mount it with the opening facing down, out of rain.