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.
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.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Full-size breadboard
- 1 × Touch TFT, 2.8 inch ILI9341 with XPT2046
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 pin | Connects to |
|---|---|
| Touch TFT, 2.8 inch ILI9341 with XPT2046 pin SCK | Arduino Uno R3 pin 13 |
| Touch TFT, 2.8 inch ILI9341 with XPT2046 pin MOSI | Arduino Uno R3 pin 11 |
| Touch TFT, 2.8 inch ILI9341 with XPT2046 pin CS | Arduino Uno R3 pin 10 |
| Touch TFT, 2.8 inch ILI9341 with XPT2046 pin DC | Arduino Uno R3 pin 9 |
| Touch TFT, 2.8 inch ILI9341 with XPT2046 pin RST | Arduino Uno R3 pin 8 |
| Touch TFT, 2.8 inch ILI9341 with XPT2046 pin T_CS | Arduino Uno R3 pin 7 |
| Touch TFT, 2.8 inch ILI9341 with XPT2046 pin VCC | Arduino Uno R3 pin 5V |
| Touch TFT, 2.8 inch ILI9341 with XPT2046 pin LED | Arduino Uno R3 pin 5V |
| Touch TFT, 2.8 inch ILI9341 with XPT2046 pin GND | Arduino Uno R3 pin GND |
| Touch TFT, 2.8 inch ILI9341 with XPT2046 pin T_DIN | Arduino Uno R3 pin A0 |
| Touch TFT, 2.8 inch ILI9341 with XPT2046 pin T_DO | Arduino Uno R3 pin A1 |
| Touch TFT, 2.8 inch ILI9341 with XPT2046 pin T_CLK | Arduino 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.
// 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. Themaplimits 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.