BME280 Sensing Principle and Output Format
The Bosch BME280 is a combined environmental sensor measuring temperature (-40 to +85°C), relative humidity (0-100%), and barometric pressure (300-1100 hPa) in a single 2.5 × 2.5 mm LGA package. Temperature is measured via a bandgap reference circuit, pressure via a piezoresistive membrane, and humidity via a polymer dielectric capacitor. The sensor's internal ADC digitizes each reading into uncompensated raw values (20-bit for pressure, 20-bit for temperature, 16-bit for humidity) stored in data registers 0xF7 through 0xFE.
The output is strictly digital — either I2C (default address 0x76 or 0x77) or SPI. There is no analog voltage output. You cannot simply read a voltage with a multimeter and derive a physical value. Every meaningful reading requires fetching the raw ADC counts, then applying the factory-programmed calibration coefficients stored in registers 0x88-0xA1 and 0xE1-0xE7. Without these 26 calibration bytes, the raw numbers are meaningless. A typical BME280 breakout (Adafruit product 2652, SparkFun SEN-13676) costs $8-$15 in 2026 and includes the required pull-up resistors and level shifting.
Wiring Pinout and Electrical Specifications
The table below covers the standard 6-pin breakout configuration for I2C mode with an ESP32 DevKit V1. Supply range is 1.71V to 3.6V — never connect VCC to 5V on a bare module without a regulator.
| BME280 Pin | ESP32 Pin | Function | Voltage Level | Notes |
|---|---|---|---|---|
| VIN / VCC | 3V3 | Power supply | 3.3V (1.71-3.6V range) | Draws ~3.6 µA in sleep, 3.6 mA peak during measurement |
| GND | GND | Ground reference | 0V | Common ground required; star-ground for noise immunity |
| SCL | GPIO 22 | I2C clock | 3.3V logic | 4.7 kΩ pull-up to 3.3V (often on breakout already) |
| SDA | GPIO 21 | I2C data | 3.3V logic | 4.7 kΩ pull-up to 3.3V; max bus capacitance 400 pF |
| CSB | 3V3 (tie high) | Chip select / I2C addr bit | 3.3V | High = addr 0x76; Low = addr 0x77; leave floating for 0x76 |
| SDO | Not connected | SPI data out | N/A in I2C mode | Leave unconnected for I2C; acts as SPI MISO if using SPI |
Raw ADC to Physical Unit Conversion Math
The BME280 does not output degrees Celsius or hPa directly. It outputs a 20-bit unsigned integer for temperature (adc_T) that must be compensated using three factory coefficients: dig_T1 (unsigned 16-bit), dig_T2 (signed 16-bit), and dig_T3 (signed 16-bit). The Bosch BME280 datasheet (section 4.2.3) defines the compensation algorithm as follows:
// Temperature compensation (returns value in 0.01°C units)
var1 = (adc_T / 16384.0 - dig_T1 / 1024.0) * dig_T2;
var2 = ((adc_T / 131072.0 - dig_T1 / 8192.0) *
(adc_T / 131072.0 - dig_T1 / 8192.0)) * dig_T3;
t_fine = var1 + var2; // This value is also used by pressure calc
temperature_C = (var1 + var2) / 5120.0;
For pressure, the algorithm uses 9 coefficients (dig_P1 through dig_P9) and the t_fine value from temperature compensation. The output is a 32-bit integer in Q24.8 fixed-point format representing Pa. Divide by 256 to get Pa, then divide by 100 for hPa (millibars). Humidity uses 6 coefficients (dig_H1 through dig_H6) and produces a 32-bit value in Q22.10 format; divide by 1024.0 for %RH.
| Parameter | Raw ADC Bits | Typical Raw Value (25°C, 1013 hPa, 50% RH) | Physical Output | Resolution |
|---|---|---|---|---|
| Temperature | 20-bit unsigned | ~509,000 | 25.00 °C | 0.01 °C |
| Pressure | 20-bit unsigned | ~338,000 | 1013.25 hPa | 0.18 Pa (indoor) |
| Humidity | 16-bit unsigned | ~28,400 | 50.00 %RH | 0.008 %RH |
| Altitude (derived) | N/A (calculated) | N/A | ~0 m (at sea level QNH) | ~0.2 m per 0.02 hPa |
ESP32 Arduino Code with Forced Mode Sampling
Use forced mode (one-shot measurement) instead of continuous mode to minimize self-heating, which can skew temperature readings by +1 to +3°C. The code below uses the Adafruit BME280 Library (v2.2.4+), which handles calibration internally.
#include <Wire.h>
#include <Adafruit_BME280.h>
Adafruit_BME280 bme;
// ESP32 DevKit V1 default I2C pins
#define SDA_PIN 21
#define SCL_PIN 22
#define I2C_FREQ 400000 // 400 kHz Fast mode
void setup() {
Serial.begin(115200);
delay(100); // Let serial stabilize
Wire.begin(SDA_PIN, SCL_PIN, I2C_FREQ);
if (!bme.begin(0x76, &Wire)) {
Serial.println("BME280 not found at 0x76. Check wiring and CSB pin.");
while (1) delay(10);
}
// Forced mode: sensor sleeps between readings (minimizes self-heating)
bme.setSampling(Adafruit_BME280::MODE_FORCED,
Adafruit_BME280::SAMPLING_X1, // Temp oversampling
Adafruit_BME280::SAMPLING_X16, // Pressure oversampling
Adafruit_BME280::SAMPLING_X1, // Humidity oversampling
Adafruit_BME280::FILTER_OFF,
Adafruit_BME280::STANDBY_MS_1000);
Serial.println("BME280 initialized. Forced mode, 10s interval.");
}
void loop() {
bme.takeForcedMeasurement(); // Triggers single measurement cycle
float temp_c = bme.readTemperature(); // °C, already compensated
float pressure_hpa = bme.readPressure() / 100.0F; // Pa → hPa
float humidity_rh = bme.readHumidity(); // %RH, already compensated
// Altitude calculation requires local QNH (sea-level pressure)
// Standard atmosphere: 1013.25 hPa. Replace with METAR value for accuracy.
float altitude_m = 44330.0 * (1.0 - pow(pressure_hpa / 1013.25, 0.1903));
Serial.printf("T: %.2f °C | P: %.2f hPa | RH: %.1f %% | Alt: %.1f m\n",
temp_c, pressure_hpa, humidity_rh, altitude_m);
delay(10000); // 10-second interval; sensor sleeps in between
}
Common Interference Sources and Mitigation
The BME280 is sensitive to three interference categories that produce systematic errors, not random noise:
1. Self-heating from nearby components. The ESP32's WiFi radio draws 180-240 mA during TX bursts, heating the PCB by 5-10°C. If the BME280 is mounted less than 2 cm from the ESP32 module, temperature readings will drift +1 to +4°C above ambient. Fix: mount the sensor on a separate daughterboard connected by a 10-15 cm 4-wire cable, or use forced mode with 60-second intervals to let thermal equilibrium settle.
2. Soldering flux residue on the humidity sensing element. The BME280's humidity sensor is an exposed polymer capacitor on the top of the package. No-clean flux or isopropyl alcohol residue absorbs moisture and causes readings to peg at 95-100% RH permanently. Fix: never wash the sensor with solvents. If contamination occurs, bake at 60°C for 12 hours to drive off volatiles — recovery is approximately 80% effective per Bosch application note AN022.
3. I2C bus noise from long wires or shared buses. At wire lengths exceeding 30 cm, the 400 pF bus capacitance limit is violated, causing ACK failures and corrupted calibration registers (which silently produce garbage readings rather than I2C errors). Fix: reduce clock to 100 kHz for runs over 20 cm, use twisted-pair for SDA/SCL, and add a dedicated I2C bus buffer like the PCA9600 for runs over 1 meter.
| Source | Symptom | Measurement Threshold | Fix |
|---|---|---|---|
| ESP32 WiFi self-heat | Temperature reads 2-4°C high | ΔT > 1.5°C vs reference at 10 cm | Remote-mount sensor ≥10 cm away; forced mode with 30s+ interval |
| Flux on humidity element | RH pegged at 95-100% | RH > 95% in dry (<40% RH) environment | Bake 60°C / 12 hrs; prevent future contamination with kapton tape during soldering |
| Long I2C wires (>30 cm) | Random NaN readings, wrong values | I2C errors > 1 per 1000 reads | Drop to 100 kHz; add 2.2 kΩ pull-ups; use PCA9600 bus extender |
| Direct sunlight / radiant heat | Temperature spikes +5 to +15°C | Reading varies with sensor orientation | Radiation shield (Stevenson screen); ventilated enclosure with 0.5 m/s airflow |
For weather station deployments, the WMO Guide to Meteorological Instruments specifies temperature accuracy of ±0.3°C and humidity ±3% RH. The BME280 meets temperature specs at ±1.0°C typical (±0.5°C best case at 25°C) — adequate for hobby stations but not research-grade without individual unit calibration against a reference.






