The ILI9341 is a great display, fast, cheap and most modules for hobbyists come with an on-board SD Card reader and built in touch screen – marvellous! But getting all these things working, especially with the ESP32 has sometimes been a little hit and miss – if you trawl the internet and all the requests for help.
So here, hopefully, is a definite guide to getting all those three functions up and running with your ESP32. Below is a full video showing all the steps from wiring to libraries to examples. This page has other supporting materials that you may need.
IMPORTANT: There is an omission in the video, for the touch screen to work you must un-comment the line below (remove the hash at the beginning) in the “User_Setup.h” file.
#define TOUCH_CS 21 // Chip select pin (T_CS) of touch screen
The JPEG Code
Here’s the code shown in the SD Card section. It displays jpeg files that are stored in the root of the SD card. No other files should be in root (folders are fine) as it will attempt to display them as an image too! After the code you will find a zip file of the images used in the demo. If you want to do your own images it’s important to note that the max length of filename you can use is 8 characters and the image must be of the resolution 320×240 and be in landscape orientation. You can look at the code if you want to change to portrait.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
// Modified verion of Bodmers example; by XTronical // This version will read the root directory and any files it finds it will // attempt to send to the jpeg decover for displaying on screen. Therefore // you should only have jpegs in the root dir. Also they must be DOS filenames // so no filenames over 8 chars. Below follows Bodmers original header // This sketch if for an ESP32, it draws Jpeg images pulled from an SD Card // onto the TFT. // As well as the TFT_eSPI library you will need the JPEG Decoder library. // A copy can be downloaded here, it is based on the library by Makoto Kurauchi. // https://github.com/Bodmer/JPEGDecoder // Images on SD Card must be put in the root folder (top level) to be found // Use the SD library examples to verify your SD Card interface works! // The example images used to test this sketch can be found in the library // JPEGDecoder/extras folder //---------------------------------------------------------------------------------------------------- #include <SPI.h> #include <FS.h> #include <SD.h> #include <TFT_eSPI.h> TFT_eSPI tft = TFT_eSPI(); // JPEG decoder library #include <JPEGDecoder.h> //#################################################################################################### // Setup //#################################################################################################### void setup() { Serial.begin(115200); // Set all chip selects high to avoid bus contention during initialisation of each peripheral digitalWrite(22, HIGH); // Touch controller chip select (if used) digitalWrite(15, HIGH); // TFT screen chip select digitalWrite( 5, HIGH); // SD card chips select, must use GPIO 5 (ESP32 SS) tft.begin(); if (!SD.begin()) { Serial.println("Card Mount Failed"); return; } uint8_t cardType = SD.cardType(); if (cardType == CARD_NONE) { Serial.println("No SD card attached"); return; } Serial.print("SD Card Type: "); if (cardType == CARD_MMC) { Serial.println("MMC"); } else if (cardType == CARD_SD) { Serial.println("SDSC"); } else if (cardType == CARD_SDHC) { Serial.println("SDHC"); } else { Serial.println("UNKNOWN"); } uint64_t cardSize = SD.cardSize() / (1024 * 1024); Serial.printf("SD Card Size: %lluMB\n", cardSize); Serial.println("initialisation done."); // all files are in landscape, you would need to change the code if you wanted portrait tft.setRotation(1); // landscape tft.fillScreen(random(0xFFFF)); } //#################################################################################################### // Main loop //#################################################################################################### void loop() { File file; File root = SD.open("/"); if(!root){ Serial.println("Failed to open root directory"); return; } file = root.openNextFile(); // Opens next file in root while(file) { if(!file.isDirectory()) { drawSdJpeg(file.name(), 0, 0); // This draws a jpeg pulled off the SD Card delay(4000); } file = root.openNextFile(); // Opens next file in root } root.close(); } //#################################################################################################### // Draw a JPEG on the TFT pulled from SD Card //#################################################################################################### // xpos, ypos is top left corner of plotted image void drawSdJpeg(const char *filename, int xpos, int ypos) { // Open the named file (the Jpeg decoder library will close it) File jpegFile = SD.open( filename, FILE_READ); // or, file handle reference for SD library if ( !jpegFile ) { Serial.print("ERROR: File \""); Serial.print(filename); Serial.println ("\" not found!"); return; } Serial.println("==========================="); Serial.print("Drawing file: "); Serial.println(filename); Serial.println("==========================="); // Use one of the following methods to initialise the decoder: boolean decoded = JpegDec.decodeSdFile(jpegFile); // Pass the SD file handle to the decoder, //boolean decoded = JpegDec.decodeSdFile(filename); // or pass the filename (String or character array) if (decoded) { // print information about the image to the serial port jpegInfo(); // render the image onto the screen at given coordinates jpegRender(xpos, ypos); } else { Serial.println("Jpeg file format not supported!"); } } //#################################################################################################### // Draw a JPEG on the TFT, images will be cropped on the right/bottom sides if they do not fit //#################################################################################################### // This function assumes xpos,ypos is a valid screen coordinate. For convenience images that do not // fit totally on the screen are cropped to the nearest MCU size and may leave right/bottom borders. void jpegRender(int xpos, int ypos) { //jpegInfo(); // Print information from the JPEG file (could comment this line out) uint16_t *pImg; uint16_t mcu_w = JpegDec.MCUWidth; uint16_t mcu_h = JpegDec.MCUHeight; uint32_t max_x = JpegDec.width; uint32_t max_y = JpegDec.height; bool swapBytes = tft.getSwapBytes(); tft.setSwapBytes(true); // Jpeg images are draw as a set of image block (tiles) called Minimum Coding Units (MCUs) // Typically these MCUs are 16x16 pixel blocks // Determine the width and height of the right and bottom edge image blocks uint32_t min_w = min(mcu_w, max_x % mcu_w); uint32_t min_h = min(mcu_h, max_y % mcu_h); // save the current image block size uint32_t win_w = mcu_w; uint32_t win_h = mcu_h; // record the current time so we can measure how long it takes to draw an image uint32_t drawTime = millis(); // save the coordinate of the right and bottom edges to assist image cropping // to the screen size max_x += xpos; max_y += ypos; // Fetch data from the file, decode and display while (JpegDec.read()) { // While there is more data in the file pImg = JpegDec.pImage ; // Decode a MCU (Minimum Coding Unit, typically a 8x8 or 16x16 pixel block) // Calculate coordinates of top left corner of current MCU int mcu_x = JpegDec.MCUx * mcu_w + xpos; int mcu_y = JpegDec.MCUy * mcu_h + ypos; // check if the image block size needs to be changed for the right edge if (mcu_x + mcu_w <= max_x) win_w = mcu_w; else win_w = min_w; // check if the image block size needs to be changed for the bottom edge if (mcu_y + mcu_h <= max_y) win_h = mcu_h; else win_h = min_h; // copy pixels into a contiguous block if (win_w != mcu_w) { uint16_t *cImg; int p = 0; cImg = pImg + win_w; for (int h = 1; h < win_h; h++) { p += mcu_w; for (int w = 0; w < win_w; w++) { *cImg = *(pImg + w + p); cImg++; } } } // calculate how many pixels must be drawn uint32_t mcu_pixels = win_w * win_h; // draw image MCU block only if it will fit on the screen if (( mcu_x + win_w ) <= tft.width() && ( mcu_y + win_h ) <= tft.height()) tft.pushImage(mcu_x, mcu_y, win_w, win_h, pImg); else if ( (mcu_y + win_h) >= tft.height()) JpegDec.abort(); // Image has run off bottom of screen so abort decoding } tft.setSwapBytes(swapBytes); showTime(millis() - drawTime); // These lines are for sketch testing only } //#################################################################################################### // Print image information to the serial port (optional) //#################################################################################################### // JpegDec.decodeFile(...) or JpegDec.decodeArray(...) must be called before this info is available! void jpegInfo() { // Print information extracted from the JPEG file Serial.println("JPEG image info"); Serial.println("==============="); Serial.print("Width :"); Serial.println(JpegDec.width); Serial.print("Height :"); Serial.println(JpegDec.height); Serial.print("Components :"); Serial.println(JpegDec.comps); Serial.print("MCU / row :"); Serial.println(JpegDec.MCUSPerRow); Serial.print("MCU / col :"); Serial.println(JpegDec.MCUSPerCol); Serial.print("Scan type :"); Serial.println(JpegDec.scanType); Serial.print("MCU width :"); Serial.println(JpegDec.MCUWidth); Serial.print("MCU height :"); Serial.println(JpegDec.MCUHeight); Serial.println("==============="); Serial.println(""); } //#################################################################################################### // Show the execution time (optional) //#################################################################################################### // WARNING: for UNO/AVR legacy reasons printing text to the screen with the Mega might not work for // sketch sizes greater than ~70KBytes because 16 bit address pointers are used in some libraries. // The Due will work fine with the HX8357_Due library. void showTime(uint32_t msTime) { //tft.setCursor(0, 0); //tft.setTextFont(1); //tft.setTextSize(2); //tft.setTextColor(TFT_WHITE, TFT_BLACK); //tft.print(F(" JPEG drawn in ")); //tft.print(msTime); //tft.println(F(" ms ")); Serial.print(F(" JPEG drawn in ")); Serial.print(msTime); Serial.println(F(" ms ")); } |
The Sample Images (if you want to use them)
That’s it, if you’ve some this far and wonder where all the detail is, then you’ll find it in the video!
Hi de ho!! Found it.
Cool 😎
Hello thanks for the video. I could not find the link to the wiring page and the connections to the il941 silkscreen
Thanks John
Wiring is in the video at 8mins in and onwards
Brilliant video, I am struggling to get the touch to work, I have removed the comments for #define TOUCH_CS 21 but I am getting these errors. Any idea what to do.
On_Off_Button:79:11: error: ‘class TFT_eSPI’ has no member named ‘getTouch’
if (tft.getTouch(&x, &y))
^
On_Off_Button:143:9: error: ‘class TFT_eSPI’ has no member named ‘setTouch’
tft.setTouch(calData);
^
On_Off_Button:162:9: error: ‘class TFT_eSPI’ has no member named ‘calibrateTouch’
tft.calibrateTouch(calData, TFT_MAGENTA, TFT_BLACK, 15);
^
Oh nevermind, I forgot to save the header file… silly me.
Ahh, no worries, we’ve all been there 😉
above code doesn’t work with sd cards, fails to compile correct, any ideas.
seems to be issue with min funtion(not defined) , and even then fails to connect to sd card
Mmm… If it’s saying min function not defined then it sounds like your missing a header file perhaps, have you definitely installed all the ones you see at the top of the code? If you have then perhaps they are different versions to what I used, check the video to make sure you are using same as I do. As you can see with my set up in the video it does compile. Just need to figure out why yours is different. Also in a later video (https://youtu.be/ZmMTr8LEa1A) I mention problems you can have with the SD card reader on these screens even when compiled. Have a look, it solved a few people’s problems.
Hope this helps
Hello
Please, regarding your video for the SD card. I tried to upload it on the Arduino Mega but have error message that it cant be uploaded. Do I have to create the header file for FS? I am using the 4.0 inch TFT display 14 pins + 4 for the SD card.
Sorry for late reply. It’s hard to say what’s wrong without seeing the error message, what is it?
no matching function for call to ‘min(uint16_t&, uint32_t)’
uint32_t min_h = min(mcu_h, max_y % mcu_h);
Is this from the demo I showed? or another?
hello i use an esp32 with 30 pins and got a tft ili9488 display and wanted to use the touch function. Image is displayed, but the touch function does not work and every time a calibration is done, it is done automatically without me touching the screen, as if there is continuous contact on the display. Can you help me there?
Sorry for late reply, real life work has been very busy and I missed this comment. Did you check the addendum? It’s written in the video description and I think the article. There was a bit missed out in the video, for the touch screen to work you must un-comment the line below (remove the hash at the beginning) in the “User_Setup.h” file.
#define TOUCH_CS 21 // Chip select pin (T_CS) of touch screen
Brilliant tutorial ! 1K Thanks !!! Everything works just fine. Displaying full size (320×240) pics works wonderful !
BUT … 🙂
I want to put a small jpg (e.g. 25 x 25 pix) on a certain (x,y) location. (without destroying text, lines etc) Reason : instead of “drawing” on the screen, I think it’s much more convenient to store e.g. “icons” on the card and “read” them when needed but I can’t find out how to achieve this. i.e. What is the minimum code to do this ? I guess that there’s a lot of lines in the original code that are not necessary … I tried, tried but till now : no success…
Any help is greatly appreciated !
You mean you want to put an icon over a background image with destroying the back ground? I think the library can handle GIF’s with transparency. You would need your GUF to have a “Mask” for where you want the background to show through. Most if not all art packages support this. However if you want to remove the icon you will have to redraw over it first with that area of background.
Good Tutorial, Thanks.. Has Anyone found a way to get rid of the artifacts, left on the touch screen, after you’ve touched the screen… needs some kind of a Re-fresh I guess.??
Are you talking about the paint type program? Yes you would need to manually clear the screen.
Great Tutorial,, Please disregard my last post,, as I thought there was a problem with
artifacts…. Finally I see the line of code that removes the spots…
// Comment out to stop drawing black spots*
//#define BLACK_SPOT
I am now vary happy with the Tutorial and Library…. Thanks..
Ha ha, sorry, I replied before seeing this comment – Doh!
I really like your straight forward approach! Perfect! I followed your instructions step by step and of course (Murphy again) ended up with something completely different. I had to dig deep in my orders from AliExpress to find out my display has a different driver chip. After changing my setup.h I was happy as can be. Thanks for your wonderful tutorial(s)
Your welcome, thank you for the feedback
These displays should run at 3.3.V for the SPI signals, looking at the specs. I see that you have hooked it up with an usb cable so the esp32 is running at 5V. can’t find the wiring diagram you are using and mine simply doesn’t work.using an ESP WROOM-32D.
Don’t worry, it’s not running at 5v, there are voltage regulators on the boards that bring it down to 3.3v. It would break otherwise. There isn’t a wiring diagram, you have to follow the video for this one – sorry. I should produce one really.
I thought your video was excellent and I look forward to getting my 3.5″ display.
There is a lot of confusion with the ESP32. The actual pin numbers do not correspond to the physical package pin numbers and depends on what they call pin 1.
I got it figured out and Im using it on my solar water system controller design.
Thank You for the effort.
Your welcome, thank you for the feedback
umm, after im following ur instruction i got a problem on testing touch there’s no calibrate after i upload the sketch on_off_button, so the button won’t change to off or on
nevermind i forgot to change calibration filename it works now.
Glad you sorted it 🙂
Brilliant tutorial for a new tinkerer, thanks.
I got this all working on the ESP32 but the touch doesn’t work on a LoRa 32 v2, it is an ESP32 based board with a LoRa transceiver built-in.
The LCD works great on the LoRa 32 v2 but the touch screen calibrates itself without being touched and then will not respond. I am using 23 and 17 for CS on the LCD and touch controllers. I have reversed then and the LCD is still fine and the touch still faulty. MOSI, MISO and CLK are common so unless it needs the IRQ I am stumped.
What is strange is you can unplug the CS and MISO wires from the touch controller and the screen still calibrates. Heaven knows where the data is coming from that the board thinks is from the touch screen when it calibrates.
As I say, I am new and so I may be overlooking the obvious. Any pointers will be very much appreciated. Thanks again.
Errr… I’m annoyed that I can’t think of anything to help you. Nothing springs to mind. I’ve not got any of those boards in either so can’t even try to replicate.I’m really sorry I’m not sure what’s going on there 🙁
I sorted it and for the benefit of others playing with this board I will explain.
This board has a LoRa radio on the SPI and the CS is connected to GPIO 18. If you don’t write GPIO 18 high then the LoRa radio ‘chatters’ onto the SPI MISO bus and this interrupts the touch controller signals. Define 18 is output and write it high is the simple fix if you don’t need the radio.
Interestingly though you can still use the LoRa radio with this display, when you include the LoRa radio in the script but never ask it to do anything then GPIO 18 is sent high by the code and you get no problem with the SPI bus.
If you want to use the LoRa radio you have 2 choices. (1) except that there may be some glitches because the SPI cannot update the display and communicate with LoRa at the same time or (2) you can define different SPI pins in the user_setup.h file, this gives another SPI channel but that drops the SPI bus from 80MHz to 40MHz.
I went with option 1 and it seems fine for my needs but if you have lots of data being transmitted over LoRa and lots of graphics updating on the screen it may be a problem that they share the SPI bus.
I am still learning this new black magic but I hope this helps someone else get LoRa and this touch screen to play nice…