In this blog I show you how you can use the GxEPD2 library to draw on a CrowPanel 4.2-inch E-Paper.
The CrowPanel E-Papers by Elecrow come with some example code that demonstrates how to draw on the display. However, the functionality of that code is fairly limited. It would be much nicer, if we could use the more capable GxEPD2 Libary. Unfortunately, as of December 2024 there is no direct support for any of the CrowPanel E-Paper displays in the GxEPD2 library (see GxEPD2_display_selection_new_style.h).
However, with a little tweak we can get the GxEPD2 library library working on the CrowPanel 4.2-inch E-Paper and in the following sections I will show you how it is done.
Required Parts
You will need a CrowPanel E-Paper Display. In this tutorial I focus on the 4.2-inch version and that is the one you should get. There are other display sizes and the code examples should largely work but you will have to make changes due to the difference in resolution and display driver.
CrowPanel 4.2-inch E-Paper
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.
CrowPanel 4.2-inch E-Paper Display
The 4.2-inch E-Paper Display by Crowpanel is a black and white display, with a resolution of 400×300 pixels. It comes as an integrated module that contains an ESP32-S3 chip, several buttons to control the display and more. The following picture shows the front of the display with three control buttons at the side:
Buttons
The MENU and EXIT buttons are simple push buttons, while the Rotary Switch supports an Up, Down and Confirm/OK function. The buttons are fully programmable. The following table shows which GPIO pins are attached to which buttons:
Button | GPIO |
---|---|
MENU | IO2 |
Rotary Switch Down | IO4 |
Rotary Switch Up | IO6 |
Rotary Switch CONF | IO5 |
EXIT | IO1 |
Features
Otherwise, the display module contains a TF Card Slot, a LiPo Battery interface (SH1.0-2Pin) with charging capability and a GPIO interface. Programming (and power) is available via a USB-C port.
GPIO
For GPIO the following pins are available: IO3; IO9; IO15; IO17; IO19; IO21; IO8; IO14; IO16; IO18; IO20; IO38 and the pinout for the GPIO port can be found on the silkscreen but also on the back of the module:
Reset and Boot
On the back of the module there are the Boot and Reset buttons. Unfortunately, they are extending beyond the housing and are easily accidentally pressed when placing the module on a table. I suggest you actually cut them down a bit (with some pliers) to avoid that from happening.
Other CrowPanel E-Paper Displays
The 4.2-inch Display is one of an entire series of similar E-Paper displays but with different resolutions, ESP32 and Driver chips. See the table below for an overview:
For this tutorial, I used the 4.2-inch display but most of the code probably works for the other display sizes as well. However, you will have to find a matching driver in the GxEPD2 library. More about that in the next section.
GxEPD2 library
The GxEPD2 Libary is an Arduino/ESP32 display library for SPI E-Paper displays. As mentioned before, it supports many different E-Paper displays but as of now not directly the CrowPanel E-Paper displays.
However, compared to the demo code that comes with the CrowPanel displays, the GxEPD2 Libary is preferable. It has more functions and better documentation, since it is derived from the Adafruit-GFX-Library, and can be installed easily and used.
Most importantly, you can (re-)use code written with the GxEPD2 Libary across a wide range of different E-Paper displays, without having to rewrite it for specific displays.
Upload Code to CrowPanel E-Paper display
Uploading code to the E-Paper module via the Arduino IDE is easy. Just connect the USB cable and select the “ESP32S3 Dev Module” as board:
Under “Tools” preferably select “Huge App” for the Partition Scheme and “OPI PSRAM” for the PSRAM settings. Though this is not essential for the code in this tutorial but those seem to be the recommended settings.
Code to use GxEPD2 library with CrowPanel E-Paper display
In this section I show you how to get the GxEPD2 library working with the CrowPanel E-Paper display. There are two important steps. First, we need to find a GxEPD2 driver that has the same resolution and supports the same driver chip as the CrowPanel display.
The 4.2-inch Display uses the SSD1683 driver chip and has a resolution of 400×300 pixels. If you search the list of supported drivers in GxEPD2_display_selection_new_style.h you will find two matches:
//#define GxEPD2_DRIVER_CLASS GxEPD2_420_GDEY042T81 // GDEY042T81 400x300, SSD1683 (no inking) //#define GxEPD2_DRIVER_CLASS GxEPD2_420_GYE042A87 // GYE042A87, 400x300, SSD1683 (HINK-E042-A07-FPC-A1)
However, if you try these drivers you get only a very faint image of whatever you draw to appear on the display. I had a closer at the demo code and it turns out that you also have to switch on the power for the display. This is achieved by setting the output on GPIO7 to HIGH
.
The following example prints the text “Makerguides” in the center of the 4.2-inch E-Paper display. Have a quick look first and then we discuss the details:
#include "GxEPD2_BW.h" #define PWR 7 #define BUSY 48 #define RES 47 #define DC 46 #define CS 45 GxEPD2_BW<GxEPD2_420_GYE042A87, GxEPD2_420_GYE042A87::HEIGHT> epd(GxEPD2_420_GYE042A87(CS, DC, RES, BUSY)); void epdPower(int state) { pinMode(PWR, OUTPUT); digitalWrite(PWR, state); } void epdInit() { epd.init(115200, true, 50, false); epd.setRotation(0); epd.setTextColor(GxEPD_BLACK); epd.setTextSize(4); epd.setFullWindow(); } void setup() { epdPower(HIGH); epdInit(); epd.fillScreen(GxEPD_WHITE); epd.drawRect(0, 0, 400, 300, GxEPD_BLACK); epd.setCursor(70, 130); epd.print("Makerguides"); epd.display(); epd.hibernate(); epdPower(LOW); } void loop() {}
Include
First, we include the GxEPD2 Libary and since we are using a black&white E-Paper we include “GxEPD2_BW.h”:
#include "GxEPD2_BW.h"
If you haven’t installed the GxEPD2 Libary yet, you will have to do so, to make this code work. You will also need the Adafruit_GFX libary. Just install the libraries the usual way. After the installation they should appear in the Library Manager as follows.
You don’t need any of the demo code that comes with the Crowpanel displays.
Pin Definitions
Next we need to define the SPI pins the display is using to communicate with the ESP32.
#define PWR 7 #define BUSY 48 #define RES 47 #define DC 46 #define CS 45
I found these pins by looking at the Schematics of the CrowPanel 4.2-inch E-Paper display. Here is the relevant section of the Schematics that describes the connection to the E-Paper:
Display Object
As mentioned before there is no direct support for the CrowPanel 4.2-inch E-Paper in the GxEPD2 Libary. We therefore use a close match to create the epd
object that represents our E-Paper display:
GxEPD2_BW<GxEPD2_420_GYE042A87, GxEPD2_420_GYE042A87::HEIGHT> epd(GxEPD2_420_GYE042A87(CS, DC, RES, BUSY));
Alternatively, you can also use the GxEPD2_420_GDEY042T81
class. Both seem to work but in the following code I used the GxEPD2_420_GYE042A87
class.
epdPower Function
The epdPower()
function is critical and special. For other E-paper displays you don’t need it, but for the CrowPanel E-Paper you do. It sets GPIO7 to high or low and therefore enables or disables the E-paper display.
void epdPower(int state) { pinMode(PWR, OUTPUT); digitalWrite(PWR, state); }
Note that you can disable the E-paper (epdPower(LOW)
) once you have drawn on it. It will keep displaying it, even without any power.
epdInit Function
The epdInit()
function performs the usual initial setup for the graphics, such as screen rotation, text color, text size and refresh mode.
void epdInit() { epd.init(115200, true, 50, false); epd.setRotation(0); epd.setTextColor(GxEPD_BLACK); epd.setTextSize(4); epd.setFullWindow(); }
setup Function
In the setup()
function, we power up the display, fill the screen with white, draw a frame, print the text “Makerguides”, display it, and then shutdown the display again.
void setup() { epdPower(HIGH); epdInit(); epd.fillScreen(GxEPD_WHITE); epd.drawRect(0, 0, 400, 300, GxEPD_BLACK); epd.setCursor(70, 130); epd.print("Makerguides"); epd.display(); epd.hibernate(); epdPower(LOW); }
If you upload and run this code you should see the following output on the E-Paper display:
So, with the addition of the epdPower()
function you can use the GxEPD2 Libary as usual to draw and write on the CrowPanel 4.2-inch E-Paper display. In the next section we test the partial refresh.
Partial refresh of CrowPanel E-Paper display
In the code above we have performed a full refresh of the content of the E-paper. This full refresh is occasionally necessary to remove after-images but is slow (several seconds) and causes the E-paper to flicker a lot.
For most applications you want to update only a part of the display, without flickering and much faster. For instance, to display the digits of a running clock. A partial refresh allows you to do that. For more details have a look at the Partial Refresh of e-Paper Display tutorial.
The following code is essentially the same code as used in that tutorial. It first performs a full refresh to clear the display and prints the text “msec:”. It then continuously performs a partial-refresh and updates the running milliseconds. Below is a screenshot of the display output:
Here is the complete code for this test. The only difference to the one in the Partial Refresh of e-Paper Display is that we need to use the epdPower()
function again to switch on the display. And of course the display driver is different.
#include "GxEPD2_BW.h" #define PWR 7 #define BUSY 48 #define RES 47 #define DC 46 #define CS 45 GxEPD2_BW<GxEPD2_420_GYE042A87, GxEPD2_420_GYE042A87::HEIGHT> epd(GxEPD2_420_GYE042A87(CS, DC, RES, BUSY)); void epdPower(int state) { pinMode(PWR, OUTPUT); digitalWrite(PWR, state); } void drawFull(const void* pv) { epd.setFullWindow(); epd.setCursor(40, 60); epd.print("msec: "); } void drawPartial(const void* pv) { uint16_t x = 120, y = 50, w = 130, h = 34; epd.setPartialWindow(x, y, w, h); epd.setCursor(x + 30, y + 10); epd.print(millis()); epd.drawRect(x, y, w, h, GxEPD_BLACK); } void setup() { epdPower(HIGH); epd.init(115200, true, 50, false); epd.setRotation(0); epd.setTextSize(2); epd.setTextColor(GxEPD_BLACK); epd.drawPaged(drawFull, 0); } void loop() { epd.drawPaged(drawPartial, 0); epd.hibernate(); }
If you upload and run this code you should see the following output. Note that the milliseconds are updated under 0.5 seconds and that there is almost no flickering (just a bit around the frame):
So, full and partial refresh work nicely on the CrowPanel 4.2-inch E-Paper display with the chosen GxEPD2 driver GxEPD2_420_GYE042A87
.
In the next and final section, I show you how to use the partial refresh and the buttons of the CrowPanel display to implement a simple menu that controls three LEDs.
Controlling GPIO via Menu on CrowPanel E-Paper display
One of the demos for the CrowPanel display showcases a simple menu with items that can be enabled or disabled via button presses.
Here we are going to build something similar. But we will use the GxEPD2 library instead and our buttons will control three LEDs. The screenshot below shows how the menu will look like:
We will be able to navigate the menu with the up and down buttons of the rotary switch and we will be able to select a menu item with the confirmation button. If an item is selected an LED of the corresponding color connected to GPIO will be switched on, e.g. selecting “Red” will switch on a Red LED:
Wiring Diagram
The wiring diagram below shows you how to connect the three LEDs to the GPIO port of the CrowPanel display. The cathode of each LED is connected to ground (GND). The anode of the red LED is connected to GPIO3, the green LED is connected to GPIO9 and the blue one is connected to GPIO15. I used a current limiting resistor of 220Ω for each of the LEDs:
Code to navigate Menu and control LEDs
Below you will find the code to navigate the menu, select and deselect items and correspondingly control the LEDs:
#include "GxEPD2_BW.h" #include <Fonts/FreeMonoBold24pt7b.h> #define PWR 7 #define BUSY 48 #define RES 47 #define DC 46 #define CS 45 #define W 400 #define H 300 #define HOME_KEY 2 #define EXIT_KEY 1 #define PREV_KEY 6 #define NEXT_KEY 4 #define OK_KEY 5 int cursor = 0; const int xoff = 70; const int n_items = 3; const char* items[] = { "Red", "Green", "Blue" }; const int pins[] = { 3, 9, 15 }; bool states[] = { false, false, false }; const int ypos[] = { 100, 100 + 60, 100 + 2 * 60 }; GxEPD2_BW<GxEPD2_420_GYE042A87, GxEPD2_420_GYE042A87::HEIGHT> epd(GxEPD2_420_GYE042A87(CS, DC, RES, BUSY)); void epdPower(int state) { pinMode(PWR, OUTPUT); digitalWrite(PWR, state); } void drawCursor() { int16_t s = 12; int16_t x0 = xoff - s; int16_t y0 = ypos[cursor] - 12; int16_t x1 = x0 - s; int16_t y1 = y0 - s; int16_t x2 = x0 - s; int16_t y2 = y0 + s; epd.fillTriangle(x0, y0, x1, y1, x2, y2, GxEPD_BLACK); } void updateCursor(const void* pv) { epd.setPartialWindow(0, 0, xoff, 300); drawCursor(); } void drawItem(int index) { int16_t xb, yb; uint16_t wb, hb; epd.setCursor(xoff + 5, ypos[index]); epd.getTextBounds(items[index], xoff + 5, ypos[index], &xb, &yb, &wb, &hb); if (states[index]) { epd.setTextColor(GxEPD_WHITE); epd.fillRect(xb - 5, yb - 3, W - 2 * xoff, hb + 6, GxEPD_BLACK); } else { epd.setTextColor(GxEPD_BLACK); epd.fillRect(xb - 5, yb - 3, W - 2 * xoff, hb + 6, GxEPD_WHITE); } epd.print(items[index]); } void updateItems(const void* pv) { epd.setPartialWindow(xoff, 0, W - xoff, 300); drawItems(); } void drawItems() { for (int i = 0; i < n_items; i++) { drawItem(i); } } void initEPD() { epdPower(HIGH); epd.init(115200, true, 50, false); epd.setRotation(0); epd.setTextColor(GxEPD_BLACK); epd.setFont(&FreeMonoBold24pt7b); epd.setFullWindow(); epd.fillScreen(GxEPD_WHITE); } void initButtons() { pinMode(HOME_KEY, INPUT); pinMode(EXIT_KEY, INPUT); pinMode(PREV_KEY, INPUT); pinMode(NEXT_KEY, INPUT); pinMode(OK_KEY, INPUT); } void initPins() { for (int i = 0; i < n_items; i++) { pinMode(pins[i], OUTPUT); } } void setup() { initEPD(); initButtons(); initPins(); drawItems(); drawCursor(); epd.display(); } void loop() { if (!digitalRead(PREV_KEY)) { cursor = --cursor < 0 ? n_items - 1 : cursor; epd.drawPaged(updateCursor, 0); } if (!digitalRead(NEXT_KEY)) { cursor = ++cursor >= n_items ? 0 : cursor; epd.drawPaged(updateCursor, 0); } if (!digitalRead(OK_KEY)) { states[cursor] = !states[cursor]; epd.drawPaged(updateItems, 0); digitalWrite(pins[cursor], states[cursor] ? HIGH : LOW); } if (!digitalRead(HOME_KEY)) { cursor = 0; epd.drawPaged(updateCursor, 0); } if (!digitalRead(EXIT_KEY)) { for (int i = 0; i < n_items; i++) { states[i] = false; digitalWrite(pins[i], LOW); } epd.drawPaged(updateItems, 0); } delay(100); }
Let’s break down the code into its components for a better understanding.
Libraries and Constants
We start by including the necessary libraries for the E-paper display and fonts. The constants define the pins used for the display and buttons, as well as the dimensions of the display.
#include "GxEPD2_BW.h" #include <Fonts/FreeMonoBold24pt7b.h> #define PWR 7 #define BUSY 48 #define RES 47 #define DC 46 #define CS 45 #define W 400 #define H 300
Button Definitions
We define the pins for the buttons that will be used to navigate the menu. Each button is assigned a specific function: HOME, EXIT, PREV, NEXT, and OK.
#define HOME_KEY 2 #define EXIT_KEY 1 #define PREV_KEY 6 #define NEXT_KEY 4 #define OK_KEY 5
Variables
Several variables are declared to manage the menu state. cursor
keeps track of the current selection, items
holds the names of the LEDs, and states
tracks whether each LED is on or off. ypos
specifies where the menu items are located on the display.
int cursor = 0; const char* items[] = { "Red", "Green", "Blue" }; const int pins[] = { 3, 9, 15 }; bool states[] = { false, false, false }; const int ypos[] = { 100, 160, 220 };
E-Paper Display Object
As before, the E-paper display object is created using the GxEPD2_BW
class. This sets up the display with the specified pins. If you have a different CrowPanel E-paper display, you will have to change this line.
GxEPD2_BW<GxEPD2_420_GYE042A87, GxEPD2_420_GYE042A87::HEIGHT> epd(GxEPD2_420_GYE042A87(CS, DC, RES, BUSY));
Power Control Function
The epdPower()
function controls the power to the E-paper display. It sets the power pin to output and writes the specified state (HIGH or LOW).
void epdPower(int state) { pinMode(PWR, OUTPUT); digitalWrite(PWR, state); }
Cursor Drawing Function
The drawCursor()
function visually represents the current selection in the menu by drawing a triangle at the corresponding position.
void drawCursor() { int16_t s = 12; int16_t x0 = xoff - s; int16_t y0 = ypos[cursor] - 12; epd.fillTriangle(x0, y0, x1, y1, x2, y2, GxEPD_BLACK); }
Note that the corresponding updateCursor()
function performs a partial refresh and only updates the section of the screen where the cursor is displayed. It will be called when the Up or Down buttons are pressed, for instance.
void updateCursor(const void* pv) { epd.setPartialWindow(0, 0, xoff, 300); drawCursor(); }
Item Drawing Function
The drawItem()
function displays an item in the menu. It checks the state of the LED and changes the background to black if an item is selected.
void drawItem(int index) { epd.setCursor(xoff + 5, ypos[index]); if (states[index]) { epd.setTextColor(GxEPD_WHITE); epd.fillRect(...); } else { epd.setTextColor(GxEPD_BLACK); epd.fillRect(...); } epd.print(items[index]); }
To draw all items of the menu the drawItems()
function is used:
void drawItems() { for (int i = 0; i < n_items; i++) { drawItem(i); } }
And the state of all items is updated by performing a partial refresh of the screen, where the menu items are located:
void updateItems(const void* pv) { epd.setPartialWindow(xoff, 0, W - xoff, 300); drawItems(); }
Initialization Functions
Several initialization functions are defined: initEPD()
initializes the E-paper display, initButtons()
sets the button pins as inputs, and initPins()
sets the LED pins as outputs.
void initEPD() { epdPower(HIGH); ... } void initButtons() { pinMode(HOME_KEY, INPUT); ... } void initPins() { for (int i = 0; i < n_items; i++) { pinMode(pins[i], OUTPUT); } }
Setup Function
The setup()
function is called once when the program starts. It initializes the E-paper display, buttons, and LED pins, and draws the initial items and cursor on the display.
void setup() { initEPD(); initButtons(); initPins(); drawItems(); drawCursor(); epd.display(); }
Loop Function
The loop()
function runs continuously. It checks the state of the buttons and updates the cursor position or toggles the LED states based on user input. The display is updated accordingly using a paged redraw with a partial refresh.
void loop() { if (!digitalRead(PREV_KEY)) { ... } if (!digitalRead(NEXT_KEY)) { ... } if (!digitalRead(OK_KEY)) { ... } if (!digitalRead(HOME_KEY)) { ... } if (!digitalRead(EXIT_KEY)) { ... } delay(100); }
The PREV_KEY
moves the cursor one menu item up and the NEXT_KEY
moves it one item down. If the end or beginning of the menu is reached, the cursor wraps around.
With the OK_KEY
you can toggle the state of an item. The HOME_KEY
moves the cursor to the first item of the menu and the EXIT_KEY
deselects all items.
Demo of the Code
In summary, this code provides a user interface on an E-paper display to control three LEDs. The user can navigate through the options and toggle the LEDs on or off using physical buttons. The following short video clip shows how this looks like:
Conclusions
In this tutorial you learned how to use the GxEPD2 library to draw and write on a CrowPanel 4.2-inch E-Paper Display.
If you need more background information on E-paper Displays have a look at the Interfacing Arduino with E-ink Display and the Partial Refresh of e-Paper Display tutorial.
Also, if you look for other applications the Weather Station on e-Paper Display, the Temperature Plotter on e-Paper Display, the Digital Clock on e-Paper Display, and the Analog Clock on e-Paper Display tutorials may be of interest.
If you have any questions, feel free to ask in the comment section. Happy tinkering ; )
Links
Below some links around the Crowpanel 4.2-inch E-Paper Display that I found useful when writing this tutorial:
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.