Built-in project · Arduino Uno R3

Touch paint on an Arduino Uno

Draw on a 2.8 inch TFT with its resistive touch panel: two SPI chips on one module, and the raw touch counts mapped to pixels. The whole build, an Arduino Uno 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 About 25 minutes to build and run. Runs in your browser. Free, and no account needed.

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

The circuit runs here once the page has loaded.

What you'll learn

  • Two SPI devices on one module
  • Reading a resistive touch controller
  • Mapping raw readings to screen pixels with map
  • Drawing with fillRect
  • A simple on-screen palette
  • Difficulty: Intermediate
  • Time: About 25 minutes
  • Board: Arduino Uno R3
  • Parts: 3

How it works

The module has two chips: an ILI9341 that draws on the screen, and an XPT2046 that senses touch. The display uses SPI pins 13, 11 and 10, with DC on 9 and RST on 8. The touch chip has its own select on pin 7 and its own wires on A0, A1 and A2, so the two never share a bus.

Six colored swatches fill the top strip. Touching one picks that color, and yellow is the first one. Touching anywhere below it and dragging paints a 5 by 5 pixel square under your finger. The raw touch numbers, roughly 200 to 3900, are scaled to screen pixels.

Parts list

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

Wiring table

Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.

Part and pinConnects to
Touch TFT, 2.8 inch ILI9341 with XPT2046 pin SCKArduino Uno R3 pin 13
Touch TFT, 2.8 inch ILI9341 with XPT2046 pin MOSIArduino Uno R3 pin 11
Touch TFT, 2.8 inch ILI9341 with XPT2046 pin CSArduino Uno R3 pin 10
Touch TFT, 2.8 inch ILI9341 with XPT2046 pin DCArduino Uno R3 pin 9
Touch TFT, 2.8 inch ILI9341 with XPT2046 pin RSTArduino Uno R3 pin 8
Touch TFT, 2.8 inch ILI9341 with XPT2046 pin T_CSArduino Uno R3 pin 7
Touch TFT, 2.8 inch ILI9341 with XPT2046 pin VCCArduino Uno R3 pin 5V
Touch TFT, 2.8 inch ILI9341 with XPT2046 pin LEDArduino Uno R3 pin 5V
Touch TFT, 2.8 inch ILI9341 with XPT2046 pin GNDArduino Uno R3 pin GND
Touch TFT, 2.8 inch ILI9341 with XPT2046 pin T_DINArduino Uno R3 pin A0
Touch TFT, 2.8 inch ILI9341 with XPT2046 pin T_DOArduino Uno R3 pin A1
Touch TFT, 2.8 inch ILI9341 with XPT2046 pin T_CLKArduino Uno R3 pin A2

The code

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

sketch.ino · Arduino Uno R3
// Touch paint: draw on a 2.8 inch ILI9341 with its XPT2046 touch panel.
//
//   display   CS -> 10   DC -> 9   RST -> 8   MOSI -> 11   SCK -> 13   LED -> 5 V
//   touch     T_CS -> 7   T_CLK -> A2   T_DIN -> A0   T_DO -> A1
//
// Press on the picture while it runs, and drag.
//
// Two chips on one module, each an SPI device with its own chip select. They
// could share one bus, since each only listens while its own select is low;
// here the touch controller has a bus of its own on A0 to A2, an SPIClass
// made on those pins, so the two sets of wires never meet.
// Each reading is a control byte and sixteen clocks: the start bit, the
// channel, then twelve bits of result in bits 14 to 3 (the >> 3). Pressure is
// 4095 + Z1 - Z2, large for a press and near nothing for none.
//
// The readings are raw counts, about 200 at one edge of the picture and 3900
// at the other, so map() turns them into pixels. With the display and the
// touch at the same rotation, a bigger x is further right.
//
// The row of colors along the top is the palette: touch one to pick it.

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <XPT2046_Touchscreen.h>

Adafruit_ILI9341 tft(10, 9, 8);
SPIClass touchBus(A2, A1, A0, 7);  // SCK, MISO, MOSI, SS
XPT2046_Touchscreen ts(7);

const uint16_t PALETTE[] = {ILI9341_RED, ILI9341_YELLOW, ILI9341_GREEN,
                            ILI9341_CYAN, ILI9341_BLUE,  ILI9341_WHITE};
const int SWATCH = 40;
uint16_t ink = ILI9341_YELLOW;

void setup() {
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(ILI9341_BLACK);
  for (int i = 0; i < 6; i++) tft.fillRect(i * SWATCH, 0, SWATCH, 20, PALETTE[i]);
  ts.begin(touchBus);
  ts.setRotation(1);
}

void loop() {
  if (!ts.touched()) return;
  TS_Point p = ts.getPoint();
  int x = map(p.x, 200, 3900, 0, tft.width());
  int y = map(p.y, 200, 3900, 0, tft.height());
  if (y < 20) {
    if (x >= 0 && x < 6 * SWATCH) ink = PALETTE[x / SWATCH];
    return;
  }
  tft.fillRect(x - 2, y - 2, 5, 5, ink);
}

Code walkthrough

The key functions of the sketch, and what each one does.

setup sketch.ino

Starts the display, rotates it to landscape and clears it to black. A loop draws the six palette swatches, each SWATCH (40) pixels wide and 20 tall. Then it starts the touch controller on its own bus, touchBus, and sets the same rotation as the display so the directions match.

void setup() {
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(ILI9341_BLACK);
  for (int i = 0; i < 6; i++) tft.fillRect(i * SWATCH, 0, SWATCH, 20, PALETTE[i]);
  ts.begin(touchBus);
  ts.setRotation(1);
}

loop sketch.ino

Returns at once if nothing is touching. Otherwise it reads the point and maps its raw x and y to pixels. A touch in the top 20 pixels picks a palette color for ink. Anywhere else, it draws a small square in the current ink. Dragging leaves a trail.

void loop() {
  if (!ts.touched()) return;
  TS_Point p = ts.getPoint();
  int x = map(p.x, 200, 3900, 0, tft.width());
  int y = map(p.y, 200, 3900, 0, tft.height());
  if (y < 20) {
    if (x >= 0 && x < 6 * SWATCH) ink = PALETTE[x / SWATCH];
    return;
  }
  tft.fillRect(x - 2, y - 2, 5, 5, ink);
}

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • The picture works but touch does nothing. This project
    Check the touch wires: T_CS to pin 7, T_CLK to A2, T_DIN to A0 and T_DO to A1.
  • The painted dots land away from your finger. This project
    The touch and display rotations must match, both set to 1 here. The map limits of 200 and 3900 can also be tuned for your panel.
  • The screen stays white or blank. Touch TFT
    Check VCC, GND, and the backlight pin (LED), and that CS, DC, and RESET match the pins in your library setup. The panel is a 3.3 V device, so use level shifters from a 5 V board.
  • Touch does not respond, or the point lands in the wrong place. Touch TFT
    The touch chip has its own CS pin, separate from the display. Share MOSI, MISO, and SCK only. Calibrate the touch against the screen corners, and fix rotation to match.
  • Upload fails or nothing runs after wiring pins 0 and 1 to a module. Arduino Uno R3
    Pins 0 and 1 are the USB serial port. Move the module to other pins, or unplug it while uploading. Also check the right board is selected.
  • Pins misbehave or the board resets when you power motors or servos from it. Arduino Uno R3
    Each I/O pin gives about 20 mA, 40 mA max. Power motors and servos from their own supply and join its ground to the Uno's GND.

Change it and keep it

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