Community project
Portable E-Ink Weather Monitor
This portable weather monitor displays real-time temperature, humidity, and pressure readings on a low-power e-ink screen. Built around an ESP32 microcontroller, BME280 environmental sensor, and Waveshare 2.9-inch e-ink display, the device runs for weeks on a single charge thanks to the display's minimal power consumption and efficient firmware that updates readings every five minutes.
The guide provides a complete parts list, wiring diagram showing all sensor and display connections, step-by-step assembly instructions, and ready-to-upload Arduino firmware. Builders will learn how to integrate I2C and SPI protocols, manage battery charging safely with the TP4056 module, and optimize display updates to maximize runtime on the included 1000mAh LiPo battery.
Wiring diagram

Gather all the parts
| Qty | Component |
|---|---|
| 1 | BME280 Bosch BME280 environmental sensor on the exact Adafruit 2652 breakout. Supports I2C via SCK/SCL and SDI/SDA, optional SDO address select, and SPI pins when needed. |
| 1 | Waveshare 2.9inch E-Ink Display Module (296x128) 296 × 128 Waveshare 2.9-inch monochrome E-Ink/e-Paper display module, 296×128 resolution, SPI interface, 3.3V logic, supports full refresh and fast partial refresh. Compatible with ESP32 via hardware SPI (VSPI/HSPI). Driven by the GxEPD2 Arduino library with built-in partial-refresh support for RSVP-style word flipping. Shares SPI bus with other peripherals using a dedicated CS pin. |
| 1 | 3.7 V, 1000 mAh Single-cell LiPo pack, nominal 3.7 V, 1000 mAh. Default rechargeable choice for portable ESP32 / Pico projects. Pair with a TP4056 charger for safe USB recharging. |
| 1 | TP4056 Li-Ion/LiPo charger module with protection Protected USB charger TP4056 single-cell Li-Ion/LiPo linear charger module, 5V USB input, 1A charge current (programmable). Common variants ship with DW01 protection. Pair with battery_lipo_storage for the cell. |
| 1 | 5V step-up regulator module 5 V boost regulator A small power board that raises the battery voltage to the steady 5V needed at the ESP32 VIN pin. |
Assemble it in 4 steps
1. Place the ESP32 and the sensor
Put the ESP32 DevKit V1 and bme280_1 on a breadboard or non-metal work surface. Connect BME280 VCC to ESP32 3V3 (power), BME280 GND to ESP32 GND (ground), BME280 SDA to GPIO21 (data), and BME280 SCL to GPIO22 (clock).
- The BME280 board normally has the pin names printed beside its header. Keep its small vent opening clear so it can sense the surrounding air.
- Make sure VCC and GND are not swapped — swapped power can damage the sensor.
2. Wire the e-paper screen
Connect epaper_1 VCC to ESP32 3V3 (power), epaper_1 GND to ESP32 GND (ground), DIN to GPIO23 (screen data), CLK to GPIO18 (screen clock), CS to GPIO4 (screen select), DC to GPIO27 (screen command/data), RST to GPIO26 (screen reset), and BUSY to GPIO25 (screen ready signal).
- Use short jumper wires for the display. Match the labels printed on the display board, not the order of the pins in a product photo.
- Power the e-paper display from 3V3, not 5V — 5V signal or supply wiring can damage a 3.3V display module.
3. Connect the rechargeable battery safely
With all USB cables unplugged, connect battery_1 +V to charger_1 B+ (battery positive) and battery_1 GND to charger_1 B- (battery negative). Connect charger_1 OUT+ to boost_5v_1 VIN (protected battery power) and charger_1 OUT- to boost_5v_1 GND (ground). Connect boost_5v_1 VOUT to ESP32 VIN (5V power) and boost_5v_1 GND to ESP32 GND (ground).
- Use a LiPo battery with its own protected connector if possible. The charger board's USB socket is only for charging the battery.
- Never connect the LiPo battery leads backwards — that can overheat or permanently damage the battery, charger, or wiring.
- Do not charge a swollen, torn, or hot battery, and do not leave a charging battery unattended.
4. Do a final power check
Before connecting the battery, look at every power wire again: all GND wires must meet at ESP32 GND, the boost module's 5V output goes only to ESP32 VIN, and the BME280 and epaper_1 both receive ESP32 3V3 (power). Then plug the ESP32 into USB for its first test.
- For the first test, leave the battery disconnected and power only the ESP32 by USB. This makes it easier to find a misplaced wire safely.
- Do not plug USB into the ESP32 while an unknown or untested boost module is also feeding VIN — incorrect regulator wiring can damage the board or computer USB port.
Review all connections
1. Connections between "bme280_1" and "ESP32"
| Function | bme280_1 | ESP32 |
|---|---|---|
| power | VCC | 3V3 |
| ground | GND | GND |
| i2c | SDA | GPIO 21 |
| i2c | SCL | GPIO 22 |
2. Connections between "epaper_1" and "ESP32"
| Function | epaper_1 | ESP32 |
|---|---|---|
| power | VCC | 3V3 |
| ground | GND | GND |
| spi | DIN | GPIO 23 |
| spi | CLK | GPIO 18 |
| spi | CS | GPIO 4 |
| digital | DC | GPIO 27 |
| digital | RST | GPIO 26 |
| digital | BUSY | GPIO 25 |
3. Connections between "battery_1" and "ESP32"
| Function | battery_1 | ESP32 |
|---|---|---|
| power | +V → TP4056 Li-Ion/LiPo charger module with protection B+ | EXT |
| ground | GND → TP4056 Li-Ion/LiPo charger module with protection B- | EXT |
4. Connections between "charger_1" and "ESP32"
| Function | charger_1 | ESP32 |
|---|---|---|
| power | IN+ → 5V USB charging input | EXT |
| ground | IN- → USB charging ground | EXT |
| power | OUT+ → 5V step-up regulator module VIN | EXT |
| ground | OUT- → 5V step-up regulator module GND | EXT |
5. Connections between "boost_5v_1" and "ESP32"
| Function | boost_5v_1 | ESP32 |
|---|---|---|
| power | VOUT | VIN |
| ground | GND | GND |
Deploy the firmware
#include <Arduino.h>
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_BME280.h>
#include <GxEPD2_BW.h>
#include <GxEPD2_290_T5.h>
#include <Fonts/FreeMonoBold9pt7b.h>
#include <Fonts/FreeMonoBold12pt7b.h>
// Forward declarations
void drawHeader();
void drawReadings();
void fullScreenDraw();
void partialReadingsDraw();
constexpr int BME_SDA_PIN = 21;
constexpr int BME_SCL_PIN = 22;
constexpr int EPD_SCK_PIN = 18;
constexpr int EPD_MOSI_PIN = 23;
constexpr int EPD_CS_PIN = 4;
constexpr int EPD_DC_PIN = 27;
constexpr int EPD_RST_PIN = 26;
constexpr int EPD_BUSY_PIN = 25;
constexpr uint32_t UPDATE_INTERVAL_MS = 300000UL;
Adafruit_BME280 bme;
GxEPD2_BW<GxEPD2_290_T5, GxEPD2_290_T5::HEIGHT> display(
GxEPD2_290_T5(EPD_CS_PIN, EPD_DC_PIN, EPD_RST_PIN, EPD_BUSY_PIN)
);
bool sensorReady = false;
uint32_t lastUpdateMs = 0;
void drawHeader()
{
display.setTextColor(GxEPD_BLACK);
display.setFont(&FreeMonoBold12pt7b);
display.setCursor(8, 20);
display.print("ENVIRONMENT");
display.drawLine(6, 28, display.width() - 6, 28, GxEPD_BLACK);
}
void drawReadings()
{
display.fillRect(0, 34, display.width(), display.height() - 34, GxEPD_WHITE);
display.setTextColor(GxEPD_BLACK);
if (!sensorReady)
{
display.setFont(&FreeMonoBold9pt7b);
display.setCursor(8, 62);
display.print("BME280 NOT FOUND");
display.setCursor(8, 86);
display.print("Check SDA/SCL wiring");
return;
}
const float temperatureC = bme.readTemperature();
const float humidity = bme.readHumidity();
const float pressureHpa = bme.readPressure() / 100.0F;
display.setFont(&FreeMonoBold9pt7b);
display.setCursor(8, 54);
display.printf("Temp: %5.1f C", temperatureC);
display.setCursor(8, 80);
display.printf("Hum : %5.1f %%", humidity);
display.setCursor(8, 106);
display.printf("Press: %4.0f hPa", pressureHpa);
}
void fullScreenDraw()
{
display.setFullWindow();
display.firstPage();
do
{
display.fillScreen(GxEPD_WHITE);
drawHeader();
drawReadings();
}
while (display.nextPage());
}
void partialReadingsDraw()
{
display.setPartialWindow(0, 32, display.width(), display.height() - 32);
display.firstPage();
do
{
drawReadings();
}
while (display.nextPage());
}
void setup()
{
Serial.begin(115200);
Wire.begin(BME_SDA_PIN, BME_SCL_PIN);
sensorReady = bme.begin(0x77);
SPI.begin(EPD_SCK_PIN, -1, EPD_MOSI_PIN, EPD_CS_PIN);
display.init(115200, true, 2, false);
display.setRotation(1);
fullScreenDraw();
lastUpdateMs = millis();
}
void loop()
{
const uint32_t now = millis();
if (now - lastUpdateMs >= UPDATE_INTERVAL_MS)
{
sensorReady = bme.begin(0x77);
partialReadingsDraw();
lastUpdateMs = now;
}
}Remix this project
Make it yours in one click
Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.




