Preview:
# AHT20 (0x38) + BMP280 (0x77) on Raspberry Pi Pico, I2C0 (GP8 SDA, GP9 SCL)
from machine import Pin, I2C
from time import sleep

# ---------- I2C ----------
i2c = I2C(0, sda=Pin(8), scl=Pin(9), freq=100000)
AHT_ADDR = 0x38
BMP_ADDR = 0x77      # your chip id 0x58 confirmed BMP280 at 0x77

# --------- AHT20 (T + RH) ----------
def aht20_init():
    try:
        i2c.writeto(AHT_ADDR, b'\xBA')  # soft reset
        sleep(0.02)
    except OSError:
        pass
    i2c.writeto(AHT_ADDR, b'\xBE\x08\x00')  # init/calibrate
    sleep(0.02)

def aht20_read():
    i2c.writeto(AHT_ADDR, b'\xAC\x33\x00')  # trigger
    # wait until not busy
    for _ in range(60):
        status = i2c.readfrom(AHT_ADDR, 1)[0]
        if (status & 0x80) == 0:
            break
        sleep(0.005)
    raw = i2c.readfrom(AHT_ADDR, 6)
    hum = ((raw[1]<<12) | (raw[2]<<4) | (raw[3]>>4)) / 1048576.0 * 100.0
    tmp = (((raw[3]&0x0F)<<16) | (raw[4]<<8) | raw[5]) / 1048576.0 * 200.0 - 50.0
    return tmp, hum

# --------- BMP280 (Pressure + Temp) ----------
# Minimal driver (forced mode, x1 oversampling)
def _u16(b): return b[0] | (b[1] << 8)
def _s16(b):
    v = _u16(b)
    return v - 65536 if v & 0x8000 else v

# Read calibration constants
cal = i2c.readfrom_mem(BMP_ADDR, 0x88, 24)
dig_T1 = _u16(cal[0:2])
dig_T2 = _s16(cal[2:4])
dig_T3 = _s16(cal[4:6])
dig_P1 = _u16(cal[6:8])
dig_P2 = _s16(cal[8:10])
dig_P3 = _s16(cal[10:12])
dig_P4 = _s16(cal[12:14])
dig_P5 = _s16(cal[14:16])
dig_P6 = _s16(cal[16:18])
dig_P7 = _s16(cal[18:20])
dig_P8 = _s16(cal[20:22])
dig_P9 = _s16(cal[22:24])

# Configure: ctrl_meas (temp x1, press x1, sleep), config (standby/filter default)
# We'll use "forced" mode per read.
i2c.writeto_mem(BMP_ADDR, 0xF4, b'\x27')  # osrs_t=1, osrs_p=1, mode=3 (normal)
i2c.writeto_mem(BMP_ADDR, 0xF5, b'\x00')  # config default

t_fine = 0
def bmp280_read():
    global t_fine
    # Force a measurement (write mode to forced would be 0x25; normal 0x27 is okay too)
    # Read raw data: 0xF7..0xFC: press_msb, press_lsb, press_xlsb, temp_msb, temp_lsb, temp_xlsb
    data = i2c.readfrom_mem(BMP_ADDR, 0xF7, 6)
    adc_p = ((data[0] << 16) | (data[1] << 8) | data[2]) >> 4
    adc_t = ((data[3] << 16) | (data[4] << 8) | data[5]) >> 4

    # Temperature compensation (datasheet)
    var1 = ((adc_t / 16384.0) - (dig_T1 / 1024.0)) * dig_T2
    var2 = (((adc_t / 131072.0) - (dig_T1 / 8192.0)) ** 2) * dig_T3
    t_fine = int(var1 + var2)
    T = (var1 + var2) / 5120.0

    # Pressure compensation (datasheet)
    var1p = t_fine / 2.0 - 64000.0
    var2p = var1p * var1p * dig_P6 / 32768.0
    var2p = var2p + var1p * dig_P5 * 2.0
    var2p = var2p / 4.0 + dig_P4 * 65536.0
    var1p = (dig_P3 * var1p * var1p / 524288.0 + dig_P2 * var1p) / 524288.0
    var1p = (1.0 + var1p / 32768.0) * dig_P1
    if var1p == 0:
        return T, 0.0  # avoid division by zero
    P = 1048576.0 - adc_p
    P = (P - var2p / 4096.0) * 6250.0 / var1p
    var1p = dig_P9 * P * P / 2147483648.0
    var2p = P * dig_P8 / 32768.0
    P = P + (var1p + var2p + dig_P7) / 16.0
    P_hPa = P / 100.0
    return T, P_hPa

# ----- Altitude from pressure -----
P0_SEA_LEVEL = 1013.25   # set this to your current sea-level pressure for best accuracy
def altitude_from_pressure(p_hPa, p0=P0_SEA_LEVEL):
    return 44330.0 * (1.0 - (p_hPa / p0) ** (1/5.255))

# ---------- Demo ----------
aht20_init()
while True:
    try:
        Taht, RH = aht20_read()
        Tbmp, P = bmp280_read()
        alt = altitude_from_pressure(P, P0_SEA_LEVEL)

        print("AHT20  ->  T = %.2f °C   RH = %.1f %%" % (Taht, RH))
        print("BMP280 ->  T = %.2f °C   P  = %.2f hPa   Alt ≈ %.1f m (P0=%.2f hPa)" %
              (Tbmp, P, alt, P0_SEA_LEVEL))
        print()
    except OSError as e:
        print("Read error:", e)
    sleep(1)
downloadDownload PNG downloadDownload JPEG downloadDownload SVG

Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!

Click to optimize width for Twitter