DIY Digital Barograph with BME280 and ESP32 - 24 Hour Pressure Trends

This is a modern digital interpretation of a traditional barograph that displays real-time atmospheric pressure trends over a 24-hour period using a high-precision BME280 sensor and ESP32 microcontroller.

A barograph is a self-recording barometer that continuously measures and records atmospheric pressure over time. It produces a graphical record of the pressure changes on a paper chart, called a barogram.

The device presented in this project is an electronic barograph and the barogram is drawn on an LCD Display instead of on paper. The trend of the barogram (rising, falling, or steady pressure) can provide insights into upcoming weather changes. A rapid drop in pressure, for example, may indicate an approaching storm.

In one of my previous videos I presented a similar device but with fewer options and information, and a smaller Display. The device presented in this video is made in the style of commercial electronic barographs, which by the way are very expensive. The idea is taken from the shem-net site but I made significant changes and additions to the original code.
The device is really simple to make and consists of only a few components:
- ESP32 Developement Board
- 128x64 LCD Display with ST7920 driver chip
- BME280 humidity, pressure and temperature sensor module
- and three Buttons

For a more accurate display of temperature and humidity, it is desirable to place the sensor module outside the housing or to make more ventilation holes. The contrast of the display is adjusted using a small trimmer potentiometer.
This project is sponsored by PCBWay. Visit the PCBWay website and save big, with a time-limited promotion on purple solder mask. From September 1st to September 30th you can get 10 pcs of 2-layer 100x100mm PCBs in purple for only $5. PCBWay has all the services you need to create your project at the best price.

As for the code, as I mentioned earlier, I made several changes and improvements. First, instead of mmHg (millimeters of mercury), the value of atmospheric pressure is expressed in hPa which is the official unit of measurement for atmospheric pressure.

Next, instead of absolute atmospheric pressure, the relative atmospheric pressure is now calculated, based on the altitude at which the device is located, and which we need to enter in the code. Three more screens have also been added to display the necessary parameters. Also, in case of accidental power failure, the graph is remembered and the next time the barograph is turned on, we do not need to wait 24 hours for fill up.
Let's take a look at how the barograph works. Immediately after turning on the device, a message appears on the screen to check the BME280 sensor.

After that, the main screen appears. Of course, the most important part here is the graph that shows the movement of atmospheric pressure over time. The entire graph is drawn with 128 vertical bars and the time covered by these bars is exactly 24 hours. This means that the value of atmospheric pressure on the graph is recorded every 11.25 minutes. On the right side are the latest measurements, and on the far left are the oldest measurements, in this case -24 hours. Similarly, in the middle are the values ​​from 12 hours ago. In this way, we can visually see very quickly and accurately whether the atmospheric pressure has a trend of rise, steady or fall, which are actually basic indicators necessary for predicting local weather. I should also mention that the vertical resolution of the graph is exactly 1 hectopascal, which is quite sufficient for this purpose.

As you can see, there is still a lot of useful information on the screen. Here is the value of the current relative atmospheric pressure with a precision of one decimal place expressed in hectopascals. Then the relative humidity of the air expressed in percent, as well as the current temperature in degrees Celsius. On the far right, in small numbers, is shown the numerical value of the pressure trend in the last hour.
Since the housing in which the device is built is from one of my previous projects, there are also three buttons on it. So I decided to give each of them a specific function. By pressing the top button, a special screen opens on which the current values ​​for the Pressure, temperature and humidity of the air are displayed in large characters.


This screen remains active until we press one of the other two buttons, at which time other appropriate information is displayed. If we press the same button once more, we return to the main screen. The other two buttons function in the same way. The middle one displays the value of the difference between the current pressure and the one from one hour ago, and the bottom button displays this difference from three hours ago.

These two values ​​are by definition the most important for short-term local weather forecasting. By default, the values ​​are refreshed every 11 minutes, but if we press a button, the current values ​​are taken, so there may be a minimal difference between different screens.
And finally, a short conclusion. This is a modern digital interpretation of a traditional barograph that displays real-time atmospheric pressure trends over a 24-hour period using a high-precision BME280 sensor and ESP32 microcontroller. The device features a 128x64 LCD display showing current barometric pressure, temperature, humidity, and a detailed pressure history graph, making it perfect for weather monitoring and forecasting.


CODE
/*
***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
    }
}

License
All Rights
Reserved
licensBg
0