NodeMCU (ESP8266) + ILI9341 TFT + SPIFFS = easy image storage and display

I had some time today to experiment with using the SPI FLASH FILING SYSTEM (SPIFFS) of the NodeMCU to store images and to retrieve and render on the TFT.

The memory used by SPIFFS is already on the NodeMCU and provides up to 3Mbytes of storage. This is sufficient to hold about 18 full screen, full colour 16 bit images. These image files are 150kbytes each ( calculated from screen pixels count and 2 bytes per pixel).

Performance is surprisingly good considering both the FLASH memory and TFT are SPI serial devices. The sketch generates some statistics for performance evaluation and copes with 2 different raw image (16 bit colour) formats with different byte orders.

The “best” performance is with 40MHz SPI clock to the TFT, in that case a full screen image of 320x240 pixels is copied from FLASH to TFT in 118ms. Most of this time is taken up by the SPIFFS pulling the image off the FLASH memory.

The typical setup for a NodeMCU1.0 (ESP-12 Module) is :

  Display SDO/MISO      to NodeMCU pin D6 <<<<<< This is not used by the library
  Display LED           to NodeMCU pin  5V or 3.3V
  Display SCK           to NodeMCU pin D5
  Display SDI/MOSI      to NodeMCU pin D7
  Display DC/RS (or AO) to NodeMCU pin D3
  Display RESET         to NodeMCU pin D4
  Display CS            to NodeMCU pin D8
  Display GND           to NodeMCU pin GND (0V)
  Display VCC           to NodeMCU pin 5V or 3.3V

See the header and comments in the sketch for more information on how to get it running.

I also have a sketch that renders JPEG compressed images from SPIFFS to the screen if anyone is interested. Many more full screen images (100 maybe?) can be stored but there is a performance penalty in the decompression as that involves a whole stack of maths, the performance may be acceptable though at 250-350ms per full screen image.

I’ve tested most features of the sketch but if you find a bug then post it here.

Have fun!

Update 23rd March 2019. A new example “ESP8266_draw_565_image3” suitable for the TFT_eSPI library is attached. (305 KB) (314 KB)

The sketch works very nicely. I am impressed by the ESP8266 and how well it works with SPI. I can write to an ILI9341 with SCK=80MHz and read GRAM with SCK=40MHz without noticing any glitches. These figures are way faster than the ILI9341 datasheet says.

Your SPIFFS images require SCK=40MHz. I am not too sure which operations are critical.

Is there any reason for not providing "read" methods in your TFT library?
e.g. make your public tft.spiwrite() method non-void.

It is messy to duplicate stuff with a GLUE class.


Hi David,

Yes, I was surprised how well the ESP8266 performs. Up to now I’ve only used the chips for sensor nodes, sending information to ThingSpeak and serving very simple we pages and no display other than a flashing LED to indicate “I am working”.

The processor actually has a quad “parallel” data line SPI to reduce transfer times but on the NodeMCU it is used with a dual data line interface according to the blurb I have read. The SPIFFS seems to take ages to get the data.

The SPI MHz figures are way over the ILI9341 data sheets but they don’t seem to get hot or consume much more power, and the serial data is probably quickly converted into lower clock rate parallel data inside the driver, so I suspect only a few registers run at the high speed. The displays are so cheap that trying to break one by over-clock won’t break the bank :slight_smile:

I actually use 20MHz in project as the performance is far more than is needed and in a networked environment the CPU wants to spend time service the WiFI etc anyway.

The SPI interface in the ESP9266 has a 64 byte buffer, it can be filled and then single instruction can be sent when the buffer is empty to keep sending the same buffer content without filling it again, so this means at 80MHz pixels bits can be sent easily at an average rate of 75Mbps for flood fills of the same colour.

The critical tipping point for the display appears to occur when long bit (aka pixel) streams are sent, a single cycle clock gap is needed if more than 32bits are sent in a burst. Gaps are conveniently inserted in TFT commands as the DC line must be toggled in software, so they happen error free, which is good obviously. At that tipping point there is a small probability (maybe 1 in 10,000) of extra pixels appearing on the screen of the same colour. I first saw it rendering RLE encoded fonts at really high speed, so has to slow that down to 32 bit bursts maximum.

At 80MHz I can see spurious pixels if I setup a test case in a copy of the fillRect() member function that sets up an address window of a given “width” and “height” but then only sends width * (height - 1) pixels at maximum possible speed. The bottom line of the window occasionally gets spurious pixels appearing in it, indicating more pixels arrived on the screen than were sent! Usually these extra pixels which usually are not seen as the pixels wrap back to the top of the address window and the colour is correct

Q: Is there any reason for not providing “read” methods in your TFT library?

A: Yes, it hasn’t got off the “To do” list. I have a function call that I can read the TFT ID, then I just label the back of the board with the driver!

An ESP32 arrived today so adding a read function has moved down the To Do list again. If you can suggest some code to do the job I can add it in :wink:

The ESP8266 has an interesting extended SPI functionality. I still do not understand the details, the documentation about the SPI registers is not easy to understand. Maybe the extended functionality is more intended to be used with the ESP8266 firmware.

I found an interesting example in:

with code in:

My test was not successful, I’ll give it another try, and re-check wiring.
(I was looking for a SPI slave as a SPI sniffer/spy).
Unfortunately SS is D8 which is GPIO15, which controls boot mode on startup.


Hi Jean-Marc,

I have not investigated the SPI slave mode though the Espressif ESP8266 Technical Reference document available on the internet lists the slave API's provided, so I assume that it is possible unless due to the Chinese to English translation those APIs are to "talk" to a slave and not actually to be a slave!

There is an easy work-around for the boot mode pin D8. The pin should have a pull down on it, all you need to do is drive that pin with a tristate buffer that is switched on (enabled) by your software. Clearly your software will only run after the processor has booted, at which point you are allowed to take control of it.

I can't help further, but I see some discussion on the internet about it.

By the way (in case this was what your post was implying after David's post on reading data from the TFT) the Node MCU does not have to become a slave to read data from a TFT.

Hi Rowboteer,

thank you for your response. I like your work!

I write quite fluently in English, and the spell-checking really helps.

But I still have some problems to express myself in such a forum, to make my intention clear.

In this case, I just wanted to "broadcast" the point, that ESP8266 has many more features to discover.

I do not really ask for help for SPI slave or sniffer/spy, but of course would be glad to hear of successful use or get links to examples or libraries. I have seen some examples, e.g. from Nick Gammon for Arduino with Atmega cpu.
The TFT I would like to "spy" on the Raspi/linux communication, is fed with 16MHz SPI, so I would need a fast processor and HW SPI support. I now spy with a Saleae logic analyzer, makes fun, the last time I used a logic analyzer was about 40 years ago!

The example I posted the link for works with 2 ESP8266 (Wemos D1 mini), at least for Master to Slave.

I had crossed MISO and MOSI, understandable, but wrong; that's why it did not work.


how to write bmp image on flash?

Be more specific.

BMP is a common format used on your PC. Create all the BMP files that you want on the PC. Copy them to a SD card. SD cards can store massive numbers of files.

You can copy a small number of BMP files from your SD to SPIFFS memory on your ESP8266.

You can embed one or two BMP files in your application.

BMP files are big. It is very easy to decode the BMP. Even a Uno can do it.

In practice you have to choose between large number of images on SD card (and physical SD card holder)
Or storing a small number of images on the ESP8266 module (application Flash or SPIFFS Flash)

JPEG is a lot more efficient for size. The ESP8266 can decode JPEG. You can get a reasonable number of JPEG images on the ESP8266 Flash.


i am using tft display SSD1963 and flash ic W25Q128FV
also SPIFFS library
now i want to write bmp image on W25Q128FV from sd card

how can i do that?
any example code or link ?

There is example code in the zip file attached to the first post.

This will need to be modified and an alternative library used as the library called up does not support the SSD1963 or the W25Q128FV. You will need to Google for more help.

thanks for reply
my question is how you wrote thr BMP image on flash ic/spiffs?

in your example code there is a read facility if i am not wrong

i want to know how to write bmp file on flash chip

The instructions can be found here.


bodmer, thanks a lot for your work. Works really cool. But can you, please, shed more light on the format of your raw files. And what converter did you use for them? You've mentioned the 16-bit bmp files. So, should it be 16-bit bmp converted to raw(R1G1B1R2G2B2...RnGnBn)?
Thank you for the answer in advance.

Looks like the images should be 16-bit bmp just withour a header. I have converted the image to 16-bit bmp and manually (in text editor) removed the header. It worked for me. The only problem, that the order of the colors seems to be incorrect. The original Tiger.raw image is shown normally, the colors are ok, but my image has the incorrect colors order. I suppose so, cause colors are very strange, not inverted.

.BMP files contain information in the header. e.g. width, height, format, ...
followed by the pixel data. Typically as three RGB bytes per pixel

You can pre-process the .BMP files into just the 16-bit pixel RAW data.

This makes smaller files that do not require any decoding.

Each 16-bit raw pixel is stored as H-L bytes (bigendian) or L-H bytes (little-endian)

Bodmer can display H-L or L-H. You just select the required style.

Quite honestly, it seems wise to use the standard .BMP format that comes from the PC. ESP32 and ESP8266 have plenty of processing power.

If you want to store as many images as possible in SPIFFS, use .JPG files. You can compress them without losing any noticeable quality.


Ok, I've found the needed converter: this on-line UTFT converter will do the job: Rinky-Dink Electronics

Thank you, for the reply David. I tried to use both Bodmers formats (UTFT defined and not), but, unfortunately, both were duspalyed incorrecly (I rbelieve, H-L instead of L-H). And I didn't find any option in the startdart image editors for changing the order. But, found it on-line. Thank you for the reply in any case.

You can either choose the style here:

  // The TFT_eSprite class inherits the following functions
  void     setWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1),
           pushColor(uint16_t color),
           pushColor(uint16_t color, uint32_t len),
           pushColors(uint16_t  *data, uint32_t len, bool swap = true), // With byte swap option
           pushColors(uint8_t  *data, uint32_t len),

or here

           // Swap the byte order for pushImage() - corrects endianness
  void     setSwapBytes(bool swap);
  bool     getSwapBytes(void);


Thank you David for pointing me out, but I use Adafruit_IL9431 library for all the graphics, cause it has a lot of useful things like buttons. And just copied Bodmer's drawRaw() function to make a background image for the screen. And I can see, he just reads a bunch of data from SPIFFS to the buffer and then writes this buffer directly to the SPI bus.
Here is the bytes order in the function:
SPI.write32(*ptr++, 1); // Byte order for UTFT ILI9341_Due file format = 1
SPI.write32(*ptr++, 0); // Byte order for UTFT = 0
But somehow the colors were wrong for me in both cases,
Newertheless, after converting the image to the proper UTFT format, the color are displayed correctly.

Go on. This thread is about ESP8266 NodeMCU and SPIFFS.

If you asked your question wisely, you would say Adafruit_ILI9341
Adafruit_ILI9341 has an example for 24-bit .BMP files

Yes, you can use some Adafruit libraries on ESP hardware. Please quote which Adafruit example on what hardware.