Zum Inhalt

bmx280.tc

BMx280 Temperature, Pressure & (optional) Humidity Sensor Driver

Source on GitHub

// BMx280 Temperature, Pressure & (optional) Humidity Sensor Driver
// Auto-detects BMP280 (ID 0x58) vs BME280 (ID 0x60)
// I2C addresses: 0x76 (SDO=GND) or 0x77 (SDO=VCC)
// Scans both I2C buses, claims via Tasmota I2C system
// Reads every second, displays on web UI + JSON teleperiod
// Includes 6h chart history

#define BMX_ADDR1  0x76
#define BMX_ADDR2  0x77
#define BMP_ID     0x58
#define BME_ID     0x60

// Measurement results
float bmx_temp = 0.0;
float bmx_humi = 0.0;
float bmx_pres = 0.0;
float bmx_dewp = 0.0;
float bmx_absh = 0.0;
int bmx_ok = 0;
int bmx_addr = 0;
int bmx_bus = 0;
int bmx_has_humi = 0;   // 1 = BME280, 0 = BMP280

// I2C data buffer
char bmx_buf[26];

// Calibration data (registers 0x88..0x9F)
// Temperature
int dig_T1;
int dig_T2;
int dig_T3;
// Pressure
int dig_P1;
int dig_P2;
int dig_P3;
int dig_P4;
int dig_P5;
int dig_P6;
int dig_P7;
int dig_P8;
int dig_P9;
// Humidity (BME280 only)
int dig_H1;
int dig_H2;
int dig_H3;
int dig_H4;
int dig_H5;
int dig_H6;

// t_fine shared between temp and pressure/humidity compensation
int t_fine;

#ifdef USE_CHARTS
// Chart history (6h at 1 sample/min = 360 points)
#define CHART_LEN 360
float hist_temp[CHART_LEN];
float hist_humi[CHART_LEN];
float hist_pres[CHART_LEN];
int hist_pos;
int hist_tick;
#endif

// Chip name for display
char bmx_name[8];
char bmx_lbl[32];

int sign16(int val) {
    if (val >= 32768) return val - 65536;
    return val;
}

int sign8(int val) {
    if (val >= 128) return val - 256;
    return val;
}

// Read calibration data
int bmx_read_calib() {
    // Read 24 bytes from 0x88..0x9F (temp + pressure)
    if (!i2cRead(bmx_addr, 0x88, bmx_buf, 24, bmx_bus)) return 0;

    dig_T1 = bmx_buf[0] | (bmx_buf[1] << 8);
    dig_T2 = sign16(bmx_buf[2] | (bmx_buf[3] << 8));
    dig_T3 = sign16(bmx_buf[4] | (bmx_buf[5] << 8));

    dig_P1 = bmx_buf[6] | (bmx_buf[7] << 8);
    dig_P2 = sign16(bmx_buf[8] | (bmx_buf[9] << 8));
    dig_P3 = sign16(bmx_buf[10] | (bmx_buf[11] << 8));
    dig_P4 = sign16(bmx_buf[12] | (bmx_buf[13] << 8));
    dig_P5 = sign16(bmx_buf[14] | (bmx_buf[15] << 8));
    dig_P6 = sign16(bmx_buf[16] | (bmx_buf[17] << 8));
    dig_P7 = sign16(bmx_buf[18] | (bmx_buf[19] << 8));
    dig_P8 = sign16(bmx_buf[20] | (bmx_buf[21] << 8));
    dig_P9 = sign16(bmx_buf[22] | (bmx_buf[23] << 8));

    if (bmx_has_humi) {
        // H1 at 0xA1
        dig_H1 = i2cRead8(bmx_addr, 0xA1, bmx_bus);
        // Read 7 bytes from 0xE1..0xE7
        if (!i2cRead(bmx_addr, 0xE1, bmx_buf, 7, bmx_bus)) return 0;
        dig_H2 = sign16(bmx_buf[0] | (bmx_buf[1] << 8));
        dig_H3 = bmx_buf[2];
        dig_H4 = sign16((bmx_buf[3] << 4) | (bmx_buf[4] & 0x0F));
        dig_H5 = sign16((bmx_buf[5] << 4) | ((bmx_buf[4] >> 4) & 0x0F));
        dig_H6 = sign8(bmx_buf[6]);
    }
    return 1;
}

int bmx_configure() {
    if (bmx_has_humi) {
        // ctrl_hum (0xF2): humidity oversampling x1
        if (!i2cWrite8(bmx_addr, 0xF2, 0x01, bmx_bus)) return 0;
    }
    // config (0xF5): standby 1000ms, filter off = 0xA0
    if (!i2cWrite8(bmx_addr, 0xF5, 0xA0, bmx_bus)) return 0;
    // ctrl_meas (0xF4): temp os x1, press os x1, normal mode = 0x27
    // must be written AFTER ctrl_hum for BME280
    if (!i2cWrite8(bmx_addr, 0xF4, 0x27, bmx_bus)) return 0;
    return 1;
}

// Scan both buses and addresses, auto-detect chip type
int bmx_scan() {
    int bus = 0;
    while (bus < 2) {
        int addr = BMX_ADDR1;
        while (addr <= BMX_ADDR2) {
            if (i2cSetDevice(addr, bus)) {
                int id = i2cRead8(addr, 0xD0, bus);
                if (id == BME_ID || id == BMP_ID) {
                    bmx_addr = addr;
                    bmx_bus = bus;
                    if (id == BME_ID) {
                        bmx_has_humi = 1;
                        strcpy(bmx_name, "BME280");
                    } else {
                        bmx_has_humi = 0;
                        strcpy(bmx_name, "BMP280");
                    }
                    i2cSetActiveFound(bmx_addr, "BMx280", bmx_bus);
                    return 1;
                }
            }
            addr++;
        }
        bus++;
    }
    return 0;
}

// Temperature compensation — sets t_fine
int bmx_comp_temp(int adc_T) {
    int var1 = ((((adc_T >> 3) - (dig_T1 << 1))) * dig_T2) >> 11;
    int var2 = (((((adc_T >> 4) - dig_T1) * ((adc_T >> 4) - dig_T1)) >> 12) * dig_T3) >> 14;
    t_fine = var1 + var2;
    return (t_fine * 5 + 128) >> 8;
}

// Pressure compensation — returns Pa
float bmx_comp_pres(int adc_P) {
    float var1 = (float)t_fine / 2.0 - 64000.0;
    float var2 = var1 * var1 * (float)dig_P6 / 32768.0;
    var2 = var2 + var1 * (float)dig_P5 * 2.0;
    var2 = var2 / 4.0 + (float)dig_P4 * 65536.0;
    var1 = ((float)dig_P3 * var1 * var1 / 524288.0 + (float)dig_P2 * var1) / 524288.0;
    var1 = (1.0 + var1 / 32768.0) * (float)dig_P1;
    if (var1 == 0.0) return 0.0;
    float p = 1048576.0 - (float)adc_P;
    p = (p - var2 / 4096.0) * 6250.0 / var1;
    var1 = (float)dig_P9 * p * p / 2147483648.0;
    var2 = p * (float)dig_P8 / 32768.0;
    p = p + (var1 + var2 + (float)dig_P7) / 16.0;
    return p;
}

// Humidity compensation (BME280 only) — returns %RH
float bmx_comp_humi(int adc_H) {
    float h = (float)t_fine - 76800.0;
    if (h == 0.0) return 0.0;
    h = ((float)adc_H - ((float)dig_H4 * 64.0 + (float)dig_H5 / 16384.0 * h)) *
        ((float)dig_H2 / 65536.0 * (1.0 + (float)dig_H6 / 67108864.0 * h *
        (1.0 + (float)dig_H3 / 67108864.0 * h)));
    h = h * (1.0 - (float)dig_H1 * h / 524288.0);
    if (h > 100.0) h = 100.0;
    if (h < 0.0) h = 0.0;
    return h;
}

// Dewpoint (Magnus formula), returns °C
float bmx_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 bmx_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);
}

void EverySecond() {
    if (!bmx_addr) {
        if (!bmx_scan()) { bmx_ok = 0; return; }
        if (!bmx_read_calib()) { bmx_ok = 0; bmx_addr = 0; return; }
        if (!bmx_configure()) { bmx_ok = 0; bmx_addr = 0; return; }
    }

    // Read from 0xF7: press[3] + temp[3] + humi[2] (8 for BME, 6 for BMP)
    int rlen = 6;
    if (bmx_has_humi) rlen = 8;
    if (!i2cRead(bmx_addr, 0xF7, bmx_buf, rlen, bmx_bus)) {
        bmx_ok = 0;
        bmx_addr = 0;
        return;
    }

    int adc_P = (bmx_buf[0] << 12) | (bmx_buf[1] << 4) | (bmx_buf[2] >> 4);
    int adc_T = (bmx_buf[3] << 12) | (bmx_buf[4] << 4) | (bmx_buf[5] >> 4);

    int T100 = bmx_comp_temp(adc_T);
    bmx_temp = (float)T100 / 100.0;
    bmx_pres = bmx_comp_pres(adc_P) / 100.0;

    if (bmx_has_humi) {
        int adc_H = (bmx_buf[6] << 8) | bmx_buf[7];
        bmx_humi = bmx_comp_humi(adc_H);
        bmx_dewp = bmx_calc_dewpoint(bmx_temp, bmx_humi);
        bmx_absh = bmx_calc_abshumi(bmx_temp, bmx_humi);
    }

    bmx_ok = 1;

#ifdef USE_CHARTS
    // Chart history every minute
    hist_tick++;
    if (hist_tick >= 60) {
        hist_tick = 0;
        hist_temp[hist_pos % CHART_LEN] = bmx_temp;
        hist_pres[hist_pos % CHART_LEN] = bmx_pres;
        if (bmx_has_humi) {
            hist_humi[hist_pos % CHART_LEN] = bmx_humi;
        }
        hist_pos++;
    }
#endif
}

#ifdef USE_CHARTS
void WebPage() {
    int n = hist_pos;
    if (n > CHART_LEN) n = CHART_LEN;
    if (n > 0) {
        WebChart('l', "Temperature", "\u00b0C", 0xe74c3c, hist_pos, CHART_LEN, hist_temp, 1, 0, 0, 0);
        WebChart('l', "Pressure", "hPa", 0x27ae60, hist_pos, CHART_LEN, hist_pres, 1, 0, 0, 0);
        if (bmx_has_humi) {
            WebChart('l', "Humidity", "%RH", 0x3498db, hist_pos, CHART_LEN, hist_humi, 1, 0, 0, 0);
        }
    }
}
#endif

void bmx_web_label(int idx) {
    char vt[32];
    LGetString(idx, bmx_lbl);
    strcpy(vt, "{s}");
    strcat(vt, bmx_name);
    strcat(vt, " ");
    strcat(vt, bmx_lbl);
    strcat(vt, "{m}");
    webSend(vt);
}

void WebCall() {
    char vt[32];
    if (bmx_ok) {
        bmx_web_label(0);
        sprintf(vt, "%.1f &deg;C{e}", bmx_temp);
        webSend(vt);
        bmx_web_label(2);
        sprintf(vt, "%.1f hPa{e}", bmx_pres);
        webSend(vt);
        if (bmx_has_humi) {
            bmx_web_label(1);
            sprintf(vt, "%.1f %{e}", bmx_humi);
            webSend(vt);
            bmx_web_label(3);
            sprintf(vt, "%.1f &deg;C{e}", bmx_dewp);
            webSend(vt);
            bmx_web_label(20);
            sprintf(vt, "%.1f g/m&sup3;{e}", bmx_absh);
            webSend(vt);
        }
    } else {
        webSend("{s}BMx280{m}not found{e}");
    }
}

void JsonCall() {
    if (!bmx_ok) return;
    char buf[96];
    // ,\"BME280\":{\"Temperature\":23.5,\"Pressure\":1013.2,\"Humidity\":45.3}
    strcpy(buf, ",\"");
    strcat(buf, bmx_name);
    strcat(buf, "\":{");
    responseAppend(buf);
    sprintf(buf, "\"Temperature\":%.1f", bmx_temp);
    responseAppend(buf);
    sprintf(buf, ",\"Pressure\":%.1f", bmx_pres);
    responseAppend(buf);
    if (bmx_has_humi) {
        sprintf(buf, ",\"Humidity\":%.1f", bmx_humi);
        responseAppend(buf);
        sprintf(buf, ",\"DewPoint\":%.1f", bmx_dewp);
        responseAppend(buf);
        sprintf(buf, ",\"AbsHumidity\":%.1f", bmx_absh);
        responseAppend(buf);
    }
    responseAppend("}");
}

// Called before VM stops — release I2C address so driver can restart
void OnExit() {
    if (bmx_addr) {
        I2cResetActive(bmx_addr, bmx_bus);
        bmx_addr = 0;
    }
}

int main() {
    char buf[64];
    bmx_ok = 0;
    bmx_addr = 0;
#ifdef USE_CHARTS
    hist_pos = 0;
    hist_tick = 0;
#endif

    if (bmx_scan()) {
        strcpy(buf, bmx_name);
        sprintfAppend(buf, " found at 0x%x on bus %d", bmx_addr, bmx_bus);
        addLog(buf);
        if (bmx_read_calib() && bmx_configure()) {
            addLog("Calibration loaded, sensor active");
        } else {
            addLog("Calibration/config failed");
            bmx_addr = 0;
        }
    } else {
        addLog("BMx280 not found on any bus");
    }
    return 0;
}