Pong on a color TFT: an ILI9341 on an Arduino Uno
Advanced
Or see every lesson.
- 261parts on the bench
- 31boards running now
- 1.00xreal time, on every board
The ILI9341 is the chip behind the common 2.4 and 2.8 inch color TFT screens, 240 by 320 pixels. It is a big step up from a small OLED. You get 65,536 colors and room for a real game or a dashboard. It talks to the Arduino over SPI with a few extra control pins.
The circuit above is an Arduino Uno playing Pong on an ILI9341, with a thumb stick to move the paddle and a piezo for the beeps. Press the stick to serve. It runs in your browser on the sketch below, and the way it draws teaches the most important thing about these screens: only redraw what moved.
What you need
- An Arduino Uno
- A 2.4 inch ILI9341 SPI TFT module (240x320)
- A thumb stick module and a piezo buzzer, for the game
- A full-size breadboard and jumper wires
Wiring
| Part and pin | Goes to | Note |
|---|---|---|
| TFT CS | Pin 10 | Chip select |
| TFT DC | Pin 9 | LOW for a command, HIGH for data |
| TFT SCK | Pin 13 | SPI clock |
| TFT MOSI | Pin 11 | SPI data to the screen |
| TFT RST | 5 V | Tied high here; or wire it to a pin and name it in the sketch |
| TFT LED | 5 V | The backlight; without it the screen stays dark |
| TFT VCC and GND | 5 V and GND | Check your module: many want 3.3 V logic |
| Stick VRX, VRY | A0, A1 | The two axes |
| Stick SW | Pin 2 | The press |
| Piezo | Pin 6 and GND | The beeps |
DC: command or data
The ILI9341 has one extra pin that makes it different from a plain SPI chip: DC, for data or command. When DC is LOW, the byte you send is a command, like "set the window" or "start writing pixels". When DC is HIGH, the bytes are the data for that command, including the pixels themselves.
Drawing a rectangle is three steps. Set the column range, set the row range, then stream the right number of pixel colors. Every library does exactly this under the hood.
Why the game only redraws what moved
Each pixel is 16 bits, and the whole screen is 76,800 pixels. That is a lot of data for an Uno. In Mokxi the Uno model has no SPI hardware, so every bit is sent by hand, and clearing the whole screen takes about a second and a half. A real Uno’s hardware SPI is faster, but a full redraw is still too slow for a smooth game.
So the sketch draws the frame, the labels and the score once. After that, each frame only repaints the ball and the strip of paddle that moved: under two thousand pixels, which keeps the game at 30 frames a second. The game also lives in a 160 by 200 window in the middle of the screen, which makes the one-time clear shorter.
This habit, draw once and then update only what changed, is the key to any fast display project, on any board.
static void drawFrame() {
tft.fillRect(FX, FY, FW, FH, BG);
tft.rect(FX - 2, FY - 2, FW + 4, FH + 4, EDGE, 2);
tft.text(FX, FY - 26, "PONG", ILI9341_WHITE, ILI9341_BLACK, 3);
tft.text(FX, FY + FH + 10, "SCORE", ILI9341_WHITE, ILI9341_BLACK, 2);
tft.text(FX + 110, FY + FH + 10, "LIVES", ILI9341_WHITE, ILI9341_BLACK, 2);
}Using the Adafruit library
The example uses mokxi_ili9341.h, a small driver that ships with Mokxi. On a real Uno, most tutorials use the Adafruit_ILI9341 and Adafruit_GFX libraries, and Mokxi ships headers with those names and their usual calls too. The hello world below is written the way those tutorials write it.
Mokxi’s version has a few differences. The built-in font is Mokxi’s own 5x8, and the Fonts/ headers are Mokxi’s letters at each font’s size. Nothing is read back from the screen. And the bus is software SPI, so draw what moved rather than filling the whole screen often.
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
Adafruit_ILI9341 tft(10, 9); // CS, DC; add a third pin for RST if it is wired
void setup() {
tft.begin();
tft.setRotation(1);
tft.fillScreen(ILI9341_BLACK);
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(10, 10);
tft.print("Hello");
}
void loop() {
}Try it in the editor
Change BALL_COLOUR to ILI9341_RED and the ball turns red. Colors are 16-bit RGB565 numbers, so you can also write your own, like 0xF81F for magenta.
Make the paddle wider by raising PADDLE_W from 36 to 60, and the game gets easier. Make the window bigger by changing FW and FH, and watch the first clear take longer.
Common mistakes
A white or dark screen. Check the LED pin: it is the backlight, and the screen stays dark without it. A white screen with the backlight on usually means the init never ran: check CS, DC and RST.
5 V logic on a 3.3 V screen. Many ILI9341 modules are 3.3 V parts. Some have a level shifter on the board and some do not. Check yours before you wire it to an Uno, and use level shifters if needed.
Clearing the whole screen every frame. It flickers and crawls. Draw the background once and repaint only what changed.
Mixing up DC and CS in the constructor. The order is CS first, then DC.
Questions
Which pins does an ILI9341 use on an Arduino Uno?
SCK to 13 and MOSI to 11 (the Uno’s SPI pins), CS and DC to any two pins you name in the sketch, RST to a pin or to the supply, and LED to the supply for the backlight.
Why is my ILI9341 so slow on an Uno?
A full screen is 76,800 pixels of 16 bits each. Redraw only what changes. In Mokxi the Uno’s SPI is done in software, so it is slower still, and the Pong example shows how to work within that.
Does Mokxi support the touch version?
Yes. There is an ILI9341 with a touch panel in the parts bin, read with the XPT2046_Touchscreen library, and a Touch paint project that uses it.
Keep going
Build this for real
Open the editor, change a value and watch the number move with it. Nothing to install, and no account needed.