98. Pressure and altitude: BMP280 and BME280
A pressure sensor returns raw ADC codes, and you can turn them into pascals only with coefficients that the factory wrote into the memory of each individual part. The manufacturer's formulas are integer, pressure is in 64 bits, and they contain a spot where a careful port to C turns into undefined behavior. From pressure you get altitude — with a trap that is off by hundreds of meters. The BME280 adds humidity with its own, oddly packed coefficients.
I²C connection and pull-ups are in section 95, humidity in general is in section 97.
Terms used in the documentation
Term Meaning trimming parameters, calibration data calibration coefficients written at the factory compensation formula the formula that turns a code into a physical value t_fine "fine" temperature — an intermediate value for pressure and humidity oversampling several measurements per output value IIR filter a digital filter with infinite impulse response forced mode, normal mode single and continuous measurement chip ID the model code in register 0xD0 burst read reading several consecutive registers in one transaction sea-level pressure, QNH pressure reduced to sea level Q24.8 a number with 24 integer bits and 8 fractional bits
98.1 BMP280 or BME280
| BMP280 | BME280 | |
|---|---|---|
| measures | pressure and temperature | humidity as well |
| code in register 0xD0 | 0x58 | 0x60 |
| I²C address | 0x76 or 0x77 — set by the SDO pin | the same |
Modules of both look the same, and they are often sold under the other's name. So the first read after power-up is register 0xD0:
#define BMX_REG_CALIB 0x88u /* 24 байта калибровки T и P: 0x88…0x9F */
#define BMX_REG_ID 0xD0u /* 0x58 — BMP280, 0x60 — BME280 */
#define BMX_REG_CTRL 0xF4u /* osrs_t[7:5], osrs_p[4:2], mode[1:0] */
#define BMX_REG_DATA 0xF7u /* давление, температура (и влажность у BME280) подряд */
#define BMP280_ID 0x58u
#define BME280_ID 0x60u
98.2 Calibration: 24 bytes of its own in every part
The coefficients are read once at startup — 24 bytes from address 0x88, least significant byte first; T1 and P1 are unsigned, the rest are signed:
typedef struct {
uint16_t t1;
int16_t t2, t3;
uint16_t p1;
int16_t p2, p3, p4, p5, p6, p7, p8, p9;
} bmx_calib_t;
void bmx_parse(const uint8_t raw[24], bmx_calib_t *c)
{
c->t1 = u16le(raw);
c->t2 = (int16_t)u16le(raw + 2);
c->t3 = (int16_t)u16le(raw + 4);
c->p1 = u16le(raw + 6);
c->p2 = (int16_t)u16le(raw + 8);
c->p3 = (int16_t)u16le(raw + 10);
c->p4 = (int16_t)u16le(raw + 12);
c->p5 = (int16_t)u16le(raw + 14);
c->p6 = (int16_t)u16le(raw + 16);
c->p7 = (int16_t)u16le(raw + 18);
c->p8 = (int16_t)u16le(raw + 20);
c->p9 = (int16_t)u16le(raw + 22);
}
The rest of the section uses the set from the example in the documentation: T1 = 27504, T2 = 26435, T3 = −1000, P1 = 36477, P2 = −10685, P3 = 3024, P4 = 2855, P5 = 140, P6 = −7, P7 = 15500, P8 = −14600, P9 = 6000. A program on the PC wrote them out as bytes and parsed them back with this function — the values matched.
98.3 Codes: one burst read
The result starts at address 0xF7: three bytes of pressure, three bytes of temperature (and two bytes of humidity on the BME280). Read them in one transaction: the sensor updates the registers between measurements, and with two reads it is easy to get pressure from one measurement and temperature from another. The code is 20 bits:
int32_t bmx_adc20(const uint8_t b[3])
{
return (int32_t)((uint32_t)b[0] << 12 | (uint32_t)b[1] << 4 | b[2] >> 4);
}
The bytes 65 5A C0 7E ED 00 give pressure code 415148 and temperature code 519888.
Register 0xF4 sets the mode: temperature ×1, pressure ×16, continuous
measurement — byte 0x37:
#define BMX_CTRL(osrs_t, osrs_p, mode) ((uint8_t)((osrs_t) << 5 | (osrs_p) << 2 | (mode)))
On the BME280 humidity oversampling is in a separate register 0xF2
(ctrl_hum, bits 2–0), and a new value takes effect only after
a write to 0xF4. The order: first 0xF2 — for example, 0x01, humidity ×1, —
then 0xF4. After reset 0xF2 holds zero: humidity is not measured, and its two
bytes hold 0x8000.
98.4 Compensation: temperature first
Temperature is computed first: besides degrees it gives t_fine — a correction
without which pressure and humidity cannot be computed.
int32_t bmx_temp(const bmx_calib_t *c, int32_t adc_t, int32_t *t_fine)
{
int32_t v1 = (((adc_t >> 3) - ((int32_t)c->t1 << 1)) * c->t2) >> 11;
int32_t v2 = (((((adc_t >> 4) - c->t1) * ((adc_t >> 4) - c->t1)) >> 12) * c->t3) >> 14;
*t_fine = v1 + v2;
return (*t_fine * 5 + 128) >> 8;
}
uint32_t bmx_press(const bmx_calib_t *c, int32_t adc_p, int32_t t_fine)
{
int64_t v1 = (int64_t)t_fine - 128000;
int64_t v2 = v1 * v1 * c->p6;
v2 += v1 * c->p5 * 131072; /* · 2^17 */
v2 += (int64_t)c->p4 * 34359738368; /* · 2^35 */
v1 = ((v1 * v1 * c->p3) >> 8) + v1 * c->p2 * 4096;
v1 = (((int64_t)1 << 47) + v1) * c->p1 >> 33;
if (v1 == 0)
return 0; /* деление на ноль */
int64_t p = 1048576 - adc_p;
p = ((p * 2147483648 - v2) * 3125) / v1; /* · 2^31 */
v1 = ((int64_t)c->p9 * (p >> 13) * (p >> 13)) >> 25;
v2 = ((int64_t)c->p8 * p) >> 19;
p = ((p + v1 + v2) >> 8) + (int64_t)c->p7 * 16;
return (uint32_t)p;
}
With the example set: temperature 2508 — 25.08 °C, t_fine = 128422;
pressure 25767233 in Q24.8 format, that is 25767233 / 256 = 100653.25 Pa.
The same manufacturer formulas in floating point give the same pressure
with a difference of less than a hundredth of a pascal.
Where the formula breaks when ported. In the original the multipliers 2¹⁷, 2³⁵, 2¹²,
2³¹ and 16 are written as left shifts, and the shifted values can be negative.
A left shift of a negative number in C is undefined behavior: today
the compiler does what you expect, tomorrow at another optimization level it
is not obliged to. So here it is multiplication: the code comes out the same, but well defined.
The right shifts are kept — for negative numbers they are implementation-defined,
and in armclang and gcc they are arithmetic.
A 64-bit multiplication and division on Cortex-M3 is a library call, not a single instruction; once per measurement this does not matter.
98.5 Altitude and its trap
Altitude relative to the level where the pressure is p0 comes from the barometric formula:
double altitude_m(double p_pa, double p0_pa)
{
return 44330.0 * (1.0 - pow(p_pa / p0_pa, 1.0 / 5.255));
}
100653.25 Pa at p0 = 101325 Pa is 56.08 m, and near sea level one hectopascal is 8.33 m of altitude. That is the trap: sea-level pressure changes with the weather by tens of hectopascals, and if you keep p0 constant, a change of 30 hPa moves the altitude by 245.1 m. Hence two correct approaches:
- absolute altitude — take p0 from the weather service or set it at a place with a known altitude;
- change in altitude — a floor, a climb, a flight — is computed from the pressure at the start, and then the weather over minutes barely interferes.
98.6 BME280: humidity and a shared nibble
Humidity has its own coefficients: H1 is byte 0xA1, H2…H6 are seven bytes from 0xE1. H4 and H5 are 12-bit numbers, and they share the low nibble — byte 0xE5:
void bme_parse_h(uint8_t a1, const uint8_t e1[7], bme_hcalib_t *h)
{
h->h1 = a1;
h->h2 = (int16_t)u16le(e1);
h->h3 = e1[2];
h->h4 = (int16_t)((int8_t)e1[3] * 16 | (e1[4] & 0x0F));
h->h5 = (int16_t)((int8_t)e1[5] * 16 | e1[4] >> 4);
h->h6 = (int8_t)e1[6];
}
The seven bytes 6A 01 00 13 29 03 1E give H2 = 362, H3 = 0, H4 = 313, H5 = 50,
H6 = 30; with H1 = 75, humidity code 30000 and t_fine from 98.4 the integer formula
gives 56317 in Q22.10 format — 54.997 %, the floating-point formula gives
55.001 %.
uint32_t bme_hum(const bme_hcalib_t *h, int32_t adc_h, int32_t t_fine)
{
int32_t v = t_fine - 76800;
v = (((adc_h * 16384 - (int32_t)h->h4 * 1048576 - h->h5 * v) + 16384) >> 15)
* (((((((v * h->h6) >> 10) * (((v * h->h3) >> 11) + 32768)) >> 10) + 2097152) * h->h2 + 8192) >> 14);
v -= ((((v >> 15) * (v >> 15)) >> 7) * h->h1) >> 4;
v = v < 0 ? 0 : v > 419430400 ? 419430400 : v;
return (uint32_t)(v >> 12);
}
98.7 Pitfalls
Meaningless pressure with a correct circuit. The calibration was not read or was parsed with the wrong byte order — the least significant byte comes first.
The BME280 gives no humidity. It is a BMP280 module: register 0xD0 holds 0x58. Check the model code before working with it.
The BME280 returns humidity 0x8000. 0xF2 was not written or was written after 0xF4 — a new value takes effect only with a write to 0xF4.
Altitude drifts by hundreds of meters over a day. Sea-level pressure changes with the weather: 30 hPa is 245.1 m. Take p0 from the weather service or compute from the start.
Pressure and temperature from different measurements. They were read in two transactions. Read six bytes from 0xF7 in one burst, eight on the BME280, together with humidity.
H4 and H5 are garbage. The fact that they share byte 0xE5 was ignored.
The formula "breaks" at another optimization level. A left shift of a negative number is undefined behavior. Write it as a multiplication.
98.8 Summary
- An ADC code without calibration means nothing: 24 bytes from 0x88 (on the BME280 also 0xA1 and 0xE1…0xE7) are read at startup, least significant byte first.
- Temperature first: it gives
t_finefor pressure and humidity. - Pressure is in 64 bits, in Q24.8 format: 25767233 / 256 = 100653.25 Pa.
- The model is in register 0xD0: 0x58 is BMP280, 0x60 is BME280.
- On the BME280 humidity is enabled separately: 0xF2 before 0xF4, otherwise its code is 0x8000.
- One hectopascal near sea level is 8.33 m, but p0 changes with the weather: 30 hPa gives 245.1 m.
- A left shift of a negative number in C is undefined — in the compensation formulas it is replaced with multiplication.
Next: section 99 "Motion: MPU-6050 and ADXL345" — acceleration and angular rate, scales, zero calibration and tilt from the accelerometer.