Introduction

I have been curious about the Raspberry Pi for a long time, but I only started working with one recently when it was provided to me for a class I have this semester. The model I received is the Raspberry Pi 3 B+. RaspberryPi.org describes it as:

The Raspberry Pi 3 Model B+ is the latest product in the Raspberry Pi 3 range, boasting a 64-bit quad core processor running at 1.4 GHz, dual-band 2.4 GHz and 5 GHz wireless LAN, Bluetooth 4.2/BLE, faster Ethernet, and PoE capability via a separate PoE HAT

As soon as I received it I started looking at the various accessories and add-ons available. The selection of available displays caught my eye, and I decided I wanted to play with an eInk display. There are numerous eInk displays available for the Raspberry Pi. For my purposes, I chose the cheapest one I could find that didn't have terrible reviews.

The eInk display

Waveshare Electronics offers a wide variety of add-ons for the Raspberry Pi, as well as other electronics. I was drawn to the 2.7" ePaper HAT because it was relatively cheap and offered plug and play compatibility. The version I bought is version B, which is the three-color black, white, and red display.

The display is only $19.99 and was easy to install on my Raspberry Pi. It does have one big drawback, though. The refresh takes 15 seconds, which is a very long time. It also needs a full refresh between screens, so that is 15 seconds every time the image changes in any way. Waveshare also offers a black and white version for the same price that takes 6 seconds to refresh.

Initial setup

Installing the display is very straight forward. Just line up the female header (black lego-looking thing) on the HAT with the male header (black spiky-looking thing) on the RPi. I find the easiest way to get it to connect is to pushed down on the two white circles with my thumbs once it is lined up. It should slide right on. No additional connections are required. The HAT comes with an SPI interface, but this is only needed if you are not connecting using the GPIO header.

Waveshare has also provided schematics, example code, and libraries for working with the display. All of this can be found in the wiki. It's important to note that the drivers and libraries are different for each display. The code in the wiki above only works with the 2.7" 3-color display. If you are working with a different display, the code will be similar but not the same.

Drawing to the screen

To get started, make sure you are connected to your Raspberry Pi, either directly connected with keyboard, mouse, and monitor or remoting into the RPi using VNC (or similar service). The ePaper display does not interfere with the regular HDMI input, so you can connect a monitor to it while working with the ePaper display without any issues.

You may also need to install the libraries below if they are not already on your Pi (they probably are).



sudo apt-get install python3-rpi.gpio python-imaging python-smbus python-dev

Create a folder for your ePaper projects. I put mine in ~/pi/epaper/ , but you can put yours anywhere you want. Waveshare provides library files in C, python2, and python3. For this project, I am working with python3. You will need two files from the example code provided by Waveshare for any projects you do; they are epd2in7b.py and epdconfig.py . We will call these the drivers.

You can put a copy of the drivers in every project you make, or you can put them into a dedicated folder and import them into your project from there. I chose the latter. This was my first python project, so it took me a minute to figure out how to make that work, but it's actually pretty simple:

Create a folder in your project directory called lib

Place copies of both files into that folder

In any project file that needs the drivers, import sys and then append the lib folder to sys so python knows where to find it. (Example code below)

and then append the lib folder to so python knows where to find it. (Example code below) Import epd2in7b into the project file. You do not need to import epdconfig , as this is imported into epd2in7b already.

import sys sys . path . insert ( 1 , "./lib" ) # Adds lib folder in this directory to sys import epd2in7b

To draw an image to the screen, we also need Image , ImageDraw , and ImageFont from the Python Image Library (PIL).



from PIL import Image , ImageDraw , ImageFont

Before we can work with the display, we need to create a variable for it and initialize the display. We also want to clear it before doing anything else. With the 2.7" display, we need to pass a color in hex to the Clear function. Even though Clear requires a color, I have not seen any difference from passing in different values, so just stick with 0xFF .



epd = epd2in7b . EPD () # get the display epd . init () # initialize the display print ( "Clear..." ) # prints to console, not the display, for debugging epd . Clear ( 0xFF ) # clear the display

This is now a fully functioning program! If you run it, you should see the display turn black and then flash several times before clearing to "white," which is actually a greyish color similar to a book page.

To print to the display, we are going to create a function that accepts a string.



def printToDisplay ( string ):

This version of the display has both red and black pixels. Each color is rendered as a separate layer, the black is rendered first and then the red. When we create anything on the red layer, we still use black and white for our colors on that layer, but it will render the black pixels in red.

We have to create a separate image for red and black, even if we are only printing in one color. To do this, for each color we call Image.new() and pass in the height and width of the display and a color to use for the display, which in this case is 255 (white).



HBlackImage = Image . new ( '1' , ( epd2in7b . EPD_HEIGHT , epd2in7b . EPD_WIDTH ), 255 ) HRedImage = Image . new ( '1' , ( epd2in7b . EPD_HEIGHT , epd2in7b . EPD_WIDTH ), 255 )

Passing in the height first, and then the width, for the display dimensions, as above, will cause the text to display horizontally. To display the text vertically just swap the heights and widths in the above statements.

For this demo we are just printing text to the screen. We first need to get a draw object and then set our font. We will only be drawing to the black layer. The red layer will stay blank.



draw = ImageDraw . Draw ( HBlackImage ) # Create draw object and pass in the image layer we want to work with (HBlackImage) font = ImageFont . truetype ( '/usr/share/fonts/truetype/google/Bangers-Regular.ttf' , 30 ) # Create our font, passing in the font file and font size

The Bangers font is from Google Fonts. If you don't have this font and don't want to install it, you can use any other font you like.

Once we create the draw and font objects, we can use them to create our text. The first argument is the starting position of the text in pixels, then the string, the font, and the fill.



draw . text (( 25 , 65 ), string , font = font , fill = 0 )

We need to add our images to the screen. We use display() and getbuffer() from the epd2in7b drivers to do this.



epd . display ( epd . getbuffer ( HBlackImage ), epd . getbuffer ( HRedImage ))

Even though we didn't do anything with the red layer, we still have to pass it to the display() function.

Finally, to see what we have done, we need to invoke our printToDisplay() function.



printToDisplay ( "Hello, World!" )

Now save and run the program. It will clear the screen first, then after a couple of seconds the screen will start flashing again to place the text. If you watch your console, you will see that the screen remains busy a few seconds after it is cleared. Once the screen is released, the text starts to render.

Here is a link to this complete program

Awesome! You can write to your display. But what about those buttons? Let's talk about that.

Accessing the buttons

For this part of the project we will use the gpiozero library provided by RaspberryPi.org to access our buttons and use them to interact with the program. Start by importing the Button module from the gpiozero library.



from gpiozero import Button

The trickiest part about working with the buttons is figuring out which pin goes with which button. Waveshare offers a schematic. Since this was my first time working with RPi, python, and schematics, it took me a bit of time to work out what it was telling me. I'll save you some trouble and tell you right now, the corresponding pins, from top to bottom are 5, 6, 13, 19.

To use the button, first assign it to a variable. You can use the Button() function imported from gpiozero and pass in the pin number from above. For this demo, we are only working with the first button, at pin 5.This assignment will be near the top of the file right below the imports.



btn = Button ( 5 ) # Assign btn to the button on the HAT at pin 5

To perform an action when the button is pressed, we will assign a function to the when_pressed property on the button. This should happen at the bottom of the file.



btn . when_pressed = handleBtnPress

Now we need to make our handleBtnPress() function. We will do this directly above the previous assignment and we will move the line where we invoked printToDisplay() into that function.



def handleBtnPress (): printToDisplay ( "Hello, World!" )

To test your program, run it and wait for the console to show that the display is not busy. It will show the message e-paper busy release . Once that is done, press the first button. If everything is correct, it should update to display "Hello, World!"

A complete version of the updated program is here

Putting it all together

Now you can print to the display and use a button to cause the display to update. But, there are four buttons! /picard

If you want each button to do a different thing, you have some options. You can create functions for each button and assign those functions to when_pressed as above or you could even have a single function that changes the behavior depending on which button is pressed. To do this, look for the pin number of the button and use a switcher to decide what to do. The when_pressed function passes the button as an argument automatically. To get the pin number, you need to access btn.pin.number .

Don't forget, you need to wait for the display to release before pressing the next button.

See the example below.



btn1 = Button ( 5 ) btn2 = Button ( 6 ) btn3 = Button ( 13 ) btn4 = Button ( 19 ) def handleBtnPress ( btn ): pinNum = btn . pin . number switcher = { 5 : "Hello, World!" , 6 : "This is my first

RPi project." , 13 : "Hope you lik it." , 19 : "Goodbye!" } msg = switcher . get ( pinNum , "Error" ) printToDisplay ( msg ) btn1 . when_pressed = handleBtnPress btn2 . when_pressed = handleBtnPress btn3 . when_pressed = handleBtnPress btn4 . when_pressed = handleBtnPress

The complete code for the multi-button project is here

Final thoughts

This was my first Raspberry Pi project and my first time working with python. It was a lot of fun, but it was also hard to find resources for working with the ePaper display. Even though Waveshare has a lot of information, they don't have any tutorials or real documentation around working with the display.

The long refresh time matters a lot. It's fun to play with, but it would be too frustrating to use for any real projects. After some extra research, I believe that 2.9" flexible eInk HAT from Waveshare would have been a better option. It is a 2-color display and has a refresh time of 2 seconds. This one is only a few dollars more at $23.99. For a more production-ready version, the 800x600 6" display has a refresh time of less than 1 second, but is a bit more pricey at $74.99.