Arduino - TFT Display of Bitmap Images From an SD Card




Intro: 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:

#define F_AS_T

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 " " !



  • Optics Contest

    Optics Contest
  • Electronics Tips & Tricks Challenge

    Electronics Tips & Tricks Challenge
  • Plastics Contest

    Plastics Contest

41 Discussions


9 months ago

I did not find Sketch!


1 year ago

hey there love the detail you go for in these instructable mate your Arduino TFT Display and Font Library works a treat just wondering if you have support for the ST7735R for this cant seem to get the SD working on my little 1.8"




2 years ago

Great tutorial, thank you.


2 years ago

Great tutorial, thank you.


2 years ago

Hello, I bought this TFT:

I would mount it on an Arduino Mega and recall the images uploaded to SD with a button ... can you help me?

Thank you and congratulations for the job!



1 reply

2 years ago


Thanks for
this great library! It's really very useful.

Instead of the
"Adafruit_ILI9341_AS" library I tried to use your `TFT_ILI9341"
library from your GitHub because it's a lot faster and leaves more space for my
sketches. It's kind of working to my surprise, but only the colours and the
screen alignment are not correct. I don't know if you’re planning to replace
the Adafruit library by your TFT_ILI9341 for this instructable but if you do I
would be very happy. Anyway, thanks again for your great work!!


2 years ago

SD was not working with sd full speed. Only sd half speed worked.

3 replies

Reply 2 years ago

Do you use a resistor divider logic level shifter, if so what value resistors do you use?


Reply 2 years ago

Thanks for the reply! I used the schematic and pin described in this instructables, 1.2k and 1.8k


Reply 2 years ago

OK, some SD Cards like fast transitions between logic levels. Try 470 Ohms and 1K instead of 1K2 and 1K8. Check your display does not already have 1K in line resistors connected to the SD Card, or post a picture of the back of the display board so I can have a look for you.


Reply 3 years ago on Introduction

Good news! I have tried various encoding methods and found that the basic run length encoding is a good compromise between reducing the font files and making the rendering of fonts to screen faster. At the moment I have the adapted graphics libraries running with a 480 x 320 display with the HX8357 driver. I can adapt other driver libraries, if you wish to try the new encoded font files then let me know which TFT driver chip you are using, then I can create a demo for you to try out.



Reply 3 years ago on Introduction

Great, excited to hear it.
So, as you found, a sweet spot with the TFTs in terms of price is those ili9341 2.2 - 2.8inch TFT modules. the 2.4 and 2.6 in particular balance price and size well. They are available with touch screen chip for around $7 delivered. The touch chips work with uTouch library apparently, i started on the touchless ones, though my touch version have arrived now.

I recently saw those 480x320 ones at $12 though, very nice, almost bought it. I take is you're using a mega/stm32 for those right? 16bit interface right? I should try one of those, look like great value. though see this one:

In cheap 480x320, if you didn't see it , you might want to check out the HX8352-A one, as it has a touch screen, $1 or so more.

Another interesting one: a 3.6" 400x240 ili9327 for under $10: (with touch apparently) (8 bit interface). I ordered one.

I shifted my TFT project to the $5 maple mini using the STM32duino core, feeling limited with lack of space and ram on the favourite 328p... I like smooth big fonts.

I spent quite a bit of time making a universal TFT controller [328p] with your library, i wrote a nice scrolling graphing function, your ringmeters modified/ more parameterised, optimised serial control, with setup of pages/elements/labels, data updates, etc.. all prompted by the need for more space for graphics code and fonts, and the wish to have this work be universal and re-usable in various projects. In this case even moving to a dedicated 328p was not enough, I found I wanted to buffer a lot of stuff in ram for the data logging and graph, and sd bitmaps were out of the question, and drawing could always be faster, so I moved again to the stm32f1.

BUT: I still want to use 328p with the Ili9341 where ideal. Having compressed fonts, and flash chips, makes that SO much more possible. And compression and flash will make the stm32 ever more useful.

- I see some of your ili9341 work is ported in that stm32 arduino repository, though i found the ported ili9341due library there is much faster than yours on all operations but fillscreen and fill rect, interestingly. Your library still seems to be the king for 328p, if using f_as_t mode.

- So, in answer to your question on what i'm using:, ili9341 for 328p and stm32. And then any of the other ones i mentioned, like that480x320 one you got. So you could/should put it in your lib for the illi9341 on AVR at least, and It would be appreciated in the stm32arduino repository illi9341 drivers too..

Here's an idea: at some point, you/I/we port it into the gText library that the ili9341due uses. that appears to be a nice library/font stardard to use, if we're going to have the space savings to be a little more indulgent with font choice. It has many fonts already imported, tools to import, etc.. Or is that lib too bulky/cumbersome for your taste?

I'm super happy with the discovery/use of the maple mini, often wanting more power/space than a 328p, but not wanting to budget the bulkier $13 mega2560+ into everything that could use it..

When it starts approaching the price of the cheapest android phone, you have to rethink what you're doing programming hardware. After all; for $40 or $50, you get a 700mhz+ arm, with battery, high-res capacitive touch screen, wifi, bluetooth, storage, gsm, etc. and mature development tools and community. Could interface to this from your mcu a number of ways. So i recon $20-25 is the sanity check level for MCU+TFT for arduino..

Thanks again for your work. this is great, if there's anything i can do to help you develop or refine it, let me know.


Reply 3 years ago on Introduction

The HX8352-A display in the link does not appear to have the 5V to 3.3V logic level translators so would need an extra board. I have a couple of these:

These use the HX8357 driver and I created a driver library for it for the Arduino Mega. These use HC245 level converter IC's so work well with SD Cards. There is a empty footprint for a SPI FLASH device too that looks Winbond compatible! It sits close down on the Mega which means the board must be dismounted to get at the SD Card and the other Mega pins are not easily accessible. But it makes a nice cheap TFT assembly with a Mega clone. Banggood sell then with a Mega clone for about £11:

I also have the 400x240 display you mention, it has resistor based level converters. The empty FLASH device footprint does not look Winbond compatible though. Typically these screens are advertised with the wrong driver but are ILI9327 based. I have adapted/created a simple Touch library for it (as the UTFT Utouch library has reliability issues with that display). I am not a fan of the cheap resistove touch screens though as the plastic on the screen is easily marked.

I see we think along the same lines, I have a cheap android phone that has never been used as a phone... instead I use the WiFi, Bluetooth, web browser and touch screen features to compliment my Arduino projects!

I will tidy up the RLE sketches atc and adapt a ILI9341 library to display the RLE fonts. It may take me a week or two to get this done as I am rather busy with paid work at the moment!

OK I think I have probably covered all your questions!



Reply 3 years ago on Introduction

Where is your driver for the HX8357? I have one mounted to a Due, where performance is pretty bad considering what the Due is capable of.



Reply 3 years ago on Introduction

I want to convert the many big/full fonts from the uTFT or gText format to this new format. Any thoughts on how I/we should approach that?


Reply 3 years ago on Introduction

I looked at a few options when I wanted to create a library that was small for the UNO/328, for example true type font converters and re-using fonts from other libraries but then I came across "Cosmic Void's" font editor software:

This is a little flaky and crashes when editing sometimes :-(... but the really nice feature is it encodes the fonts into a convenient C code friendly format. This is configurable and the output is a nice individual character array text file format that is in an almost ready to use form (minor edits needed) to compile with the Arduino IDE. It also creates the proportional spacing width table for each character too and the font related variables (height etc). This makes things so much easier! It is also quite human readable and permits individual characters to be replaced as each character is an array with the ASCII code in the array name.

The software has some fonts included, I just wanted a proportional space Helvetica style non-serif font which fortunately it has in the set in a few different sizes, bold, italic etc. It will also produce outline character fonts.

As I am familiar with that software (and the minor edits needed) I can now create a new font file and test it with a TFT display in about 30 minutes. So... to answer the question, now I have gone down that route I am not sure I would bother to convert fonts from other libraries, though I can see why this might be of general interest.