feat: BME280 full readout — temp, humidity, pressure telemetry (#30)
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
76dc03db1b
commit
ca23407ceb
@ -9,10 +9,19 @@
|
|||||||
* Probes I2C1 at 0x76 then 0x77.
|
* Probes I2C1 at 0x76 then 0x77.
|
||||||
* Returns chip_id (0x58=BMP280, 0x60=BME280) on success, negative if not found.
|
* Returns chip_id (0x58=BMP280, 0x60=BME280) on success, negative if not found.
|
||||||
* Requires i2c1_init() to have been called first.
|
* 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);
|
int bmp280_init(void);
|
||||||
void bmp280_read(int32_t *pressure_pa, int16_t *temp_x10);
|
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. */
|
/* Convert pressure (Pa) to altitude above sea level (cm), ISA p0=101325 Pa. */
|
||||||
int32_t bmp280_pressure_to_alt_cm(int32_t pressure_pa);
|
int32_t bmp280_pressure_to_alt_cm(int32_t pressure_pa);
|
||||||
|
|
||||||
|
|||||||
78
src/bmp280.c
78
src/bmp280.c
@ -3,20 +3,33 @@
|
|||||||
*
|
*
|
||||||
* Probes 0x76 first, then 0x77. Requires i2c1_init() before bmp280_init().
|
* Probes 0x76 first, then 0x77. Requires i2c1_init() before bmp280_init().
|
||||||
* Returns chip_id on success (0x58=BMP280, 0x60=BME280), negative if absent.
|
* 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 "bmp280.h"
|
||||||
#include "i2c1.h"
|
#include "i2c1.h"
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
/* Shift I2C address for HAL (7-bit left-shifted) */
|
|
||||||
static uint16_t s_addr;
|
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 uint16_t dig_T1;
|
||||||
static int16_t dig_T2, dig_T3;
|
static int16_t dig_T2, dig_T3;
|
||||||
static uint16_t dig_P1;
|
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 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) {
|
static uint8_t i2c_read(uint8_t reg) {
|
||||||
uint8_t val = 0;
|
uint8_t val = 0;
|
||||||
@ -36,8 +49,9 @@ static int try_init(uint16_t addr) {
|
|||||||
s_addr = addr;
|
s_addr = addr;
|
||||||
uint8_t id = i2c_read(0xD0);
|
uint8_t id = i2c_read(0xD0);
|
||||||
if (id != 0x58 && id != 0x60) return -(int)id;
|
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];
|
uint8_t cal[26];
|
||||||
i2c_read_burst(0x88, cal, 26);
|
i2c_read_burst(0x88, cal, 26);
|
||||||
dig_T1 = (uint16_t)(cal[1] << 8 | cal[0]);
|
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_P8 = (int16_t) (cal[21] << 8 | cal[20]);
|
||||||
dig_P9 = (int16_t) (cal[23] << 8 | cal[22]);
|
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(0xF5, 0x00); /* config: standby=0.5ms, filter=off */
|
||||||
i2c_write(0xF4, 0xB7); /* ctrl_meas: osrs_t=×16, osrs_p=×16, normal mode */
|
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_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));
|
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 v1 = ((((adc_T >> 3) - ((int32_t)dig_T1 << 1))) * ((int32_t)dig_T2)) >> 11;
|
||||||
int32_t v2 = (((((adc_T >> 4) - (int32_t)dig_T1) *
|
int32_t v2 = (((((adc_T >> 4) - (int32_t)dig_T1) *
|
||||||
((adc_T >> 4) - (int32_t)dig_T1)) >> 12) * (int32_t)dig_T3) >> 14;
|
((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;
|
*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) {
|
int32_t bmp280_pressure_to_alt_cm(int32_t pressure_pa) {
|
||||||
/* Barometric formula: h = 44330 * (1 - (p/p0)^(1/5.255)) metres */
|
/* Barometric formula: h = 44330 * (1 - (p/p0)^(1/5.255)) metres */
|
||||||
float ratio = (float)pressure_pa / 101325.0f;
|
float ratio = (float)pressure_pa / 101325.0f;
|
||||||
|
|||||||
18
src/main.c
18
src/main.c
@ -130,10 +130,11 @@ int main(void) {
|
|||||||
motor_driver_init(&motors);
|
motor_driver_init(&motors);
|
||||||
|
|
||||||
/* Probe I2C1 for optional sensors — skip gracefully if not found */
|
/* 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;
|
mag_type_t mag_type = MAG_NONE;
|
||||||
if (i2c1_init() == 0) {
|
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();
|
mag_type = mag_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,12 +251,21 @@ int main(void) {
|
|||||||
n = snprintf(p, rem, ",\"hd\":-1"); /* not ready */
|
n = snprintf(p, rem, ",\"hd\":-1"); /* not ready */
|
||||||
p += n; rem -= n;
|
p += n; rem -= n;
|
||||||
}
|
}
|
||||||
if (baro_ok) {
|
if (baro_chip) {
|
||||||
int32_t pres_pa; int16_t temp_x10;
|
int32_t pres_pa; int16_t temp_x10;
|
||||||
bmp280_read(&pres_pa, &temp_x10);
|
bmp280_read(&pres_pa, &temp_x10);
|
||||||
int32_t alt_cm = bmp280_pressure_to_alt_cm(pres_pa);
|
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;
|
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");
|
n = snprintf(p, rem, "}\n");
|
||||||
len = (int)(p + n - buf);
|
len = (int)(p + n - buf);
|
||||||
|
|||||||
@ -53,6 +53,9 @@
|
|||||||
<div class="stat"><span class="label">MOTOR</span> <span class="val" id="v-motor">--</span></div>
|
<div class="stat"><span class="label">MOTOR</span> <span class="val" id="v-motor">--</span></div>
|
||||||
<div class="stat" id="row-hdg" style="display:none"><span class="label">HEADING</span> <span class="val" id="v-hdg">--</span>°</div>
|
<div class="stat" id="row-hdg" style="display:none"><span class="label">HEADING</span> <span class="val" id="v-hdg">--</span>°</div>
|
||||||
<div class="stat" id="row-alt" style="display:none"><span class="label">ALT</span> <span class="val" id="v-alt">--</span> m</div>
|
<div class="stat" id="row-alt" style="display:none"><span class="label">ALT</span> <span class="val" id="v-alt">--</span> m</div>
|
||||||
|
<div class="stat" id="row-temp" style="display:none"><span class="label">TEMP</span> <span class="val" id="v-temp">--</span> °C</div>
|
||||||
|
<div class="stat" id="row-hum" style="display:none"><span class="label">HUMIDITY</span> <span class="val" id="v-hum">--</span> %</div>
|
||||||
|
<div class="stat" id="row-pres" style="display:none"><span class="label">PRESSURE</span> <span class="val" id="v-pres">--</span> hPa</div>
|
||||||
<div class="stat"><span class="label">HZ</span> <span class="val" id="v-hz">--</span></div>
|
<div class="stat"><span class="label">HZ</span> <span class="val" id="v-hz">--</span></div>
|
||||||
<div>
|
<div>
|
||||||
<button class="btn" id="connect-btn" onclick="toggleSerial()">CONNECT USB</button>
|
<button class="btn" id="connect-btn" onclick="toggleSerial()">CONNECT USB</button>
|
||||||
@ -212,6 +215,18 @@ window.updateIMU = function(data) {
|
|||||||
document.getElementById('row-alt').style.display = '';
|
document.getElementById('row-alt').style.display = '';
|
||||||
document.getElementById('v-alt').textContent = (data.alt / 100.0).toFixed(1);
|
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°
|
// Pitch bar: center at 50%, ±90°
|
||||||
document.getElementById('bar-pitch').style.width = ((pitch + 90) / 180 * 100) + '%';
|
document.getElementById('bar-pitch').style.width = ((pitch + 90) / 180 * 100) + '%';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user