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