Built-in project · ESP32 DevKit V1

ESP32 ILI9488 touch paint

A 3.5 inch 320x480 ILI9488 TFT on TFT_eSPI with XPT2046 touch: pick a color from the palette and draw with your finger. 18-bit color, three bytes a pixel. The whole build, an ESP32 DevKit and 1 more part, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Intermediate Runs in your browser. Free, and no account needed.

The parts retail for about $22. On Mokxi they're free.

ESP32 ILI9488 touch paint · ESP32 DevKit V1live0.000 s 0.00x
Click to open it in the editor
The circuit itself, running here on the simulator. Press what can be pressed; click anything else to open it in the editor.

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · ESP32 DevKit V1
// Touch paint on a 3.5 inch ILI9488 TFT (320x480) with an ESP32 and TFT_eSPI.
//
// TFT_eSPI reads its driver and pins from User_Setup.h. In Mokxi they are
// #defines above the #include, with the same names: these lines are what
// goes in User_Setup.h on the bench.
//
// The ILI9488 has no 16-bit color mode over SPI, so TFT_eSPI sends it 18-bit
// color, three bytes a pixel. The display's MISO is left unconnected: the
// ILI9488 never lets go of that line, and the touch controller (an XPT2046
// on the same SCK and MOSI, with its own chip select) answers on GPIO 19.
#define ILI9488_DRIVER
#define TFT_MISO 19
#define TFT_MOSI 23
#define TFT_SCLK 18
#define TFT_CS   15
#define TFT_DC    2
#define TFT_RST   4
#define TOUCH_CS 21

#include <TFT_eSPI.h>

TFT_eSPI tft = TFT_eSPI();

const uint16_t COLORS[8] = {TFT_RED, TFT_ORANGE, TFT_YELLOW, TFT_GREEN, TFT_CYAN, TFT_BLUE, TFT_MAGENTA, TFT_WHITE};
const int SWATCH = 40;   // eight swatches across the 320-pixel width
const int TOP = 80;      // the canvas starts below the palette and the title
uint16_t ink = TFT_YELLOW;

// The raw readings at the edges of the glass: the touch film is a little
// bigger than the picture, so it reads about 200 to 3900, not 0 to 4095.
// The film is laminated to the glass, and rotation 0 (MADCTL's MX) draws the
// columns from the glass's far edge, so a bigger raw X is further left: the
// X map runs backwards. Calibrate a real panel with tft.calibrateTouch().
const int RAW_MIN = 205;
const int RAW_MAX = 3890;

void drawPalette() {
  for (int i = 0; i < 8; i++) {
    tft.fillRect(i * SWATCH, 0, SWATCH, SWATCH, COLORS[i]);
    if (COLORS[i] == ink) tft.drawRect(i * SWATCH + 2, 2, SWATCH - 4, SWATCH - 4, TFT_BLACK);
  }
}

void clearCanvas() {
  tft.fillRect(0, TOP, 320, 480 - TOP, TFT_BLACK);
  tft.drawRect(0, TOP, 320, 480 - TOP, TFT_DARKGREY);
}

void setup() {
  Serial.begin(115200);
  tft.init();
  tft.setRotation(0);  // portrait, 320x480
  tft.fillScreen(TFT_BLACK);
  drawPalette();
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.drawString("Pick a color, draw below. Tap here to clear.", 6, 52, 2);
  tft.drawRect(0, TOP, 320, 480 - TOP, TFT_DARKGREY);  // the screen is already black
  Serial.print("TFT ");
  Serial.print(tft.width());
  Serial.print("x");
  Serial.println(tft.height());
}

void loop() {
  // Pressure first: with nothing pressed, X and Y mean nothing.
  if (tft.getTouchRawZ() < 300) return;
  uint16_t rx, ry;
  tft.getTouchRaw(&rx, &ry);
  int x = constrain(map(rx, RAW_MAX, RAW_MIN, 0, 319), 0, 319);
  int y = constrain(map(ry, RAW_MIN, RAW_MAX, 0, 479), 0, 479);

  if (y < SWATCH) {
    ink = COLORS[x / SWATCH];
    drawPalette();
    Serial.print("ink ");
    Serial.println(x / SWATCH);
    delay(200);
  } else if (y < TOP) {
    clearCanvas();
    Serial.println("cleared");
    delay(200);
  } else {
    tft.fillCircle(x, y, 4, ink);
    Serial.print("dot ");
    Serial.print(x);
    Serial.print(",");
    Serial.println(y);
  }
}

Parts list

2 parts, plus the jumper wires. They retail for about $22; on Mokxi they're free.

How it is wired

10 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.

  • Ground: ESP32 DevKit V1 pin GND; Touch TFT, 3.5 inch ILI9488 with XPT2046 pin GND
  • ESP32 DevKit V1 pin VIN; Touch TFT, 3.5 inch ILI9488 with XPT2046 pin VCC
  • ESP32 DevKit V1 pin D23; Touch TFT, 3.5 inch ILI9488 with XPT2046 pin MOSI; Touch TFT, 3.5 inch ILI9488 with XPT2046 pin T_DIN
  • ESP32 DevKit V1 pin D21; Touch TFT, 3.5 inch ILI9488 with XPT2046 pin T_CS
  • ESP32 DevKit V1 pin D19; Touch TFT, 3.5 inch ILI9488 with XPT2046 pin T_DO
  • ESP32 DevKit V1 pin D18; Touch TFT, 3.5 inch ILI9488 with XPT2046 pin SCK; Touch TFT, 3.5 inch ILI9488 with XPT2046 pin T_CLK
  • ESP32 DevKit V1 pin D4; Touch TFT, 3.5 inch ILI9488 with XPT2046 pin RST
  • ESP32 DevKit V1 pin D2; Touch TFT, 3.5 inch ILI9488 with XPT2046 pin DC
  • ESP32 DevKit V1 pin D15; Touch TFT, 3.5 inch ILI9488 with XPT2046 pin CS
  • ESP32 DevKit V1 pin 3V3; Touch TFT, 3.5 inch ILI9488 with XPT2046 pin LED

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.