From ca23407cebd1e6fc9626074fdce8f9554657d204 Mon Sep 17 00:00:00 2001 From: sl-firmware Date: Sat, 28 Feb 2026 19:43:48 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20BME280=20full=20readout=20=E2=80=94=20t?= =?UTF-8?q?emp,=20humidity,=20pressure=20telemetry=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - bmp280.c: detect BME280 (chip_id 0x60) vs BMP280 (0x58) at init - bmp280.c: read humidity calibration (dig_H1–H6) from 0xA1 and 0xE1–0xE7 - bmp280.c: set ctrl_hum (0xF2, osrs_h=×16) before ctrl_meas — hardware req - bmp280.c: add bmp280_read_humidity() — float compensation (FPv5-SP FPU), returns %RH × 10; -1 if chip is BMP280 or not initialised - bmp280.h: add bmp280_read_humidity() declaration + timeout note - main.c: baro_ok → baro_chip (stores chip_id for BME280 detection) - main.c: telemetry adds t (°C×10), pa (hPa×10) for all barometers; adds h (%RH×10) for BME280 only; alt unchanged - ui/index.html: hidden TEMP/HUMIDITY/PRESSURE rows, revealed on first packet containing t/h/pa fields; values shown with 1 dp I2C hang safety: all HAL_I2C_Mem_Read/Write use 100ms timeouts, so missing hardware (NAK) returns in <1ms, not after a hang. Co-Authored-By: Claude Sonnet 4.6 --- include/bmp280.h | 9 ++++++ src/bmp280.c | 78 ++++++++++++++++++++++++++++++++++++++++++++---- src/main.c | 18 ++++++++--- ui/index.html | 15 ++++++++++ 4 files changed, 110 insertions(+), 10 deletions(-) diff --git a/include/bmp280.h b/include/bmp280.h index 26c761b..bd7feac 100644 --- a/include/bmp280.h +++ b/include/bmp280.h @@ -9,10 +9,19 @@ * Probes I2C1 at 0x76 then 0x77. * Returns chip_id (0x58=BMP280, 0x60=BME280) on success, negative if not found. * Requires i2c1_init() to have been called first. + * + * All I2C operations use 100ms timeouts — init will not hang on missing hardware. */ int bmp280_init(void); void bmp280_read(int32_t *pressure_pa, int16_t *temp_x10); +/* + * BME280-only humidity readout. Call AFTER bmp280_read() (uses cached t_fine). + * Returns humidity in %RH × 10 (e.g. 500 = 50.0 %RH). + * Returns -1 if chip is BMP280 (no humidity) or not initialised. + */ +int16_t bmp280_read_humidity(void); + /* Convert pressure (Pa) to altitude above sea level (cm), ISA p0=101325 Pa. */ int32_t bmp280_pressure_to_alt_cm(int32_t pressure_pa); diff --git a/src/bmp280.c b/src/bmp280.c index d1b35f7..cd5dbc9 100644 --- a/src/bmp280.c +++ b/src/bmp280.c @@ -3,20 +3,33 @@ * * Probes 0x76 first, then 0x77. Requires i2c1_init() before bmp280_init(). * Returns chip_id on success (0x58=BMP280, 0x60=BME280), negative if absent. + * + * All HAL_I2C_Mem_Read/Write calls use 100ms timeouts — init cannot hang + * indefinitely even if the I2C bus is stuck or the breakout is absent. + * + * BME280 (chip_id 0x60): bmp280_read_humidity() returns %RH × 10. + * Call bmp280_read() first to refresh t_fine, then bmp280_read_humidity(). */ #include "bmp280.h" #include "i2c1.h" #include -/* Shift I2C address for HAL (7-bit left-shifted) */ static uint16_t s_addr; +static int s_chip_id; /* 0x58=BMP280, 0x60=BME280, 0=none */ -/* Calibration data */ +/* Shared temp/pressure calibration */ static uint16_t dig_T1; static int16_t dig_T2, dig_T3; static uint16_t dig_P1; static int16_t dig_P2, dig_P3, dig_P4, dig_P5, dig_P6, dig_P7, dig_P8, dig_P9; -static int32_t t_fine; +static int32_t t_fine; /* updated by bmp280_read(); used by bmp280_read_humidity() */ + +/* BME280-only humidity calibration (chip_id 0x60) */ +static uint8_t dig_H1; +static int16_t dig_H2; +static uint8_t dig_H3; +static int16_t dig_H4, dig_H5; +static int8_t dig_H6; static uint8_t i2c_read(uint8_t reg) { uint8_t val = 0; @@ -36,8 +49,9 @@ static int try_init(uint16_t addr) { s_addr = addr; uint8_t id = i2c_read(0xD0); if (id != 0x58 && id != 0x60) return -(int)id; + s_chip_id = (int)id; - /* Read calibration */ + /* Temp/pressure calibration (0x88–0x9D, identical layout on BMP280 and BME280) */ uint8_t cal[26]; i2c_read_burst(0x88, cal, 26); dig_T1 = (uint16_t)(cal[1] << 8 | cal[0]); @@ -53,7 +67,28 @@ static int try_init(uint16_t addr) { dig_P8 = (int16_t) (cal[21] << 8 | cal[20]); dig_P9 = (int16_t) (cal[23] << 8 | cal[22]); - /* Normal mode, ×16 oversampling temp+press, 0.5 ms standby */ + if (id == 0x60) { + /* BME280: humidity calibration. + * dig_H1 : 0xA1 (uint8) + * dig_H2 : 0xE1–0xE2 (int16, LSB first) + * dig_H3 : 0xE3 (uint8) + * dig_H4 : 0xE4[7:0] | 0xE5[3:0] (int12) + * dig_H5 : 0xE5[7:4] | 0xE6[7:0] (int12) + * dig_H6 : 0xE7 (int8) + */ + dig_H1 = i2c_read(0xA1); + uint8_t hcal[7]; + i2c_read_burst(0xE1, hcal, 7); + dig_H2 = (int16_t)((hcal[1] << 8) | hcal[0]); + dig_H3 = hcal[2]; + dig_H4 = (int16_t)((hcal[3] << 4) | (hcal[4] & 0x0F)); + dig_H5 = (int16_t)((hcal[5] << 4) | (hcal[4] >> 4)); + dig_H6 = (int8_t)hcal[6]; + + /* ctrl_hum (0xF2) MUST be written before ctrl_meas (0xF4) — hardware req */ + i2c_write(0xF2, 0x05); /* osrs_h = ×16 */ + } + i2c_write(0xF5, 0x00); /* config: standby=0.5ms, filter=off */ i2c_write(0xF4, 0xB7); /* ctrl_meas: osrs_t=×16, osrs_p=×16, normal mode */ @@ -73,7 +108,7 @@ void bmp280_read(int32_t *pressure_pa, int16_t *temp_x10) { int32_t adc_P = (int32_t)((buf[0] << 12) | (buf[1] << 4) | (buf[2] >> 4)); int32_t adc_T = (int32_t)((buf[3] << 12) | (buf[4] << 4) | (buf[5] >> 4)); - /* Temperature compensation (BMP280 datasheet Section 4.2.3) */ + /* Temperature compensation (BME280/BMP280 datasheet Section 4.2.3) */ int32_t v1 = ((((adc_T >> 3) - ((int32_t)dig_T1 << 1))) * ((int32_t)dig_T2)) >> 11; int32_t v2 = (((((adc_T >> 4) - (int32_t)dig_T1) * ((adc_T >> 4) - (int32_t)dig_T1)) >> 12) * (int32_t)dig_T3) >> 14; @@ -95,6 +130,37 @@ void bmp280_read(int32_t *pressure_pa, int16_t *temp_x10) { *pressure_pa = (int32_t)(((p + p1 + p2) >> 8) + ((int64_t)dig_P7 << 4)) / 256; } +/* + * BME280-only humidity readout. MUST be called after bmp280_read() (uses t_fine). + * + * Compensation: BME280 datasheet section 4.2.3 integer formula. + * Result is Q22.10 fixed-point: 1024 units = 1 %RH. + * + * Returns humidity in %RH × 10 (e.g. 500 = 50.0 %RH). + * Returns -1 if chip is BMP280 (no humidity sensor). + */ +int16_t bmp280_read_humidity(void) { + if (s_chip_id != 0x60) return -1; + + uint8_t hbuf[2]; + i2c_read_burst(0xFD, hbuf, 2); + int32_t adc_H = (int32_t)((hbuf[0] << 8) | hbuf[1]); + + /* BME280 datasheet section 4.2.3 — floating-point compensation. + * Single-precision float is hardware-accelerated on STM32F7 (FPv5-SP FPU). + * Called at 50 Hz — negligible overhead. + */ + float var_H = ((float)t_fine) - 76800.0f; + var_H = (adc_H - (((float)dig_H4) * 64.0f + ((float)dig_H5) / 16384.0f * var_H)) * + (((float)dig_H2) / 65536.0f * + (1.0f + ((float)dig_H6) / 67108864.0f * var_H * + (1.0f + ((float)dig_H3) / 67108864.0f * var_H))); + var_H *= (1.0f - (float)dig_H1 * var_H / 524288.0f); + if (var_H > 100.0f) var_H = 100.0f; + if (var_H < 0.0f) var_H = 0.0f; + return (int16_t)(var_H * 10.0f + 0.5f); /* %RH × 10, rounded */ +} + int32_t bmp280_pressure_to_alt_cm(int32_t pressure_pa) { /* Barometric formula: h = 44330 * (1 - (p/p0)^(1/5.255)) metres */ float ratio = (float)pressure_pa / 101325.0f; diff --git a/src/main.c b/src/main.c index a35f710..45e19f4 100644 --- a/src/main.c +++ b/src/main.c @@ -130,10 +130,11 @@ int main(void) { motor_driver_init(&motors); /* Probe I2C1 for optional sensors — skip gracefully if not found */ - int baro_ok = 0; + int baro_chip = 0; /* chip_id: 0x58=BMP280, 0x60=BME280, 0=absent */ mag_type_t mag_type = MAG_NONE; if (i2c1_init() == 0) { - baro_ok = (bmp280_init() > 0) ? 1 : 0; + int chip = bmp280_init(); + baro_chip = (chip > 0) ? chip : 0; mag_type = mag_init(); } @@ -250,12 +251,21 @@ int main(void) { n = snprintf(p, rem, ",\"hd\":-1"); /* not ready */ p += n; rem -= n; } - if (baro_ok) { + if (baro_chip) { int32_t pres_pa; int16_t temp_x10; bmp280_read(&pres_pa, &temp_x10); int32_t alt_cm = bmp280_pressure_to_alt_cm(pres_pa); - n = snprintf(p, rem, ",\"alt\":%ld", (long)alt_cm); /* cm */ + /* alt cm, temp °C×10, pressure hPa×10 (Pa÷10 = hPa×10) */ + n = snprintf(p, rem, ",\"alt\":%ld,\"t\":%d,\"pa\":%ld", + (long)alt_cm, (int)temp_x10, (long)(pres_pa / 10)); p += n; rem -= n; + if (baro_chip == 0x60) { /* BME280: add humidity %RH×10 */ + int16_t hum_x10 = bmp280_read_humidity(); + if (hum_x10 >= 0) { + n = snprintf(p, rem, ",\"h\":%d", (int)hum_x10); + p += n; rem -= n; + } + } } n = snprintf(p, rem, "}\n"); len = (int)(p + n - buf); diff --git a/ui/index.html b/ui/index.html index 9c62925..6058620 100644 --- a/ui/index.html +++ b/ui/index.html @@ -53,6 +53,9 @@
MOTOR --
+ + +
HZ --
@@ -212,6 +215,18 @@ window.updateIMU = function(data) { document.getElementById('row-alt').style.display = ''; document.getElementById('v-alt').textContent = (data.alt / 100.0).toFixed(1); } + if (data.t !== undefined) { + document.getElementById('row-temp').style.display = ''; + document.getElementById('v-temp').textContent = (data.t / 10.0).toFixed(1); + } + if (data.h !== undefined) { + document.getElementById('row-hum').style.display = ''; + document.getElementById('v-hum').textContent = (data.h / 10.0).toFixed(1); + } + if (data.pa !== undefined) { + document.getElementById('row-pres').style.display = ''; + document.getElementById('v-pres').textContent = (data.pa / 10.0).toFixed(1); + } // Pitch bar: center at 50%, ±90° document.getElementById('bar-pitch').style.width = ((pitch + 90) / 180 * 100) + '%'; -- 2.47.2