In a previous tutorial, I explained what ePaper displays are and the basic principles behind their operation. I also introduced the ePaper adapter board from Seeed Studio, which makes it easy and convenient to use these displays with various boards from the XIAO family. I then explored the main features of the GDEY0213F51 display and the steps to display a full-screen image using a program written with Arduino and the XIAO RA4M1 board. This time, I’ll delve deeper into the capabilities of this display, now utilizing the GxEPD2 library, a popular Arduino library for controlling ePaper displays.
The GxEPD2 LibraryGxEPD2 is the evolution of GxEPD, the first version of an Arduino library specialized in controlling ePaper displays (EPD - Electronic Paper Display) with an SPI interface. Developed by Jean-Marc Zingg (ZinggJM), GxEPD2 expands the capabilities of its predecessor and provides support for a wide range of displays from leading manufacturers such as Good Display and Waveshare.
The GxEPD2 library relies on another widely-used library, Adafruit_GFX, for its operation. This library, developed by Adafruit, is one of the most popular libraries in the Arduino ecosystem. It provides a set of functions for drawing text and geometric shapes on a variety of displays, such as LCD and OLED screens.
The relationship between these two libraries can be summarized as follows: while Adafruit_GFX handles the graphical primitives and provides the basic tools for rendering graphics and text, GxEPD2 complements it by specifically managing the control of ePaper displays (EPD) and their SPI communication interface. In this way, Adafruit_GFX manages the visual representation, and GxEPD2 focuses on the operation and functionality of EPDs.
In the following sections, I will explain how to install the GxEPD2 and Adafruit_GFX libraries in the Arduino IDE 2 for use with the XIAO RA4M1 board, and how to leverage their capabilities to display text, graphics, and images on the 4-color GDEY0213F51 display from Seeed Studio.
XIAO Models: Although this tutorial was created using the XIAO RA4M1 board, it generally applies to other boards in the XIAO family that are compatible with the ePaper adapter.Installation
Before using these libraries, you need to install them in the Arduino IDE following the standard procedure.
Since GxEPD2 depends on Adafruit_GFX, you only need to install GxEPD2 directly. During the installation process, the IDE will detect the dependency and prompt you to install the Adafruit library. At that point, simply confirm and proceed with the installation.
The process is the same as for installing any other library. Click on the Library Manager in the left-hand toolbar or select Tools → Manage Libraries from the main menu.
Using either method will open a search box. Type GxEPD2 to search for the library, and when you find it, click the INSTALL button.
Version: Make sure to install the latest version of GxEPD2, don't confuse it with GxEPD, which is the older version.
In the IDE, you should see something like the following:
After clicking INSTALL, the IDE will detect dependencies with other libraries (such as Adafruit_GFX) and will ask if you also want to install them. Confirm by clicking INSTALL ALL
After a few moments, the IDE will download and install all the necessary files.
ePaper ConnectionsAs we’ll see later, one of the first steps when using GxEPD2 is specifying how the ePaper is connected to our board—in this case, the XIAO RA4M1. In this guide, I’ll be using the eInk Expansion Board from Seeed Studio.
This adapter board simplifies the connection between the XIAO and the ePaper. The details about which XIAO pins are connected to the ePaper can be found in the Seeed Wiki:
Connections: If you’re not using the Seeed Studio adapter board and are making the connection to the ePaper yourself, you must keep in mind how you’ve wired those connections when you start using GxEPD2. Additionally, you need to be cautious about the voltage levels supported by the ePaper.Using GxEPD2
The following is a basic example using GxEPD2 to display the classic "Hello, World!" on the screen
// Include the GxEPD2 libraries
#include <GxEPD2_4C.h>
// Include font for the text
#include <Fonts/FreeSans9pt7b.h>
// Define pins for the Seeed Studio adapter board
const int EINK_BUSY = D5; // D5
const int EINK_RST = D0; // D0
const int EINK_DC = D3; // D3
const int EINK_CS = D1; // D1
const int EINK_SCK = D8; // D8 (SCK)
const int EINK_MOSI = D10; // D10 (MOSI)
// Create display object
GxEPD2_4C<GxEPD2_213c_GDEY0213F51, GxEPD2_213c_GDEY0213F51::HEIGHT>
display(GxEPD2_213c_GDEY0213F51(EINK_CS, EINK_DC, EINK_RST, EINK_BUSY));
void setup() {
// Initialize the e-paper
display.init(115200);
display.setFullWindow();
display.fillScreen(GxEPD_WHITE);
display.setRotation(1);
// Initialize text parameters
display.setTextColor(GxEPD_BLACK);
display.setTextSize(2);
display.setFont(&FreeSans9pt7b);
// Print text
display.setCursor(0, 30);
display.print ("Hello world!");
// Update ePaper
display.display ();
}
void loop() {
// Does nothing
}
When running the program, the screen displays the following result:
Let's analyze this code to begin understanding the logic behind GxEPD2.
In the first lines, we include the GxEPD2_4C library, which contains the functions and definitions for 4-color displays, and FreeSans9pt7b, which defines a "font" or typeface.
//Include the GxEPD2 libraries
#include <GxEPD2_4C.h>
//Include font for the text
#include <Fonts/FreeSans9pt7b.h>
Next, we define the connection pin names for the display as constants, as shown earlier in Fig. 3. This is done purely for clarity and is not strictly necessary—we could use D5
, D0
, or D3
instead of EINK_BUSY
, EINK_RST
, or EINK_DC
without affecting the functionality of the code.
//Define pins for the Seeed Studio adapter board
const int EINK_BUSY = D5; // D5
const int EINK_RST = D0; // D0
const int EINK_DC = D3; // D3
const int EINK_CS = D1; // D1
const int EINK_SCK = D8; // D8 (SCK)
const int EINK_MOSI = D10; // D10 (MOSI)
Next, there is a somewhat complex definition that involves creating the object associated with the display, in this case named display
.
// Create display object
GxEPD2_4C<GxEPD2_213c_GDEY0213F51, GxEPD2_213c_GDEY0213F51::HEIGHT> display(GxEPD2_213c_GDEY0213F51(EINK_CS, EINK_DC, EINK_RST, EINK_BUSY));
To create this object, a template called GxEPD2_4C
is used. This template takes two parameters: GxEPD2_213c_GDEY0213F51
, which is the class identifier that acts as the specific driver for this particular display, and the constant GxEPD2_213c_GDEY0213F51::HEIGHT
, which defines the height of the display and determines whether paged or non-paged mode is used (don’t worry, I’ll explain what this means later).
Finally, a constructor is used to create the display object (you can change its name if you want), passing as parameters the display identifier again and the connection pin details (using the constants we defined earlier).
Constructor: This declaration is essential for the rest of the code because it defines the display you will use, and therefore determines all the functions or methods that will be available for that display and its features.
GxEPD2 does not support just any display arbitrarily. Before using an ePaper, we must ensure it is supported and identify the corresponding driver.
Continuing with the analysis of the example code, we have the setup
function (where all the interesting things happen) and the loop
function (which does nothing).
At the beginning of setup
, we have a preliminary initialization:
void setup()
{
// Initialize the e-paper
display.init(115200);
display.setFullWindow();
display.fillScreen(GxEPD_WHITE);
display.setRotation(1);
As you can see, these consist of calls to different methods belonging to the display
object, which is why the syntax display.method(parameters)
is used. Let’s look at what each one does:
init
: Initializes various elements of the display object. In this case, we only provide the value 115200, which is the baud rate for diagnostic output via the serial monitor. You can disable it by setting it to 0, but it’s useful for understanding what the library is doing.setFullWindow
: Specifies that you are going to use the full screen, as it is also possible to use only a portion of the screen to speed up the refresh process (although this is not possible with the display used in this tutorial).fillScreen
: Fills the screen, in this case with white (GxEPD_WHITE
), erasing any content that may have been left from before. You can use any of the colors supported by this display: Black (GxEPD_BLACK
), White (GxEPD_WHITE
), Red (GxEPD_RED
) and Yellow (GxEPD_YELLOW
)setRotation
: Defines the screen orientation in multiples of 90 degrees.
The next section of code prepares how the text will appear on the screen:
display.setTextColor(GxEPD_BLACK);
display.setTextSize(2);
display.setFont(&FreeSans9pt7b);
The setTextColor
method sets the text color, setTextSize
defines the text size, and setFont
specifies the font or typeface that will be used to render the text.
Finally, the cursor is positioned, and the text is printed:
display.setCursor(0, 30);
display.print ("Hello world!");
At this point, the code doesn’t actually display anything on the screen. All the instructions operate on a buffer or memory area, which must then be transferred to the screen to make it visible. One way to do this is by using the display
method—we'll explore another method later.
// Update ePaper
display.display ();
This transfer of the memory buffer to the screen is also referred to as a refresh.
Using FontsAs we saw in the previous example, when displaying text on the screen, you can change its size and color. Additionally, you can choose from a variety of fonts with different characteristics, sizes, and styles.
The available fonts are defined in the Adafruit_GFX library, inside the Fonts folder. Each font is a header file (.h
).
The name of each font indicates the typeface, style, and size. For example, the font FreeSans9pt7b used in the previous example can be broken down into the following elements:
- Free: Indicates it is a free font, without licensing restrictions.
- Sans: Refers to a modern style font, like Arial. It can also be Mono, a simple monospaced font, or Serif, a more decorative font like Times New Roman.
- Bold: Means the font is bold. It could also be Oblique (italic) or regular if nothing is specified.
- 9pt: Refers to the font size in points.
- 7b: Indicates that the font is defined using 7-bit values.
To select the font for displaying text, you must use the setFont
method, but first, you need to load the font definition by including the corresponding header file.
Here’s an example that displays text using different fonts, styles, and sizes:
// Include necessary libraries
#include <GxEPD2_4C.h>
// Include font definitions
#include <Fonts/FreeMono9pt7b.h>
#include <Fonts/FreeMono12pt7b.h>
#include <Fonts/FreeMonoBold12pt7b.h>
#include <Fonts/FreeMonoOblique12pt7b.h>
#include <Fonts/FreeSerifBold18pt7b.h>
// Define pins for Seeed Studio adapter board
const int EINK_BUSY = D5; // D5
const int EINK_RST = D0; // D0
const int EINK_DC = D3; // D3
const int EINK_CS = D1; // D1
const int EINK_SCK = D8; // D8 (SCK)
const int EINK_MOSI = D10; // D10 (MOSI)
// Create display object
GxEPD2_4C<GxEPD2_213c_GDEY0213F51, GxEPD2_213c_GDEY0213F51::HEIGHT> display(GxEPD2_213c_GDEY0213F51(EINK_CS, EINK_DC, EINK_RST, EINK_BUSY));
void setup()
{
// Initialize the e-paper
display.init(115200);
display.setFullWindow();
display.fillScreen(GxEPD_WHITE);
display.setRotation(1);
// Initialize text parameters
display.setTextColor(GxEPD_BLACK);
display.setTextSize(1);
// Print with different fonts
display.setFont(&FreeMono9pt7b);
display.setCursor(0, 20);
display.print ("Hello world!");
display.setFont(&FreeMono12pt7b);
display.setCursor(0, 40);
display.print ("Hello world!");
display.setFont(&FreeMonoBold12pt7b);
display.setCursor(0, 60);
display.print ("Hello world!");
display.setFont(&FreeMonoOblique12pt7b);
display.setCursor(0, 80);
display.print ("Hello world!");
display.setFont(&FreeSerifBold18pt7b);
display.setCursor(0, 110);
display.print ("Hello world!");
// Update ePaper
display.display ();
}
void loop() {
// Does nothing
}
As you can see, the same text is printed using four different fonts and in different positions. The result can be seen in the following image:
Memory: Load only the fonts you plan to use, as each font definition occupies memory. This is especially important for boards with limited memory, such as the RA4M1.
In addition to the fonts available in the library, you can search for other compatible fonts in online repositories. There are even online or command-line tools that allow you to adapt any font into a format compatible with GxEPD2.
Drawing GraphicsThe GxEPD2 library also includes methods for drawing graphic elements such as points, lines, and geometric shapes like rectangles, circles, and triangles, both hollow and filled.
The following example demonstrates some of these methods combined with text:
// Include the necessary libraries
#include <GxEPD2_4C.h>
// Include the font definitions
#include <Fonts/FreeMonoBold12pt7b.h>
// Pin definitions for Seeed Studio adapter board
const int EINK_BUSY = D5; // D5
const int EINK_RST = D0; // D0
const int EINK_DC = D3; // D3
const int EINK_CS = D1; // D1
const int EINK_SCK = D8; // D8 (SCK)
const int EINK_MOSI = D10; // D10 (MOSI)
// Create the object associated with the display
GxEPD2_4C<GxEPD2_213c_GDEY0213F51, GxEPD2_213c_GDEY0213F51::HEIGHT> display(
GxEPD2_213c_GDEY0213F51(EINK_CS, EINK_DC, EINK_RST, EINK_BUSY)
);
void setup()
{
// Initialize the epaper
display.init(115200);
display.setFullWindow();
display.fillScreen(GxEPD_WHITE);
display.setRotation(1);
display.setTextSize(1);
// Clear the screen
display.setFullWindow();
display.fillScreen(GxEPD_YELLOW); // Yellow background
// 1. Pie chart
// Define the center and radius of the circle
int centerX = 65;
int centerY = 80;
int radius = 40;
// Draw the filled red circle
display.fillCircle(centerX, centerY, radius, GxEPD_RED);
// Calculate the angles for the chart sections
float angle1 = 0;
float angle2 = 144; // 40% of 360 degrees (0.4 * 360)
float angle3 = 252; // 144 + 108 (30% of 360 degrees)
// Convert degrees to radians
float angle1_rad = angle1 * PI / 180;
float angle2_rad = angle2 * PI / 180;
float angle3_rad = angle3 * PI / 180;
// Calculate the coordinates of the points on the circle's edge
int x1 = centerX + radius * cos(angle1_rad);
int y1 = centerY + radius * sin(angle1_rad);
int x2 = centerX + radius * cos(angle2_rad);
int y2 = centerY + radius * sin(angle2_rad);
int x3 = centerX + radius * cos(angle3_rad);
int y3 = centerY + radius * sin(angle3_rad);
// Draw lines from the center to the edge
display.drawLine(centerX, centerY, x1, y1, GxEPD_WHITE);
display.drawLine(centerX, centerY, x2, y2, GxEPD_WHITE);
display.drawLine(centerX, centerY, x3, y3, GxEPD_WHITE);
// 2. Bar chart
int originX = 135; // X of the coordinate origin
int originY = 115; // Y of the coordinate origin
int barW = 20; // Width of the bars
int barGap = 5; // Gap between bars
int barH1 = 50; // Height of bar1
int barH2 = 70; // Height of bar2
int barH3 = 45; // Height of bar3
// Draw the X and Y axes
display.drawLine(originX, originY, originX + 90, originY, GxEPD_BLACK);
display.drawLine(originX, originY, originX, originY - 70, GxEPD_BLACK);
// Draw the bars
display.fillRect(originX + barGap, originY - barH1, barW, barH1, GxEPD_RED);
display.fillRect(originX + 2 * barGap + barW, originY - barH2, barW, barH2, GxEPD_RED);
display.fillRect(originX + 3 * barGap + 2 * barW, originY - barH3, barW, barH3, GxEPD_RED);
// 3. Title text
// Text configuration
display.setTextColor(GxEPD_BLACK);
display.setCursor(15, 20);
display.setFont(&FreeMonoBold12pt7b);
// Print text
display.print("ACME Corporation");
// Refresh screen
display.display();
}
void loop() {
// Does nothing
}
The code starts with initialization, similar to previous examples, followed by three blocks where a pie chart, a bar chart, and the title are displayed. Let's take a look at each of them:
// 1. Pie chart
// Define the center and radius of the circle
int centerX = 65;
int centerY = 80;
int radius = 40;
// Draw the filled red circle
display.fillCircle(centerX, centerY, radius, GxEPD_RED);
// Calculate the angles for the chart sections
float angle1 = 0;
float angle2 = 144; // 40% of 360 degrees (0.4 * 360)
float angle3 = 252; // 144 + 108 (30% of 360 degrees)
// Convert degrees to radians
float angle1_rad = angle1 * PI / 180;
float angle2_rad = angle2 * PI / 180;
float angle3_rad = angle3 * PI / 180;
// Calculate the coordinates of the points on the circle's edge
int x1 = centerX + radius * cos(angle1_rad);
int y1 = centerY + radius * sin(angle1_rad);
int x2 = centerX + radius * cos(angle2_rad);
int y2 = centerY + radius * sin(angle2_rad);
int x3 = centerX + radius * cos(angle3_rad);
int y3 = centerY + radius * sin(angle3_rad);
// Draw lines from the center to the edge
display.drawLine(centerX, centerY, x1, y1, GxEPD_WHITE);
display.drawLine(centerX, centerY, x2, y2, GxEPD_WHITE);
display.drawLine(centerX, centerY, x3, y3, GxEPD_WHITE);
This block displays the pie chart, which consists of a red-filled circle with three white lines inside, dividing it into three distinct sections of the "pie."
The first step is to define the center of the circle and its radius using variables. This allows the graphic to be positioned on the screen conveniently by adjusting these values.
Next comes a series of calculations to determine the coordinates of the endpoints for the lines that separate each section of the circle.
First, the angles of these lines relative to the horizontal are determined in degrees. These values are then converted to radians and treated as vectors. By calculating their x and y components, the exact points for drawing the straight lines are determined.
Finally, the white lines are drawn to mark the three sections.
The second block is somewhat simpler and is responsible for drawing the bar chart:
// 2. Bar chart
int originX = 135; // X of the coordinate origin
int originY = 115; // Y of the coordinate origin
int barW = 20; // Width of the bars
int barGap = 5; // Gap between bars
int barH1 = 50; // Height of bar1
int barH2 = 70; // Height of bar2
int barH3 = 45; // Height of bar3
// Draw the X and Y axes
display.drawLine(originX, originY, originX + 90, originY, GxEPD_BLACK);
display.drawLine(originX, originY, originX, originY - 70, GxEPD_BLACK);
// Draw the bars
display.fillRect(originX + barGap, originY - barH1, barW, barH1, GxEPD_RED);
display.fillRect(originX + 2 * barGap + barW, originY - barH2, barW, barH2, GxEPD_RED);
display.fillRect(originX + 3 * barGap + 2 * barW, originY - barH3, barW, barH3, GxEPD_RED);
As you can see, the approach is similar to the previous one: variables are used to define the position and size of the graph, making it easy to modify.
First, the coordinates for the origin of the axes, the width of the bars, the spacing between them, and the heights of each of the three bars are defined.
Next, the axes are drawn as two black lines, followed by the three bars.
The final block prints the title and refreshes the display (remember that the display
method is essential for transferring the content from the memory buffer to the screen).
// 3. Title text
// Text configuration
display.setTextColor(GxEPD_BLACK);
display.setCursor(15, 20);
display.setFont(&FreeMonoBold12pt7b);
// Print text
display.print("ACME Corporation");
// Refresh screen
display.display();
As you can see, the text's color, position, and font are configured before printing it.
The result of the above code is as follows:
If the graphical elements mentioned earlier are not enough and you want to display bitmap images, I have good news: GxEPD2 supports this as well. However, there is a limitation—you can only display images in a single color. But don’t worry; with a simple trick that I'll show you later, you can display bitmap images that take advantage of all the colors your ePaper supports.
Monochromatic Images
For simplicity, let’s start by learning how to display monochromatic black-and-white images.
The first step is to prepare the image you want to display. It must be in a monochromatic format (e.g., BMP) and have dimensions suitable for your ePaper (less than 122 x 250 pixels for the ePaper used in this article). Additionally, the image needs to be rotated 90 degrees to the right.
Here’s an example image, 120 x 120 pixels, already rotated 90 degrees:
Once your image is ready, with the appropriate size and color depth, you need to convert it into a different format so it can be incorporated into the code written in the Arduino IDE.
This can be done using the image2lcd program, which takes the image and converts it into a header file (a ".h" file) containing the image data in the form of a byte array (unsigned char
).
If you’re not familiar with this program, I recommend reading this previous article where I explain in detail how to download, install, and register it, as well as the steps required to convert an image.
By completing this process, you will generate a header file like the one shown below:
Copy this header file into the same folder as the example code, shown below.
// Include the necessary libraries
#include <GxEPD2_4C.h>
// Include font definitions
#include <Fonts/FreeSansBold18pt7b.h>
#include <Fonts/FreeSansBold24pt7b.h>
// Include the bitmap
#include "cafe120.h"
// Pin definitions for Seeed Studio adapter board
const int EINK_BUSY = D5; // D5
const int EINK_RST = D0; // D0
const int EINK_DC = D3; // D3
const int EINK_CS = D1; // D1
const int EINK_SCK = D8; // D8 (SCK)
const int EINK_MOSI = D10; // D10 (MOSI)
// Create the object associated with the display
GxEPD2_4C<GxEPD2_213c_GDEY0213F51, GxEPD2_213c_GDEY0213F51::HEIGHT> display(GxEPD2_213c_GDEY0213F51(EINK_CS, EINK_DC, EINK_RST, EINK_BUSY));
void setup()
{
// Initialize the ePaper display
display.init(115200);
display.setFullWindow();
display.setRotation(1);
display.setTextSize(1);
// Clear the screen
display.fillScreen(GxEPD_YELLOW); // Yellow background
// Load bitmap
display.drawBitmap(6, 6, gImage_cafe120, 120, 120, GxEPD_BLACK);
// Print "Latte"
display.setTextColor(GxEPD_BLACK);
display.setCursor(145, 50);
display.setFont(&FreeSansBold18pt7b);
display.print("Latte");
// Print "-10%"
display.setTextColor(GxEPD_RED);
display.setCursor(130, 100);
display.setFont(&FreeSansBold24pt7b);
display.print("-10%");
// Refresh display
display.display();
}
void loop() {
// Does nothing
}
In the previous code, I introduced the drawBitmap
method. This method displays a bitmap on the screen at the specified coordinates and in a specific color from the display's color palette. Its format is as follows:
drawBitmap(x, y, image, hor, ver, color);
Where:
- x, y: The coordinates where the bitmap will begin to appear.
- image: The bitmap to display. After converting it to a ".h" file, include it at the beginning of the code.
- hor, ver: The number of horizontal and vertical pixels in the bitmap.
- color: The color in which the bitmap is displayed.
The result looks like this:
Color Images
In the previous example, we saw how the drawBitmap
method can display a bitmap in a single color. I used a black design on a yellow background, but other colors, such as red or white, could also be used, depending on the ePaper's color palette.
This might already give you a clue about the trick to displaying multicolor images: decompose the original image into several individual images, one for each color, convert each to a ".h" file, and then overlay them to reconstruct the original image.
Since the ePaper used in this example supports four colors, you would need to generate four images: one for white, one for black, one for yellow, and one for red. However, the process can be simplified by omitting the image for the background color. For instance, if the background is white, only three images are needed—one for each of the other three colors.
From a coding perspective, this is straightforward: you only need three #include
directives and three drawBitmap
methods. However, more time is required to prepare the images.
As an example, I'll walk you through the process to load the ePaper with the image shown below, which I’ve used in another tutorial:
The original image contains only the four colors supported by the ePaper and shares the same size: 122 x 250 pixels.
Using the GIMP program (though you can likely achieve the same with another tool), I separated each color into a layer and saved them as monochrome BMPs, ensuring they were rotated 90 degrees beforehand.
The resulting files are as follows (corresponding to black, red, and yellow, from left to right):
Following the same procedure explained earlier for monochromatic images, I generated three ".h" files, one for each color, using the img2lcd program.
Finally, here is the code I used. As you can see, at the beginning, the three ".h" files containing the data for each color are included. Then, the three bitmaps are displayed in exactly the same position but using three different colors (an offset of 6 points is required for the y-coordinate to display them correctly).
// Include the necessary libraries
#include <GxEPD2_4C.h>
// Include the bitmaps of each color
#include "CatBlack.h"
#include "CatRed.h"
#include "CatYellow.h"
// Pin definitions for Seeed Studio adapter board
const int EINK_BUSY = D5; // D5
const int EINK_RST = D0; // D0
const int EINK_DC = D3; // D3
const int EINK_CS = D1; // D1
const int EINK_SCK = D8; // D8 (SCK)
const int EINK_MOSI = D10; // D10 (MOSI)
// Create the object associated with the display
GxEPD2_4C<GxEPD2_213c_GDEY0213F51, GxEPD2_213c_GDEY0213F51::HEIGHT> display(GxEPD2_213c_GDEY0213F51(EINK_CS, EINK_DC, EINK_RST, EINK_BUSY));
void setup()
{
// Initialize the ePaper display
display.init(115200);
display.setFullWindow();
display.setRotation(1);
// Clear the screen
display.fillScreen(GxEPD_WHITE); // White background
// Display bitmaps (add offset in Y)
display.drawBitmap(0, 6, gImage_gatoNegroRot, 250, 122, GxEPD_BLACK);
display.drawBitmap(0, 6, gImage_gatoRojoRot, 250, 122, GxEPD_RED);
display.drawBitmap(0, 6, gImage_gatoAmarilloRot, 250, 122, GxEPD_YELLOW);
// Refresh display
display.display();
}
void loop() {
// Does nothing
}
Here is the corresponding image:
Certainly, you can use a smaller bitmap than the screen size and combine it with other elements such as text, shapes, etc.
Paged ModeThe various methods of GxEPD2, such as print
, fillRect
, or drawBitmap
, do not directly access the ePaper display but instead modify a memory buffer, which is later copied to the screen when the display
method is invoked.
The larger the screen, the larger the buffer required. Adding more colors further complicates the situation. This can be a problem for microcontrollers with limited RAM, as the buffer may occupy a significant portion of the available memory.
To address this, GxEPD2 offers an alternative working mode called Paged Mode. In this mode, a smaller buffer is defined—for example, half the size of what would be required for the entire screen—and the screen refresh is done in stages. First, the buffer is used to generate the content for one half of the screen and transfer it. Then, the same buffer is reused to prepare the other half, which is subsequently transferred. While this process is slower than transferring the entire buffer at once, it uses significantly less RAM.
All the examples we've seen so far do not use pagination. To use Paged Mode, you need to modify the code as follows:
- Set the buffer as a fraction of the full screen: When creating the display object, specify that the buffer will only be a portion of the screen. For example, to use half the buffer, set it to
HEIGHT / 2.
- Start the pagination cycle: Use the
firstPage
method to indicate the beginning of the pagination process. - Wrap screen-modification methods in aloop: Enclose the code that modifies the screen content in a
do...while
loop. - Continue until all pages are displayed: The loop should repeat as long as the
nextPage
method returnstrue
, which means there is still content to transfer to the screen.
One of the earlier examples, which displays the text "Hello world!" using Paged Mode, would look like this:
// Include the GxEPD2 libraries
#include <GxEPD2_4C.h>
// Include font for the text
#include <Fonts/FreeSans9pt7b.h>
// Pin definitions for Seeed Studio adapter board
const int EINK_BUSY = D5; // D5
const int EINK_RST = D0; // D0
const int EINK_DC = D3; // D3
const int EINK_CS = D1; // D1
const int EINK_SCK = D8; // D8 (SCK)
const int EINK_MOSI = D10; // D10 (MOSI)
// Create display object with HEIGHT/2
GxEPD2_4C<GxEPD2_213c_GDEY0213F51, GxEPD2_213c_GDEY0213F51::HEIGHT/2> display(GxEPD2_213c_GDEY0213F51(EINK_CS, EINK_DC, EINK_RST, EINK_BUSY));
void setup()
{
// Initialize the ePaper display
display.init(115200);
display.setFullWindow();
display.fillScreen(GxEPD_WHITE);
display.setRotation(1);
// Initialize text parameters
display.setTextColor(GxEPD_BLACK);
display.setTextSize(2);
display.setFont(&FreeSans9pt7b);
// Start the pagination cycle
display.firstPage();
do
{
// Print text
display.setCursor(0, 30);
display.print("Hello World!");
}
while (display.nextPage()); // Repeat while there are pages left
}
void loop() {
// Does nothing
}
As you can see, when creating the display
object, a buffer is defined with half the screen size (HEIGHT / 2
). Then, pagination begins with the firstPage
call, and the method that displays text on the screen (print
) is placed inside a do...while
loop, using nextPage
as the condition.
There's no need to use the display
method because the screen refresh happens automatically when nextPage
is called.
Some ePaper devices offer the possibility of performing a partial refresh of the screen. This means that instead of transferring the entire buffer (either at once or in multiple steps if using Paged Mode), only the portion corresponding to the changed area of the screen is updated, leaving the rest intact.
This technique enables much faster updates since only a portion of the screen is modified, significantly reducing the amount of data to transfer. It is especially useful for displaying frequently changing information, such as sensor data.
However, not all ePaper displays support this feature. It is more common in monochromatic screens and less so in multi-color screens. Furthermore, in cases where it is supported, partial refresh is often available only for a single color (e.g., black) and not for all colors.
To use partial refresh, you need to specify the area to refresh with the setPartialWindow
method instead of using setFullWindow
. However, the display model GDEY0213F51, used in this tutorial, does not support this feature, so I won’t go into further details.
All the examples shown in this tutorial are available in the following repository
ConclusionsIn this tutorial, I showed you how to make the most of ePaper displays using the GxEPD2 library in combination with XIAO boards like the RA4M1. I guided you through installing the necessary libraries and provided Arduino examples to display text, graphics, and images in different colors. Additionally, I explained how to optimize memory usage through Paged Mode and briefly discussed the concept of partial refresh, a technique that enables faster updates on certain displays.
My goal was to provide you not only with practical examples that you can replicate but also to help you understand the strengths and limitations of this technology and the possibilities offered by the GxEPD2 library. To keep it concise, I covered only some of the library's methods, but with this foundation, you can continue exploring and experimenting with other features.
I hope you found this tutorial helpful and that you can apply what you’ve learned to your projects. See you next time!
For more information and projects, you can check out my blog and social media.
Comments