Introduction: Arduino - TFT Display of Bitmap Images From an SD Card
Following on from my other Instructables on the Arduino and TFT display here is an updated library and Sketch to draw bitmaps (BMP or Raw) stored on an SD Card onto the TFT display.
Most of the available TFT displays have a SD Card slot on the back, the connections to this are usually separate to the display interface so must be wired up.
This instructable will tell you how to wire an Arduino UNO to a 2.2" TFT 320x240 display that incorporates the ILI9341 driver chip. These displays are available at low cost on eBay.
The library and function should work OK with other displays that use that driver chip.
By using an optimized version of the Adafruit_GFX library and an efficient function a raw 320x240 full colour bitmap can be plotted to the screen in 660ms, not bad for a humble UNO.
Step 1: Connecting It Up
Connections are different to those in my other Instructables (this is for my convenience!) you can change the pin allocations, but do use the hardware SPI pins:
- UNO +5V to display pin 1 (VCC)
- UNO +5V through a 56 Ohm resistor to display pin 8 (LED)
- UNO 0V (GND) to display pin 2 (GND)
- UNO digital pin 7 through a 1K2 resistor to display pin 4 (RESET), add a 1K8 resistor from display pin 4 to GND
- UNO digital pin 8 through a 1K2 resistor to SD card SD_CS, add a 1K8 resistor from SD_CS to GND
- UNO digital pin 9 through a 1K2 resistor to display pin 5(DC/RS), add a 1K8 resistor from display pin 5 to GND
- UNO digital pin 10 through a 1K2 resistor to display pin 3 (CS), add a 1K8 resistor from display pin 3 to GND
- UNO digital pin 11 through a 1K2 resistor to both display pin 6 (MOSI) and SD card pin SD_MOSI, add a 1K8 resistor from display pin 6 to GND
- UNO digital pin 12 direct to SD card SD_MISO (or via 1K2, see below)
- UNO digital pin 13 through a 1K2 resistor to both display pin 7 and SD card pin SD_SCK, add a 1K8 resistor from display pin 7 to GND
Other pin outs at the Arduino can be used by adapting the sketch should you have a display from another supplier or wish to use an existing setup.
You may wish to put an inline 1K2 (or there about) resistor in series with the MISO signal line (between UNO pin 12 and SD_MISO) this will help protect the SD Card in case you accidentally switch the UNO pin 12 to output a +5V logic 1... The SD card might survive such abuse but you may not be lucky... and the resistor will limit the current into the device to a couple of mA.
Bear in mind the display I have incorporates 10K pullups to the +3.3V supply on the SD Card SPI lines, this means that using higher value resistors in the voltage divider (these drop the UNO 5V levels to around 3V logic) will not achieve a reliable logic zero and it won't work! Ideally we would use a logic level converter but resistors are so cheap and they work fine at these frequencies because the stray capacitance of the lines we are driving is not that significant. The display is quite greedy on power so the dissipation in the resistors is not that significant in comparison.
Do not insert and remove the SD card when the Arduino is powered, this can (does!) corrupt the card and it will need reformatting!
Step 2: Libraries and Example Sketches (updated 2/4/15)
You will need the libraries in the attached zip file and the SdFat library (included for convenience). The standard SD library can be used but this requires minor changes to the sketch and will run slower.
The main changes to the ILI9341 library are to enhance speed and to add the pushColors() function, one to handle integer arrays (for BMP drawing) and one for byte arrays (raw images).
In the zip file you will find a folder containing images, put these on a FAT formatted SD card for the Arduino to read (not in a directory!). You can modify the SD library examples to use the above pins to check a sketch can access the files and the wiring is good. If in trouble post a plea for help... I may have made a mistake or may be able to help you get your setup going, bear in mind that it is difficult to debug major problems by exchanging a few messages...
Most of the image files are in BMP format, so it is left as an exercise to convert them to raw format, amend the sketch and see the improved drawing speed.
The example "ILI9341_draw_bitmap" (in the Adafruit_ILI9341_AS folder) sketch uses 90% of UNO FLASH and 54% of RAM for dynamic storage when compiled under IDE 1.6.1.
Only enable Font 2 (or other small font file) or the UNO will run out of FLASH memory!
I use the compiler -O2 option as in my Instructable here. The standard -Os option will make smaller code images but it runs slower (when IDE 1.6.1 used). IDE 1.0.6 compiled sketches may not fit in an UNO as the old version of the GCC compiler produces fast but quite large executables.
In the copies of the libraries in the zip files F_AS_T is disabled and only Font 2 is enabled, be aware of this if you try other sketch examples!
The image drawing function is part of the sketch not the library, this is deliberate as it is all too easy to create a heavyweight memory hungry feature rich library that puts a strain on the AVR based Arduino's capability! Well that's my excuse ;-) Some folk solve this by making a simple wrapper library that contains these functions and calls lower level libraries, so this is an option for the future. You can cut the drawBMP() function out and just use drawRAW() to save some space.
The main draw function prototypes are:
void drawBMP(char *filename, int16_t x, int16_t y, boolean flip)
The file name must be in the 8.3 format, see the SD library documentation. x andy are the coords where the top left pixel of the image will be drawn. See the last Step of this Instructable for the function of the flip flag. The width and height of the image are extracted from the BMP file.
The equivalent one for raw bitmaps is:
void drawRAW(char *filename, int16_t x, int16_t y, int16_t rawWidth, int16_t rawHeight)
As there is just pixel data in this file the width and height of the image must be provided to the function.
The library and sketch is targetted at AVR processors (UNO, Mega etc). I do not have a DUE, so do not plan at the moment to make it compatible with ARM processors. If someone does get the sketch running on a DUE I would be interested to here how it performs, if the SPI can be run at 48MHz then an entire screen update could probably be performed in less than 100ms... To run the SPI at this frequency you would probably need a digital logic level converters.
Note: On 2/4/15 the library sketch has been updated to further improve performance.
3/4/15: Minor bug fix
Step 3: Adding Images
If you add your own images make sure they are 24 bit color BMP files and they have a width and height compatible with the display. In Windows the free Paint software can be used to crop, resize, flip and save images in 24bit BMP format. There is little error checking in the code, so images can be corrupted by drawing off the screen edge and may not get drawn at all if the file cannot be found or format is wrong etc, but the Arduino should keep going and I have not had a crash yet. I have removed all debugging print statements to reduce the code size, but Serial is called up in setup().
Paint can also be used to pick colours off images to see the 24 bit RGB value, these can be converted using a calculator or a library function to the 16 bit 565 format used for the display.
Raw image files are quicker to draw and can be created straight from the BMP file using a utility called "ImageConverter565.exe", this comes with the UTFT library and is in the tools folder of the library. Use the ".raw" option, load a file and save it. The file will be ~2/3 the size. The pixel ordering means they draw top down too, this image converter is a great little utility that saves me writing a sketch for the UNO to seek BMP files and convert them (I might do that anyway!).
Yes, I had to include a picture of a cat, at least it's a "proper" one ;-) and a mouse.
Step 4: BMP File Format and Graphics Overlay
The common BMP format is to store the image in raster fashion from the bottom upwards but the graphics and plot windowing expects an image to be drawn top down. Manipulating file pointers to draw top to bottom slows down image rendering significantly, so the drawing routine and library uses the hardware TFT SGRAM rotation feature, this means we can quickly draw from bottom to top. After plotting the image the display is returned to the normal top left is 0,0 orientation.
If you visually prefer to draw top down then flip the image top to bottom before putting it on the SD card and use the "Top-Down" TD_BMP flag in the function instead of the Bottom-Up BU_BMP flag. This has been done in sympathy with the capabilities of the UNO by sacrificing a neater (but bigger and slower) software implementation and style for a significant performance advantage! The price to pay is a couple of mouse clicks when preparing the image (just flip vertically before saving in Paint).
The displayed images are nice and crisp with vibrant colours, so the screen shots made with my webcam don't do it full justice. The raw bitmap images are more representative of what you will see on the display.
Standard GFX functions (line drawing, text, etc.) can be used and overlaid on the drawn images. As a trivial example the Terminator eyes are made to glow more brightly red in the ILI9341_tftbmp demo sketch by plotting bright red circles in the appropriate place. Again Paint can be used to help work out the coordinates when plotting over images.
Step 5: Raw Bitmap Drawing
It is surprising how quickly the humble UNO can plot SD Card images to the screen when the code loops are kept short and sweet. The speed is quite good when using 16 bit raw images in a TFT friendly format because this avoids the tedious conversion of 24 bits into 16 bit words and only 2/3rds of the number of bytes needs to be read. In fact this library and sketch can fetch an image from the SD Card and draw it on screen in less time than some graphics libraries take to just clear the screen...
The library and sketch has been test on the new 1.6.2 IDE. (which I have only just noticed has been released!)
On FLASH size we are pushing the boundaries for an UNO and only IDE 1.6.x will create a small enough file with a single Font 2 (or smaller) loaded. I use optimisation level -02 as in my Instructable here. for speed but the standard -Os helps a tad with smaller size at the expense of a 35% speed reduction.
These libraries are highly optimised version of libraries created by others hence the Adafruit label. All graphics drawing functions are still their under the bonnet ("hood" for readers in the USA). The line drawing algorithm has been optimised and runs much faster than the basic Bressenham method because it uses the fact that short multi-pixel horizontal and vertical line segments can be drawn much more quickly that individual pixels. The further the angle away from 45 degrees (no multipixel line segments) the faster a line will draw. An example modified UTFT graphics test sketch is included to show the impressive speed improvement :-)
Thank you for reading my Instructables, it has been gratifying to get such positive feedback. The message reporting service on Instructables seems to be a little unreliable for some reason, so sometimes I do not see new posts, bear this in mind!
Step 6: Library Evolutions and Speed Enhancements
The Adafruit_xxx_AS libraries associated with this Instructable have been enhanced significantly by myself from the originals produced by Adafruit. It could (unintentionally) be the case that the libraries are no longer compatible with Adafruit products, so bear this in mind and do NOT contact Adafruit if you have a problem!
As the changes made are now quite significant it is the intent to rename this library in future whilst keeping the acknowledgement to Adafruit within. If this change does happen then I will create an associated Instructable.
The table shows how significant the performance improvements are, these improvements are a result of adapting the software to the capabilities of the processor and displays. There are no plans to migrate the Library to ARM processors (DUE etc) as I do not have the need, the projects I have planned only need to update the display infrequently (every few seconds) as the parameters being monitored do not change quickly and by writing the software efficiently to only update the areas of the screen that change means I can get flicker free updates.
The result for the 5.2 x improvement reported for line drawing and 6.2 x for triangle outlines is due to enhancements to the Bresenham line drawing algorithm. Essentially making use of the fact that short horizontal and vertical line segments are in the lines (apart from one case at 45 degrees), these segments can be drawn much more quickly than plotting individual pixels.
The 2x improvements are simply made by making the SPI send loops tighter and more efficient.
Step 7: The Future...
I have tested the F_AS_T option which uses direct port access on an UNO and this is now working fine. Uncomment the line:
in the "Adafruit_ILI9341_FAST.h" file when using Atmega328 (UNO/Micro Pro/Nano) processors. Put // comment in front to disable for other processors.
Run Length Encoding
One of my next graphics projects will use icons, these are simple images such as thermometers, compass dials etc that generally use few colours with relatively large areas of the same colour, this makes them suitable for compression with a Run Length Encoding (RLE) algorithm. RLE is quite simple and fast to compress/decompress so is great for the AVR. This will make pulling them off the SD card and plotting them even faster as the typically the RLE compressed files will be significantly smaller than a raw bit map. Potentially icons could also be stored in FLASH as well. As an example a raw image of 60 x 60 pixel icon is 7200 bytes, RLE of a simple graphic of a warning triangle could be only 1000 bytes.
RLE of fonts
The GFX library contains fonts and the larger ones lend themselves to RLE, this will mean that a UNO will be able to have more fonts resident in FLASH. Watch this space " " !