Skip to content

scd30.tc

SCD30 CO2/Temperature/Humidity Sensor Driver

Source on GitHub

// SCD30 CO2/Temperature/Humidity Sensor Driver
// I2C address: 0x61
// Measures: CO2 (ppm), Temperature (C), Humidity (%RH)
// Data as IEEE754 floats, CRC8 poly 0x31, init 0xFF (Sensirion)
// Continuous measurement mode, interval 2s

int scd_addr = 0;
int scd_bus = 0;
int scd_ok = 0;
int scd_retry = 0;
float scd_co2 = 0.0;
float scd_temp = 0.0;
float scd_humi = 0.0;
float scd_dewp = 0.0;
float scd_absh = 0.0;
char scd_buf[20];
char scd_lbl[32];

// CRC8 Sensirion: poly 0x31, init 0xFF, over 2 data bytes
int scd_crc(int b1, int b2) {
    int crc = 0xFF ^ b1;
    int j = 0;
    while (j < 8) {
        if (crc & 0x80) crc = ((crc << 1) ^ 0x31) & 0xFF;
        else crc = (crc << 1) & 0xFF;
        j++;
    }
    crc = crc ^ b2;
    j = 0;
    while (j < 8) {
        if (crc & 0x80) crc = ((crc << 1) ^ 0x31) & 0xFF;
        else crc = (crc << 1) & 0xFF;
        j++;
    }
    return crc;
}

// Send 16-bit command (no data)
void scd_cmd(int cmd) {
    scd_buf[0] = cmd & 0xFF;
    i2cWrite(scd_addr, cmd >> 8, scd_buf, 1, scd_bus);
}

// Send 16-bit command with 16-bit argument + CRC
void scd_cmd_arg(int cmd, int arg) {
    scd_buf[0] = cmd & 0xFF;
    scd_buf[1] = arg >> 8;
    scd_buf[2] = arg & 0xFF;
    scd_buf[3] = scd_crc(scd_buf[1], scd_buf[2]);
    i2cWrite(scd_addr, cmd >> 8, scd_buf, 4, scd_bus);
}

// Reconstruct IEEE754 float from 6-byte response (2 words + 2 CRCs)
// Layout: [hi_MSB, hi_LSB, CRC, lo_MSB, lo_LSB, CRC]
// Returns float via intBitsToFloat, or 0.0 on CRC error
float scd_get_float(int offset) {
    int hi_msb = scd_buf[offset];
    int hi_lsb = scd_buf[offset + 1];
    int lo_msb = scd_buf[offset + 3];
    int lo_lsb = scd_buf[offset + 4];
    // Validate both CRCs
    if (scd_crc(hi_msb, hi_lsb) != scd_buf[offset + 2]) return 0.0;
    if (scd_crc(lo_msb, lo_lsb) != scd_buf[offset + 5]) return 0.0;
    int bits = (hi_msb << 24) | (hi_lsb << 16) | (lo_msb << 8) | lo_lsb;
    return intBitsToFloat(bits);
}

// Dewpoint (Magnus formula), returns °C
float scd_calc_dewpoint(float t, float h) {
    if (h <= 0.0) return 0.0;
    float gamma = (17.271 * t) / (237.7 + t) + log(h / 100.0);
    return (237.7 * gamma) / (17.271 - gamma);
}

// Absolute humidity in g/m³
float scd_calc_abshumi(float t, float h) {
    float ah = 6.112 * exp((17.67 * t) / (t + 243.5)) * h * 2.1674;
    return ah / (273.15 + t);
}

int scd_scan() {
    int bus = 0;
    while (bus < 2) {
        if (i2cSetDevice(0x61, bus)) {
            scd_addr = 0x61;
            scd_bus = bus;
            // Get firmware version to verify sensor (cmd 0xD100)
            scd_cmd(0xD100);
            delay(3);
            if (i2cRead0(scd_addr, scd_buf, 3, scd_bus)) {
                if (scd_crc(scd_buf[0], scd_buf[1]) == scd_buf[2]) {
                    // Set measurement interval to 2 seconds (cmd 0x4600, arg 2)
                    scd_cmd_arg(0x4600, 2);
                    delay(3);
                    // Start continuous measurement, no pressure compensation (cmd 0x0010, arg 0)
                    scd_cmd_arg(0x0010, 0);
                    delay(3);
                    i2cSetActiveFound(scd_addr, "SCD30", scd_bus);
                    return 1;
                }
            }
            scd_addr = 0;
        }
        bus++;
    }
    return 0;
}

void EverySecond() {
    if (!scd_addr) {
        // Retry scan every 5 seconds if sensor wasn't found at boot
        scd_retry++;
        if (scd_retry >= 5) {
            scd_retry = 0;
            if (scd_scan()) {
                char buf[48];
                sprintf(buf, "SCD30 found at 0x%x on bus %d (retry)", scd_addr, scd_bus);
                addLog(buf);
            }
        }
        return;
    }

    // Check data ready (cmd 0x0202)
    scd_cmd(0x0202);
    delay(3);
    if (!i2cRead0(scd_addr, scd_buf, 3, scd_bus)) return;
    if (scd_crc(scd_buf[0], scd_buf[1]) != scd_buf[2]) return;
    int ready = (scd_buf[0] << 8) | scd_buf[1];
    if (!ready) return;

    // Read measurement (cmd 0x0300) — 18 bytes: 3 floats × 6 bytes each
    scd_cmd(0x0300);
    delay(3);
    if (!i2cRead0(scd_addr, scd_buf, 18, scd_bus)) return;

    float co2 = scd_get_float(0);
    float temp = scd_get_float(6);
    float humi = scd_get_float(12);

    // Sanity check CO2 (valid range ~400-10000 ppm)
    if (co2 > 0.0) {
        scd_co2 = co2;
        scd_temp = temp;
        scd_humi = humi;
        scd_dewp = scd_calc_dewpoint(temp, humi);
        scd_absh = scd_calc_abshumi(temp, humi);
        scd_ok = 1;
    }
}

void scd_web_label(int idx) {
    char vt[32];
    LGetString(idx, scd_lbl);
    strcpy(vt, "{s}SCD30 ");
    strcat(vt, scd_lbl);
    strcat(vt, "{m}");
    webSend(vt);
}

void WebCall() {
    char vt[32];
    if (scd_ok) {
        scd_web_label(4);
        sprintf(vt, "%.0f ppm{e}", scd_co2);
        webSend(vt);
        scd_web_label(0);
        sprintf(vt, "%.1f &deg;C{e}", scd_temp);
        webSend(vt);
        scd_web_label(1);
        sprintf(vt, "%.1f %{e}", scd_humi);
        webSend(vt);
        scd_web_label(3);
        sprintf(vt, "%.1f &deg;C{e}", scd_dewp);
        webSend(vt);
        scd_web_label(20);
        sprintf(vt, "%.1f g/m&sup3;{e}", scd_absh);
        webSend(vt);
    } else {
        webSend("{s}SCD30{m}not ready{e}");
    }
}

void JsonCall() {
    if (!scd_ok) return;
    char buf[64];
    sprintf(buf, ",\"SCD30\":{\"CarbonDioxide\":%.0f", scd_co2);
    responseAppend(buf);
    sprintf(buf, ",\"Temperature\":%.1f", scd_temp);
    responseAppend(buf);
    sprintf(buf, ",\"Humidity\":%.1f", scd_humi);
    responseAppend(buf);
    sprintf(buf, ",\"DewPoint\":%.1f", scd_dewp);
    responseAppend(buf);
    sprintf(buf, ",\"AbsHumidity\":%.1f}", scd_absh);
    responseAppend(buf);
}

void OnExit() {
    if (scd_addr) {
        // Stop continuous measurement (cmd 0x0104)
        scd_cmd(0x0104);
        delay(3);
        I2cResetActive(scd_addr, scd_bus);
    }
}

int main() {
    scd_ok = 0;
    scd_addr = 0;

    if (scd_scan()) {
        char buf[48];
        sprintf(buf, "SCD30 found at 0x%x on bus %d", scd_addr, scd_bus);
        addLog(buf);
    } else {
        addLog("SCD30 not found");
    }
    return 0;
}