You can support me by reading this article on Medium

Picture of the finished device

When researching the e-paper displays for the art project described here, I bought a Waveshare ECP 600x488 7-color E-paper Display (just a normal link, I’m not making any money with this), but ended up never using it. I really try to avoid stuff piling up in my drawers, so I decided to use an old (vintage: 2011 😀) Raspberry Pi 1B I had lying around too and build a simple photo frame.

Using Raspberry Pi To Control the E-paper Display

First, set up a headless Pi using Raspberry Pi OS Lite.

Next, you have to wire the display to the Pi header. The Waveshare Wiki instructions are pretty good and the setup should work out of the box for most people.

I had to make a few changes for my setup, because I wanted to use my old Raspberry Pi 1B to keep it from the e-waste bin. But it has different GPIO pins, so I ended up using the SPI pins on the Pi 1 and GPIO0, GPIO2, and GPIO3 for the DC, RST, and BUSY signals. This required a change in the /RaspberryPi_JetsonNano/python/lib/waveshare_epd/epdconfig.py file. Under # Pin definition I changed the defs this way:

 # Pin definition
    RST_PIN  = "WPI2"
    DC_PIN   = "WPI0"
    CS_PIN   = "WPI10"
    BUSY_PIN = "WPI3"
    PWR_PIN  = "WPI1"
    MOSI_PIN = "WPI12"
    SCLK_PIN = "WPI14"

This enabled the Waveshare e-paper library to work with my old Pi. If you will be using a newer Pi (2 or higher, with the 40-pin header), you don’t need to do this.

Now just run the /RaspberryPi_JetsonNano/python/examples/epd_5in65f_test.py file to see if everything works.

Preparing the Image Files for Display

The 7-color e-paper display works differently than the normal LCD displays. Instead of every pixel having an RGB value, each pixel can only display one of the seven available colors: black, white, green, blue, violet, yellow, and red. Then there is the fixed 600x488 pixel resolution. Finally, the Waveshare library is written to accept bitmap (*.bmp) files only.

Color Conversion

The 7 color limitation means we have to do a color conversion to be able to display the pictures on this display. Selecting simply the nearest color for each pixel will give pretty underwhelming results, so we need a better method, called Floyd-Steinberg dithering. This is a very good explanation of it.

You could code this yourself, but there is a lovely program called ImageMagick that will take care of this for us. But first we need an file that contains only the colors we need. Luckily, the Waveshare library comes with example pictures and you only need this one: RaspberryPi_JetsonNano/python/pic/5in65_n0.bmp, just copy it to the folder you will be using.

Select the Files

This is a pretty obvious first step—collect all the pictures you want displayed on your photo frame. In my case, I did a keyword search in my Google Photo account and simply exported all the results to a temporary directory. Then I did a quick scroll through to delete some photos I did not want to include. Since I was dealing with about 7000 photos, “quick” was a relative term, but it only took a couple of hours.

Do the Conversion

First install ImageMagick (I used winget install ImageMagick.Q16-HDRI on my windows PC). Next, run the following command-line command (replace the folder name accordingly):

magick *.jpg -resize 600x488 -dither FloydSteinberg -define dither:diffusion-amount=85% -remap "folder/your/color/definition/file/is/in/5in65_n0.bmp" BMP3:images.bmp

This opens all the JPEG files in your folder, resizes them to max 600x488 pixels (leaving the unused space blank, so the aspect ratio stays correct), applies the Floyd-Steinberg dithering using the specified file, and, finally, saves them as bitmap files using the given filename, to which the sequence number of the file is appended.

Of course, there are many other ways of doing this, but this worked for me.

Doing the Conversion in Batches

ImageMagick is a very powerful program, but once you convert more than about 1000 pics at once, it will create a crazy amount of temporary files and the conversion itself will take a LONG time. So it’s best to limit the conversion to batches of about 500-2000 files. This depends on your computer: 2000 pics took almost an hour on my rather new and quite powerful computer, producing over hundred GBs of temporary files in the process.

I think the best way is to copy all the image files to a folder called todo first. Then you copy the first batch of, say, 1000 files to your working folder. You then run the conversion, wait until it completes, and then copy everything but the resulting BMP files to the done folder. Then copy the next 1000 files, run the conversion again, but add “_1” to the result filename. Repeat, incrementing the suffix, until the todo folder is empty.

Congratulations, you now have a large pile of bitmap files suitable for displaying on the 7 color e-paper display 😀 All you have to do now is copy it to a folder of your choice on the Raspberry Pi.

The Python Code

The code to display the files we just prepared is quite trivial, I simply copied the /RaspberryPi_JetsonNano/python/examples/epd_5in65f_test.py demo code and removed all the unnecessary parts. Then I added the code to be able to display bitmaps that don’t fill the whole screen. Finally, I added random selection to keep the files from repeating too quickly. The code uses Pillow and you should take care to use proper file handling or you will have problems.

import sys
import os
import random
import logging
import time
from PIL import Image
import traceback
# Imports the Waveshare library
libdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib')
if os.path.exists(libdir):
    sys.path.append(libdir)
from waveshare_epd import epd5in65f

# Let's keep the log short and only collect warnings. Use levels "INFO" or "DEBUG" for setup.
logging.basicConfig(level=logging.WARN)

try:
    logging.info("Random files")
    
    # Initialize the display driver
    epd = epd5in65f.EPD()
    logging.info("init")

    #The forever loop
    while True:
        logging.info("read bmp file")
        image_folder="/the/folder/with/the/bitmaps/"
        #Randomly select the file from the folder
        random_file=random.choice(os.listdir(image_folder))
        logging.info("Chosen file: " + image_folder + random_file)

        #This is important: if you don't use proper file handling using "with",
        #the file stays open and displaying the next file will fail
        with Image.open(image_folder + random_file).convert('RGBA') as bitmap:
            #First set up a white background
            Himage = Image.new('RGB', (epd.width, epd.height), 0xffffff)
            #Get image dimensions from the bitmap file
            width, height = bitmap.size
            #Paste the image on the middle of the white background
            Himage.paste(bitmap, (int((600-width)/2), int((488-height)/2), int((600-width)/2)+width, int((488-height)/2+height)))
        #Initialize the display
        epd.init()    
        #Display the bitmap on background
        epd.display(epd.getbuffer(Himage))
        #Turn off the display internals and wait 10 minutes before you show the next pic
        logging.info("Goto Sleep...")
        epd.sleep()
        time.sleep(600)
    
except Exception as e:
    logging.info(e)
    
except KeyboardInterrupt:    
    logging.info("ctrl + c:")
    epd5in65f.epdconfig.module_exit(cleanup=True)
    exit()

Insert the image path as needed, then run and check if it works.

Set Up the Pi To Run Unattended

If everything works as intended, set up the Pi to auto-run the code on startup using this handy guide. I like the SYSTEMD method best.

Once everything works well after you reboot the Pi, don’t forget to turn on the Overlay File System to prevent SD card corruption if the power goes out unexpectedly.

Putting Everything Together

You now have an e-paper display showing beautiful pictures next to a Raspberry Pi. But it’s not a digital photo frame yet, it needs some kind of a housing first. I decided to use a simple 3D-printed VESA 75 mm case for my Pi and then designed a simple stand to hold everything together.

The stand from the back
You need to glue the two parts together using a drop of superglue

Conclusion

The first thing anybody will probably ask is: Why? Why use a strange display with a rather low resolution and only 7 colors to display your family photos? Without even keeping it low-power?

The answer is a bit complicated, but I can explain it best like this:

  • Constraints spur creativity.
  • It’s not about the resolution or color fidelity, it’s about the pictures.
  • The grainy dithered images have a nice rough and timeless vintage feel to them. They give a new quality to the images I saw many times before.
  • The slow tempo of changing images does not demand your attention. It is a decor item, not another gadget to waste time with.

And I got to rescue an 13-year old Raspberry Pi from the drawer in the process 😄

If you found this article useful, you can buy me a beer here.