Mirko Pavleski
Published © GPL3+

DIY Digital Barograph with BME280 and ESP32

This is a modern digital interpretation of a traditional barograph that displays real-time atmospheric pressure trends over a 24-hour period

BeginnerFull instructions provided2 hours237
DIY Digital Barograph with BME280 and ESP32

Things used in this project

Hardware components

Espressif ESP32 Development Board - Developer Edition
Espressif ESP32 Development Board - Developer Edition
×1
SparkFun Atmospheric Sensor Breakout - BME280
SparkFun Atmospheric Sensor Breakout - BME280
×1
ST7920 LCD 128X64
×1
Pushbutton Switch, Momentary
Pushbutton Switch, Momentary
×3
Trimmer Potentiometer, 10 kohm
Trimmer Potentiometer, 10 kohm
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free

Story

Read more

Schematics

Schematic

...

Code

Code

C/C++
...
/*
***Barometer/Barograph***
Components:
- ESP32 microcontroller (esp32 devkit v1)
- 128x64 LCD display
- BME280 pressure, temperature and humidity sensor

Features:
* Pressure change graph for the last 24 hours (measurement interval: 11 minutes 15 seconds)
* Temperature display
* Humidity display
* Pressure measurement history storage (to protect against graph reset during power loss)
* Ability to check all sensor readings
* Ability to adjust the pressure range displayed on screen (min and max)
*/

#include <U8g2lib.h>               // U8g2 library for graphical displays
#include <Wire.h>                  // Wire library for I2C
#include <Adafruit_Sensor.h>       // Common sensor library
#include <Adafruit_BME280.h>       // BME280 sensor library
#include <Preferences.h>           // For saving settings to memory (pressure array)
#include <EncButton.h>             // Button library by Gyver

#define BUFFER_SIZE 128            // Pressure buffer size
#define MIN_PRES 985              // Minimum pressure in hPa
#define PRESSURE_RANGE 50         // Pressure range in hPa (max = MIN_PRES + PRESSURE_RANGE)
#define ALTITUDE 700.0            // Set your location's altitude in meters here
#define EEPROM_MARKER 0xABCE      // Unique marker (for detecting first save)
#define BUTTON1_PIN 13
#define BUTTON2_PIN 12
#define BUTTON3_PIN 14 
#define BUTTON_PIN 16 // Button pin
#define USE_MEMORY 

// Use memory to store pressure array

Button b1(BUTTON1_PIN);
Button b2(BUTTON2_PIN);
Button b3(BUTTON3_PIN);

char pressureDiff1h[10];      // For -1h difference
char pressureDiff3h[10];      // For -3h difference

float hourlyPressureBuffer[6]; // Store actual pressure values for the last hour
int pressureBufferIndex = 0;
unsigned long lastPressureStoreTime = 0;


/*Required objects*/
Preferences preferences;                                                             // Object for working with persistent memory
class RingBuffer {                                                                   // Ring buffer object
  private:
    int buffer[BUFFER_SIZE];  // The buffer itself
    int head = 0;             // Pointer to data start
    int tail = 0;             // Pointer to data end
    bool full = false;        // Buffer full flag

  public:
    void push(int value) {
      value = constrain(value, 0, PRESSURE_RANGE);
      buffer[tail] = value;
      tail = (tail + 1) % BUFFER_SIZE;

      if (full) {
        head = (head + 1) % BUFFER_SIZE;  // Overwrite old data when full
      }

      full = (tail == head);
    }

  float getPressureValue(int index) {
    if (index >= size()) return -1;
    int pos = (head + index) % BUFFER_SIZE;
    return MIN_PRES + (float)buffer[pos] * PRESSURE_RANGE / PRESSURE_RANGE;
  }
    bool pop(int &value) {
      if (isEmpty()) return false;

      value = buffer[head];
      head = (head + 1) % BUFFER_SIZE;
      full = false;

      return true;
    }

    int peek(int index) {
      if (index >= size()) return -1;  // Check index validity

      int pos = (head + index) % BUFFER_SIZE;
      return buffer[pos];
    }

    bool isEmpty() {
      return (!full && (head == tail));
    }

    bool isFull() {
      return full;
    }

    int size() {
      if (full) return BUFFER_SIZE;
      if (tail >= head) return tail - head;
      return BUFFER_SIZE - head + tail;
    }

    void clear() {
      head = tail = 0;
      full = false;
    }

    void saveBufferToEEPROM() {
      preferences.begin("ring_buffer", false);  // Open memory area
      preferences.putUShort("marker", EEPROM_MARKER);
      preferences.putBytes("data", buffer, sizeof(buffer));
      preferences.putInt("head", head);
      preferences.putInt("tail", tail);
      preferences.putBool("full", full);
      preferences.end();  // Close memory area
    }

    void loadBufferFromEEPROM() {
      preferences.begin("ring_buffer", true);  // Open memory area in read mode
      if (preferences.getUShort("marker", 0) != EEPROM_MARKER) {
        clear();
        for (int i = 0; i < 128; i++) {                          // Fill buffer with maximum pressure if memory is empty
          push(PRESSURE_RANGE);
        }
      } else {
        if (preferences.isKey("data")) {
          preferences.getBytes("data", buffer, sizeof(buffer));
          head = preferences.getInt("head", 0);
          tail = preferences.getInt("tail", 0);
          full = preferences.getBool("full", false);
        }
      }
      preferences.end();  // Close memory area
    }
};

Button b(BUTTON_PIN);                                                                // Button on pin 13
RingBuffer ringBuffer;                                                               // Create ring buffer for pressure array
U8G2_ST7920_128X64_F_SW_SPI u8g2(U8G2_R0,/* SCLK = */18,/* MOSI = */23,/* CS = */4); // Display initialization via software SPI
Adafruit_BME280 bme;                                                                 // Object for working with BME280 sensor

/*Variables*/
uint32_t myTimer1, myTimer3;                                                         // Timer variables

char pressure[7],                                                                    // Strings for displaying data on screen
     pressureDiff[6],
     pres_scale_1[7],
     pres_scale_2[7],
     pres_scale_3[7],
     pressureDiffSign[2] = " ",
     temperature[6],
     humidity[6];

uint16_t varPress;
float varPressNorm;
uint8_t varTemp;
uint8_t varHum;
int varPressureDiff;

float getRelativePressure() {
  float absolute_pressure = bme.readPressure() / 100.0f; // Convert Pa to hPa
  return absolute_pressure * pow(1.0 - (ALTITUDE / 44330.0), -5.255);
}

void drawCurrentConditions() {
  u8g2.clearBuffer();
    u8g2.drawFrame(0,0,128,64);
    
  // Title with smaller font
  u8g2.setFont(u8g2_font_04b_03_tr);  // Smaller font for title
  u8g2.drawStr(5, 8, "Current Conditions:");
  
  // Values with large font
  u8g2.setFont(u8g2_font_9x18B_tf);  // Larger font for readings
  
  char tempStr[20];
  
  // Pressure
  sprintf(tempStr, "P: %.1f hPa", varPressNorm);
  u8g2.drawStr(5, 23, tempStr);
  
  // Temperature
  sprintf(tempStr, "T: %d C", varTemp);
  u8g2.drawStr(5, 41, tempStr);
  
  // Humidity
  sprintf(tempStr, "H: %d %%", varHum);
  u8g2.drawStr(5, 59, tempStr);
  
  u8g2.sendBuffer();
}

void firstScreen() {                                                                // Sensor check screen
  u8g2.clearBuffer();
   u8g2.drawFrame(0,0,128,64);
   u8g2.setFont(u8g2_font_7x14B_tf);
  u8g2.drawStr(7, 35, "Checking sensors");
   u8g2.sendBuffer();
  delay(2000);

  bool bme280_ok = bme.begin(0x76);

  if (bme280_ok) {
    varPressNorm = getRelativePressure();
    varTemp = round(bme.readTemperature());
    varHum = round(bme.readHumidity());
  }
  u8g2.clearBuffer();
  u8g2.drawFrame(0,0,128,64);
  u8g2.setFont(u8g2_font_7x14B_tf);
  u8g2.drawStr(30, 35, "BME280:");
  u8g2.drawStr(78, 35, bme280_ok ? "Ok" : "Err");
  u8g2.sendBuffer();
  delay(500);
  if (!bme280_ok) {
    while (1);                                                                      // Freeze if BME280 isn't working
  }
  delay(2000);
}

void drawScreen() {
    u8g2.clearBuffer();
    const char* errStr = "---";
    
    if ((varTemp > 100)||(varHum > 100)) {
        sprintf(pressure, "%s", errStr);
        sprintf(temperature, "%s", errStr);
        sprintf(humidity, "%s", errStr); 
    } else {
        sprintf(pressure, "%.1f", varPressNorm);
        sprintf(temperature, "%d", varTemp);
        sprintf(humidity, "%d", varHum);
    }
    
    // Calculate pressure difference using the hourly buffer
    if (pressureBufferIndex >= 5) {  // We have enough readings
        float currentPressure = varPressNorm;
        float oldPressure = hourlyPressureBuffer[(pressureBufferIndex - 5) % 6];
        float pressureDiffFloat = currentPressure - oldPressure;
        
        if (fabs(pressureDiffFloat) < 0.1) {
            sprintf(pressureDiffSign, " ");
            sprintf(pressureDiff, "0.0");
        } else {
            if (pressureDiffFloat > 0) {
                sprintf(pressureDiffSign, "+");
            } else {
                sprintf(pressureDiffSign, "-");
            }
            sprintf(pressureDiff, "%.1f", fabs(pressureDiffFloat));
        }
    } else {
        sprintf(pressureDiffSign, " ");
        sprintf(pressureDiff, "---");
    }

  sprintf(pres_scale_3, "%d", MIN_PRES);
  sprintf(pres_scale_2, "%d", MIN_PRES + (PRESSURE_RANGE/2));
  sprintf(pres_scale_1, "%d", MIN_PRES + PRESSURE_RANGE);

  for (uint8_t i = 0; i < 128; i++) {                                               // Draw pressure graph (flip array and limit upper/lower bounds)
    uint8_t gr = ringBuffer.peek(i);
    
    //gr = map(gr, PRESSURE_RANGE, 0, 63, 23);  // Old version - inverted    
    gr = map(gr, 0, PRESSURE_RANGE, 63, 23);  // New version - correct
    
    u8g2.drawBox(i, gr, 1, (64 - gr));
  }

  u8g2.setFont(u8g2_font_7x14B_tf);                                                 // Set font and display data (pressure, humidity, temperature)
  u8g2.drawStr(2, 19, pressure);
  u8g2.drawStr(60, 19, humidity);
  u8g2.drawStr(90, 19, temperature);

  u8g2.setFont(u8g2_font_04b_03_tr );                                               // Pressure change over 24 hours
  u8g2.drawStr(111, 19, pressureDiffSign);
  u8g2.drawStr(115, 19, pressureDiff);
  u8g2.drawStr(15, 5, "hPa        H%      C   -1h");
  u8g2.drawCircle(92, 1, 1);

  u8g2.setDrawColor(0);                                                             // Draw light areas for pressure scale numbers
  u8g2.drawBox(0, 58, 15, 6);
  u8g2.drawBox(0, 40, 15, 7);
  u8g2.drawBox(0, 23, 15, 6);

  u8g2.setDrawColor(1);                                                             // Draw pressure scale numbers
  u8g2.drawStr(0, 64, pres_scale_3);
  u8g2.drawStr(0, 46, pres_scale_2);
  u8g2.drawStr(0, 28, pres_scale_1);

  drawDashedLine(15, 43, 128);                                                      // Draw dashed lines
  drawDashedLine(1, 53, 128);
  drawDashedLine(1, 33, 128);

  drawVerticalDashedLine(32, 24, 64);
  drawVerticalDashedLine(64, 24, 64);
  drawVerticalDashedLine(96, 24, 64);

  u8g2.sendBuffer();
}

void drawDashedLine(uint8_t x_start, uint8_t y, uint8_t length) {                   // Draw horizontal line
  for (uint8_t i = x_start; i < length; i += 2) {
    u8g2.setDrawColor(1);                                                             // Dark pixel
    u8g2.drawPixel(i, y);
    u8g2.setDrawColor(0);                                                             // Light pixel
    u8g2.drawPixel(i + 1, y);
  }
  u8g2.setDrawColor(1);                                                               // Restore normal color for other elements
}

void drawVerticalDashedLine(uint8_t x, uint8_t y_start, uint8_t y_end) {            // Draw vertical line
  for (uint8_t i = y_start; i < y_end; i += 2) {
    u8g2.setDrawColor(1);                                                             // Dark pixel
    u8g2.drawPixel(x, i);
    u8g2.setDrawColor(0);                                                             // Light pixel
    u8g2.drawPixel(x, i + 1);
  }
  u8g2.setDrawColor(1);                                                               // Restore normal color
}

void getData() {                                                                    // Periodic sensor data reading during operation
  bme.begin(0x76);
  varPressNorm = getRelativePressure();                                            // Get relative pressure in hPa
  varTemp = round(bme.readTemperature());
  varHum = round(bme.readHumidity());
  varPressureDiff = ringBuffer.peek(0) - ringBuffer.peek(127);                      // Calculate pressure difference
  switch (varPressureDiff) {                                                        // Difference with + or - sign
    case -64 ... -1: sprintf(pressureDiffSign, "-");  break;
    case 0: sprintf(pressureDiffSign, " ");  break;
    case 1 ... 64: sprintf(pressureDiffSign, "+");  break;
  }
}

void resetBuffer() {
  float initialPressure = getRelativePressure();
  int pressureForGraph = round(initialPressure - MIN_PRES);
  pressureForGraph = constrain(pressureForGraph, 0, PRESSURE_RANGE);
  
  for (int i = 0; i < 128; i++) {
    ringBuffer.push(pressureForGraph);  // Initialize all slots with current pressure
  }
}

void drawPressureDiffScreen1h() {
  u8g2.clearBuffer();
  u8g2.drawFrame(0,0,128,64);
  
  // Title
  u8g2.setFont(u8g2_font_7x14B_tf);
  u8g2.drawStr(10, 15, "Pressure Change");
  u8g2.drawStr(30, 30, "-1 hour");
  
  // Value in large font
  u8g2.setFont(u8g2_font_10x20_tf);
  if (pressureBufferIndex >= 5) {  // We have enough readings for -1h
    float currentPressure = varPressNorm;
    float oldPressure = hourlyPressureBuffer[(pressureBufferIndex - 5) % 6];
    float pressureDiffFloat = currentPressure - oldPressure;
    
    char sign[2] = " ";
    char value[10];
    
    if (fabs(pressureDiffFloat) < 0.1) {
      sprintf(sign, " ");
      sprintf(value, "0.0");
    } else {
      if (pressureDiffFloat > 0) {
        sprintf(sign, "+");
      } else {
        sprintf(sign, "-");
      }
      sprintf(value, "%.1f", fabs(pressureDiffFloat));
    }
    
    u8g2.drawStr(20, 55, sign);
    u8g2.drawStr(30, 55, value);
    u8g2.drawStr(70, 55, "hPa");
  } else {
    u8g2.drawStr(50, 55, "N/A");
  }
  
  u8g2.sendBuffer();
}

void drawPressureDiffScreen3h() {
  u8g2.clearBuffer();

   u8g2.drawFrame(0,0,128,64);
  
  // Title
  u8g2.setFont(u8g2_font_7x14B_tf);
  u8g2.drawStr(10, 15, "Pressure Change");
  u8g2.drawStr(30, 30, "-3 hours");
  
  // Calculate 3-hour difference
  if (pressureBufferIndex >= 15) {  // We need at least 15 readings (3 hours)
    float currentPressure = varPressNorm;
    float oldPressure = hourlyPressureBuffer[(pressureBufferIndex - 15) % 6];
    float pressureDiffFloat = currentPressure - oldPressure;
    
    char sign[2] = " ";
    char value[10];
    
    if (fabs(pressureDiffFloat) < 0.1) {
      sprintf(sign, " ");
      sprintf(value, "0.0");
    } else {
      if (pressureDiffFloat > 0) {
        sprintf(sign, "+");
      } else {
        sprintf(sign, "-");
      }
      sprintf(value, "%.1f", fabs(pressureDiffFloat));
    }
    
    // Value in large font
    u8g2.setFont(u8g2_font_10x20_tf);
    u8g2.drawStr(20, 55, sign);
    u8g2.drawStr(30, 55, value);
    u8g2.drawStr(70, 55, "hPa");
  } else {
    u8g2.setFont(u8g2_font_10x20_tf);
    u8g2.drawStr(50, 55, "N/A");
  }
  
  u8g2.sendBuffer();
}
void setup() {
  pinMode(BUTTON1_PIN, INPUT_PULLUP);
  pinMode(BUTTON2_PIN, INPUT_PULLUP);
  pinMode(BUTTON3_PIN, INPUT_PULLUP);

  Wire.begin();
  u8g2.begin();
  delay(20);

  resetBuffer();                                                                     // Clear pressure buffer

  firstScreen(); // Check sensors before startup

      float initialPressure = getRelativePressure();
    for (int i = 0; i < 6; i++) {
        hourlyPressureBuffer[i] = initialPressure;
    }

#ifdef USE_MEMORY                                                                    // If using persistent memory
  ringBuffer.loadBufferFromEEPROM();                                                 // Restore data from memory

  if (!(digitalRead(BUTTON_PIN))) {                                                  // If button is pressed, reset pressure array
    resetBuffer();
    ringBuffer.saveBufferToEEPROM();                                                 // Save data to memory
    u8g2.clearBuffer();
    u8g2.setFont(u8g2_font_7x14B_tf);
    u8g2.drawStr(23, 19, "Buffer Reset");                                            // Report success
    u8g2.sendBuffer();
    delay(2000);
  }
#endif
}

void loop() {
    b1.tick();
    b2.tick();
    b3.tick();
    
    static bool showingCurrentConditions = false;
    static bool showingDiff1h = false;
    static bool showingDiff3h = false;
    
    if (b1.click()) {
        showingCurrentConditions = !showingCurrentConditions;
        showingDiff1h = false;
        showingDiff3h = false;
        getData();
    }
    
    if (b2.click()) {
        showingDiff1h = !showingDiff1h;
        showingCurrentConditions = false;
        showingDiff3h = false;
        getData();
    }
    
    if (b3.click()) {
        showingDiff3h = !showingDiff3h;
        showingCurrentConditions = false;
        showingDiff1h = false;
        getData();
    }
    
    if (showingCurrentConditions) {
        drawCurrentConditions();
    } else if (showingDiff1h) {
        drawPressureDiffScreen1h();
    } else if (showingDiff3h) {
        drawPressureDiffScreen3h();
    } else {
        drawScreen();
    }

    // Store pressure readings every 11.25 minutes
    if (millis() - myTimer1 >= 675000) {
        myTimer1 = millis();
        float relPressure = getRelativePressure();
        varPressNorm = relPressure;
        
        // Store actual pressure value in hourly buffer
        hourlyPressureBuffer[pressureBufferIndex % 6] = relPressure;
        pressureBufferIndex++;
        
        // Store mapped value for graph
        int pressureForGraph = round(relPressure - MIN_PRES);
        pressureForGraph = constrain(pressureForGraph, 0, PRESSURE_RANGE);
        ringBuffer.push(pressureForGraph);
    }

    if (millis() - myTimer3 >= 3600000) {
        myTimer3 = millis();
#ifdef USE_MEMORY
        ringBuffer.saveBufferToEEPROM();
#endif
    }
}

Credits

Mirko Pavleski
192 projects • 1474 followers

Comments