computers

Cheap ILI9488 TFT LCD Displays

I’ve been continuing my experiments with cheap TFT displays but my latest experiments have proved a little more tricky than I anticipated.

So I’ll start off by saying, if you like to tinker and work out how things work (like I do), read on, but if you want a display to plug-in and “just work” then it is really well-worth paying a bit more and pick up a well supported screen from the likes of Adafruit or Raspberry Pi or Pimoroni or similar.

If you’re still planning on “going it alone” then I suggest having a proper thorough look through the “lcdwiki” to see if the board you’re looking at is there – http://www.lcdwiki.com/Main_Page.  This might make things a lot easier!

This post is all about my display that looks like the following and supports a resistive touchscreen:

There are a number of variants of this display, but mine has the following properties:

  • Screen has an SPI interface using MISO, CLK, MOSI, with additional pins for CS, DC, RESET.
  • Touchscreen has an SPI interface too.
  • Requires 3.3V logic levels.
  • Will (apparently) accept 3.3V to 5V power.
  • Supports an SD card SPI interface (that I’ve not used).
  • 320×480 pixel resolution in full 24-bit colour.
  • Uses the LCD driver chip: ILI9488.
  • Uses the Touchscreen driver chip: XPT2046.

Now the usual way to short-cut any of the hard work of working out how to use something like this display is to look for some existing libraries where someone has already worked their way through the driver chip datasheet, worked out all the configuration parameters for the specific display being driven, and wrapped it all up in something you can just “install and go”.  Well that is the benefit of purchasing one of the above mentioned, very well supported modules – that is what you get and you are supporting all the time and effort that has gone into working out the technical details to make things easy for you.

The next level is to see if your cheap module shares any driver code with any of the above mentioned modules.  And in many cases you’ll find that the open source Adafruit libraries cover a huge range of devices.  However, they don’t have a product themselves based on the ILI9488, it is isn’t supported directly in their libraries. They have one using the ILI9341 which is close, but driving these displays is complicated, so “close” won’t do it!

So what are your options if you’ve got this far and haven’t turned up an obvious “just install and go” library?  Here is the list of all things I’ve looked into so far with varying degrees of success.

For Microcontrollers:

For Linux/Raspberry Pi:

The short summary being that these displays take a bit of work whatever you are trying to do with them!  And some of the links above are now out of date, so even when they were a “recipe” for getting them going, chances are you will need to do some figuring out yourself still.

The “TL;DR” version is that to use the display with the widest range of microcontrollers, I’d recommend taking a look at the Arduino_GFX library by “Moon on our Nation” and read the LCD Wiki for more specific details.

This post details my experiments getting the display and touchscreen to work with the Arduino Uno.  Further posts might look at other options in the future.

Arduino Uno and ILI9488

So, as my starting point was “can I get this running on my Arduino Uno”, there are several issues right off:

  • The board is 3.3V logic (even though it says it will accept 3.5-5V power), so we are going to need some kind of 5V to 3.3V logic shifter that can work at SPI speeds.
  • The most complete library support from the list above do not support 8-bit microcontrollers.  And for good reason – driving a 320×480 full colour display will be slow!
    • Update: After my initial experiments, I discovered the Arduino_GFX library by “Moon on our Nation”, which appears to do the job better than any of the rest, so that is what I ended up using.

But I thought I’d have a go anyway.

There is a jumper on the back of my display (marked J1) that the LCD wiki seems to think would allow 5V operation.

3-5 TFT SPI 480x320 jumper

But it also cautionsThe short-circuit method has the advantages of simple operation, short wiring, and no need for external devices.  The disadvantage is that the module generates a large amount of heat during operation.  Will affect the life of the module.”

I was tempted to give it a try, but as I’m also thinking of using this display in the future with a 3.3V device, I decided not to go this route.  Instead I’m using two 74HC125 buffers as the level shifters as shown below.

Arduino ILI9844 SPI Display_bb

The 74HC125 is a “Quadruple Buffers with 3-State Outputs” and includes four buffers that take an input and simply echo its state onto an output.  It can work with anything from 2 to 6V (check your specific datasheet for details of course), so powering it from 3.3V means that a 5V logic level in becomes a 3.3V logic level out.  Most importantly, unlike many of the simple “level shifter boards” you might find cheaply online, this approach will work at SPI speeds!  Those cheap level-shifters are simply not quick enough.  They work ok for I2C (and I’ve used them myself like this), but they just seem to completely mangle the higher speed SPI signals.

There is also an (active low) “enable” pin too, so each buffer can be individually enabled or disabled as required.

74HC125 - pinout74HC125 - table

This is my (electronics amateur person) simplified understanding of the device anyway, so I suggest you do your own research or find a tutorial by someone who really knows what they are talking about.

I’ve used two devices as there are five signals that need level shifting: MOSI, CLK, CS, DC, RESET.  MISO does not need to be level shifted as this is the “LCD to microcontroller” line, so that is being driven at 3.3V but going into a 5V microcontroller line which is fine.

Note that I’ve not bothered connecting the LED pin for the backlight to the microcontroller, instead just connecting it straight to 3.3V.  This mean that the backlight is “always on”. If you want to be able to control that from the Arduino too, then that will require a 6th level shift.

Hardware wise this does seem to largely do the job for me!  However the display is a little large for leaving balanced on a solderless breadboard so it is vulnerable to dodgy connections.  If you are going this route, and having issues, it is worth taking things slowly and checking things like:

  • Can you see a 5V signal on the SPI lines?
  • Can you see these coming out as 3.3V signals on the other sides of the buffers?
  • Have connected 3.3V and GND to both 74HC125s?
  • Have you connected the OE pins for all used buffers to GND to “enable” them?
  • Is the display a bit wobbly?  It is worth finding it some support to ensure good connections.
  • Double check (preferably before switching it all on) that you are connected to the Arduino’s 3V3 output not the 5V.

Software wise it was a little more tricky.  Initially, my best result was from Jaret Burkett, who wasn’t one to give up with this and created a library for the Uno that supports the ILI9488 based on the Adafruit ILI9341 library.  It supports the Adafruit GFX graphics primitives that are familiar to many other devices using the Adafruit libraries.

Unfortunately it is now four years out of date, so whilst it works “as is”, it doesn’t seem to be maintained anymore and I don’t know if it will continue to work with the rest of the Adafruit GFX libraries or with any future release of the library.

I did take a quick look myself at the latest version of the ILI9341 library to see if I could port it across myself.  Whilst I managed to get something on the display, the scaling and rotations were all a bit weird. I think the main issue is probably, as Jaret describes in his readme for his own library:

“the typical Adafruit TFT libraries are designed to work with 16bit color (RGB565), and the ILI9488 can only do 24bit (RGB888) color in 4 wire SPI mode.”

“What this means is, things will be slower than normal. Not only do you have to write twice as many pixels as a normal 240×320 display, 153,600px (320×480) vs 76,800px (240×320), but you also have to do a lightweight conversion on each color, and write 3 bytes vs 2bytes per pixel.”

But I did get something going with the “graphicstest” example configured as follows to match the Fritzing diagram above.

#define TFT_CS 8
#define TFT_DC 9
#define TFT_RST 10

ILI9488 tft = ILI9488(TFT_CS, TFT_DC, TFT_RST);

Some further experimentation implies we can probably get away without needing a RESET signal.  The official Adafruit libraries include the option to disable RESET and will go on to use a “software RESET” command over SPI.  The library I’m using can be told to ignore RESET but doesn’t replace the hardware RESET with a software RESET.  But it still seemed to work ok.

As I was experimenting I read about another library – Arduino_GFX by “Moon on our Nation”.  This appears to take its inspiration from a whole range of other attempts, including many of those I list above and supports both the ATmega328 based Arduino boards and the ILI9488.  The level of device support in this library is staggering.  I don’t know what the developer is planning for the future, but for the time being, this definitely appears to be your best bet for getting this display working with an 8-bit based Arduino.

Arduino Uno ILI9488 Display Shield

I want to try to get the touchscreen working, so my initial attempt was to add wiring into the touchscreen interface via the level shifters on a breadboard.  But it was proving so unreliable in terms of connections, that eventually I gave up and decided to make a DIY “shield” to support the module.

Here is an approximation for what I’m doing, focusing first on the display itself.

Note: This needed updating to support the touchscreen and display at the same time, for reasons I’ll go into later, but for now, just note that the circuit below isn’t the final version if you want touchscreen support, although it is fine for just the display.

Arduino ILI9844 SPI Display Shield V2_bb

I’ve included the connections for the touchscreen, which largely consist of adding it to the SPI bus and adding two IO pins for the Select and IRQ lines.  I’ve used slightly different IO pins for some of the TFT connections too to make the wiring slightly simpler. The updated wiring for the display is as follows:

#define TFT_CS  10
#define TFT_DC  7
#define TFT_RST 9
#define T_IRQ   2
#define T_CS    8

My first iteration had both of the new touchscreen pins going through the level shifter, then I realised (doh) that the IRQ will be an INPUT pin, so actually can be connected straight to the Arduino… Also, that the Arduino only has two external interrupt pins on D2 and D3, so T_IRQ has to be one of those (I’d originally wired it to D6)!

The diagram above shows the updated circuit, but that is why there is an unused level shifter output close to the pins.  With that in mind, you might get a more simplified design if you route MISO via that spare shifter, but that’s up to you.  Like all these things, this is a first go and I’m sure there is a more optimal way to wire it up if someone worked at it.

When building, I decided some of those wires would work better underneath too, so that is what I did.  The following all went on the underside of the board:

  • All GND links.
  • Level shifter to level shifter 3.3V power.
  • CLK to level shifter output.
  • D10, D9 and D7 to the level shifter input.

But again, do what seems to work best for you!

Component list:

  • blank “PCB shield”
  • Arduino male header pins
  • 1×14 way female header
  • 2x 14 way (2×7) DIP sockets
  • 2x HC74125 level shifters
  • Connecting wires

Here are some photos of the build process for reference.  I’ve tried to stick to the same colours as the Fritzing diagram (although my “ochre” looks very like my “orange” in places!).

I’ve started with the two sockets, then the header socket and then hooked up the GND connections on the underside.  Note that one of the GND connections to the buffer gets removed again later!

Next up are the power lines.  This includes hard-wiring the LED signal on the header connector to 3.3V. Note that I opted to tie the two VCC lines on the chips together on the underside.

The MISO link does not need to go via the level shifters in the same way.  The initial build of the shield has them linked directly as shown below.  This was the cyan link in the diagram, but I’ve used pink wire here. It turns out that the display and touchscreen don’t play nicely together on the SPI bus, so I ended up having to do something different with MISO for the display, but more on that later.

Next up are all the “ochre” wires which are the input side of the level shifters. As you can see below, I wired the three that need to cross the chips to the underside of the board.  Also this is where the mistake with T_IRQ creeps in.  That ochre wire to D6 is actually not needed (you’ll see it in a few photos before I’ve fixed it).

The blue wires complete the links between the level shifters and the TFT header.  Once again, you can see the extra wire to T_IRQ which I remove later! I opted to take one of these underneath, the CLK signal seemed to make more sense that what as shown below.

So about that mistake – here you can see the before (erroneous) and after (fixed) versions.  Basically T_IRQ should go direct into D2.  The observant among you might notice something odd going on with MISO too in that second photo, but more about that later!

Finally a set of male Arduino header pins are added as shown below.

At this point is is a really good idea to visually inspect with a magnifying glass, visually follow through the connections from Arduino via the shifter to the header, and finally check for shorts and continuity with a multimeter.

Once I was confident everything seemed ok, it was a case of plugging in the two HC74125 chips (observing the correct orientation of course) and trying it with an Arduino and the screen.  One thing to consider – that screen is quite large to be supported just by a single set of headers on one side, so I used mounting holes in the shield PCB to add some 10mm M3 stand-offs.  There were two minor issues with this:

  • The nut for one wouldn’t fit alongside the additional SCL/SDA header pins – so I had to trim it down.  If I’d have noticed this first, I would probably have just used a 6-way set of pins rather than the full 8.
  • One of the spacers part clashes with the SD socket on the back of the display – so again, I cut part of it out.  There isn’t anything I could do about this unless I wanted to move the header (I don’t, and if I redesigned it, not sure I’d want to either) or create a new mounting hole in the shield a little further in.

IMG_5247

Here it is with the chips in and all mounted and ready to go.

IMG_5243

It is certainly a lot more reliable than what came before:

IMG_5249

Touchscreen Support

Now I have a reliable way to actually talk to the display module, I can get on with trying to drive the touchscreen.  If you want to follow along with a solderless breadboard (not recommended – it caused me no end of grief as there are just so many connections) here is an updated Fritzing diagram.

Arduino ILI9844 SPI Display Touch_bb

There are a number of points to note:

  • First of all, I personally gave up trying to use solderless breadboard, so I believe the above is correct, but haven’t verified it as seen myself – I just went straight to my “proto shield” version.
  • You can hopefully see that I’ve wired up T_CS (cyan) via the level shifter and connected T_IRQ (purple) directly to the Arduino D2.
  • I’ve also added the loops to connect MOSI and CLK together.
  • Initially I also hooked up MISO directly to the Arduino (and the display’s MISO link), but that caused me problems, so read on below for the update and how it was resolved.

After enabling the touchscreen, I was finding that no-matter what I did I couldn’t get the touchscreen controller to give me a reading.  The touchscreen controller is a XPT2046 and there is a library from Paul Stoffregen (designer and developer of the Teensy Microcontroller), so that is what I was using.  It uses hardware SPI and just needs to be told the IRQ pin (optional) and “chip select” (CS) pin to be used for the touch controller.  You can find it here: https://github.com/PaulStoffregen/XPT2046_Touchscreen

But I couldn’t get anything to work.  Eventually I jumpered out the display from my shield and disconnected the SPI links to the display leaving just the touchscreen.  It turns out that the problems I was seeing are quite common with cheap, poorly implemented devices.  Paul Stoffregen once again provides the complete details of the issue in this article.  There are two common problems, which both manifest themselves if you attempt to use more than one SPI device on the SPI bus:

  • The chip select pins often require an external “pull up” resistor to ensure they have a good “inactive” state when the devices are not in use. This is particularly critical on start-up before the IO pins are set up properly.
  • The MISO line, which is the “device to microcontroller” link, sometimes isn’t properly disabled for the device when it isn’t selected.  The technical details are in the above article, but essentially you get one device interfering with the sending of information from another.

With MISO from the display unconnected, the touch screen worked. With both display and touch screen on the SPI bus, it doesn’t, although the display keeps working fine.  The solution is to build in a “tri-state buffer” to the MISO line for the problematic device and only have it enabled when that device’s “chip select” is active.

As it happens, I have a spare buffer on my shield!  Now I’ve been using the buffer to go from 5V to 3.3V with each line of the buffer permanently enabled by tying the enable pin to GND.

But there is no reason why the “input” signal can’t from from the 3.3V side and the “output” signal fed directly into a 5V logic pin even though that “output” signal will be at 3.3V too. And as the CS pin is already active low, it can be used directly to drive the “enable” pin for the buffer too.  Either the 5V or 3.3V CS pin can be used.

This means the SPI interface to the display is now something like this.

IMG_5368

I’ve reflected all this in the Fritzing diagram above, but it is untested as I went back to my proto shield and updated it as shown below.

Arduino ILI9844 SPI Display Shield V3_bb

I’ve had to remove the GND enable link to the bottom left buffer, hook it’s enable pin to the CS pin on the buffer right next to it (purple), and then tie in MISO from the display header as the input (cyan), and link the output to the microcontrollers MISO pin (also cyan).

I’ve updated this based on the fact I’ve already built most of my shield, so I’m using the spare buffer at the bottom of the left hand 74HC125 as it is easier to get to. If you are building this fresh from the start, then it might make more sense to use the spare buffer on the 74HC125 on the right, which is close to the display’s MISO pin.  The downside is it is further away from the display’s CS pin.

Alternatively, although I didn’t need to do this, you might want to consider using the remaining buffer for the touchscreen MISO pin too, so that both MISO connections go through enabling buffers.  I didn’t seem to need this with mine, but see how you go.  Maybe the XPT2046 does SPI better than the ILI9488.

Basically I’m saying this is definitely a first, evolved iteration.  If you were building from scratch there is almost certainly a better mapping of buffers to pins possible.

I toyed with the idea of adding pull-up resistors on the two CS pins, as described in the article I mentioned above, but  in the end opted for the “software solution” to pre-initialise the two CS pins and set then HIGH “by hand” prior to initialising the SPI devices. This seems to work fine for me.

If you wanted to do it properly, you’d need to find a place to install two 10k resistors (or thereabouts) between each CS pin and VCC.  I don’t know if it matters which side of the buffer you do this, but if you do it on the 5V side you’ll need to pull-up to 5V, but if you do it on the 3.3V side, you pull-up to 3.3V. I’ve not tried this myself, so you’ll need to do some reading and be sure to do the right thing here as I’m just guessing from not very much knowledge of such things.

Here are photos of the “final” build (for now).

Here is my test code to drive the display and respond to touch, with debug information over the serial port.  It includes two functions at the end that convert touch coordinates (0 to 4095) into display coordinates to match the screen resolution.  This is all relative to the current rotation providing that w and h are set appropriately i.e. obtained from the GFX library once setRotation() has been called.

Note that the touch screen library and the GFX library have the same understanding of rotation, which is really handy, but I suspect that is because both are designed to work with or are based on the original Adafruit GFX library.

#include <XPT2046_Touchscreen.h>
#include <SPI.h>
#include <Arduino_GFX_Library.h>

// XPT2046 Touchscreen Definitions
//
#define T_CS 8
// MOSI=11, MISO=12, SCK=13
#define T_IRQ 2 // Set to 255 to disable interrupts and rely on software polling
XPT2046_Touchscreen ts(T_CS, T_IRQ);

// ILI9488 Display Definitions
//
#define ILI_CS 10
#define ILI_DC 7
#define ILI_RST 9
Arduino_DataBus *bus = new Arduino_HWSPI(ILI_DC, ILI_CS);
Arduino_GFX *gfx = new Arduino_ILI9488_18bit(bus, ILI_RST, 0, false);

unsigned w, h;
#define ROTATION 0

void setup() {
  Serial.begin(9600);
  Serial.println("Arduino_GFX library ILI9488 and XPT2046 Test");

  // Pre-initialise the "chip select" pins to "deselected" (HIGH)
  pinMode(ILI_CS, OUTPUT);
  pinMode(T_CS, OUTPUT);
  digitalWrite(ILI_CS, HIGH);
  digitalWrite(T_CS, HIGH);
  delay(10);

  ts.begin();
  ts.setRotation(ROTATION);

  gfx->begin();

  gfx->setRotation(ROTATION);
  w = gfx->width();
  h = gfx->height();

  gfx->fillScreen(WHITE);
  gfx->fillScreen(RED);
  gfx->fillScreen(GREEN);
  gfx->fillScreen(BLUE);
}

unsigned long millicnt;
void loop() {
  if (millis() > millicnt) {
    gfx->fillScreen(BLACK);
    millicnt = 10000+millis();
  }

  if (ts.touched()) {
    TS_Point p = ts.getPoint();
    Serial.print("Pressure = ");
    Serial.print(p.z);
    Serial.print(", x = ");
    Serial.print(p.x);
    Serial.print(", y = ");
    Serial.print(p.y);
    Serial.print(", (");
    Serial.print(xt2xdsp(p.x));
    Serial.print(",");
    Serial.print(yt2ydsp(p.y));
    Serial.print(")\n");

    gfx->drawFastHLine(0, yt2ydsp(p.y), w, RED);
    gfx->drawFastVLine(xt2xdsp(p.x), 0, h, RED); 
  }
}

unsigned xt2xdsp(unsigned touch_x) {
  return (unsigned)((((unsigned long)w)*touch_x)/4096UL);
}

unsigned yt2ydsp(unsigned touch_y) {
  return (unsigned)((((unsigned long)h)*touch_y)/4096UL);
}

This is probably as far as I’ll go with this display on an Arduino Uno now.  I’ve shown it works, but I suspect anyone wanting to really use one of these displays should consider using a more powerful microcontroller.

Kevin

IMG_5377

2 thoughts on “Cheap ILI9488 TFT LCD Displays

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s