Description

It is between 2.5 and 12 times faster than the Adafruit libraries for SPI LCDs, but it aims to be completely "sketch compatible" (so you can easily speed up sketches that are using Adafruit's library). You just need to change the name of the #include and "tft" object. A modified version of the Adafruit "benchmark" example is included (PDQ_graphicsbest.ino) with each driver library.

LCDs supported include 1.8", 2.2" and 2.8" SPI TFT LCD boards or "touch shields" that are commonly available from Adafruit and many other vendors. These are commonly 128x128, 128x160 or 240x320 (but the library supports rotation).

An inexpensive way to add a nice color display to an AVR (the modules are generally between $6 and $25, more for shields). The SPI LCD versions can work with only 4 pins.

Details

PDQ_GFX Library Development

The motivation for PDQ_GFX

A while ago, I noticed an article on Hackaday about how Paul Stoffregen (and crew) had optimized the Adafruit GFX SPI LCD driver for the Teensy 3.1 to achieve "warp speed" (see TFT LCDS HIT WARP SPEED WITH TEENSY 3.1). It was a nice demonstration of using advanced features of the 32-bit Teensy 3.1 micro-controller (along with code optimization). They used things like hardware /CS control and a hardware SPI FIFO to really speed things up from the generic Arduino API version (even when recompiled for the faster Teensy 3.1). Previously I had purchased an Adafruit SPI LCD breakout board that used this same controller and found it to be disappointingly slow (my AVR LCD gaming dreams were mostly dashed, and I didn't do much with it). At the time I just chalked it up to the fact that 8-bit AVR just wasn't up to the task of LCD graphics (especially over a slow-ish SPI bus). After seeing the impressive gain that the Teensy 3.1 was able to get, I decided it would be interesting to see if I could perhaps significantly speed up the library for Arduino AVR users without any "fancy 32-bit hardware". [Even though I have a Teensy 3.1 and they are great, I like an optimization challenge. :-) ]

In this write-up I hoped it might be interesting for me to go over some of the things I did to get about a 2.5 to 12 times times speedup (depending on primitive) using the same hardware. I did the bulk of this project many months ago, but am only now getting around to documenting it (so hopefully I am not too fuzzy on the details).

A look at the AVR SPI hardware in action

Since I had read about Paul's experience I had some ideas about what I could improve on the 8-bit AVR (however many of those optimizations were excluded as they used hardware capabilities that the AVR lacks). But the first thing to do was to take a look for myself. To start, I used my logic analyzer (Open Workbench Logic Sniffer) to see how the IL9341 driver was operating the SPI bus (a logic analyzer is a super handy and cost effective tool - I use mine to debug and explore digital hardware all the time).

In the "before" logic analyzer capture picture, you can see part of a "drawPixel" command being sent from the AVR 328P to the LCD controller over the SPI bus. In case you aren't familiar with SPI or logic analyzer captures, the important thing to notice here is "channel-3". This is the SPI clock signal (called SCK). It goes high and then low once for every bit transfered over the SPI bus. The Adafruit library normally uses the AVR hardware SPI channel, as it is in this case (it can also "bit-bang" SPI, but that is much slower). They "crank up" the SPI speed to the maximum supported by a 16MHz AVR, which is 8MHz (this means one bit can be transferred every two AVR clock cycles). So the "blue chunks" on channel-3 represent 8-bits getting sent over the SPI bus (or one byte). Now, this is the "fastest" speed that the AVR can possibly send data over SPI (I believe the LCD SPI controller can go a maximum of ~25MHz and some micro-controllers and devices support 100MHz SPI or more, for comparison). However, while the bytes are clocked out at the maximum (fixed) speed, you can see there is a lot of "dead time" between each byte that could in theory be used to speed things up. Another thing I noticed is that the Adafruit library toggles the /CS signal on channel-0 almost every single byte sent. The /CS signal is "chip-select", when it is low the LCD will listen to the SPI bus (that "/" means active-low signal), when it is high it will ignore the bus so it can be shared with another device). Since we are going to be sending a whole bunch of commands all to the LCD, it seemed to me we can just pull /CS low once, do a bunch of commands (until we are going to return to the calling sketch) and then restore /CS back to high once (in case the SPI bus will be used to talk to another device, like the SD card that is on many of these LCD modules).

Discussions

Become a member

If i upload the graphicstest with leaving the config as it is all works fine, but when i change something in the config and try to upload, there is an error error: #error Oops! You need to #include "PDQ_ILI9341_config.h" (modified with your pin configuration and options) from your sketch before #include "PDQ_ILI9341.h".

I am seeing the same issue with the ILi9341, all other functions appear fine but drawBitmap definitely has issues (just corrupts the display). Such good performance, hopefully Xark can identify the SPI issue soon!

I need to work with 8 Mhz clock (2.5V)SPI is at maximun speed ?This bit are enabled ?

Bit 0 – SPI2X0: Double SPI Speed BitWhen this bit is written logic one the SPI speed (SCK Frequency) will be doubled when the SPI is in Master mode (refer to Table 23-5). This means that the minimum SCK period will be two CPU clock periods.

I wanted to replace my adafruit libraries in my current project, which consists of a .ino file and two .h and one cpp file. I replaced the includes of all adafruit libs with the PDQ_GFX libs but then I get a ton of compile errors. Btw. my project runs perfect with the adafruit lib but a bit to slow. Can't imagine what I'm doing wrong. Could you please be so kind and point me in the right direction?

Thanks alot!!

Compiling 'VFDController' for 'Arduino/Genuino Uno'Screen.h:In file included fromScreen.cpp:fromPDQ_ILI9340.h:In static member function 'static void PDQ_ILI9340::begin()PDQ_ILI9340.h:627:2: error: 'SPI' was not declared in this scope:SPI.begin()PDQ_ILI9340.h:629:18: error: 'SPI_MODE0' was not declared in this scope:SPI.setDataMode(SPI_MODE0)PDQ_ILI9340.h:630:22: error: 'SPI_CLOCK_DIV2' was not declared in this scope:SPI.setClockDivider(SPI_CLOCK_DIV2); \\ 8 MHz (full! speed!) [1 byte every 18 cycles]Error compiling project sources

Here is a re-mix of this library that allows it to be a drop-in replacement for those of us using Seeed TFT v2 screens. I did not want to modify a rather large sketch to take advantage of the faster routines. Sketches can also be re-compiled to use Adafruit screens. Thanks for this work!

Any suggestions from the community on how I could make this work with a 480x320 display? I'm able to run the ILI9341 example on my Adafruit 3.5" breakout display using SPI, but I can't see how to change the resolution. It seems like plenty of other people would have run into this too, but I couldn't find any code updates or examples using this nice big display. :) I also want to rotate the display from portrait to landscape. Thanks in advance for any help/suggestions!

Like the post below this, it sounds like there is some problem with other SPI use. I am speculating it may be from the newer "SPI transaction" changes in Arduino API (but just a guess). As I mentioned, I'll try to take a look at this this weekend.

P.D: With adafruit software I have no problem. There is something in PDQ_GFX different that makes it not working. I can help if you need something. I have posted the issue in github. Sorry if it was not fine :)

One issue I have only just discovered is I recently decided to start displaying a startup colour bitmap on the Adafruit 2.8" touch display (using the ILI9341 ), however the screen goes blank and doesn't seem to work, I swapped back to the Adafruit libraries and it works (albeit painfully slow :) . The code is base on the Adafruit spitftbmp example.

great project . I have hit a few bugs :1 teh swap vs swapValue is still in the the ST7735 libs .

2 using ST7735_INITR_144GREENTAB the gutter / pixel alignments dont quite match . my screen is 128x128, only rotation 2 comes cles but has 2px wide area on top and left that wont update. other orientations look to have 20 or so pixels of clipped area that wont draw .

It looks as if the pixel offsets dont match 128x128 some how .

is there any place i can look , I check force setting dimesions in the set rotation function .

tested , it fixes rotation 0 , others 1,2,3 have that dead border gap .and only the HaD logo test fails on rotation 0 . I'll have a look and see if i can sort out the rotations.

I suspect the HaD logo is issue that it is bigger than the screen . it just flashes thin white lines at top 1/2 of the screen . Other wise it is stinking fast and working just fine in orientation 0, so isuspect if i make a 144 sized logo it should be ok?

If i sort that out i will post code , possibly as a new screen flavor lik 144B or such.

Hmm, I am guessing you mean how did I create the data for the HaD logo that is drawn by the example program (and pictured on this project).It is a super-simple "run-length" encoded image. There is a quick and dirty tool (source) to create an image data array from a BMP file here https://github.com/XarkLabs/PDQ_GFX_Libs/tree/master/PDQ_ILI9341/tools (but be aware it probably has limitations, I just made it work for the HaD logo).Hope that helps.

Hi! Just wanting to know how far the "SetFont"-support has gone. Any progress? Or is it possible to change the main font in the library only?Totally in love with your library! Thanks for all hard work!!

Hello. The basic support for the new font types is all working, lightly tested and ready to go. Basically I just need to get it merged into GitHub. This has been delayed (mostly) by me getting sucked into finishing Nand2Tetris (almost done) and some files got "re-tabbed" which made the "diff" a bit messy. I'll try to get on it soon and get it up there (perhaps this weekend).

I tried to compile the ST7735 but got the error that "swap" wasn't declared. Checked the code and saw that in the new PDQ_GFX library, swap has bin renamed to swapValue and it isn't changed in the ST7735 library. Manualy changed it and it's now working for me! Will try out some fonts right now!

Thanks for the report Jacob (below - can't reply directly to that post). D'oh, yeah, sorry about the Swap vs SwapValue. That is what happens when I didn't do any testing with the a ST7735 part (no handy shield). :-) Is only the HaD bitmap corrupted (the rest of graphicstest looks okay)? I'll guess I'll need to setup a ST7735 circuit on a breadboard and take a look (probably something minor, but nothing comes to mind). Sorry to keep you waiting a bit longer.

I'm about to try your PDQ libraries, hoping to use them for a whole bunch of things. Looking at your code and the example graphicstest, I have a couple of questions:

1) I don't see AVR_HARDWARE_SPI defined anywhere, but it seems to be necessary to enable essential code in PDQ_ILI9341.h?

2) The example graphicstest PDQ_ILI9341_config.h contains "#define ST7735_SAVE_SPCR". Shouldn't this be "#define ILI9341_SAVE_SPCR" to meet the needs of PDQ_ILI9341.h?

Edit: I tried the graphicstest with both the above defined in PDQ_ILI9341_config.h, and my testbed sprang immediately to life and matches your results very closely. You've done very impressive work here!

Incidentally, if it interests anyone, the level converters I am using are 10x4-bit intended for i2c but perfecty for SPI, costing £2.20 (about $3). They are fast and work well. Am I allowed to post links here?

1) AVR_HARDWARE_SPI is defined in FastPin.h if it determines that there is hardware support for the current AVR MCU (e.g., yes for Atmega328, but no for Attiny85).

2) Yes, this is a "typo" (so it acts like it is set to 0 for ILI9341). I believe I have this fixed locally and am planning to update github soon (with the font updates mentioned in an earlier comment).

AFAIK, there is no problem posting links here. I have also used "bi-directional level converters" intended for I2C with SPI and they worked fine (but these might be more expensive than other unidirectional level converters).

They operate identically to the rather more expensive SparkFun and Adafruit versions (among others). The design originates with a Philips application note published 19 years ago. Here's a description of how it works: http://delphys.net/d.holmes/hardware/levelshift.html

Hello and thanks. Hmm, I just checked Github and it looks like Adafruit added "SetFont" and friends to Adafruit_GFX fairly recently (Dec 2015). So it looks like I will need to update PDQ_GFX with these recent changes. Stay tuned...

Small update, I have been working to merge some recent additions in Adafruit GFX (mostly the new proportional font support, but also some simple "graphical button" support).

I am not too impressed with the API additions from Adafruit, but I will support it (but note that Adafruit support is half-baked and no good options for low-memory MCUs like AVR [in memory "canvas" is bad idea there]).

I'll probably submit to Github as soon as I do some testing (I notice Adafruit has no example code to test their new interfaces...).

IMHO, it needs a way to draw text within a rectangle (clearing the background while it draws the text). Currently Adafruit code only supports transparent background (so you get massive "flicker" updating text). I am thinking I might add this (even if Adafruit doesn't have a good plan).

Hey Xark, great work on the library and the write up. I was able to get the library working using the example project provided but I'm running into some compile issues when I attempt to incorporate it into a current project. I've done some testing on a new project and discovered that the error I get occurs when I reference the PDQ_ST7735 class outside of the main ino file. Specifically, I"m getting 'multiple definition of 'PDQ_ST7735::<all methods>' errors, saying that the method was defined in function 'Print'.

I've double checked all of my header guards and I've tried doing a fresh install of the Arduino IDE and the same error persists. If you could take a look at this and let me know what you think I'd be grateful.

I can understand that you would run into issues trying to include the library from multiple files in the same sketch. Until recently the Arduino IDE didn't really support more than one ".ino" file in a sketch (other than libraries).

I apologize for this limitation, but the library was never intended to be used from multiple files in the same sketch and I don't think there is an easy fix for this. I suggest structure your application to do all the PDQ_GFX calls from one file and if needed perhaps add some trivial "wrapper" functions in that one file you that can call from other files if needed. Hopefully this won't be too difficult.

Thanks for the quick reply. This is what I was attempting to accomplish, but perhaps I'm going about it wrong. I have one 'Screen' class that acts as a wrapper for all the drawing operations I need and I have it defined in its own .h and .cpp files. In order to get this class to compile though, I need to include it in my main .ino file, which causes the above error.

Since I can't define my wrapper in the main .ino file, I don't see a way around this? I imagine a simple solution exists though and I don't see it at the moment.

For reference, here is an example of what I'm trying to do, with all the extraneous code pulled out. This version of the code doesn't compile on my system.

You have the right idea with your Screen.h, but the key is that you include the PDQ_GFX headers from *only* one .ino or .cpp file (not in a header).

So, I think you just need to move the PDQ_ST7735 object out of the class and header (it can be a "static" or in anonymous namespace in the .ino/.cpp file where the code for Screen.h functions are, e.g.).

Hi. I have thought about it, but not gotten around to it so far. This library is a bit AVR specific (trying to get the most form AVR SPI). However, I don't see any big issues with porting it (and it would probably be pretty fast without too many "games").

Be sure and get the latest version from GitHub (Aug-2-2015) which has some small fixes for newer toolchains and or compile options (as used by Arduino IDE 1.6.5). Basically, the assembly "delay" functions were getting optimized by the assembler (which is nice, except in the case of delay functions). :-)