Built-in project · ESP32-C3-DevKitM-1

Two barometers on an ESP32-C3

A BME280 and a BMP280 on one I2C bus, told apart by their address straps and their chip ids. Raw counts, a factory calibration, and the datasheet sums in integers. The whole build, an ESP32-C3 and 2 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Intermediate About 20 minutes to build and run. Runs in your browser. Free, and no account needed.

The parts retail for about $18.50. On Mokxi they're free.

The circuit runs here once the page has loaded.

What you'll learn

  • Two I2C devices on one bus, told apart by address
  • Chip id registers for telling similar parts apart
  • Why sensors need calibration math
  • Altitude from air pressure
  • Printing fixed-point numbers as decimals
  • Difficulty: Intermediate
  • Time: About 20 minutes
  • Board: ESP32-C3-DevKitM-1
  • Parts: 4

How it works

A BME280 and a BMP280 share the same two I2C wires on an ESP32-C3: SDA on GPIO 8 and SCL on GPIO 9. They can share because their address straps differ. The BME280 is at 0x76 and the BMP280 is at 0x77.

At start-up the sketch checks each address and reports which chip answers. The BME280 also measures humidity. The BMP280 does not.

Every second it prints temperature, pressure in hPa and altitude for each chip. The BME280 line adds %RH. The raw readings are corrected with each chip's own factory calibration.

Parts list

4 parts, plus the jumper wires. They retail for about $18.50; on Mokxi they're free.

Wiring table

Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.

Part and pinConnects to
Pressure, humidity and temperature, BME280 pin VINESP32-C3-DevKitM-1 pin 3V3
Pressure and temperature, BMP280 pin VINESP32-C3-DevKitM-1 pin 3V3
Pressure, humidity and temperature, BME280 pin GNDESP32-C3-DevKitM-1 pin GND
Pressure and temperature, BMP280 pin GNDESP32-C3-DevKitM-1 pin GND
Pressure, humidity and temperature, BME280 pin SDAESP32-C3-DevKitM-1 pin 8
Pressure and temperature, BMP280 pin SDAESP32-C3-DevKitM-1 pin 8
Pressure, humidity and temperature, BME280 pin SCLESP32-C3-DevKitM-1 pin 9
Pressure and temperature, BMP280 pin SCLESP32-C3-DevKitM-1 pin 9

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · ESP32-C3-DevKitM-1
// Two pressure sensors on one I2C bus: a BME280 at 0x76 and a BMP280 at 0x77,
// on the ESP32-C3's own I2C pins, GPIO 8 (SDA) and GPIO 9 (SCL).
//
// The two chips are the same die, less the humidity sensor on the BMP280, and
// on their purple boards they look identical. The address strap is what lets
// both share one bus, and the chip-id register, 0x60 or 0x58, is how the
// sketch tells which is which.
//
// Neither reports degrees or pascals. Each reports raw counts and carries its
// own factory calibration, and the datasheet's integer formulas turn one into
// the other: mokxi_bme280.h is those formulas. The temperature is always
// worked first, because the pressure and humidity formulas need a by-product
// of it. The altitude is the barometric formula against 1013.25 hPa, worked in
// integers too.
//
// Drag the pressure sliders: every 12 hPa or so is a hundred meters.

#include <Wire.h>
#include "mokxi_bme280.h"

MokxiBme280 sensor[2];
const uint8_t ADDRESS[2] = {0x76, 0x77};

// A whole number of hundredths (or tenths) printed with its decimal point.
void printFixed(long value, long scale) {
  if (value < 0) {
    Serial.print('-');
    value = -value;
  }
  Serial.print(value / scale);
  Serial.print('.');
  long rest = value % scale;
  for (long digit = scale / 10; digit > 1 && rest < digit; digit /= 10) Serial.print('0');
  Serial.print(rest);
}

void setup() {
  Serial.begin(9600);
  Wire.begin();
  for (int i = 0; i < 2; i++) {
    Serial.print("0x");
    Serial.print(ADDRESS[i], HEX);
    if (!sensor[i].begin(&Wire, ADDRESS[i])) {
      Serial.println(": nothing there");
      continue;
    }
    Serial.println(sensor[i].humid() ? ": BME280, id 0x60" : ": BMP280, id 0x58");
    // Normal mode, 2x temperature, 16x pressure, 1x humidity, filter 4.
    sensor[i].configure(0x01, (0x02 << 5) | (0x05 << 2) | 0x03, 0x02 << 2);
  }
}

void loop() {
  delay(1000);
  for (int i = 0; i < 2; i++) {
    if (sensor[i].id() == 0) continue;
    int32_t t, p, h;
    if (!sensor[i].readRaw(t, p, h)) continue;
    long hundredths = sensor[i].temperature100(t);  // first: it sets t_fine
    long pascals = (long)sensor[i].pressurePa(p);
    Serial.print(sensor[i].humid() ? "BME280 " : "BMP280 ");
    printFixed(hundredths, 100);
    Serial.print(" C  ");
    printFixed(pascals, 100);
    Serial.print(" hPa  ");
    printFixed(mokxi_altitude_cm((uint32_t)pascals, 101325u) / 10, 10);
    Serial.print(" m");
    if (sensor[i].humid()) {
      Serial.print("  ");
      printFixed((long)sensor[i].humidity1024(h) * 10 / 1024, 10);
      Serial.print(" %RH");
    }
    Serial.println();
  }
}

Code walkthrough

The key functions of the sketch, and what each one does.

printFixed sketch.ino

Prints a whole number as a decimal. value is hundredths or tenths, and scale is 100 or 10. It handles a minus sign and pads leading zeros after the point, so 5 hundredths prints as .05.

// A whole number of hundredths (or tenths) printed with its decimal point.
void printFixed(long value, long scale) {
  if (value < 0) {
    Serial.print('-');
    value = -value;
  }
  Serial.print(value / scale);
  Serial.print('.');
  long rest = value % scale;
  for (long digit = scale / 10; digit > 1 && rest < digit; digit /= 10) Serial.print('0');
  Serial.print(rest);
}

setup sketch.ino

Starts serial at 9600 and I2C. For each address it tries begin, prints that address, and says if nothing is there. Otherwise it reports BME280 or BMP280 by whether the chip has humidity, then sets up the measurement options.

void setup() {
  Serial.begin(9600);
  Wire.begin();
  for (int i = 0; i < 2; i++) {
    Serial.print("0x");
    Serial.print(ADDRESS[i], HEX);
    if (!sensor[i].begin(&Wire, ADDRESS[i])) {
      Serial.println(": nothing there");
      continue;
    }
    Serial.println(sensor[i].humid() ? ": BME280, id 0x60" : ": BMP280, id 0x58");
    // Normal mode, 2x temperature, 16x pressure, 1x humidity, filter 4.
    sensor[i].configure(0x01, (0x02 << 5) | (0x05 << 2) | 0x03, 0x02 << 2);
  }
}

loop sketch.ino

Once a second, for each chip that answered, it reads raw counts. It works temperature first, because pressure needs a value from it. Then it prints temperature, pressure and altitude, and humidity for the BME280.

void loop() {
  delay(1000);
  for (int i = 0; i < 2; i++) {
    if (sensor[i].id() == 0) continue;
    int32_t t, p, h;
    if (!sensor[i].readRaw(t, p, h)) continue;
    long hundredths = sensor[i].temperature100(t);  // first: it sets t_fine
    long pascals = (long)sensor[i].pressurePa(p);
    Serial.print(sensor[i].humid() ? "BME280 " : "BMP280 ");
    printFixed(hundredths, 100);
    Serial.print(" C  ");
    printFixed(pascals, 100);
    Serial.print(" hPa  ");
    printFixed(mokxi_altitude_cm((uint32_t)pascals, 101325u) / 10, 10);
    Serial.print(" m");
    if (sensor[i].humid()) {
      Serial.print("  ");
      printFixed((long)sensor[i].humidity1024(h) * 10 / 1024, 10);
      Serial.print(" %RH");
    }
    Serial.println();
  }
}

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • "nothing there" for one address This project
    Check SDA, SCL, power and ground on that chip. This sketch looks at 0x76 and 0x77 only.
  • Both chips report the same address This project
    Two devices on one bus must have different addresses. One chip must use 0x76 and the other 0x77.
  • Humidity is missing for a chip This project
    That is expected for the BMP280. Only the BME280 measures humidity.
  • The sketch cannot find the sensor, or reads zeros. Pressure
    The I2C address is 0x76 when SDO is tied to GND and 0x77 when tied to VCC. Run an I2C scanner, and make sure the library address matches. Wire SDA and SCL correctly.
  • Humidity always reads zero or looks wrong. Pressure
    A BMP280 looks the same but has no humidity sensor. Check the chip ID register: 0x60 is a BME280 and 0x58 is a BMP280. Use the right library for yours.
  • The sensor is not found over I2C. Pressure and temperature
    The address is 0x76 or 0x77, set by the SDO pin. Try both in your library call. Power it at 3.3 V, since many modules are not 5 V safe.
  • The altitude reading drifts or is wrong. Pressure and temperature
    Altitude is computed from sea-level pressure. Set that value to the local pressure for your day. Temperature may read warm if the board self heats.
  • Upload fails or the board is not found by the computer. ESP32-C3-DevKitM-1
    Pick the ESP32-C3 board in your tools and the right port. If it still fails, hold BOOT, tap RESET, then release BOOT to enter download mode and try again.

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.