Highly optimized ILI9341 (320x240 TFT color display) library


Well-known member
In Teensyduino 1.20-rc2, I reverted Adafruit_ILI9341 back to Adafruit's version, with SPI transactions added.

1.20-rc1 had a highly optimized ILI9341 library, from KurtE, based on earlier work I'd done on optimizing ST7735, which was based on earlier optimizations originally written by Peter Loveday.

I want to bring this back in 1.20-rc3 and the final 1.20 release, with far more optimization, using a new name. So far, I'm drawing a blank on ideas for a better name.

Soon PJRC will be reselling the generic ILI9341 display. It's the same as on numerous other site and ebay, at about the same price. These are great little displays and I believe it's time to make a really awesome ILI9341 library that's truly as optimized as possible for Teensy 3.1.

Anyone have ideas on the naming?
Here's the latest code, not yet supporting SPI transactions (which will add some overhead). It's renamed, so development can be done without conflicting with an install of Adafruit's version.


Here's the current speed on Teensy 3.1 at 96 MHz. I'm hoping to dramatically improve many of these....

Benchmark                Time (microseconds)
Screen fill              544037
Text                     26975
Lines                    245784
Horiz/Vert Lines         44407
Rectangles (outline)     28240
Rectangles (filled)      1129447
Circles (filled)         155577
Circles (outline)        107520
Triangles (outline)      77953
Triangles (filled)       365880
Rounded rects (outline)  52092
Rounded rects (filled)   1228089
Sounds great, I will clone and try it out! Also, unless something else changed I believe the same code will work for the ILI9340 as I did not find any real functional differences in the code bases.

You probably already know it, but I synced up to your changes for the fast driver. The graphics test program is working, but the Touch library fails to init (actually Adafruit_STMPE610::begin fails). It works with your other non fast version of ILI9341.

I have verified this both with my own test program as well as the Adafruit TouchPaint program

The read stuff isn't using SPI transactions yet.

Or I could have made some other mistake. I changed a lot of stuff...

Will look into this tomorrow.
I have just replaced the SPI library by your latest version in my project, added ILI9341 driver and
it seems like it broke the SD support (which is the latest from your git)

$ make
[CXX] src/PatchManager.cpp
In file included from src/PatchManager.cpp:2:0:
libraries/SPI/SPI.h:979:8: error: 'SPIClass' does not name a type
src/PatchManager.cpp: In member function 'int32_t PatchManager::saveSingle(patch_t*, char*, uint8_t)':
src/PatchManager.cpp:14:3: error: 'SPI' was not declared in this scope
src/PatchManager.cpp:34:1: warning: no return statement in function returning non-void [-Wreturn-type]
src/PatchManager.cpp: In member function 'int32_t PatchManager::loadSingle(patch_t*, char*, uint8_t)':
src/PatchManager.cpp:38:3: error: 'SPI' was not declared in this scope
src/PatchManager.cpp:54:1: warning: no return statement in function returning non-void [-Wreturn-type]
make: *** [src/PatchManager.o] Error 1

or is this the issue in my code?

is it still needed in the current SD library version?
In the old version SD was not working if this was not set on each write.
Last edited:
You're building with something other than Arduino, which isn't obviously going things the same way Arduino does (otherwise, it would compile without error, as it does in Arduino).

Please compile with Arduino, for comparison and reference, to figure out what's broken with this non-Arduino build.
Just for the record: which Teensyduino version should I use for this? I should just place the Libraries in the Library folder instead of replacing them, right?
I'd suggest using 1.20-rc2, installed into a fresh copy of Arduino 1.0.5, as your reference.

Usually, it's safe to install "on top of" an older version of Teensyduino. But if you're trying to resolve strange problems, it's probably worthwhile to start with a freshly extracted copy of Arduino 1.0.5 (extract the ZIP file into an empty location, NOT on top of an older copy... yes, people do this sometimes!) and run the 1.20-rc2 installer on that fresh copy.
Adding -DTEENSYDUINO to my build flags on my build system fixed the problem. Works all fine now!
At least it compiled. I can tell you if my hardware still works later.
I just committed an optimization for drawLine.

It speeds the "Lines" benchmark by 45% and more than doubles the "Triangles (outline)" benchmark speed.
Sounds great!

I have been playing around trying to figure out why the Touchpaint part of your Optimized library is not working.

Found some stuff:
1) The version that is part of your github changed the CS pins from 8 to 6... Took me awhile trying to figure out why the CS was not showing up. So I changed it in my test version back to 8. Also got rid of reset pin usage.

2) in the STMPE begin method, the calls to GetVersion is failing. Looking at the data using Saleae Logic Analyzer, the data being returned looks valid: Should give 0x811... Figured out it was giving some bogus values that were queued up... So in my test program, I looped doing poprs from SPI until empty before calling to ts.begin, and now the getversion is returning the right stuff. But Some of the other touch calls are not giving the right stuff, probably similar issue of keeping the SPI popr stack in sync.

I don't kinow if it helps, but I include the mucked up touchscreen program.


  • Optimized_IL9341_touchpaint-140804a.zip
    2.1 KB · Views: 3,222
Oh, that's a good point. I need the RX FIFO!!

I just committed a speedup for drawChar. Sadly, there's only a minor speedup for the common transparent background.
I just pushed code to clear the RX FIFO before ending every SPI transaction. Hopefully that'll fix the compatibility issue?

Here's the latest benchmark I'm getting on Teensy 3.1 at 96 MHz (24 MHz SPI clock):

Benchmark                Time (microseconds)
Screen fill              280093
Text                     16069
Lines                    93961
Horiz/Vert Lines         22922
Rectangles (outline)     14638
Rectangles (filled)      581601
Circles (filled)         84421
Circles (outline)        74050
Triangles (outline)      21293
Triangles (filled)       191373
Rounded rects (outline)  33327
Rounded rects (filled)   634068

For comparison, the original code on Arduino Uno at 16 MHz (8 MHz SPI clock):

Benchmark                Time (microseconds)
Screen fill              2560996
Text                     291964
Lines                    2797576
Horiz/Vert Lines         221548
Rectangles (outline)     146548
Rectangles (filled)      5319244
Circles (filled)         1100216
Circles (outline)        1217136
Triangles (outline)      888480
Triangles (filled)       2047340
Rounded rects (outline)  469504
Rounded rects (filled)   5942788
The fixes help at least to have the paint program to init the touch device properly. However the touch read commands do not appear to be returning valid data. Not sure yet if the spi reads are in sync yet.

Still need to do some more debugging.

Sometimes looking at the documentation is somewhat confusing to me, example:
void writedata16_last(uint16_t d) __attribute__((always_inline)) {
		SPI0.PUSHR = d | (pcs_data << 16) | SPI_PUSHR_CTAS(1);
		while (!(SPI0_SR & SPI_SR_TCF)) ; // wait until transfer complete

Suppose at the point I call this, I have 3 items on the FIFO TX queue of SPI. This clears out the TCF flag, and then pushes a new command on the queue.
What is unclear to me, is: while (!(SPI0_SR & SPI_SR_TCF))
My looking at the documentation, gives me the impression that the TCF will be set when a transfer completes. So won't this condition happen when the first transfer is output (either 8 or 16 bits), but we still may have a few more items in the queue?

Also: then with the command:

The documetentation implies that only the MDIS and HALT bits can be changed when the module is running... So not sure if that is the issue or not...

I did a quick hack of the function above to do closer what I did to switch stuff in my earlier code:
	void writedata16_last(uint16_t d) __attribute__((always_inline)) {
	SPI0.PUSHR = d | (pcs_data << 16) | SPI_PUSHR_CTAS(1);

        uint16_t wTimeout = 0xffff;
        while (((SPI0_SR) & (15 << 12)) && (--wTimeout)) ; // wait until empty
        // Make sure the last frame has been sent...
        SPI0_SR = SPI_SR_TCF;   // dlear it out;
        wTimeout = 0xffff;
        while (!((SPI0_SR) & SPI_SR_TCF) && (--wTimeout)) ; // wait until it says the last frame completed

I did this one knowing that this program uses the fillcircle function to paint where your finger is. And the paint program appears to be working better now.

However not sure what that will do to your performance stuff, as it is now wasting time waiting for each logical command to complete before starting the next one.

Might be nice if it did it only at the real times when we are ending a set of drawings or when some other device wants to do something. Like maybe only at a SPI.beginTransaction where the data passed in is different than the current setup... Not sure if that makes sense or is doable or not.

With the one modified function above my test program now works, without my own saving and restoring SPI registers :)

So I thought I would try the spitftbitmap example (examples part of library). It does not compile as your library has:
void setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);

As a private method. On my copy I made it public and it now compiles and runs :)

Edit: Side note, your examples appear to hook up RST to pin 8?, But from the shield documentation on Adafruit:
The display uses digital pins 13-9. Touchscreen controller requires digital pin 8. microSD pin requires digital #4.
So I normally have to edit these to not pass through Reset...
Last edited:
I committed a redesign of the SPI access, which drains the RX FIFO, rather than trying to reset it. I tested with touchpaint and it seems to work.

Can you please give it another try with the latest version?