ESP32 + E-Paper Display: A Power-Saving Screen for IoT Devices
Intermediate1/8/2026- Author: IoTSpark Maker

ESP32 + E-Paper Display: A Power-Saving Screen for IoT Devices

Connect an ESP32 DevKit to a 2.13" SSD1680 SPI e-paper (e-ink) display and show periodically updated data using the GxEPD2 library, taking advantage of the display's ability to hold an image indefinitely with no continuous power — ideal for battery-powered IoT devices.

ESP32E-PaperE-InkSSD1680SPILow PowerPower Saving
0 steps4 components

E-paper (e-ink) displays behave completely differently from LCD/OLED: they draw meaningful current only while drawing/updating an image (tens of mA for 1-2 seconds), and then need NO power at all to keep the displayed content — the physical pixels hold their state indefinitely until the next update.

This property makes e-paper an ideal choice for battery-powered IoT devices that display slowly-changing data (electronic price labels, outdoor environmental monitoring stations, notice boards). This article shows how to wire an ESP32 to a 2.13" SPI e-paper display (SSD1680 driver) and write a program that updates periodically using the GxEPD2 library, with an explanation of pairing it with deep sleep to maximize battery life.

Detailed guide

Connect an ESP32 DevKit to a 2.13" SSD1680 SPI e-paper display, updating data periodically with GxEPD2, taking advantage of holding an image with no continuous power.

1. Introduction

E-paper (e-ink) displays behave completely differently from LCD/OLED: they draw meaningful current only while drawing/updating an image (tens of mA for 1-2 seconds), and then need NO power at all to keep the displayed content — the physical pixels hold their state indefinitely until the next update. This property makes e-paper an ideal choice for battery-powered IoT devices that display slowly-changing data (electronic price labels, outdoor environmental monitoring stations, notice boards). This article shows how to wire an ESP32 to a 2.13" SPI e-paper display (SSD1680 driver) and write a program that updates periodically using the GxEPD2 library.

Component note: The 2.13" SPI e-paper module is available in the IoTLabs Workspace with a standard pinout matching common SPI e-paper modules on the market: VCC, GND, DIN, CLK, CS, DC, RST, BUSY.

2. Components Needed

ComponentQtyReference Price
ESP32 DevKit V4 (30/38 pin)1~75,000 - 120,000₫
E-Paper 2.13" SPI (SSD1680)1~180,000 - 280,000₫
Female-Female Jumper Wires (8 wires)1 set~15,000₫

3. Wiring Diagram

E-Paper SSD1680ESP32 DevKitNote
VCC3V33.3V supply
GNDGNDCommon ground
DINGPIO23SPI MOSI (ESP32 default VSPI)
CLKGPIO18SPI SCK
CSGPIO5Chip Select
DCGPIO17Data/Command
RSTGPIO16Reset
BUSYGPIO4Input pin signaling the module is busy processing an update (wait before sending a new command)

Voltage safety: every common SPI e-paper module (Waveshare, Good Display) uses 3.3V onboard logic — powering it directly from the ESP32's 3V3 is correct and needs no level shifter.

4. Full Sample Code

The program initializes the e-paper via GxEPD2, draws a status screen (a simulated battery voltage + update count) right at boot, then refreshes it every 60 seconds (shortened for easy observation in this demo — a real deployment should use a few minutes to a few hours).

#include <SPI.h>
#define ENABLE_GxEPD2_display 1
#include <GxEPD2_BW.h>

#define EPD_CS 5
#define EPD_DC 17
#define EPD_RST 16
#define EPD_BUSY 4

GxEPD2_BW<GxEPD2_213_B74, GxEPD2_213_B74::HEIGHT> display(
    GxEPD2_213_B74(EPD_CS, EPD_DC, EPD_RST, EPD_BUSY));

unsigned long lastUpdate = 0;
const unsigned long UPDATE_INTERVAL_MS = 60000; // cap nhat moi 60s (demo); thuc te co the vai phut/gio
bool epdOk = false;
int readingCount = 0;

float readBatteryVoltage() {
  // Demo: gia lap dien ap pin. Thay bang analogRead(ADC_PIN) * he so chia ap thuc te.
  return 3.85f - (float)(readingCount % 10) * 0.01f;
}

void renderStatusScreen(float batteryV, int count) {
  display.setRotation(1);
  display.setFont(nullptr);
  display.setTextColor(GxEPD_BLACK);
  display.firstPage();
  do {
    display.fillScreen(GxEPD_WHITE);
    display.setCursor(5, 15);
    display.print("IoTLabs - E-Paper");
    display.setCursor(5, 35);
    display.print("Power-save Node");
    display.setCursor(5, 60);
    display.print("Battery: ");
    display.print(batteryV, 2);
    display.print("V");
    display.setCursor(5, 80);
    display.print("Update #: ");
    display.print(count);
  } while (display.nextPage());
}

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println(F("[BOOT] ESP32 E-Paper power-save demo starting..."));

  display.init(115200, true, 2, false);
  epdOk = true;
  Serial.println(F("[OK] E-Paper SSD1680 khoi tao thanh cong."));

  float v = readBatteryVoltage();
  readingCount++;
  renderStatusScreen(v, readingCount);
  Serial.printf("[DATA] battery=%.2fV update=%d (man hinh giu hinh khong can dien)\n", v, readingCount);
}

void loop() {
  unsigned long now = millis();
  if (now - lastUpdate >= UPDATE_INTERVAL_MS) {
    lastUpdate = now;

    if (epdOk) {
      float v = readBatteryVoltage();
      readingCount++;
      renderStatusScreen(v, readingCount);
      Serial.printf("[DATA] battery=%.2fV update=%d\n", v, readingCount);
    } else {
      Serial.println(F("[WARN] E-Paper khong san sang, chi in Serial."));
    }
    // Trien khai thuc te: goi esp_deep_sleep(UPDATE_INTERVAL_MS * 1000ULL) o day
    // de tat toan bo MCU giua 2 lan cap nhat, chi e-paper "giu" hinh anh.
  }
}

Compile result: Sketch uses 425232 bytes (32%) of program storage space. Global variables use 27708 bytes (8%) of dynamic memory. FQBN: esp32:esp32:esp32.

5. Detailed Code Walkthrough

  • GxEPD2_BW<GxEPD2_213_B74, ...> — selects the exact driver class matching the module's real size/controller chip (SSD1680, 2.13", 122x250px); GxEPD2 supports dozens of different e-paper types through similar classes.

  • display.firstPage()/nextPage() — GxEPD2's characteristic "paged" drawing model: the whole content is redrawn inside a do-while loop to optimize RAM usage when the framebuffer can't hold the entire screen at once.

  • The EPD_BUSY pin is automatically read by the library to wait for the module to finish its physical update cycle (typically 1-2 seconds for a full refresh) before allowing the next drawing command.

  • The comment at the end of loop() suggests using esp_deep_sleep() — the single most important optimization for a real deployment: the ESP32 shuts down entirely between updates while only the e-paper "remembers" the image, letting a battery-powered device (CR2032/18650) run for months.

6. Real Applications / Extensions

Possible extensions: (1) replace the simulated readBatteryVoltage() with an actual analogRead() through a voltage-divider circuit reading a real battery; (2) add esp_sleep_enable_timer_wakeup() + esp_deep_sleep_start() so the MCU sleeps deeply between updates (dropping current draw from ~80mA down to ~10µA when idle); (3) use display.setPartialWindow() to update part of the screen faster (partial refresh) instead of a full refresh every time.

7. Common Issues

IssueCauseFix
The display doesn't seem to change content, just flashes black and whiteNormal — this is the characteristic "flash" effect of a full e-paper refresh, clearing ghostingNot a bug; if you want to avoid the flash, use partial refresh for small updates
Compile error "GxEPD2_BW.h: No such file"The GxEPD2 library isn't installedarduino-cli lib install "GxEPD2"
The program hangs at display.init()Wrong BUSY or RST pin, the module isn't respondingRecheck the 4 control pins (CS/DC/RST/BUSY), make sure the class constructor's parameter order is correct

8. Summary

E-paper is the most power-efficient display choice for battery-powered IoT devices, thanks to its ability to hold an image with no continuous power.

This article showed how to wire an ESP32 to an SSD1680 SPI e-paper display, along with guidance on pairing it with deep sleep to maximize battery life in a real deployment.