Highly optimized ILI9341 (320x240 TFT color display) library

Good work Kurt (not that I tested it) - I saw that problem in GraphicsTest extraneous to what I was doing - and fixed the symptom enough to see a good display ... figured if there were 2 problems there were probably 3 but I never got back to it as it was in the July K66_beta timeframe and my post and follow up got lost in the noise it seems . . .
 
I merged most of the pending pull requests over the last couple days. Last night I did some testing with a few programs, and then this morning I published 1.31-beta3 with the latest ILI3941_t3.

Please give 1.31-beta3 a try. If anything is broken, now the time to get fixes (but not new feature) into the code. I want to release 1.31 within the next few days, which will remain the "stable" version until 1.32 in December or January.
 
I have a test program that I am using to see if my frame buffer code is working in my altenative library ili9341_t3n. One test I was trying was to call readRect after I did a text write and then do a writeRect to put those pixels on the screen. My test code alternates writing the test screen with frame buffer and without. My with was working, but my without was not... So tested readRect on this library and it also failed and return all 0s

Started debugging. Started reviewing the ili9341.pdf file section 8.2.35 again and found on the Read_Memory_continue command (0x3E), that it looked like we needed to probably send 0x3ff bytes to read the data and not 0x00... So I tried it and it appears to work.

Also looking over the readRect found a possible problem, that if you tried to read too many pixels we would overrun the c variable. Example read full screen 320x240x3 > 65536, so I changed this variable to unit32_t.

Also added define for the SPI speed to use on read operations, and used that instead of hardcoded 2000000 values.

Issued Pull Request.
 
Paul,
Thanks for all your hard work with this library. I have just started using this TFT, have it all hooked up and working with my Teensie 3.2, the examples look fantastic, but I have no idea how to get started writing my own rudimentary little programs. I can do simple text, but that's about it. I have spent a few days scouring the internet looking for a list of commands without success.
Is there a tutorial, or at least a list of all the functions for people who are just starting?
As an example of my low level, I can't even figure out how to display changing values. I'm monitoring voltages and currents in an amp and with each loop, the new value is written without erasing the old value, so that almost immediately there is just a solid square. Using tft.fillScreen() results in unacceptable flicker. :)

Thanks again for your work,
Michael Smith,
Weston, CT
 
Paul,
Thanks for all your hard work with this library. I have just started using this TFT, have it all hooked up and working with my Teensie 3.2, the examples look fantastic, but I have no idea how to get started writing my own rudimentary little programs. I can do simple text, but that's about it. I have spent a few days scouring the internet looking for a list of commands without success.
Is there a tutorial, or at least a list of all the functions for people who are just starting?
As an example of my low level, I can't even figure out how to display changing values. I'm monitoring voltages and currents in an amp and with each loop, the new value is written without erasing the old value, so that almost immediately there is just a solid square. Using tft.fillScreen() results in unacceptable flicker. :)

Thanks again for your work,
Michael Smith,
Weston, CT
 
Screen fill writes the whole screen of blanking data over SPI - that is a lot of data and a great example of worst case update.

There are installed IDE samples ( benchmark ) that demonstrate all sorts of common updates and manipulations. Reading this thread and forum should show a number of other samples - I reposted a link to this code on the prior page of posts on this thread.
 
Thanks for the reply. My question really was about some kind of tutorial or concentrated explanation. I simply can't make heads or tails of this by reading through this thread. There must be some kind of documentation somewhere for beginners. No?
 
I submitted a patch with flicker-free background color drawing for external fonts, text measuring methods, as well as a bunch of fixes for clipping bugs (and the ability to do global clipping in a subset of the screen).

It's here:

https://github.com/PaulStoffregen/ILI9341_t3/pull/13

You can also pull it from my fork here:

https://github.com/blackketter/ILI9341_t3

Let me know if you try it and have any issues with it!

Did the flicker-free background color drawing for external fonts make it into a release Teensyduino or 1.34 Beta #3? I'm holding off updating until the next release but this could change my mind as I'm playing with a TFT at the moment.
 
Hi Guys,

First of all thanks for all the hard work on this library!

I have a problem with significant flicker. Basically I need to fillRect about 20-70% of the screen, then change the size of the rect and draw it again, repeat... .
I tried implementing a z buffer since so many pixels are unchanged between redraws, but was unsuccessful. I think only updating the changed pixels would make a vast difference, but I think modifying the driver without some insights is probably beyond my capabilities. Any suggestions? Am I missing something perhaps in my code to take advantage of the buffering?

It seems like all the examples to show speed involve drawing lines of some sort. I can get lines to draw nicely. But these rectangles are not cooperating.

This is an ebay $10 2.2 QVGA SPI display and a PJRC Teensy 3.6 (the BNO055 in the video is not in use for this sketch). I'm using the out-of-the-box wiring with short wires (none longer than 15cm) and reset on pin 8.
Code:
ILI9341_t3(uint8_t _CS, uint8_t _DC, uint8_t _RST = 8, uint8_t _MOSI=11, uint8_t _SCLK=13, uint8_t _MISO=12);
Here is a short video:

I'm running the Teensy at 180Mhz.

Here is my code:
Code:
#include "SPI.h"
#include "ILI9341_t3.h"

#define TFT_CS   10
#define TFT_DC  9

ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC);

// Color definitions
#define TRANSPARENT     -1
#define BROWN 0xFA00
#define BLUE  0x001F

#define SPI_BITRATE     50000000L

#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 240
uint16_t i = 0;
uint16_t bgColor = BLUE;

void tft_init() {
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(bgColor);
}

void loop() {
  unsigned long startTime = millis();
  tft.fillRect(0, DISPLAY_HEIGHT / 2 + i, DISPLAY_WIDTH, DISPLAY_HEIGHT / 2, bgColor);
  tft.fillRect(0, DISPLAY_HEIGHT / 2 + ++i, DISPLAY_WIDTH, DISPLAY_HEIGHT / 2, BROWN);
  if (i == 40) i = 0;
  delay(100 - (millis() - startTime));
}

void setup() {
  Serial.begin(9600);
  tft_init();
}
 
I think it is more with the logic of how your program is doing the update... For example here is a variation on your code:

Code:
#include "SPI.h"
#include "ILI9341_t3.h"
#include "SPI.h"
#include "ILI9341_t3.h"

#define TFT_DC 22
#define TFT_CS 15
#define TFT_RST -1
#define TFT_SCK 14
#define TFT_MISO 12
#define TFT_MOSI 7
#define TOUCH_CS  8

ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCK, TFT_MISO);


// Color definitions
#define TRANSPARENT     -1
#define BROWN 0xFA00
#define BLUE  0x001F

#define SPI_BITRATE     50000000L

#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 240
uint16_t y = DISPLAY_HEIGHT/2;
uint16_t y_prev = DISPLAY_HEIGHT/2;
uint16_t bgColor = BLUE;

void tft_init() {
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(bgColor);
}

void loop() {
  unsigned long startTime = millis();
  if (y > y_prev)
    tft.fillRect(0, y_prev, DISPLAY_WIDTH, (y-y_prev)+1, bgColor);
  tft.fillRect(0, y, DISPLAY_WIDTH, DISPLAY_HEIGHT / 2, BROWN);

  y_prev = y;
  y += 10;
  if (y == (DISPLAY_HEIGHT/2) + 40) {
    y = DISPLAY_HEIGHT/2;
  }
  delay(250 - (millis() - startTime));
}

void setup() {
  Serial.begin(9600);
  tft_init();
}
Warning, It is setup with my pin numbers for my pseudo flex board... So need to edit... It still updates more than it should, but I don't think you will notice.

Alternatives also include, with my ili9341_t3n library I have the ability to turn on a pseudo frame buffer where you can make these types of updates and then say update now, which would take care of the flicker. Maybe also with Frank's DMA version as well.
 
Kurt, thanks for that. However, you code assumes that Y will be part of a predicable loop. I wrote this code to demonstrate the flickering I get with refreshing large rectangles (or any large shape). I wrote code like yours in my actual implementation that does all the shape drawing based on values from a sensor, where on each loop, if the previous reading == current reading, skip the drawing code. This helps, but if many different readings come in, as is normal, then I still have lots of flickering.

I'm very interested in your comment about using the framebuffer in your ili9341_t3n library. I have tried that library a couple of days ago, but could not get it to compile.

Would you provide a simple example where you draw the rectangle as shown in your code above, using the buffer please?
 
Hi Robb,

Yes what I showed was the typical type of code I do when doing graphics controls or the like. Example like a scrollbar or progress bar or the like where you remember the state you last drew the control at, and then when you get a new value you calculate the areas that need to be updated...

With my version of the library, which started off mainly to build it to be able use any of the SPI busses of the T3.5/T3.6 is up at: https://github.com/KurtE/ILI9341_t3n

Everything is more or less the same as the main one, except I have added the support I mentioned, plus some other stuff, that I have as a PR to the main library. This includes the ability set a clipping rectangle as well as drawing origin.

But I wanted to try out some of Franks DMA buffer stuff without the DMA stuff, so I brought that in.

To turn it on, I added a couple of new members:
To use the frame buffer, a call like: tft.useFrameBuffer(1);

When on, all of the graphic primitives are written to memory instead of to the display. Once you are done with your updates you call: tft.updateScreen();

There are many caveats and extensions that could be done to this code, like when you turn on the frame buffer, it does not read the current state of the display and then use it, instead when you call update it will blast over everything...

Also code could be added to detect a bounding rectangle of what was updated in the frame buffer since the last update and only output that portion.

Also needless to say when you turn on the FB it eats up a reasonable amount of memory 320*240*2 bytes. So only on T3.5/3.6

Now another caveat, to my above statements. That is my updateScreen also works with the clipping rectangle.

So with this code you could do things in your drawing routine. like assuming you are you turned on Frame buffer earlier.
Code:
  tft.setClipRect(x, y, w, h); // where x, y, w, h are the bounds of control. 
  tft.fillRect(x, y, w, h, bgColor);

  // do any new drawing you wish to do like ...
  tft.fillRect(0, DISPLAY_HEIGHT / 2 + ++i, DISPLAY_WIDTH, DISPLAY_HEIGHT / 2, BROWN);
  tft.updateScreen();
  tft.setClipRect();  // clear out the clipping rectangle

Hope that makes sense?
 
Kurt,

Yes, some of what you have here makes sense, and is _exactly_ what I was seeking - for some days now! :)
I implemented your library and the accompanying SPIN library, added tft.useFrameBuffer(1); and tft.updateScreen(); to my code respectively and the display is vastly improved. I think about as good as it will probably get for a sub $10 display. Its nice and bright, but the viewing angle could be better.

Now, with regard to the clipping rectangle: Is this a means of constraining the framebuffer to a specific area of the screen, given that I can determine that only that area will be affected by the coming updates - vs - brute force buffering of the entire display? I'll look through the API in your header file and see what I can learn about it. If it is what I think it is here, then I think in terms of overal processing, its perhaps a happy medium between parsing an entire z buffer for dirty pixels, and buffering the entire display. I'm interested in your thoughts.

Also, If you have time, I'd like to understand more how you implemented the buffering and see if I can apply that strategy to the ILI9163 that runs a 128x128 TFT.

For those looking for the same framebuffering solution, here is my working sample using Kurt's elegant buffer:
Code:
#include "ILI9341_t3n.h"

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

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

// Color definitions
#define TRANSPARENT     -1
#define BROWN 0xFA00
#define BLUE  0x001F

#define SPI_BITRATE     50000000L

#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 240
uint16_t i = 0;
uint16_t bgColor = BLUE;

void tft_init() {
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(bgColor);
}

void loop() {
  unsigned long startTime = millis();
  //start the buffer
  tft.useFrameBuffer(1);
  tft.fillScreen(bgColor);
  tft.fillRect(0, DISPLAY_HEIGHT / 2 + ++i, DISPLAY_WIDTH, DISPLAY_HEIGHT / 2, BROWN);
  if (i == 40) i = 0;
  //write the buffer to the screen
  tft.updateScreen();
  delay(100 - (millis() - startTime));
}

void setup() {
  Serial.begin(9600);
  tft_init();
}
 
Last edited:
The clip rectangle is a way to constrain all of the graphic primitives to only draw in a desired location, likewise the Origin allows you to offset all of the graphic primitives. So in theory you could write your display code for some object, example button, starting at 0, 0... This code came from the member Blackketter as a PR to the main library. I integrated it first into my fork...

There are a couple of different ways/reasons I may use this in my own projects using this, like maybe if I have a button object, and I wish to use a font to draw buttons, I would maybe use the Opaque font drawing code, which will draw the background for each character through the full spacing of the character, including where to start the next character and to the start of the next row of text. This likely would extend lower than I would want in a graphical element like button, so I would probably set a clipping rectangle as to keep it from extending too low. Likewise if I am only going to update a small portion of the display and I am using Frame buffer, then I might set up the clipping just before the updateScreen as to make the update as quick as possible...

Now the question can simple frame buffer stuff be extended to other displays... Yes, there is no real magic here. In the simplest of cases, you can have Adafruit_GFX do most of the work for you starting off. That is all of the graphic primitives by default will funnel to calling drawPixel(x, y, color) for each pixel.
In my current stuff this is implemented like:
Code:
void ILI9341_t3n::drawPixel(int16_t x, int16_t y, uint16_t color) {
	x += _originx;
	y += _originy;
	if((x < _displayclipx1) ||(x >= _displayclipx2) || (y < _displayclipy1) || (y >= _displayclipy2)) return;

	#ifdef ENABLE_ILI9341_FRAMEBUFFER
	if (_use_fbtft) {
		_pfbtft[y*_width + x] = color;

	} else 
	#endif
	{
		beginSPITransaction();
		setAddr(x, y, x, y);
		writecommand_cont(ILI9341_RAMWR);
		writedata16_last(color);
		endSPITransaction();
	}
}
So the beginning part is the part that uses the origin and clipping rectangle.

Then in the Frame Buffer mode, we simply save away the color into a logical two dimensional array. Again I was not the first one to do this. Frank did this in his DMA version of the code. In his code it was actually a two dimensional array. in my case I emulate it with a one dimensional array, as to allow me to have the code handle orientating the display in the 4 rotations. But it simply saves it into the _pfbtft array in the appropriate place.

You could easily start off with just that or you could do like the ILI9341_t3 (and t3n and dma) libraries and overwrite several of the other virtual functions, like drawFastVLine, drawRect... If one wanted to only support the FB mode, you could simply copy the code that is in the #ifdef ENABLE_ILI9341_FRAMEBUFFER and remove the tests for _use_fbtft...

The only other thing you then need t do is to handle the updateScreen code. Note this code is more or less a duplicate of the code writeRect and would of course differ for each display...

Hope that helps. Also if desired may want to continue on another thread... As sort of off the topic of the main ili9341_t3 library
 
Hi Kurt,

I was having some weirdness with drawing triangles today using t3n. I found that changing the code at line 1592
from:
Code:
 int16_t
    dx01 = x1 - x0,
    dy01 = y1 - y0,
    dx02 = x2 - x0,
    dy02 = y2 - y0,
    dx12 = x2 - x1,
    dy12 = y2 - y1,
    sa   = 0,
    sb   = 0;
to
Code:
  int16_t
    dx01 = x1 - x0,
    dy01 = y1 - y0,
    dx02 = x2 - x0,
    dy02 = y2 - y0,
    dx12 = x2 - x1,
    dy12 = y2 - y1;
int32_t
    sa   = 0,
    sb   = 0;

Seems to have cleared up the issue. Basically, when I have points in the triangle that extend beyond the edge of the screen, the triangle would draw short of that edge by the amount it should be "drawn" past the edge. This code change seems to allow the triangles to be drawn as would be expected.
 
Hello,

Thank you for the optimized library (t3).
I try to use these library in combination with my arduino uno and my 2.8 tft touchscreen (ili9341(SPI) and 4-wires for the touchpanel).
I'm using a example code (onoffbutton_breakout.ino) but receiving the following errors:

C:\Users\frank\Documents\Arduino\libraries\ILI9341_t3/ILI9341_t3.h: In member function 'void ILI9341_t3::waitFifoNotFull()':

C:\Users\frank\Documents\Arduino\libraries\ILI9341_t3/ILI9341_t3.h:304:9: error: 'KINETISK_SPI0' was not declared in this scope

sr = KINETISK_SPI0.SR;


These are not all the errors but they are all relative to the ILI9341_t3.h .

I hope somebody can help me.

Thank you.

Frank
 
This library won't work with Arduino UNO. You can use the Adafruit_ili9341 library. This code is specific for Teensy and makes use of it's FIFO (first in first out) SPI hardware queue.
 
To clarify: my recollection is that this library ONLY works for the Teensy 3.x series (hence the name). Teensy LC, Teensy1.x and Teensy 2.x are not supported. Isn't that still the case?

I thought Paul mentioned that a big part of the speedup was due to the hardware FIFO buffer and other SPI goodies, which IIRC the LC does not have.
 
To clarify: my recollection is that this library ONLY works for the Teensy 3.x series (hence the name). Teensy LC, Teensy1.x and Teensy 2.x are not supported. Isn't that still the case?

I thought Paul mentioned that a big part of the speedup was due to the hardware FIFO buffer and other SPI goodies, which IIRC the LC does not have.
Yes still true.

My guess is we could create a version of it that may work faster than the Adafruit library, that makes use of the double buffering you could do with the data register for input/output. However it does not have the other capabilities of the 3.x which include better control over DC...
 
@KurtE...I hope this is not off subject but I'm wondering if the following is possible with your ILI9341_t3n library using a T3.1??
Print two separate floating point numbers that are changing (..tachometer) at the rate of two times per second with an Arial font of size 48
without flicker. If this is possible could you post a simple example??
 
Two times a second to write out two numbers is probably not going to tax things too much.

As for not flicker.

Sorry I don't have time to write up complete example, also things may change depending on how things are laid out. But for example in the program I am playing with right now to display some Well monitor information. Here is what I am doing, to display some text lines. This code is setup that everything to the right of where I am displaying is part of the txt line, so I erase to the end.

There are things I could do to speed this up as well. But some of the code looks like:

First I have a dumb helper function that writes in the background color everything from the current Text Cursor to the end of line for the height of current font.
Code:
void EraseRestOfTextLine(const ILI9341_t3_font_t &f) {
  int16_t x;
  int16_t y;
  tft.getCursor(&x, &y);
  tft.fillRect(x, y, tft.width(), f.line_space, ILI9341_BLACK); // width will be truncated.
}

Then an extract of code that is doing the output to here:
Code:
      tft.setFont(Arial_14);
      tft.setTextColor(ILI9341_GREEN, ILI9341_BLACK);
      tft.setCursor(TFT_STATE_X, y_start + TFT_STATE_OFFSET_Y);
      time_t t = g_Sensors[iSensor]->onTime();
      tft.printf("ON %d/%d/%d %d:%02d", month(t), day(t), year(t)%100, hour(t), minute(t));
      EraseRestOfTextLine(Arial_14);
So the code sets the font, sets the text color. I am setup to do Opaque text. It then outputs the appropriate text and then calls the Erase function which writes in the background color everything beyond that to background.

Again this is not yet optimized. I could for example remember the last cursor X from previous outputs to this line and only call fillRect if the new text is shorter than previous text and only fill up to end of previous text.

Other things that maybe could be added. Could for example have the ability to pass in the X position for end of field, such that maybe I have other stuff to right not to over write. Or alternatively could set a clipping rectangle for the field.

Other issue with Opaque text. It will write in the background color the height of chararacter to the next row, which may be taller than you want to have blanked out. If so than again can use clip rectangle.

Note sometimes it can be simpler than all of this. For example if I am outputting some simple number, you can sometimes get away with doing something like:
tft.printf("%d ", mynum);
Where the output of one or two blanks after the number takes care of the issue if some numbers use a few less pixels than others.

Now need to go and do some other stuff. Hope that helps
 
Two times a second to write out two numbers is probably not going to tax things too much.
As for not flicker.
Does this relate to my question about 'tearing' prevention? There can be flicker due to your programming: that's not what I mean here.

As I understand it (please correct me as needed) the display has a refresh cycle constantly going on and if we write randomly to it while it is in that refresh then we get the tearing/flicker. The only way to eliminate this specific artifact is using the TE signal from the ILI9341. My interest is in eliminating this on a new board we designed which can access the TE signal from ILI9341.

Why care so much? We want to use ILI9341 displays in commercial products, including a medical device, and the display needs to avoid obvious artifacts. The displays I have here look great and have passed customer tests so far, which is amazing considering the low price.
 
Back
Top