Embedded HandbookApps
All sections › XI. Sensors: connection, circuits, drivers
STM32F103, STM32F407 · level intermediate · 8 min read
Open in the interactive handbook

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

BME280 on the I²C bus: CSB selects I²C, SDO sets the address

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:

Altitude from pressure: the same reading, but the weather shifted p0 by 30 hPa — and the altitude "drifted" by 245 m


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

Next: section 99 "Motion: MPU-6050 and ADXL345" — acceleration and angular rate, scales, zero calibration and tilt from the accelerometer.

Open in the interactive handbook