Design UX on epaper with ESP32. Add touch!

When I first bought one Good Display 2.7 inch epaper with a FocalTech touch panel, it was a curiosity that I never though it will call my attention, and will end up in one of the wood boxes under my desk. This is the short story of how this small piece of technology really caught my attention and evolved to a full pledged ESP-IDF component that will enable you to make simple UX on epaper for your Firmware.

The cool thing about touch is that wiring 3 cables more, you are able to read X and Y coordinates plus the touch event, and react in the same epaper. Kind of having a small UX playground that enables you to make buttons and a free canvas to draw or take decisions.
It took me one week of research to add the ESP-IDF I2C library, which is based on the work of Strange-V, a developer that did the FT6X36 for the Arduino-ESP32-framework.

My work was to take this as a base and do the I2C communication using the ESP-IDF I2C peripheral documentation as a model. One of the things I love in this framework is that is not a given to do I2C (Wire in Arduino) or SPI. All needs to be low-level instantiated, which makes it a bit harder when you are accustomed to the easy-peasy approach of Arduino, but also unleashes the whole low-level potential of the ESP-IDF.
Don’t take me wrong, I like both frameworks, and ultimately the mission is that it works good together and you achieve in C++ or C an stable Firmware.
So after achieving this first step in the Fork that I renamed to FT6X36-IDF, also to connect to I2C and read what is coming from the FocalTech touch panel, the next immediate goal was to inject this class into my existing epaper component for IDF. The CalEPD component. This is how this new class was inserted:

NOTE: The FT6X36-IDF is injected into the Gdew027w3T class (Same as the SPI class)

The touch panel working together with the epaper class had a very obvious pitfall: My epaper component, same as GxEPD, extends popular Adafruit GFX library to let you use graphics and fonts. This also makes the epaper rotation aware, since we extend the original drawPixel, and make it our own for every different epaper model:

void Gdew027w3T::drawPixel(int16_t x, int16_t y, uint16_t color) {
  if ((x < 0) || (x >= width()) || (y < 0) || (y >= height())) return;

  // check rotation, move pixel around if necessary
  switch (getRotation())
  {
    case 1:
      swap(x, y);
      x = GDEW027W3_WIDTH - x - 1;
      break;
    case 2:
      x = GDEW027W3_WIDTH - x - 1;
      y = GDEW027W3_HEIGHT - y - 1;
      break;
    case 3:
      swap(x, y);
      y = GDEW027W3_HEIGHT - y - 1;
      break;
  }
  uint16_t i = x / 8 + y * GDEW027W3_WIDTH / 8;
  if (color) {
    _buffer[i] = (_buffer[i] & (0xFF ^ (1 << (7 - x % 8))));
    } else {
    _buffer[i] = (_buffer[i] | (1 << (7 - x % 8)));
    }
}

But what happens if we call setRotation(1) and rotate our display 90° to the right (To make it clear, we rotate our code, the epaper knows nothing and just receives a pixel buffer)
The display rotates, and if we sent a pixel to the X:1 Y:1 corner, it will be effectively drawn 90° rotated. But the touch will be still giving our old X and Y data. The touch is a completely hardware detached panel, in the top of the epaper, that again knows nothing about what we are doing.’
So here is the catch, let’s allow this new touch component to be injected in the epaper, just as we inject the SPI IO (Input /Output) class, that is really I for Good Display since the SPI is only a slave that does not communicate with the master, expect of a Busy line. Another epapers, such as the PlasticLogic line, also send temperature readings using the same SPI line so they are IO for real.
This is how it looks in the C++ class Gdew027w3T that is my clon of the non-touch class:

// Constructor of the Gdew027w3T class:
Gdew027w3T::Gdew027w3T(EpdSpi& dio, FT6X36& ts): 
  Adafruit_GFX(GDEW027W3_WIDTH, GDEW027W3_HEIGHT),
  Epd(GDEW027W3_WIDTH, GDEW027W3_HEIGHT), IO(dio), Touch(ts)
{
  printf("Gdew027w3T() %d*%d\n", GDEW027W3_WIDTH, GDEW027W3_HEIGHT);  
}

// Sample of how this is injected in main.cpp
#include "FT6X36.h"
#include <gdew027w3T.h>

// INTGPIO is touch interrupt, goes low when it detects a touch, which coordinates are read by I2C
FT6X36 ts(CONFIG_TOUCH_INT);
EpdSpi io;
Gdew027w3T display(io, ts);

So that’s it now we can inject the FT6X36 into the epaper class itself. This is the perfect moment to think what methods we can add to Gdew027w3T class to make the rotation touch aware:

/**
 * Helper method to set both epaper and touch rotation
 */
void Gdew027w3T::displayRotation(uint8_t rotation) {
  if (rotation>3) {
    printf("INVALID rotation value (valid: 0 to 3, got %d) rotation*90\n",rotation);
    return;
  }
  setRotation(rotation);
  Touch.setRotation(rotation);
}

Perfect. Now if we call display.displayRotation(1) our display will rotate and also signalize the Touch that we’ve rotated so we can recalculate the X and Y rotation in the touch panel.
Note that the Adafuit method setRotate is still there, since I didn’t wanted to extend it, so if we call setRotate then the same as before will happen. I just wanted to demonstrate how powerful is to build on top of C++ object oriented classes.
I hope you can get one of this touch displays, the only low-down is that you need to get one 6 flat adapters, and to check the PDF documentation on Good Display to wire this 3 cables to the ESP32.

Parts list:

Flat cable to PINs adapter – 3 to 4 u$ in Aliexpress, important: The INT pin should be wired to an Input pin in ESP32

One 2.7 inch b/w epaper with touch – 15 u$

One epaper SPI adapter (24 flat cable to SPI Pins) please be aware that this two connectors work independently of each other and both need a common ground and 3.3 volts to work.

That’s all you need. The schematic is the same as any other SPI epaper with this I2C on top, I can provide samples if needed, here my list of how I wired this for this example:

#
# Mosi and Clock should be set for any epaper
#
CONFIG_EINK_SPI_MOSI=23
CONFIG_EINK_SPI_CLK=18
CONFIG_EINK_SPI_CS=5
CONFIG_EINK_DC=16
CONFIG_EINK_RST=4
CONFIG_EINK_BUSY=15

#
# The FT6X36 touch chip needs an I2C port and one input GPIO
#
CONFIG_TOUCH_SDA=21
CONFIG_TOUCH_SDL=22
CONFIG_TOUCH_INT=17
CONFIG_I2C_MASTER_FREQUENCY=200000

To browse a full implementation example you can just checkout the following repository:

https://github.com/martinberlin/cale-idf this touch example you can find in main/demos/demo-touch-epd-implemented.cpp (Make sure to select this target in CMakeLists.txt)

Cale-idf is the repository where I test this components together before updating them to new releases

If you like this and it works for you I will be really happy to get a ★ Star in the repository.
And if you use it for a real life project I will appreciate a small donation following the links on the github repository. As I mention sometimes:

Open source is for free. But the developer’s time has value. So I think is fair sometimes to ask for a donation and also to support another developers donating to them when we use their components.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s