Skip to Content

Weather Station on e-Paper Display

Weather Station on e-Paper Display

Learn how to build weather station with an ESP32 that displays local or internet weather information on an e-Paper display.

In this tutorial you will learn how to build a battery powered weather station that uses the ESP32 deep-sleep mode and a e-Paper display for long running time. We are actually going to build two versions. One version displays ambient temperature, humidity and air pressure collected via a BME280 Sensor. The other version reads weather data from the internet using OpenWeather and uses the ESP32-e-Paper-Weather-Display software to display it.

Let’s get started!

Required Parts

For this project, I am using an older ESP32 board (ESP32 lite), which has been deprecated but you can still get it. That’s the one listed below. There is a successor model with improved specs. And the default board the ESP32-e-Paper-Weather-Display library is using, is a LOLIN D32, which has an integrated battery voltage monitor.

But any other ESP32, ESP8266 or Arduino (with WiFi) will work as well. Preferably, however you want a development board with battery charging capabilities and a low deep-sleep current, such as the ESP32 boards mentioned above.

2.9″ e-Paper Display

ESP32 lite Lolin32

ESP32 lite

BME280

BME280 Sensor

USB data cable

USB Data Cable

Dupont wire set

Dupont Wire Set

Half_breadboard56a

Breadboard

Makerguides.com is a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means for sites to earn advertising fees by advertising and linking to products on Amazon.com. As an Amazon Associate we earn from qualifying purchases.

E-paper Display

A quick word about the e-Paper display we are going to use for our weather station. e-Paper displays, also called e-Ink displays, have the advantage of consuming very little power. Actually, power is only needed to update the display. Once updated the current content displayed pretty much remains there forever, without using any power at all.

Front and Back of 2.9" e-Paper display module
Front and Back of 2.9″ e-Paper display module

Another advantage of an e-Paper display is that they have fantastic view angles (>170 degrees) and are easy to read even under sunlight.

The disadvantages are the slow update time, the limited color range and that there is no backlighting; so you cannot read them in the dark.

It usually takes 2 seconds for an e-Paper display to update, though you can do partial updates of sections of the display that are much faster (0.3 seconds). That is for black-and-white displays. Color displays are much, much slower to refresh (> 20 seconds), have only a few colors, and are much more expensive.

But for a battery-powered weather station that updates its data only every few minutes, e-Paper displays are a great choice and they look really good.

2.9″ e-Paper display module

For this project, I am specifically using a 2.9″ e-Paper display module, with 296×128 pixels resolution and an embedded controller with an SPI interface.

Back of display module with SPI-interface and controller
Back of display module with SPI-interface and controller

Note that most e-Paper display modules with SPI have a little switch or jumper that allows you to switch from 4-wire SPI to 3-wire SPI. We are going to use the default 4-wire SPI here.

The display module runs on 3.3V or 5V, has a super-low sleep current of 0.01µA and consumes only about 26.4mW while refreshing/updating its content. That means, you can run an e-Paper display for a long time even on a small battery.

For more information on e-Paper displays have a look at our tutorial Interfacing Arduino To An E-ink Display.

BME280 Sensor

For our weather station we also want to measure some ambient data, such as temperature, humidity and air pressure. I am going to use an BME280 sensor here. The main reason is that it also has a low power, sleep mode, where it consumes only 0.1µA (3.6 μA in normal mode).

In combination with an e-Paper and the ESP32-lite with a deep-sleep current of 5.65mA (at 5V), this makes for a very low-power setup that can run a long time on battery power.

The BME280 sensor itself is tiny and typically comes on a breakout board with an I2C interface; see the picture below.

BME280 breakout board
BME280 breakout board

The I2C interface makes it very easy to connect and use. The sensor can measure pressure from 300 hPa to 1100 hPa, temperature from -40°C to +85°C, and humidity from 0% to 100%. For more information on the BME280 read our tutorial on How To Use BME280 Pressure Sensor With Arduino.

Connecting and Testing the e-Paper

Before trying any fancy, let’s connect and test the function of the e-Paper. The following picture shows the complete wiring for power and SPI.

Connecting e-Paper to ESP32 via SPI
Connecting e-Paper to ESP32 via SPI

And here is the table with all the connections. Note that you can power the display with 3.3V or 5V but the ESP32-lite has only a 3.3V output and the SPI data lines must be 3.3V.

e-Paper displayESP32 lite
CS/SS5
SCL/SCK 18
SDA/DIN/MOSI23
BUSY15
RES/RST2
DC0
VCC3.3V
GNDG

Install GxEPD2 library

Before we can use the e-Paper display we need to install two libraries. The Adafruit_GFX library is a core graphics library that provides a common set of graphics primitives (text, points, lines, circles, etc.). And the GxEPD2 library provides the graphics driver software to control an E-Paper Display via SPI.

Just install the libraries the usual way. After the installation you should see them in them in the Library Manager as follows.

Adafruit_GFX and GxEPD2 libraries in Library Manager
Adafruit_GFX and GxEPD2 libraries in Library Manager

Test code

Here is some simple test code that shows the text “Hello Makers!” on the display. Have a look at complete code first, and then we discuss some of the details.

#define ENABLE_GxEPD2_GFX 0

#include "GxEPD2_BW.h"
#include "Fonts/FreeMonoBold9pt7b.h"

//CS(SS)=5, SCL(SCK)=18, SDA(MOSI)=23, BUSY=15, RES(RST)=2, DC=0
GxEPD2_BW<GxEPD2_290_BS, GxEPD2_290_BS::HEIGHT> epd(GxEPD2_290_BS(5, 0, 2, 15));

void initDisplay() {
  epd.init(115200, true, 50, false);
  epd.setRotation(1);
  epd.setFont(&FreeMonoBold9pt7b);
  epd.setTextColor(GxEPD_BLACK);   
  epd.setFullWindow();
  epd.fillScreen(GxEPD_WHITE);   
}

void displayText() {
  epd.setCursor(20, 20);
  epd.print("Hello");
  epd.setCursor(20, 40);
  epd.print("Makers!");    
}

void setup() {
  initDisplay();
  displayText();
  epd.display();
  epd.hibernate();
}

void loop() {
}

Let’s break down the code into its components to understand how it works.

Constants and Libraries

The code starts by defining a constant ENABLE_GxEPD2_GFX as 0. You can set it to 1, which according tot the document enables the base class GxEPD2_GFX to pass references or pointers to the display instance as parameter. But it uses ~1.2k more code and we don’t need it, so it is set to 0.

#define ENABLE_GxEPD2_GFX 0

Next we include the GxEPD2_BW.h header file for a black and white (BW) e-Paper display and the font that we are going to use. If you have a 3-color display you would have to include GxEPD2_3C.h, or GxEPD2_4C.h for a 4-color display, and GxEPD2_7C.h for a 7-color display, instead.

#include "GxEPD2_BW.h"
#include "Fonts/FreeMonoBold9pt7b.h"

The Adafruit_GFX library comes with many different fonts and you can find them in your library folder under {application_path}\Arduino\libraries\Adafruit_GFX_Library\Fonts.

Display Object

The next line is important. It creates the display object and it depends on the display type or brand. I tried a WeAct and a WaveShare display and the following line works for me for both displays.

GxEPD2_BW<GxEPD2_290_BS, GxEPD2_290_BS::HEIGHT> epd(GxEPD2_290_BS(5, 0, 2, 15));

The Readme for GxEPD2 library lists a massive number of supported displays and you can find the specifics in the header files, e.g. GxEPD2.h.

Display Initialization

The initDisplay() function is responsible for initializing the e-paper display. It sets up the display parameters such as communication speed, rotation, font, text color, and screen fill color. If you see issues with the refresh of the display, you may have to play with the parameter for the init() function.

void initDisplay() {
  epd.init(115200, true, 50, false);
  epd.setRotation(1);
  epd.setFont(&FreeMonoBold9pt7b);
  epd.setTextColor(GxEPD_BLACK);   
  epd.setFullWindow();
  epd.fillScreen(GxEPD_WHITE);   
}

Displaying Text

The displayText() function positions the cursor on the display and prints the text “Hello” and “Makers!” at specific coordinates.

void displayText() {
  epd.setCursor(20, 20);
  epd.print("Hello");
  epd.setCursor(20, 40);
  epd.print("Makers!");    
}

Setup Function

In the setup() function, the display is initialized using initDisplay(), text is displayed using displayText(), and then the content is displayed on the e-paper screen. Finally, the display is put into hibernation mode. That powers of the display and puts the display controller into deep-sleep mode.

void setup() {
  initDisplay();
  displayText();
  epd.display();
  epd.hibernate();
}

Loop Function

The loop() function is empty in this code as the display content is set up in the setup() function and does not need to be updated continuously. The ESP32 will not execute any specific actions in the loop.

void loop() {
}

Upload and Run Code

Now, we are ready to upload and run the code. Select the board you have in the board manager. In my case it is the WEMOS LOLIN32 Lite that was listed in the Required Parts:

WEMOS LOLIN32 Lite selected in Board Manager
WEMOS LOLIN32 Lite selected in Board Manager

Then press upload and after some flickering, your display should show the following text:

Test output on e-Paper display
Test output on e-Paper display

If that works, we can move on to something a bit more complicated. If not, check the wiring and the line where the display object is created. Specifically, which pins are assigned for the SPI interface.

Connecting the e-Paper and BME280 to ESP32

We want to show ambient temperature, humidity and air pressure using the BME280 sensor. Adding the sensor is easy, due to the I2C interface. Just connect SDA to pin 33 and SCL to pin 25, as show below.

Connecting BME280 to ESP32
Connecting BME280 to ESP32

You can use different pins for I2C but if you do, don’t forget to adjust the code in the next section accordingly.

Note that there are 5V and 3.3V versions of the BME280 breakout board. I am using the 3.3V version and therefore connect VCC to 3.3V pin of the ESP32.

Since the WEMOS LOLIN32 Lite has a built in battery port and charger, you can power the entire system from a LiPo battery. The picture below shows the complete wiring with a LiPo battery attached:

WEMOS LOLIN32 Lite with e-Paper, BME280 and LiPo battery
WEMOS LOLIN32 Lite with e-Paper, BME280 and LiPo battery

Even with the tiny 420mAh LiPo battery used here, I was able to run the system for several days (with a 5 minute update cycle).

In the next section we are going to write the code to show the sensor data on the e-Paper display.

Code for Local Weather Station

The following code reads ambient temperature, humidity and air pressure from the BME280 every 5 minutes and displays that information plus the altitude on the e-Paper display. The output looks as follows:

Showing BME280 data on e-Paper display
Showing BME280 data on e-Paper display

Between the 5 minute intervals, the ESP32, the e-Paper and the BME280 all go into deep-sleep mode, which greatly reduces the power consumption of the system. Have a look at the complete code first, and then we look at the details:

#define ENABLE_GxEPD2_GFX 0

#include "Wire.h"
#include "GxEPD2_BW.h"
#include "Fonts/FreeMonoBold9pt7b.h"
#include "Fonts/FreeMono9pt7b.h"
#include "Fonts/FreeMonoBold18pt7b.h"
#include "Adafruit_BME280.h"
#include "esp_sleep.h"

#define SECONDS (1000 * 1000)
#define SEALEVELPRESSURE_HPA 1013.25
#define BME280_ADDRESS 0x76

Adafruit_BME280 bme;
GxEPD2_BW<GxEPD2_290_BS, GxEPD2_290_BS::HEIGHT> epd(GxEPD2_290_BS(5, 0, 2, 15));

void initDisplay() {
  epd.init(115200, true, 50, false);
  epd.setRotation(0);
  epd.setFullWindow();
  epd.setTextColor(GxEPD_BLACK);
  epd.fillScreen(GxEPD_WHITE);
}

void initSensor() {
  Wire.begin(33, 25);  // Software I2C for BME280
  bme.begin(BME280_ADDRESS, &Wire);
  bme.setSampling(Adafruit_BME280::MODE_FORCED,
                  Adafruit_BME280::SAMPLING_X1,  // temperature
                  Adafruit_BME280::SAMPLING_X1,  // pressure
                  Adafruit_BME280::SAMPLING_X1,  // humidity
                  Adafruit_BME280::FILTER_OFF);
}

void displayText(int x, int y, const GFXfont* f, const char* text) {
  epd.setFont(f);
  epd.setCursor(x, y);
  epd.print(text);
}

void displayHLine(int y) {
  int o = 8;
  epd.drawFastHLine(o, y, 128 - 2 * o, GxEPD_BLACK);
}

void displayLargeValue(int y, const char* name, float val, const char* unit) {
  static char buffer[32];
  sprintf(buffer, "%7.1f", val);

  displayText(8, y, &FreeMono9pt7b, name);
  displayText(4, y + 30, &FreeMonoBold9pt7b, buffer);
  displayText(86, y + 30, &FreeMono9pt7b, unit);
  displayHLine(y + 50);
}

void displaySmallValue(int y, char* name, float val, char* unit) {
  static char buffer[32];
  sprintf(buffer, "%5.1f", val);

  displayText(8, y, &FreeMono9pt7b, name);
  displayText(4, y + 35, &FreeMonoBold18pt7b, buffer);
  displayText(110, y + 35, &FreeMono9pt7b, unit);
  displayHLine(y + 50);
}

void setup() {
  initSensor();
  initDisplay();

  int o = 20, d = 76;
  bme.takeForcedMeasurement();
  float temp = bme.readTemperature();
  displaySmallValue(o, "temp", temp, "C");
  float hum = bme.readHumidity();
  displaySmallValue(o + d, "hum", hum, "%");
  float alt = bme.readAltitude(SEALEVELPRESSURE_HPA);
  displayLargeValue(o + 2 * d, "alt", alt, "m");
  float pres = bme.readPressure() / 100.0;
  displayLargeValue(o + 3 * d, "pres", pres, "hPa");
  
  epd.display();
  epd.hibernate();

  esp_sleep_enable_timer_wakeup(5 * 60 * SECONDS);
  esp_deep_sleep_start();
}

void loop() {
}

In the code above, we are displaying temperature, humidity, and air pressure readings from a BME280 sensor on an e-Paper display. The sensor is connected via software I2C and the display is controlled using the GxEPD2 library.

Constants and Libraries

We start by including the required libraries plus three different font libraries. Note that you will need to install the Adafruit_BME280 library, if you haven’t already.

#define ENABLE_GxEPD2_GFX 0

#include "Wire.h"
#include "GxEPD2_BW.h"
#include "Fonts/FreeMonoBold9pt7b.h"
#include "Fonts/FreeMono9pt7b.h"
#include "Fonts/FreeMonoBold18pt7b.h"
#include "Adafruit_BME280.h"
#include "esp_sleep.h"

#define SECONDS (1000 * 1000)
#define SEALEVELPRESSURE_HPA 1013.25
#define BME280_ADDRESS 0x76

The I2C address of the BME280 sensor typically is 0x76 but yours may be different and on some breakout boards, you can switch between two addresses. The constant SEALEVELPRESSURE_HPA is used by the BME280 library to calculate the altitude based on air pressure. This again, is something you have to adjust to your location. See our tutorial on How To Use BME280 Pressure Sensor With Arduino, for more details.

Sensor and Display Objects

Next we create the objects for the BME280 sensor and the e-Paper display. As mentioned before, if you use a different display, or different wiring you will need to adjust the construction of the display object.

Adafruit_BME280 bme;
GxEPD2_BW<GxEPD2_290_BS, GxEPD2_290_BS::HEIGHT> epd(GxEPD2_290_BS(5, 0, 2, 15));

Initialization Functions

The initDisplay() function initializes the e-Paper display by setting up communication, rotation, color, and clearing the screen. The initSensor() function initializes the BME280 sensor by starting the I2C communication and configuring sensor settings.

void initDisplay() {
  ...
}

void initSensor() {
  Wire.begin(33, 25);  // Software I2C for BME280
  bme.setSampling(Adafruit_BME280::MODE_FORCED,
  ..
  )
}

Since we are using software I2C for the BME280 sensor, we need to call Wire.begin(33, 25) with the SDA and SCL pins we are using. If you connect the BME280 sensors to different pins, you will have to change the code here.

Note that we run the BME280 sensor with the MODE_FORCED setting. In forced mode the sensor performs one measurement, stores the results and then goes into deep-sleep. That is what we want, since between measurements we are going to put the ESP32 and the e-Paper into deep-sleep as well.

Display Functions

There are multiple functions to display text, horizontal lines, large values (pressure, altitude), and small values (temperature, humidity) on the e-Paper display. These functions handle the formatting and positioning of the data to be shown.

void displayText(int x, int y, const GFXfont* f, const char* text) {
  // Display text code
}
void displayHLine(int y) {
  // Display horizontal line code
}
void displayLargeValue(int y, const char* name, float val, const char* unit) {
  // Display large value code
}
void displaySmallValue(int y, char* name, float val, char* unit) {
  // Display small value code
}

Setup Function

In the setup() function, the sensor and display are initialized. Sensor readings for temperature, humidity, altitude, and pressure are taken and displayed on the e-Paper screen. The display is then updated and switched into hibernation mode.

After that we also switch the ESP32 to deep-sleep mode and automatically wake it up after 5 minutes.

void setup() {
  initSensor();
  initDisplay();

  bme.takeForcedMeasurement();
  float temp = bme.readTemperature();
  displaySmallValue(o, "temp", temp, "C");
  ...

  epd.display();
  epd.hibernate();

  esp_sleep_enable_timer_wakeup(5 * 60 * SECONDS);
  esp_deep_sleep_start();
}

Loop Function

The loop() function is empty as the ESP32 enters deep sleep after setup and never enters the loop function. But it wakes up every 5 minutes and then executes the setup function again.

Every refresh of the display takes 2-3 seconds and comes with a lot of flickering, which is quite distracting and doesn’t look good. You can avoid this by performing a partial refresh. For more details on this have a look at the Partial Refresh of e-Paper Display tutorial.

Apart from that you now have a nice, little, battery-powered weather station that displays and updates ambient temperature, humidity, air pressure and altitude!

Code for Internet Weather Station

If you want a more advanced weather station that retrieves its data from the internet then there is the fantastic ESP32-e-Paper-Weather-Display library that does it for you.

Depending on the display size it shows temperature, humidity, pressure, wind direction, moon phases, weather conditions, weather forecast and more. On the 2.9″ e-Paper display I am using here, it looks like this:

Output of ESP32-e-Paper-Weather-Display on 2.9" e-Paper display
Output of ESP32-e-Paper-Weather-Display on 2.9″ e-Paper display

Installing of the ESP32-e-Paper-Weather-Display library

To install the ESP32-e-Paper-Weather-Display library, you need to download the zip file from the github repo and then install it via Sketch -> Include Library -> Add .ZIP Library ....

Download ZIP file for ESP32-e-Paper-Weather-Display library
Download ZIP file for ESP32-e-Paper-Weather-Display library

You will also need the GxEPD2 library and the Adafruit_GFX library, but you have those already installed in the sections above.

Setting up ESP32-e-Paper-Weather-Display library

Before you can display internet weather data using the ESP32-e-Paper-Weather-Display library, there is a little bit of work to do. First we need to get an API key from OpenWeather, then we need to download example code for our display, and finally we need to update the settings file in the example code with the API key and our Wi-Fi credentials.

OpenWeather API key

The ESP32-e-Paper-Weather-Display library uses the free OpenWeather service to receive weather data from the internet. Before you can use any of the OpenWeather APIs, you need an API-key and for that you need an account. To create a free account go to the sign-up page and enter your details there.

Sign-up page at OpenWeather
Sign-up page at OpenWeather

After that go to the api-key creation page and create an API-key. The API-key is that long string “sdfd87fakeby6apikeysf4z” that you see in the screenshot below. Your key will look different to mine.

Create API-key at OpenWeather
Create API-key at OpenWeather

If you need more help, have a look at our tutorial: Simple ESP32 Internet Weather Station.

Next, download the example code for your display size and type from the github repo of the ESP32-e-Paper-Weather-Display libary. For instance, I downloaded the code for the Waveshare_2_9 example.

Alternatively, you can use the Arduino IDE that also has some but not all of the examples:

Download example code for ESP32-e-Paper-Weather-Display library
Download example code for ESP32-e-Paper-Weather-Display library

Settings file owm_credential.h

Finally, open the .ino file (e.g. Waveshare_2_9.ino) in the Arduino IDE and click on the tab for the owm_credential.h file.

This file has all the specific settings for your weather station such as location, units, language, time zone and so on. Most importantly, it has the constants for your Wi-Fi credentials (ssid, password) and the OpenWeather apikey that you need to set there.

owm_credential.h file with settings for ESP32-e-Paper-Weather-Display
owm_credential.h file with settings for ESP32-e-Paper-Weather-Display

As mentioned, depending on the size of the e-Paper display, the ESP32-e-Paper-Weather-Display library shows more or less detailed weather information. On a large 7.5″ display, you get a very a rich set of weather data and it looks absolutely fantastic:

Weather data on 7.5″ display (source)

Battery monitor

Finally, the ESP32-e-Paper-Weather-Display library also natively displays battery info if you use a Lolin D32 board that has GPIO-35 as an ADC input:

Battery info from ESP32-e-Paper-Weather-Display library
Battery info from ESP32-e-Paper-Weather-Display library

On other boards, you will need to change the analogRead(35) statement in the code and specify the pin where your voltage monitor is connected.

analogRead(35) statement in ESP32-e-Paper-Weather-Display library
analogRead(35) statement in ESP32-e-Paper-Weather-Display library

A voltage monitor can be a simple voltage divider attached to the battery terminals. Have a look at How to Monitor Battery Voltage for Battery Powered Projects, for more details on that. For more accurate estimates of battery charge, you can use specific battery monitor ICs, such as the MAX1704X.

Conclusions

In this tutorial you learned how to build a battery-powered weather station with an ESP32, a BME280 sensor and an e-Paper display.

We used a BME280 Sensor to measure ambient temperature, humidity and air pressure. And we used the ESP32-e-Paper-Weather-Display software to display weather data from the internet.

With that you have all the bits and pieces to build your own version that, for instance, could combine internet weather data with local weather information. You could also collect information from remote sensors and add them to the display.

Have fun tinkering and if you have any questions feel free to ask 😉