Introduction: How to Add an E-Ink Display to Your Project

Lots of projects involve monitoring of some kind of data, such as environmental data, often using an Arduino for control. In my case, I wanted to monitor the salt level in my water softener. You might want to access the data over your home network, but equally you might want to display it where it's being measured. Or you could have an always-on remotely connected display in a more convenient location.

There are now several kinds of display you can use, all quite cheap, but having different advantages and disadvantages:

  • An alphanumeric LCD display is the cheapest but also the most limited.
  • An OLED display can display graphics but the cheap ones are very small. The second photo shows a 128x64 pixel OLED display next to an E-Ink one.
  • An E-Ink (or E-Paper) display is somewhat larger and hence easier to read, and has the advantage that the display is retained even when it's switched off! But it takes several seconds to redraw the display.

The E-Ink display seemed ideal for my application as I could program the Arduino to wake up only every few hours, take a reading and display it before going back to sleep. It's then of no consequence that it takes several seconds to redraw the display.

In such an application, the average current consumption can be arranged to be so low that a 9V lithium smoke detector battery can be made to last for 10 years! What's more, some of these displays will display three colors: white, black, and red (or yellow). Ideal if you'd like to display a warning or alert in red.

Supplies

The cheapest E-Ink displays I've found are sold by BuyDisplay, also available from many eBay sellers. Unfortunately the documentation leaves quite a lot to be desired so I took it upon myself to write a tutorial - read on!

Depending on your requirements and your budget, you have the choice of various sizes:

  • 1.54" (152x152 = 23,104 pixels)
  • 2.13" (212x104 = 22,048 pixels)
  • 2.6" (296x152 = 44,992 pixels)
  • 2.7" (176x264 = 46,464 pixels)
  • 2.9" (296x128 = 37,888 pixels)
  • 4.2" (400x300 = 120,000 pixels)
  • 5.83"(640x480 = 307,200 pixels)
  • 7.5" (880x528 = 464,640 pixels)

(The range has exanded since last time I looked so may have expanded further by th time you read this.)

They are available as either 2-colour (black/white) or 3 colour (black/red/white or black/yellow/white). This Instructable assumes you are using the red one, but if you have chosen the yellow version, simply read "yellow" for "red" throughout.

Choose an SPI (4-wire) version. I used the 1.54" model, which is a very nice size.

Step 1: Connecting Your Display

These displays come with a 2x4 pin header. The pin numbers are clearly labelled, pins 7, 5, 3 and 1 (from left to right) along the top row and 8, 6, 4, 2 along the bottom.

Your display may come with an 8-way patch cable, which makes connection easy. (My patch cable has 2 red wires and 2 brown. They are not interchangeable!

The following table gives the connections, which apply to most types of Arduino (including Uno, Pro Mini, Pro Micro and Nano).

E-ink Module
Arduino
Pin
Name
Pin
Name
1
VDD
Vcc
3.3/5V
2
VSS
Gnd
Gnd
3
Serial Data In
11
MOSI
4
Serial Clock In
13
SCK
5
/Chip Select
10

6
Data/Instr
9

7
Reset
8

8
Device Busy
7

Step 2: Download the Provided Software

You can use the provided software as described in this step, or you can use my enhanced library in the next step but one.

Find your device at BuyDisplay.com. Towards the bottom of the page you will find a download ZIP file "Arduino Library and Example for 4-wire SPI". Click on this to download and open in Windows Explorer.

Windows Explorer will show this as containing a single top-level folder "Libraries-Examples_ER-EPM0154-1R". (The name will be slightly different if yours is not the 1.54" model.)

Copy this top-level folder into your Arduino libraries folder. Right-click to rename the folder, and delete "Libraries-Examples_" from the name.

(To find your Arduino libraries folder, in the Arduino IDE, click File... Preferences, and note the Sketchbook Location. Navigare to this, and you'll find the Arduino "libraries" folder amongst your sketch folders.)

Open this folder and open the folder "Libraries" within it. Drag and drop all the files in this folder into the parent folder one level up ("ER-EPM0154-1R"). Delete the (now empty) "Libraries" folder.

You have now installed the files and an examle sketch as an Arduino library. Note that if your display is not the 1.54" one, the only difference seems to be two lines in ER-ERM*-1.h defining WIDTH and HEIGHT.

In the Arduino IDE, click on File... Exampes and scroll down to ER-EPM0154-1R for the demo sketch, which you should be able to compile and run as soon as you have connected your display to your Arduino.

Step 3: Running the Demo

In the Arduino IDE, click File... Examples... ER-EPM0154-1R.

Connect your Arduino to your computer with a USB cable, or however you normally do.

Under Tools, set the Board, Processor and Port.

Under Sketch, click Upload.

There will be a slight delay after uploading completes, and ten the delay will flash a number of times as it paints the first image. Watch while it goes through the demo.

Step 4: Using the Enhanced Library

You can download my enhanced library from github at https://github.com/p-leriche/E-ink_ER-EPM

N.B. I have a high degree of confidence that my library will work with any size compatible display, but I've only in fact tested it with the 1.54" model. If you use a different one, please let me know in the comments at th end of this Instructable, to confirm that it works. But if it doesn't, I'll do my best to get you going.

Download and save the zip file. In the Arduino IDE, click Sketch... Include Library... Add .ZIP Library and select the saved zip file.

My library contains several minor enhancements:

  • It allows different Arduino pin numbers to be used (except for MOSI).
  • The same library can be used forany size device.
  • A new 50% shaded fill, and a speckled fill (random pixels set) are provided.

The library comes as a standard Arduino compressed (zip) file. Download it to your Downloads folder (or where you prefer), and in the Arduino IDE, click Sketch... Include Library ... Add ZIP Library.

Under Examples, you will now find E-ink_ER-EPM. There are 3 example sketches:

  • ER_EPM154-1R-Test: The original vendor-provided demonstration
  • E-ink_demo: The sketch developped in the next steps
  • E-ink_rotate: A demonstration of image rotation.

Step 5: Programming It Yourself

There is unfortunately no documentation with the vendor-provided code, nor is the example code commented adequately. This makes it harder than it should be to use, and the main purpose of this Instructable is to put that right.

Basic Concepts

Since an Arduino is limited in the amount of RAM available, the library allows you to draw or write in small sections of the screen at a time, upoading them individually to the device's intermal memory. Only once you have uploaded all the portions you need do you tell it to display what it has in memory.

These sections of screen are known as "Paint" objects. You only need one, and for each section of screen you define its height, width and rotation. When complete, you upload it, defining the position on the screen to load it to and whether it should be black and white or red and white.

The top left hand corner of the screen has horizontal (x) and vertical (y) coordinates (0,0), the bottom left is (0, 151) and the top right is (151, 0).

Initialisation

Open te E-ink_demo sketch in the Arduino IDE and follow it as I describe how to use the library.

At the top of the sketch you will see the following lines, which are always needed:

#include <SPI.h
#include "ER-ERM0154-1.h"
#include "imagedata.h"
#include "epdpaint.h"

#define COLORED     0
#define UNCOLORED   1
    Epd epd; 

The #include lines pull in the required libraries. SPI.h is a standard Arduino library but the others form part of the e-ink library.

We define names for UNCOLORED (white) pixels and COLORED (black or red ones). (Note to my fellow Europeans: the American spelling of COLOR is used.)

The Epd epd; line creates the electronic paper device object, on which we will be displaying. This has to be here at the start of the sketch to make it available to bith the setup() and loop() functions.

If you have a different size display you can replace the EPD line by:

    Epd epd(WIDTH, HEIGHT);

(having previously defined WIDTH and HEIGHT in #define statements.)

In the same way you can specify non-default pin numbers with:

    Epd epd(WIDTH, HEIGHT, BUSY_PIN, RESET_PIN, DC_PIN, CS_PIN);

Within setup() we need to initialise the device as follows:

Serial.begin(9600)
    if (epd.Init() != 0) {
        Serial.print("e-Paper init failed");
        return;
    }

(In fact, epd.Init() never returns an error, but a future enhancement might detect the absence of a display, or a non-functioning one.)

Step 6: Writing Text

In the E-ink_demo, turn your attention to loop(). First, let's clear the display:

  epd.ClearFrame()

(This is not actually necessary if you're about to display your own image.)

Before we can draw anything (whether text or graphics) we need to create a Paint object to draw on:

  unsigned char image[1024]
  Paint paint(image, 152, 18);    //width should be the multiple of 8

This reserves some space (1024 bytes) and allocates it to the Paint object, ceated by the second line. This is provisionally configured as 152 pixels wide and 18 pixels deep. We can reconfigure it later for reuse as necessary, but note: the width must be a muultiple of 8 since 8 pixels are stored per byte and we can't split bytes. (It will in fact round it up if necessary, but it can then be puzzling when your display doesn't look how it should.

Now we must clear the paint object to UNCOLORED (white), then at position (x, y) = (22, 2) we write "e-ink Demo" using a 16-pixel high font, and COLORED (to show against the UNCOLORED background.

  paint.Clear(UNCOLORED)
  paint.DrawStringAt(12, 2, "e-paper Demo", &Font16, COLORED);

Note that the co-ordinates (22, 2) are the top left hand corner of the first character of the string, and are 22 pixels in and 2 pixels down relative to the top left hand corner of the paint object, not the entire display. Text looks best at least one pixel down from the top of the paint object.

The following fonts are available:

Font8 - 5x8 pixels
Font12 - 7x12 pixels
Font16 - 11x16 pixels
Font20 - 14x20 pixels
Font24 - 17x24 pixels

We now just have to send the paint object ("paint") to the device ("epd"):

  epd.SetPartialWindowBlack(paint.GetImage(), 0, 3, paint.GetWidth(), paint.GetHeight());

SetPartialWindowBlack is a method which we apply to the epd object, using the image and its width and depth properties of the paint object. We're telling it to write this image to the device at (x, y) = (0, 3). And we're saying the COLORED pixels are to be black.

That wasn't too hard, was it? Let's try another one.

  paint.Clear(COLORED);
  paint.DrawStringAt(20, 2, "(White on color)", &Font12, UNCOLORED);
  epd.SetPartialWindowRed(paint.GetImage(), 0, 24, paint.GetWidth(), paint.GetHeight());

We reuse the same paint object, and the same width and height, but this time, let's clear it to COLORED and write an UNCOLORED string to it. And for a change, we'll make the COLORED pixels red and write it to the device at (0, 24), just below the first one.

We've written the two paint objects to the device's memory but not yet told it to display them. We do this with the following statement:

  epd.DisplayFrame();

(In the E-ink_demo sketch we actually leave this until the end, after drawing some more stuff, but you could insert it here if you like, mybe followed by delay(10000); to give you time to admire your handiwork.

Step 7: Drawing Lines and Rectangles

Let's see how to draw lines and rectangles. We're going to use the same paint object, but we need to reconfigure it as 40 pixels wide and 36 pixels heigh. We'll clear it to UNCOLORED.

  paint.SetWidth(40);
  paint.SetHeight(36);
  paint.Clear(UNCOLORED);

We're going to draw a (COLORED) rectangle with top left corner (5, 3) and bottom right (35, 33), relative to the paint object, as usual. We'll also draw its diagonals as lines from (5, 3) to (35, 33) and from (35, 3) to (5, 33). Finally, we'll write the whole paint object (red) to the screen at (32, 42).

//TOP ROW:
// Rectange
  paint.Clear(UNCOLORED);
  paint.DrawRectangle(5, 3, 35, 33, COLORED;)
  paint.DrawLine(5, 3, 35, 33, COLORED);
  paint.DrawLine(35, 3, 5, 33, COLORED);
  epd.SetPartialWindowRed(paint.GetImage(), 32, 42, paint.GetWidth(), paint.GetHeight());

The library, as it came, also provided a filled rectangle, but hey, I wanted a shaded one, so I added a new method. We'll do two more rectangles, one shaded and one filled, and place them to the right of the first one, alternating them black and red.

// Shaded Rectange<br>  paint.Clear(UNCOLORED);
  paint.DrawShadedRectangle(5, 3, 35, 33);
  epd.SetPartialWindowBlack(paint.GetImage(), 72, 42, paint.GetWidth(), paint.GetHeight());

// Filled Rectangle
  paint.Clear(UNCOLORED);
  paint.DrawFilledRectangle(5, 3, 35, 33, COLORED);
  epd.SetPartialWindowRed(paint.GetImage(), 112, 42, paint.GetWidth(), paint.GetHeight());

Step 8: Drawing Circles

Circles are just as easy to draw. Instead of the coordinates of two corners, we have to prvide the coordinates of the centre, and the radius. We'll clear the paint object then put a circle at (20, 15) (relative to the paint object) and radius 15. And repeat for a shaded and a filled circle.

//SECOND ROW
// Circle
  paint.Clear(UNCOLORED);
  paint.DrawCircle(20, 18, 15, COLORED);
  epd.SetPartialWindowBlack(paint.GetImage(), 32, 78, paint.GetWidth(), paint.GetHeight());

// Shaded Circle
  paint.Clear(UNCOLORED);
  paint.DrawShadedCircle(20, 18, 15);
  epd.SetPartialWindowRed(paint.GetImage(), 72, 78, paint.GetWidth(), paint.GetHeight());

//Filled circle
  paint.Clear(UNCOLORED);
  paint.DrawFilledCircle(20, 18, 15, COLORED);
  epd.SetPartialWindowBlack(paint.GetImage(), 112, 78, paint.GetWidth(), paint.GetHeight());

Step 9: UNCOLORED on a COLORED Bckground

We're getting on famously here! So while we're on a roll, let's do 3 more circles on a row below, this time UNCOLORED on a COLORED paint object, like we did with the second line of text.

//THIRD ROW
// Circle
  paint.Clear(COLORED);
  paint.DrawCircle(20, 18, 15, UNCOLORED);
  epd.SetPartialWindowRed(paint.GetImage(), 32, 114, paint.GetWidth(), paint.GetHeight());

// Shaded Circle
  paint.Clear(COLORED)
  paint.DrawShadedCircle(20, 18, 15);
  epd.SetPartialWindowBlack(paint.GetImage(), 72, 114, paint.GetWidth(), paint.GetHeight());

//Filled circle
  paint.Clear(COLORED);
  paint.DrawFilledCircle(20, 18, 15, UNCOLORED);
  epd.SetPartialWindowRed(paint.GetImage(), 112, 114, paint.GetWidth(), paint.GetHeight());

In addition to the shaded fill, there is also a speckled fill, which colours random pixels. So instead of the shaded circle above we could have put

paint.DrawSpeckledCircle(20, 18, 15, 25);

The final parameter (25) is the density, i.e. the percentage of pixels which are to be coloured. If omitted, 50% is assumed.

There is also a DrawSpeckledRectangle, with an optional extra parameter specifying the density.

Step 10: Rotation

Anything we can draw, we can rotate through 90, 180 or 270 degrees. (We count rotations clockwise.)

We can apply a ROTATE property to a paint object, but it's important to understand that it's not the paint object that's rotated but everything you write to it. So if you want vertical text you need to configure your paint object as long and thin in a vertical direction instead of horizontally.

So if you want your text rotated clockwise by 90 degrees so that it reads from top to bottom (instead of left to right), the top right hand corner of the paint object will be (0, 0) for the purposes of anything you write or draw in it, with x mesured from that corner downwards, and y from that corner towards the left.

You probably noticed that we left a space down the left hand side of the display. So let's write some text there rotated 270 degrees, i.e. reading from the bottom up. This will put (0, 0) at the bottom left hand corner.

Note that however you rotate a paint object the rotation only applies as you draw pixels onto it. When you come to write it to the device it's still the coordinates of the top left hand corner that you have to give to SetPartialWindow.

So to recap, let's configure our paint object to have width 32 and height 110, and we'll give it a ROTATE_270 property. Not that we have to do all this before writing or drawing anything to it.

  paint.SetWidth(32);
  paint.SetHeight(110);
  paint.SetRotate(ROTATE_270);

We'll clear it to COLORED and write an UNCOLORED string to it, then place it at (0, 42). (That's the top left hand corner, remember. Forget about any rotation of the pixels in it.)

  paint.Clear(COLORED);<br>  paint.DrawStringAt(8, 8, "Sideways!", &Font16, UNCOLORED);
  epd.SetPartialWindowBlack(paint.GetImage(), 0, 42, paint.GetWidth(), paint.GetHeight());

Finally, we need to tell the device to display all the pixels we've given it. And if we're not going to want to change it for a while and want to conserve battery power we can put it to sleep, and why not put the Arduino to sleep as well, to be woken when it's time to take and display another measurement.

  epd.DisplayFrame();
  epd.Sleep();

A second example sketch shows rotation through 90, 180 and 270 degrees. By now you should be able to follow it by yourself.

Step 11: Drawing Bitmaps

The vendor’s demo includes the display of a couple of bitmap images. These are easy to create using a tool which can be downloaded from

https://www.buydisplay.com/image2lcd

It comes as a zip file containing a .exe install file and a text file containing a license key. Expand it then double-click on the .exe file to install it.

Clearly, there are fairly severe restrictions on what you can display since E-ink pixels can only be either on or off and so can’t represent levels of grey. But it is possible to overlay a rectangular paint object onto an image. You might want to display a logo, symbols, or fixed text in a fancy font, onto which you could overlay variable text or graphics such as a bar or pie chart, perhaps indicating something like a liquid level.

You can create your image with whatever drawing software you’re familiar with, or you can scan in a sketch or drawing, but in either case you need to be able to reduce it to just 2 levels. Save it as .gif, .jpg or .bmp, but not .png which isn’t supported

Launch Image2Lcd. Along the bottom, you will see a Register tab. Click on this and enter the registration code which came in a text file in the zip file you downloaded. This will remove an overlay on the image.

In Image2Lcd, open your image file. In the left hand pane, ensure that you have

  • Output filetype: C array
  • Scan mode: Horizontal Scan
  • BitsPixel: Monochrome
  • Max Width and Height: the size of your display, and
  • Include head data must be unchecked.

Click the button next to Max Width and Height to process. The result of processing will be shown. You may have to adjust the Brightness and Contrast sliders to get the best results.

Click the Reverse color checkbox above the Brightness slider, making it a negative image, which for some reason is necessary, then click Save to save it as imagedata.cpp in the folder containing your Arduino sketch. Next time you open the sketch with the Arduino IDE you should see it as a new tab.

Step 12: Displaying Your Bitmap

In the main file of your Arduino sketch, immediately following the #include lines at the top, insert:

#include "imagedata.h"

Create a new tab (click the down arrow at the end of the tabs line) and call it imagedata.h. Insert the following 2 lines in it:

extern const unsigned char IMAGE_BLACK[];
extern const unsigned char IMAGE_RED[];

In your imagedata.cpp file, the first line will start with

const unsigned char gImage_image[2888] = {

(The number in square brackets will be different if you’re not using the 1.54” display.) Replace this by

const unsigned char IMAGE_BLACK[] PROGMEM = {

This is for a black and white image. If you want it red and white, change it to

const unsigned char IMAGE_RED[] PROGMEM = {

Just before this line, add

#include <avr/pgmspace.h>
#include "imagedata.h"

You’re now ready to display your image. In loop() in your main file, add

epd.ClearFrame();
epd.DisplayFrame(IMAGE_BLACK, NULL);

Or, if it was a red image you’d created, the second line should be

epd.DisplayFrame(NULL, IMAGE_RED);

In fact you can create a combined red and black image by converting the red and black parts separately with Image2Lcd, and displaying them both with

epd.DisplayFrame(IMAGE_BLACK, IMAGE_RED);

However, any pixels which are specified both as black in the black and white image, and red in the red and white one, will come out red.

Finally, you can overlay your image with any of the text or graphics functions we learned about earlier. I wanted to add my Twitter handle, so I added

Paint paint(image, 20, 152);    //width should be the multiple of 8
paint.SetRotate(ROTATE_270);    paint.Clear(UNCOLORED);

paint.DrawStringAt(20, 2, "@pleriche", &Font16, COLORED);
epd.SetPartialWindowRed(paint.GetImage(), 0, 0, paint.GetWidth(), paint.GetHeight());
epd.SetPartialWindowRed(paint.GetImage(), 0, 0, paint.GetWidth(), paint.GetHeight());
epd.DisplayFrame();