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
BME280 Sensor
USB Data Cable
Dupont Wire Set
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.
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.
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.
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.
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 display | ESP32 lite |
---|---|
CS/SS | 5 |
SCL/SCK | 18 |
SDA/DIN/MOSI | 23 |
BUSY | 15 |
RES/RST | 2 |
DC | 0 |
VCC | 3.3V |
GND | G |
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.
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:
Then press upload and after some flickering, your display should show the following text:
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.
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:
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:
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:
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 ...
.
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.
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.
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:
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-DisplayAs 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:
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:
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.
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 😉
Stefan is a professional software developer and researcher. He has worked in robotics, bioinformatics, image/audio processing and education at Siemens, IBM and Google. He specializes in AI and machine learning and has a keen interest in DIY projects involving Arduino and 3D printing.