TJCTM24024-SPI - ILI9341 - Arduino UNO R3


For those who try to get this screen working, here is what I came up with :

Board : TJCTM24024-SPI

Touch : XPT2046

Wiring :

Libraries :

  • Adafruit_GFX, Adafruit_ILI9341 => For the TFT
  • UTouch, UTFT => For the touch

Sketches : Adafruit_ILI9341 examples => graphicstest (screen test) / spitftbitmap (display bmp from sd card)

To test the touch, I modified the “spitftbitmap” sketch in order to use the touch with the UTouch library. If I look in the serial monitor, I can see values varying as I touch the screen. Here is that sketch :

  This is our Bitmap drawing example for the Adafruit ILI9341 Breakout and Shield

  Check out the links above for our tutorials and wiring diagrams
  These displays use SPI to communicate, 4 or 5 pins are required to
  interface (RST is optional)
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution

#include <Adafruit_GFX.h>    // Core graphics library
#include "Adafruit_ILI9341.h" // Hardware-specific library
#include <SPI.h>
#include <SD.h>
#include <UTouch.h>

// TFT display and SD card will share the hardware SPI interface.
// Hardware SPI pins are specific to the Arduino board type and
// cannot be remapped to alternate pins.  For Arduino Uno,
// Duemilanove, etc., pin 11 = MOSI, pin 12 = MISO, pin 13 = SCK.

#define TFT_DC 9
#define TFT_CS 10
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);

#define SD_CS 4

UTouch  myTouch(5, 8, 6, 7, 2);

void setup(void) {
  Serial.print("Initializing SD card...");
  if (!SD.begin(SD_CS)) {

  bmpDraw("ghost.bmp", 0, 0);

int x;
int y;

void loop()
    if (myTouch.dataAvailable())

// This function opens a Windows Bitmap (BMP) file and
// displays it at the given coordinates.  It's sped up
// by reading many pixels worth of data at a time
// (rather than pixel by pixel).  Increasing the buffer
// size takes more of the Arduino's precious RAM but
// makes loading a little faster.  20 pixels seems a
// good balance.

#define BUFFPIXEL 20

void bmpDraw(char *filename, uint8_t x, uint16_t y) {

  File     bmpFile;
  int      bmpWidth, bmpHeight;   // W+H in pixels
  uint8_t  bmpDepth;              // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;        // Start of image data in file
  uint32_t rowSize;               // Not always = bmpWidth; may have padding
  uint8_t  sdbuffer[3*BUFFPIXEL]; // pixel buffer (R+G+B per pixel)
  uint8_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean  goodBmp = false;       // Set to true on valid header parse
  boolean  flip    = true;        // BMP is stored bottom-to-top
  int      w, h, row, col;
  uint8_t  r, g, b;
  uint32_t pos = 0, startTime = millis();

  if((x >= tft.width()) || (y >= tft.height())) return;

  Serial.print(F("Loading image '"));

  // Open requested file on SD card
  if ((bmpFile = == NULL) {
    Serial.print(F("File not found"));

  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.print(F("File size: ")); Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    Serial.print(F("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    Serial.print(F("Header size: ")); Serial.println(read32(bmpFile));
    bmpWidth  = read32(bmpFile);
    bmpHeight = read32(bmpFile);
    if(read16(bmpFile) == 1) { // # planes -- must be '1'
      bmpDepth = read16(bmpFile); // bits per pixel
      Serial.print(F("Bit Depth: ")); Serial.println(bmpDepth);
      if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed

        goodBmp = true; // Supported BMP format -- proceed!
        Serial.print(F("Image size: "));

        // BMP rows are padded (if needed) to 4-byte boundary
        rowSize = (bmpWidth * 3 + 3) & ~3;

        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if(bmpHeight < 0) {
          bmpHeight = -bmpHeight;
          flip      = false;

        // Crop area to be loaded
        w = bmpWidth;
        h = bmpHeight;
        if((x+w-1) >= tft.width())  w = tft.width()  - x;
        if((y+h-1) >= tft.height()) h = tft.height() - y;

        // Set TFT address window to clipped image bounds
        tft.setAddrWindow(x, y, x+w-1, y+h-1);

        for (row=0; row<h; row++) { // For each scanline...

          // Seek to start of scan line.  It might seem labor-
          // intensive to be doing this on every line, but this
          // method covers a lot of gritty details like cropping
          // and scanline padding.  Also, the seek only takes
          // place if the file position actually needs to change
          // (avoids a lot of cluster math in SD library).
          if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else     // Bitmap is stored top-to-bottom
            pos = bmpImageoffset + row * rowSize;
          if(bmpFile.position() != pos) { // Need seek?
            buffidx = sizeof(sdbuffer); // Force buffer reload

          for (col=0; col<w; col++) { // For each pixel...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
    , sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning

            // Convert pixel from BMP to TFT format, push to display
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
          } // end pixel
        } // end scanline
        Serial.print(F("Loaded in "));
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      } // end goodBmp

  if(!goodBmp) Serial.println(F("BMP format not recognized."));

// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.

uint16_t read16(File &f) {
  uint16_t result;
  ((uint8_t *)&result)[0] =; // LSB
  ((uint8_t *)&result)[1] =; // MSB
  return result;

uint32_t read32(File &f) {
  uint32_t result;
  ((uint8_t *)&result)[0] =; // LSB
  ((uint8_t *)&result)[1] =;
  ((uint8_t *)&result)[2] =;
  ((uint8_t *)&result)[3] =; // MSB
  return result;

Hope it helps…

Moderator edit: Added link to image from

Hello Dougyyy!

Your picture and program really helped me out. Unfortunately, I am still having issues with the touch. I have everything wired exactly how yours is, but the screen isn't registering that any pressure is on it. I've used your exact program and I receive no errors, and I have the Utouch library downloaded. Any ideas on how I can fix this? Thank you!

Wiring diagram picture is broken. Can anyone repost it? Thank you!

An excellent wiring diagram. I could click on it fine.

This display should work reliably with the 1k0 resistors as shown. The RESET pin is not 5V tolerant. I would connect the RESET pin to a GPIO pin on the UNO via a 1k0 resistor. The constructor would be:

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RESET);

I suggest that you put a 47R resistor in series with the backlight LED to limit the current.

The Touch controller should work fine on the SPI bus with a proper library. The UTouch library has no concept of using hardware SPI. The Touch controller is 5V tolerant. Since you are using dedicated GPIO pins to bit-bash the Touch controller it does not need 1k0 resistors. It does need the T_IRQ pin connecting to D2.


Now i can see the picture:)

I cant see the picture. only white screen. Hooked it up like on the picture. What did you do?

It says this:

Loading image ‘ghost.bmp’
File size: 222656
Image Offset: 54
Header size: 40
Bit Depth: 24
Image size: 233x318
Loaded in 6978 ms

but only white screen.

It has been pointed out in another thread that the Touch controller is powered by the display’s 3.3V regulator. So if you have 5V GPIO connected to the Touch signals it will backfeed the regulator.

This is bad news for the display, disk, … and the world in general.

You should be ok with series resistors on the Touch signals. I have NOT tested it.

If you only have the TFT and SD card connected, the display should work.

Life is a lot simpler with 3.3V boards like Pro Mini 3.3 or Due.

If you are determined to use 5V, the best solution is to use proper level shifter chips. Note that this applies to every output signal from the Arduino.


Please Help, I connected as the diagram ut Im getting this in the serial monitor and the screen is just white

ILI9341 Test! Display Power Mode: 0x0 MADCTL Mode: 0x0 Pixel Format: 0x0 Image Format: 0x0 Self Diagnostic: 0x0 Benchmark Time (microseconds) Screen fill 2368140 Text 263336 Lines 2451004 Horiz/Vert Lines 198920 Rectangles (outline) 131340 Rectangles (filled) 4918408 Circles (filled) 975568 Circles (outline) 1071040 Triangles (outline) 777508 Triangles (filled) 1993328 Rounded rects (outline) 409032 Rounded rects (filled) 5441768 Done!

I suggest that you insert a series resistor for the Reset line. And remove the Touch lines for the moment. Or use series resistors.

Life is much simpler if you use a 3.3V Arduino. e.g. Seeeduino or Due. No series resistors. The ILI9341_due library will blow your socks off.


Thank you it worked for me with an intel edison using a modified library and changing the logic levels to 3.3 V

Can touch pins be shared (except for cs) with the normal SPI?

Yes, of course they can. The whole purpose of a SPI bus is to connect multiple devices.

Bear in mind that most devices will work with 3.3V logic. You need to look after the levels.
e.g. use resistors or level translators on every signal.

And obviously use libraries that understand SPI.
UTouch and UTFT have no concept of hardware SPI.
Use proper libraries like ILI9341_due


You might find it easier to use cd74hc4050 to buffer the 5V signals to 3.3V levels (SCK, MOSI, chip selects) and us 74HC125 to bring MISO back to 5V level from the device with chip select enabling the driver. That keeps the 3.3V signals isolated from the 5V signals so that the devices with clamping diodes to 3.3V don't interfere with the 5V devices, or programming, etc.


I know this is an old post but I still found it usefull. I was having erratic results with this circuit for this display, it would work some times then appear to stop and just display the blank white screen. Sometimes it would display the test pattern through one cycle then revert to the white screen.

In the end I threw a resistor in the supply line to the LED backlight(24R then 12R) and this seems to have fixed my problems.

Just wanted to share my experience in case it helps someone else.

Next step is to check the SD and touch operation.

how is it you connected MISO back to the arduino through a 1k resistor? this does not make any sens

I was dealing with a

  • TJCTM24028-SPI
  • 2,8" TFT 240*320, driven by a
  • ILI9341 on
  • Arduino UNO R3

It was not working, always showing white screen only.

Wiring is, to be honest, simple: SPI (Clock, MOSI) and CS, DC, RST. It did not work. Ok, it is an 3.3 V device: Voltage divider by some resistors. Does not work at breadboard level. Using oszi it became obvious: signal quality ... was not there. Signal shape became better going to lover R levels, e.g. 200 ohms+300 ohms, but not great.

---> Use an active level converter !

These level converters provide often 4 channels only - how to deal with RST (Reset)? My display did not accept RST on

  • 5V,
  • Open,
  • GND or
  • Out PIN like PIN 8 - It is 5 V as well

---> Put RST / Reset on 3.3 V.

Some devices resume their work after a short bus break without restarting. My display did not. Fiddeling on wiring - having something changed, ---> Always start with reset again.

Now it works a treat.