March 29, 2024

TFT Adventures (Playing with a ILI9341-based display)

Not long ago I purchased a few 320×240 touchscreen displays from ebay that looked pretty decent and were also fairly cheap. They conform to the Arduino shield form-factor, so they snap directly  onto any standard Arduino board, making them really easy to test and use. They also have an integrated microSD slot (connected to the same bus used by the display controller) which is a nice touch and widens the number of possible applications for these modules.

As seen on literally every eBay listing that sells them.
As seen on literally every eBay listing that sells them.

Of course the displays didn’t come with any code, and the most reasonable thing you’ll find online to make them work is Adafruit’s TFT-LCD Library, which includes support for a number of different controllers, and is fully compatible with their very own and versatile GFX framework.

Adafruit’s library comes with many examples, being the “graphicstest” demo one of the most interesting, as it showcases all the different primitives and graphic operations available.

The first thing you will need to check when you open the graphicstest sketch, is the pin assignment, which should match the connections of your board. For the display I got this is the correct mapping (apparently the current default):

#define LCD_CS A3 // Chip Select goes to Analog 3
#define LCD_CD A2 // Command/Data goes to Analog 2
#define LCD_WR A1 // LCD Write goes to Analog 1
#define LCD_RD A0 // LCD Read goes to Analog 0

#define LCD_RESET A4 // Can alternately just connect to Arduino's reset pin

Scrolling down a little further to the setup() entry point, you’ll spot the controller auto-detect block:

uint16_t identifier = tft.readID();

if(identifier == 0x9325) {
    Serial.println(F("Found ILI9325 LCD driver"));
} else if(identifier == 0x9328) {
    Serial.println(F("Found ILI9328 LCD driver"));
} else if(identifier == 0x7575) {
    Serial.println(F("Found HX8347G LCD driver"));
} else if(identifier == 0x9341) {
    Serial.println(F("Found ILI9341 LCD driver"));
} else if(identifier == 0x8357) {
    Serial.println(F("Found HX8357D LCD driver"));
} else {
    Serial.print(F("Unknown LCD driver chip: "));
    Serial.println(identifier, HEX);
    Serial.println(F("If using the Adafruit 2.8\" TFT Arduino shield, the line:"));
    Serial.println(F("  #define USE_ADAFRUIT_SHIELD_PINOUT"));
    Serial.println(F("should appear in the library header (Adafruit_TFT.h)."));
    Serial.println(F("If using the breakout board, it should NOT be #defined!"));
    Serial.println(F("Also if using the breakout, double-check that all wiring"));
    Serial.println(F("matches the tutorial."));
    return;
}

The first line in particular ( uint16_t identifier = tft.readID() ) is what tries to read the ID from the controller, but as numerous people online have found (including myself), this doesn’t always work, so your best bet is to check the manufacturer’s website (or forums to see if someone have purchased the same board) and find which controller your hardware uses.

I found that my display uses the ILI9341 controller, so I changed the first line to uint16_t identifier = 0x9341 essentially bypassing the auto-detection (which was always giving me “Unknown LCD driver chip: 0” anyway).

Now, after this change was done my TFT succesfully ran the demo, but to be honest, it wasn’t looking particularly good:

Well, something clearly is not working well.
Well, something clearly is not working well.

Ignoring the weird X-axis mirroring, there’s a lot of noise and weird blue-ish lines all over the screen. I originally thought that my display was broken or faulty, but I tried the other units I purchased, and they all had the same problem, so it couldn’t be that all of them were defective.

I quickly checked online and failed to find anyone else running into this problem, so I was puzzled.

What I did find however was people making modifications to the library or creating their own from scratch, and when I compared the code between Adafruit’s library and other available implementations, I found that a few of them had a slightly different initialization routine.  Paul Stoffregen’s ILI9341_t3 library in particular had what seems to be the second most common initialization sequence for this controller. His library unfortunately does not seem to support regular Atmega328p-based boards (at least right now), but the code is easy to follow, so I used it as a reference.

If you open the Adafruit_TFTLCD.cpp file from Adafruit’s library, you’ll find the initialization code for the ILI9431 controller in the begin() function:

 . . .
 . . .
} else if (id == 0x9341) {
    uint16_t a, d;
    driver = ID_9341;
    CS_ACTIVE;
    writeRegister8(ILI9341_SOFTRESET, 0);
    delay(50);
    writeRegister8(ILI9341_DISPLAYOFF, 0);

    writeRegister8(ILI9341_POWERCONTROL1, 0x23);
    writeRegister8(ILI9341_POWERCONTROL2, 0x10);
    writeRegister16(ILI9341_VCOMCONTROL1, 0x2B2B);
    writeRegister8(ILI9341_VCOMCONTROL2, 0xC0);
    writeRegister8(ILI9341_MEMCONTROL, ILI9341_MADCTL_MY | ILI9341_MADCTL_BGR);
    writeRegister8(ILI9341_PIXELFORMAT, 0x55);
    writeRegister16(ILI9341_FRAMECONTROL, 0x001B);

    writeRegister8(ILI9341_ENTRYMODE, 0x07);
    /* writeRegister32(ILI9341_DISPLAYFUNC, 0x0A822700);*/

    writeRegister8(ILI9341_SLEEPOUT, 0);
    delay(150);
    writeRegister8(ILI9341_DISPLAYON, 0);
    delay(500);
    setAddrWindow(0, 0, TFTWIDTH-1, TFTHEIGHT-1);
    return;
}
 . . .
 . . .

This is where several differences with Paul’s (and other sources) can be found, so I adapted the differences to Adafruit’s writeRegisterXX() functions and constants, so the code above ended up looking like this:

 . . .
 . . .
} else if (id == 0x9341) {
    uint16_t a, d;
    driver = ID_9341;
    CS_ACTIVE;
    writeRegister8(ILI9341_SOFTRESET, 0);
    delay(50);
    writeRegister8(ILI9341_DISPLAYOFF, 0);

    /* Changes start here ------- */
    writeRegister8(ILI9341_POWERCONTROL1, 0x23);
    writeRegister8(ILI9341_POWERCONTROL2, 0x10);
    writeRegister16(ILI9341_VCOMCONTROL1, 0x3e28);                              /* OLD: 0x2B2B */
    writeRegister8(ILI9341_MEMCONTROL, ILI9341_MADCTL_MY | ILI9341_MADCTL_BGR);
    writeRegister8(ILI9341_VCOMCONTROL2, 0x86);                                 /* OLD: 0xc0 */
    writeRegister8(ILI9341_PIXELFORMAT, 0x55);
    writeRegister16(ILI9341_FRAMECONTROL, 0x0018);                              /* OLD: 0x001B */


    writeRegister8(ILI9341_ENTRYMODE, 0x07);
    /* writeRegister32(ILI9341_DISPLAYFUNC, 0x0A822700);*/

    /* Added this: */
    writeRegister8 (0xF2, 0x00); 
    writeRegister8(ILI9341_GAMMASET, 0x01);

    /* -- End of Changes ------- */

    writeRegister8(ILI9341_SLEEPOUT, 0);
    delay(150);
    writeRegister8(ILI9341_DISPLAYON, 0);
    delay(500);
    setAddrWindow(0, 0, TFTWIDTH-1, TFTHEIGHT-1);
    return;
}
 . . .
 . . .

After compiling and running the project again (with the changes) this is what I got:

Lookin' good!
Lookin’ good!

The rest of the demo was looking just as crisp and clean:

Liiiiiines!

Given the amount of people who seem to be successfully using Adafruit’s library it’s hard to tell why their code didn’t fully work for me, and why the “alternative” initialization sequence actually made the whole thing work as intended with the displays I purchased.

I may eventually do further research to find the reason why there’s two different recommended sequences, but for now I’ll be happy if this information helps other people facing the same or similar issues with their displays.

Leave a Reply

Your email address will not be published. Required fields are marked *