Elegoo Display and Stolen Code Standard So I bought myself another one of the Elegoo 2.8″ touchscreen LCD modules recently, and decided to have a deeper dive into the code. I looked through the examples and I thought “This looks very similar to code from Adafruit.” You can download the original “tutorial” files from Elegoo directly at https://www.elegoo.com/pages/arduino-kits-support-files if you’d like to take a look for yourself. [Update #1 added at 22:12 below] If you’re in a rush and just want a better way to do this, use the > give me the code < link. The code was the same. The Elegoo_GFX library was an exact copy of the Adafruit GFX library, just with a global find and replace “Adafruit” for Elegoo. You can test this by changing the library definition in the code to use the Adafruit one and everything will still work. One of the problems I had though, I could not figure out what they had changed to Adafruit’s TFTLCD library other than making it look as if they had written all the code themselves. It took me the best part of an afternoon to finally figure out exactly how it worked. They have only made (from what I can tell) a single change to the library under definitions for the Arduino Mega 2560 in the code. In the examples, it looks as if they use readID(); to get the chip Identifier that they use, however later on in the example they just set a static identifier. I know this because the readID(); function actually returns zero and they just statically set identifier=0x9341; to make everything work. The only differences between Adafruit’s libraries and Elegoo’s are in the Elegoo_TFTLCD library file pin_magic.h at line 165. If you want to make the LCD work with the 1.0.3 of the Adafruit TFTLCD Library, you can change lines 206 to 219 (which are) : #define write8inline(d) { PORTA = (d); WR_STROBE; } #define read8inline(result) { RD_ACTIVE; DELAY7; result = PINA; RD_IDLE; } #define setWriteDirInline() DDRA = 0xff #define setReadDirInline() DDRA = 0 #define write8inline(d) {\ PORTH &= ~(0x78);\ PORTH |= ((d&0xC0) >> 3) | ((d&0x3) << 5);\ PORTE &= ~(0x38);\ PORTE |= ((d & 0xC) << 2) | ((d & 0x20) >> 2);\ PORTG &= ~(0x20);\ PORTG |= (d & 0x10) << 1; \ WR_STROBE; } #define read8inline(result) { RD_ACTIVE; DELAY7; result = (PINH & 0x60) >> 5;result |= (PINH & 0x18) << 3;result |= (PINE & 0x8) << 2;result |= (PINE & 0x30) >> 2;result |= (PING & 0x20) >> 1;RD_IDLE;} #define setWriteDirInline() { DDRH |= 0x78;DDRE |= 0x38;DDRG |= 0x20; } #define setReadDirInline() { DDRH &= ~0x78;DDRE &= ~0x38;DDRG &= ~(0x20); } The only other changes you need to make to your code are when you start the tft.begin, you need to specify a fixed identifier, for example the following code (based on the graphicstest example) // IMPORTANT: Adafruit_TFTLCD LIBRARY MUST BE SPECIFICALLY // CONFIGURED FOR EITHER THE TFT SHIELD OR THE BREAKOUT BOARD. // SEE RELEVANT COMMENTS IN Adafruit_TFTLCD.h FOR SETUP. #include <Adafruit_GFX.h> // Core graphics library #include <Adafruit_TFTLCD.h> // Hardware-specific library #define LCD_CS A3 // Chip Select goes to Analog 3 #define LCD_CD A2 // Command/Data goes to Analog 2 #define LCD_WR A1 // LCD Write goes to Analog 1 #define LCD_RD A0 // LCD Read goes to Analog 0 #define LCD_RESET A4 // Can alternately just connect to Arduino's reset pin // Assign human-readable names to some common 16-bit color values: #define BLACK 0x0000 #define BLUE 0x001F #define RED 0xF800 #define GREEN 0x07E0 #define CYAN 0x07FF #define MAGENTA 0xF81F #define YELLOW 0xFFE0 #define WHITE 0xFFFF Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET); void setup(void) { Serial.begin(9600); Serial.println(F("TFT LCD test")); Serial.print("TFT size is "); Serial.print(tft.width()); Serial.print("x"); Serial.println(tft.height()); tft.reset(); tft.begin(0x9341); Serial.println(F("Benchmark Time (microseconds)")); Serial.print(F("Screen fill ")); Serial.println(testFillScreen()); delay(500); Serial.print(F("Text ")); Serial.println(testText()); delay(3000); Serial.print(F("Lines ")); Serial.println(testLines(CYAN)); delay(500); Serial.print(F("Horiz/Vert Lines ")); Serial.println(testFastLines(RED, BLUE)); delay(500); Serial.print(F("Rectangles (outline) ")); Serial.println(testRects(GREEN)); delay(500); Serial.print(F("Rectangles (filled) ")); Serial.println(testFilledRects(YELLOW, MAGENTA)); delay(500); Serial.print(F("Circles (filled) ")); Serial.println(testFilledCircles(10, MAGENTA)); Serial.print(F("Circles (outline) ")); Serial.println(testCircles(10, WHITE)); delay(500); Serial.print(F("Triangles (outline) ")); Serial.println(testTriangles()); delay(500); Serial.print(F("Triangles (filled) ")); Serial.println(testFilledTriangles()); delay(500); Serial.print(F("Rounded rects (outline) ")); Serial.println(testRoundRects()); delay(500); Serial.print(F("Rounded rects (filled) ")); Serial.println(testFilledRoundRects()); delay(500); Serial.println(F("Done!")); } void loop(void) { for(uint8_t rotation=0; rotation<4; rotation++) { tft.setRotation(rotation); testText(); delay(2000); } } unsigned long testFillScreen() { unsigned long start = micros(); tft.fillScreen(BLACK); tft.fillScreen(RED); tft.fillScreen(GREEN); tft.fillScreen(BLUE); tft.fillScreen(BLACK); return micros() - start; } unsigned long testText() { tft.fillScreen(BLACK); unsigned long start = micros(); tft.setCursor(0, 0); tft.setTextColor(WHITE); tft.setTextSize(1); tft.println("Hello World!"); tft.setTextColor(YELLOW); tft.setTextSize(2); tft.println(1234.56); tft.setTextColor(RED); tft.setTextSize(3); tft.println(0xDEADBEEF, HEX); tft.println(); tft.setTextColor(GREEN); tft.setTextSize(5); tft.println("Groop"); tft.setTextSize(2); tft.println("I implore thee,"); tft.setTextSize(1); tft.println("my foonting turlingdromes."); tft.println("And hooptiously drangle me"); tft.println("with crinkly bindlewurdles,"); tft.println("Or I will rend thee"); tft.println("in the gobberwarts"); tft.println("with my blurglecruncheon,"); tft.println("see if I don't!"); return micros() - start; } unsigned long testLines(uint16_t color) { unsigned long start, t; int x1, y1, x2, y2, w = tft.width(), h = tft.height(); tft.fillScreen(BLACK); x1 = y1 = 0; y2 = h - 1; start = micros(); for(x2=0; x2<w; x2+=6) tft.drawLine(x1, y1, x2, y2, color); x2 = w - 1; for(y2=0; y2<h; y2+=6) tft.drawLine(x1, y1, x2, y2, color); t = micros() - start; // fillScreen doesn't count against timing tft.fillScreen(BLACK); x1 = w - 1; y1 = 0; y2 = h - 1; start = micros(); for(x2=0; x2<w; x2+=6) tft.drawLine(x1, y1, x2, y2, color); x2 = 0; for(y2=0; y2<h; y2+=6) tft.drawLine(x1, y1, x2, y2, color); t += micros() - start; tft.fillScreen(BLACK); x1 = 0; y1 = h - 1; y2 = 0; start = micros(); for(x2=0; x2<w; x2+=6) tft.drawLine(x1, y1, x2, y2, color); x2 = w - 1; for(y2=0; y2<h; y2+=6) tft.drawLine(x1, y1, x2, y2, color); t += micros() - start; tft.fillScreen(BLACK); x1 = w - 1; y1 = h - 1; y2 = 0; start = micros(); for(x2=0; x2<w; x2+=6) tft.drawLine(x1, y1, x2, y2, color); x2 = 0; for(y2=0; y2<h; y2+=6) tft.drawLine(x1, y1, x2, y2, color); return micros() - start; } unsigned long testFastLines(uint16_t color1, uint16_t color2) { unsigned long start; int x, y, w = tft.width(), h = tft.height(); tft.fillScreen(BLACK); start = micros(); for(y=0; y<h; y+=5) tft.drawFastHLine(0, y, w, color1); for(x=0; x<w; x+=5) tft.drawFastVLine(x, 0, h, color2); return micros() - start; } unsigned long testRects(uint16_t color) { unsigned long start; int n, i, i2, cx = tft.width() / 2, cy = tft.height() / 2; tft.fillScreen(BLACK); n = min(tft.width(), tft.height()); start = micros(); for(i=2; i<n; i+=6) { i2 = i / 2; tft.drawRect(cx-i2, cy-i2, i, i, color); } return micros() - start; } unsigned long testFilledRects(uint16_t color1, uint16_t color2) { unsigned long start, t = 0; int n, i, i2, cx = tft.width() / 2 - 1, cy = tft.height() / 2 - 1; tft.fillScreen(BLACK); n = min(tft.width(), tft.height()); for(i=n; i>0; i-=6) { i2 = i / 2; start = micros(); tft.fillRect(cx-i2, cy-i2, i, i, color1); t += micros() - start; // Outlines are not included in timing results tft.drawRect(cx-i2, cy-i2, i, i, color2); } return t; } unsigned long testFilledCircles(uint8_t radius, uint16_t color) { unsigned long start; int x, y, w = tft.width(), h = tft.height(), r2 = radius * 2; tft.fillScreen(BLACK); start = micros(); for(x=radius; x<w; x+=r2) { for(y=radius; y<h; y+=r2) { tft.fillCircle(x, y, radius, color); } } return micros() - start; } unsigned long testCircles(uint8_t radius, uint16_t color) { unsigned long start; int x, y, r2 = radius * 2, w = tft.width() + radius, h = tft.height() + radius; // Screen is not cleared for this one -- this is // intentional and does not affect the reported time. start = micros(); for(x=0; x<w; x+=r2) { for(y=0; y<h; y+=r2) { tft.drawCircle(x, y, radius, color); } } return micros() - start; } unsigned long testTriangles() { unsigned long start; int n, i, cx = tft.width() / 2 - 1, cy = tft.height() / 2 - 1; tft.fillScreen(BLACK); n = min(cx, cy); start = micros(); for(i=0; i<n; i+=5) { tft.drawTriangle( cx , cy - i, // peak cx - i, cy + i, // bottom left cx + i, cy + i, // bottom right tft.color565(0, 0, i)); } return micros() - start; } unsigned long testFilledTriangles() { unsigned long start, t = 0; int i, cx = tft.width() / 2 - 1, cy = tft.height() / 2 - 1; tft.fillScreen(BLACK); start = micros(); for(i=min(cx,cy); i>10; i-=5) { start = micros(); tft.fillTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i, tft.color565(0, i, i)); t += micros() - start; tft.drawTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i, tft.color565(i, i, 0)); } return t; } unsigned long testRoundRects() { unsigned long start; int w, i, i2, cx = tft.width() / 2 - 1, cy = tft.height() / 2 - 1; tft.fillScreen(BLACK); w = min(tft.width(), tft.height()); start = micros(); for(i=0; i<w; i+=6) { i2 = i / 2; tft.drawRoundRect(cx-i2, cy-i2, i, i, i/8, tft.color565(i, 0, 0)); } return micros() - start; } unsigned long testFilledRoundRects() { unsigned long start; int i, i2, cx = tft.width() / 2 - 1, cy = tft.height() / 2 - 1; tft.fillScreen(BLACK); start = micros(); for(i=min(tft.width(), tft.height()); i>20; i-=6) { i2 = i / 2; tft.fillRoundRect(cx-i2, cy-i2, i, i, i/8, tft.color565(0, i, 0)); } return micros() - start; } I’m waiting for Amazon to publish my product review, as I think this a totally unfair use of Adafruit’s source code and quite a clear ethical violation of the ethos of open source code. They could have easily forked the library and added their changes… Maybe it would have even been accepted up stream if people would find using similar displays with the Adafruit library on the Mega 2650. Here’s a copy of my pending Amazon review. This article is research in progress and may change as I make future discoveries. I plan to test the code on a regular Arduino Uno to see if it works on that or there’s any other trickery afoot. [Update #1 – 22:12]: After much more research, I discovered the library MCUFRIEND_kbv. It’s included showBMP_not_Uno didn’t seem to work with the latest version of SdFat or on a Mega 2650, so I have updated this example (included below) to work mostly out of the box. You don’t have to do any library edits like above, other than defining #define SPI_DRIVER_SELECT 2 within SdFatConfig.h (in your libraries folder), then you can put your 24bit BMP images on your SD card and load them when using this module without any of the massive amount of effort I went through today 🙂 // Modified from MCUFRIEND-kbv version 2.99 by jcx 2021-04-23 // Upload and play sketch for Arduino Mega 2560 and Elegoo 2.8" TFT LCD // MCUFRIEND UNO shields have microSD on pins 10, 11, 12, 13 // The official <SD.h> library only works on the hardware SPI pins // e.g. 11, 12, 13 on a Uno // e.g. 50, 51, 52 on a Mega2560 // e.g. 74, 75, 76 on a Due // // if you are not using a UNO, you must use Software SPI: // install the latest version of the <SdFat.h> library with the Arduino Library Manager. // edit the src/SdFatConfig.h file to #define SPI_DRIVER_SELECT 2 // // copy all your BMP files to the root directory on the microSD with your PC // (or another directory) #include <SPI.h> // f.k. for Arduino-1.5.2 #include <SdFat.h> // Use the SdFat library SdFat SD; // Pin numbers in templates must be constants. const uint8_t SOFT_MISO_PIN = 12; const uint8_t SOFT_MOSI_PIN = 11; const uint8_t SOFT_SCK_PIN = 13; // Chip select may be constant or RAM variable. const uint8_t SD_CS_PIN = 10; // SdFat software SPI template SoftSpiDriver<SOFT_MISO_PIN, SOFT_MOSI_PIN, SOFT_SCK_PIN> softSpi; // Speed argument is ignored for software SPI. #if ENABLE_DEDICATED_SPI #define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(0), &softSpi) #else // ENABLE_DEDICATED_SPI #define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SD_SCK_MHZ(0), &softSpi) #endif // ENABLE_DEDICATED_SPI SdFat32 sd; File32 file; #include <Adafruit_GFX.h> // Hardware-specific library #include <MCUFRIEND_kbv.h> MCUFRIEND_kbv tft; #define NAMEMATCH "" // "" matches any name //#define NAMEMATCH "tiger" // *tiger*.bmp #define PALETTEDEPTH 8 // support 256-colour Palette char namebuf[32] = "/"; //BMP files in root directory //char namebuf[32] = "/bitmaps/"; //BMP directory e.g. files in /bitmaps/*.bmp File root; int pathlen; void setup() { uint16_t ID; Serial.begin(9600); Serial.print("Show BMP files on TFT with ID:0x"); ID = tft.readID(); Serial.println(ID, HEX); if (ID == 0x0D3D3) ID = 0x9481; tft.begin(ID); tft.fillScreen(0x001F); tft.setTextColor(0xFFFF, 0x0000); bool good = SD.begin(SD_CONFIG); if (!good) { Serial.print(F("cannot start SD")); while (1); } root = SD.open(namebuf); pathlen = strlen(namebuf); } void loop() { char *nm = namebuf + pathlen; File f = root.openNextFile(); uint8_t ret; uint32_t start; if (f != NULL) { f.getName(nm, 32 - pathlen); f.close(); strlwr(nm); if (strstr(nm, ".bmp") != NULL && strstr(nm, NAMEMATCH) != NULL) { Serial.print(namebuf); Serial.print(F(" - ")); tft.fillScreen(0); start = millis(); ret = showBMP(namebuf, 5, 5); switch (ret) { case 0: Serial.print(millis() - start); Serial.println(F("ms")); delay(5000); break; case 1: Serial.println(F("bad position")); break; case 2: Serial.println(F("bad BMP ID")); break; case 3: Serial.println(F("wrong number of planes")); break; case 4: Serial.println(F("unsupported BMP format")); break; case 5: Serial.println(F("unsupported palette")); break; default: Serial.println(F("unknown")); break; } } } else root.rewindDirectory(); } #define BMPIMAGEOFFSET 54 #define BUFFPIXEL 20 uint16_t read16(File& f) { uint16_t result; // read little-endian f.read(&result, sizeof(result)); return result; } uint32_t read32(File& f) { uint32_t result; f.read(&result, sizeof(result)); return result; } uint8_t showBMP(char *nm, int x, int y) { File bmpFile; int bmpWidth, bmpHeight; // W+H in pixels uint8_t bmpDepth; // Bit depth (currently must be 24, 16, 8, 4, 1) uint32_t bmpImageoffset; // Start of image data in file uint32_t rowSize; // Not always = bmpWidth; may have padding uint8_t sdbuffer[3 * BUFFPIXEL]; // pixel in buffer (R+G+B per pixel) uint16_t lcdbuffer[(1 << PALETTEDEPTH) + BUFFPIXEL], *palette = NULL; uint8_t bitmask, bitshift; boolean flip = true; // BMP is stored bottom-to-top int w, h, row, col, lcdbufsiz = (1 << PALETTEDEPTH) + BUFFPIXEL, buffidx; uint32_t pos; // seek position boolean is565 = false; // uint16_t bmpID; uint16_t n; // blocks read uint8_t ret; if ((x >= tft.width()) || (y >= tft.height())) return 1; // off screen bmpFile = SD.open(nm); // Parse BMP header bmpID = read16(bmpFile); // BMP signature (void) read32(bmpFile); // Read & ignore file size (void) read32(bmpFile); // Read & ignore creator bytes bmpImageoffset = read32(bmpFile); // Start of image data (void) read32(bmpFile); // Read & ignore DIB header size bmpWidth = read32(bmpFile); bmpHeight = read32(bmpFile); n = read16(bmpFile); // # planes -- must be '1' bmpDepth = read16(bmpFile); // bits per pixel pos = read32(bmpFile); // format if (bmpID != 0x4D42) ret = 2; // bad ID else if (n != 1) ret = 3; // too many planes else if (pos != 0 && pos != 3) ret = 4; // format: 0 = uncompressed, 3 = 565 else if (bmpDepth < 16 && bmpDepth > PALETTEDEPTH) ret = 5; // palette else { bool first = true; is565 = (pos == 3); // ?already in 16-bit format // BMP rows are padded (if needed) to 4-byte boundary rowSize = (bmpWidth * bmpDepth / 8 + 3) & ~3; if (bmpHeight < 0) { // If negative, image is in top-down order. bmpHeight = -bmpHeight; flip = false; } w = bmpWidth; h = bmpHeight; if ((x + w) >= tft.width()) // Crop area to be loaded w = tft.width() - x; if ((y + h) >= tft.height()) // h = tft.height() - y; if (bmpDepth <= PALETTEDEPTH) { // these modes have separate palette //bmpFile.seek(BMPIMAGEOFFSET); //palette is always @ 54 bmpFile.seek(bmpImageoffset - (4<<bmpDepth)); //54 for regular, diff for colorsimportant bitmask = 0xFF; if (bmpDepth < 8) bitmask >>= bmpDepth; bitshift = 8 - bmpDepth; n = 1 << bmpDepth; lcdbufsiz -= n; palette = lcdbuffer + lcdbufsiz; for (col = 0; col < n; col++) { pos = read32(bmpFile); //map palette to 5-6-5 palette[col] = ((pos & 0x0000F8) >> 3) | ((pos & 0x00FC00) >> 5) | ((pos & 0xF80000) >> 8); } } // Set TFT address window to clipped image bounds tft.setAddrWindow(x, y, x + w - 1, y + h - 1); for (row = 0; row < h; row++) { // For each scanline... // Seek to start of scan line. It might seem labor- // intensive to be doing this on every line, but this // method covers a lot of gritty details like cropping // and scanline padding. Also, the seek only takes // place if the file position actually needs to change // (avoids a lot of cluster math in SD library). uint8_t r, g, b, *sdptr; int lcdidx, lcdleft; if (flip) // Bitmap is stored bottom-to-top order (normal BMP) pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize; else // Bitmap is stored top-to-bottom pos = bmpImageoffset + row * rowSize; if (bmpFile.position() != pos) { // Need seek? bmpFile.seek(pos); buffidx = sizeof(sdbuffer); // Force buffer reload } for (col = 0; col < w; ) { //pixels in row lcdleft = w - col; if (lcdleft > lcdbufsiz) lcdleft = lcdbufsiz; for (lcdidx = 0; lcdidx < lcdleft; lcdidx++) { // buffer at a time uint16_t color; // Time to read more pixel data? if (buffidx >= sizeof(sdbuffer)) { // Indeed bmpFile.read(sdbuffer, sizeof(sdbuffer)); buffidx = 0; // Set index to beginning r = 0; } switch (bmpDepth) { // Convert pixel from BMP to TFT format case 24: b = sdbuffer[buffidx++]; g = sdbuffer[buffidx++]; r = sdbuffer[buffidx++]; color = tft.color565(r, g, b); break; case 16: b = sdbuffer[buffidx++]; r = sdbuffer[buffidx++]; if (is565) color = (r << 8) | (b); else color = (r << 9) | ((b & 0xE0) << 1) | (b & 0x1F); break; case 1: case 4: case 8: if (r == 0) b = sdbuffer[buffidx++], r = 8; color = palette[(b >> bitshift) & bitmask]; r -= bmpDepth; b <<= bmpDepth; break; } lcdbuffer[lcdidx] = color; } tft.pushColors(lcdbuffer, lcdidx, first); first = false; col += lcdidx; } // end cols } // end rows tft.setAddrWindow(0, 0, tft.width() - 1, tft.height() - 1); //restore full screen ret = 0; // good render } bmpFile.close(); return (ret); } [Give Me The Code:] If you’re too lazy efficient and just want some code for an Arduino Mega 2560 that can display an image from an SD card (a 24 bit BMP called image.bmp), draw some buttons on the screen and toggle something as an example, you’re in luck. You need to install some libraries for this to work, MCUFRIEND_kbv, Adafruit_GFX, SdFat and Adafruit_Touchscreen. You might need to run the touch screen calibration sketch included with MCUFRIEND_kbv (TouchScreen_Calibr_native.ino) which will give you your calibration to just copy and paste into your code. Enough talk, give me the code! Righto! // This Example file is based on examples from MCUFRIEND_kbv, Adafruit_GFX, SdFat and Adafruit_Touchscreen and was updated by jcx on 2021-04-23 with <3 #include <Adafruit_GFX.h> #include <MCUFRIEND_kbv.h> MCUFRIEND_kbv tft; #include <TouchScreen.h> #include "SdFat.h" #define MINPRESSURE 200 #define MAXPRESSURE 1000 SdFat SD; // Pin numbers in templates must be constants. const uint8_t SOFT_MISO_PIN = 12; const uint8_t SOFT_MOSI_PIN = 11; const uint8_t SOFT_SCK_PIN = 13; // Chip select may be constant or RAM variable. const uint8_t SD_CS_PIN = 10; // SdFat software SPI template SoftSpiDriver<SOFT_MISO_PIN, SOFT_MOSI_PIN, SOFT_SCK_PIN> softSpi; // Speed argument is ignored for software SPI. #if ENABLE_DEDICATED_SPI #define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(0), &softSpi) #else // ENABLE_DEDICATED_SPI #define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SD_SCK_MHZ(0), &softSpi) #endif // ENABLE_DEDICATED_SPI SdFat32 sd; File32 file; // ALL Touch panels and wiring is DIFFERENT // copy-paste results from TouchScreen_Calibr_native.ino const int XP=8,XM=A2,YP=A3,YM=9; //240x320 ID=0x9341 const int TS_LEFT=913,TS_RT=118,TS_TOP=80,TS_BOT=896; TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300); Adafruit_GFX_Button on_btn, off_btn, meh_btn; int pixel_x, pixel_y; //Touch_getXY() updates global vars bool Touch_getXY(void) { TSPoint p = ts.getPoint(); pinMode(YP, OUTPUT); //restore shared pins pinMode(XM, OUTPUT); digitalWrite(YP, HIGH); //because TFT control pins digitalWrite(XM, HIGH); bool pressed = (p.z > MINPRESSURE && p.z < MAXPRESSURE); if (pressed) { pixel_x = map(p.x, TS_LEFT, TS_RT, 0, tft.width()); //.kbv makes sense to me pixel_y = map(p.y, TS_TOP, TS_BOT, 0, tft.height()); } return pressed; } #define BLACK 0x0000 #define BLUE 0x001F #define RED 0xF800 #define GREEN 0x07E0 #define CYAN 0x07FF #define MAGENTA 0xF81F #define YELLOW 0xFFE0 #define WHITE 0xFFFF void setup(void) { Serial.begin(9600); Serial.print(F("Initializing SD card...")); if (!SD.begin(SD_CONFIG)) { Serial.println(F("failed!")); return; } Serial.println(F("OK!")); uint16_t ID = tft.readID(); Serial.print("TFT ID = 0x"); Serial.println(ID, HEX); Serial.println("Calibrate for your Touch Panel"); if (ID == 0xD3D3) ID = 0x9486; // write-only shield tft.begin(ID); tft.setRotation(0); //PORTRAIT tft.fillScreen(BLACK); bmpDraw("image.bmp", 0,0); delay(4000); on_btn.initButton(&tft, 60, 200, 100, 40, WHITE, MAGENTA, BLACK, "ON", 2); off_btn.initButton(&tft, 180, 200, 100, 40, WHITE, MAGENTA, BLACK, "OFF", 2); //btn name, tft declaration, xpos, ypos, width, height, border colour, background color, text color meh_btn.initButton(&tft, 215, 25, 50, 50, WHITE, MAGENTA, BLACK, "MEH", 2); on_btn.drawButton(false); off_btn.drawButton(false); meh_btn.drawButton(false); // "PRESSED?" tft.fillRect(40, 80, 160, 80, RED); } /* * updating multiple buttons from a list * * anything more than two buttons gets repetitive * * you can place button addresses in separate lists * e.g. for separate menu screens */ // Array of button addresses to behave like a list Adafruit_GFX_Button *buttons[] = {&on_btn, &off_btn, &meh_btn, NULL}; /* update the state of a button and redraw as reqd * * main program can use isPressed(), justPressed() etc */ bool update_button(Adafruit_GFX_Button *b, bool down) { b->press(down && b->contains(pixel_x, pixel_y)); if (b->justReleased()) b->drawButton(false); if (b->justPressed()) b->drawButton(true); return down; } /* most screens have different sets of buttons * life is easier if you process whole list in one go */ bool update_button_list(Adafruit_GFX_Button **pb) { bool down = Touch_getXY(); for (int i = 0 ; pb[i] != NULL; i++) { update_button(pb[i], down); } return down; } /* compare the simplicity of update_button_list() */ void loop(void) { update_button_list(buttons); //use helper function if (on_btn.justPressed()) { tft.fillRect(40, 80, 160, 80, GREEN); } if (off_btn.justPressed()) { tft.fillRect(40, 80, 160, 80, RED); } } #define BUFFPIXEL 20 void bmpDraw(char *filename, int x, int y) { File bmpFile; int bmpWidth, bmpHeight; // W+H in pixels uint8_t bmpDepth; // Bit depth (currently must be 24) uint32_t bmpImageoffset; // Start of image data in file uint32_t rowSize; // Not always = bmpWidth; may have padding uint8_t sdbuffer[3*BUFFPIXEL]; // pixel in buffer (R+G+B per pixel) uint16_t lcdbuffer[BUFFPIXEL]; // pixel out buffer (16-bit per pixel) uint8_t buffidx = sizeof(sdbuffer); // Current position in sdbuffer boolean goodBmp = false; // Set to true on valid header parse boolean flip = true; // BMP is stored bottom-to-top int w, h, row, col; uint8_t r, g, b; uint32_t pos = 0, startTime = millis(); uint8_t lcdidx = 0; boolean first = true; if((x >= tft.width()) || (y >= tft.height())) return; Serial.println(); Serial.print(F("Loading image '")); Serial.print(filename); Serial.println('\''); // Open requested file on SD card bmpFile = SD.open(filename); if (bmpFile==NULL) { Serial.println(F("File not found")); return; } // Parse BMP header if(read16(bmpFile) == 0x4D42) { // BMP signature Serial.println(F("File size: ")); Serial.println(read32(bmpFile)); (void)read32(bmpFile); // Read & ignore creator bytes bmpImageoffset = read32(bmpFile); // Start of image data Serial.print(F("Image Offset: ")); Serial.println(bmpImageoffset, DEC); // Read DIB header Serial.print(F("Header size: ")); Serial.println(read32(bmpFile)); bmpWidth = read32(bmpFile); bmpHeight = read32(bmpFile); if(read16(bmpFile) == 1) { // # planes -- must be '1' bmpDepth = read16(bmpFile); // bits per pixel Serial.print(F("Bit Depth: ")); Serial.println(bmpDepth); if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed goodBmp = true; // Supported BMP format -- proceed! Serial.print(F("Image size: ")); Serial.print(bmpWidth); Serial.print('x'); Serial.println(bmpHeight); // BMP rows are padded (if needed) to 4-byte boundary rowSize = (bmpWidth * 3 + 3) & ~3; // If bmpHeight is negative, image is in top-down order. // This is not canon but has been observed in the wild. if(bmpHeight < 0) { bmpHeight = -bmpHeight; flip = false; } // Crop area to be loaded w = bmpWidth; h = bmpHeight; if((x+w-1) >= tft.width()) w = tft.width() - x; if((y+h-1) >= tft.height()) h = tft.height() - y; // Set TFT address window to clipped image bounds tft.setAddrWindow(x, y, x+w-1, y+h-1); for (row=0; row<h; row++) { // For each scanline... // Seek to start of scan line. It might seem labor- // intensive to be doing this on every line, but this // method covers a lot of gritty details like cropping // and scanline padding. Also, the seek only takes // place if the file position actually needs to change // (avoids a lot of cluster math in SD library). if(flip) // Bitmap is stored bottom-to-top order (normal BMP) pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize; else // Bitmap is stored top-to-bottom pos = bmpImageoffset + row * rowSize; if(bmpFile.position() != pos) { // Need seek? bmpFile.seek(pos); buffidx = sizeof(sdbuffer); // Force buffer reload } for (col=0; col<w; col++) { // For each column... // Time to read more pixel data? if (buffidx >= sizeof(sdbuffer)) { // Indeed // Push LCD buffer to the display first if(lcdidx > 0) { tft.pushColors(lcdbuffer, lcdidx, first); lcdidx = 0; first = false; } bmpFile.read(sdbuffer, sizeof(sdbuffer)); buffidx = 0; // Set index to beginning } // Convert pixel from BMP to TFT format b = sdbuffer[buffidx++]; g = sdbuffer[buffidx++]; r = sdbuffer[buffidx++]; lcdbuffer[lcdidx++] = tft.color565(r,g,b); } // end pixel } // end scanline // Write any remaining data to LCD if(lcdidx > 0) { tft.pushColors(lcdbuffer, lcdidx, first); } Serial.print(F("Loaded in ")); Serial.print(millis() - startTime); Serial.println(" ms"); } // end goodBmp } } bmpFile.close(); if(!goodBmp) Serial.println(F("BMP format not recognized.")); } // These read 16- and 32-bit types from the SD card file. // BMP data is stored little-endian, Arduino is little-endian too. // May need to reverse subscript order if porting elsewhere. uint16_t read16(File& f) { uint16_t result; ((uint8_t *)&result)[0] = f.read(); // LSB ((uint8_t *)&result)[1] = f.read(); // MSB return result; } uint32_t read32(File& f) { uint32_t result; ((uint8_t *)&result)[0] = f.read(); // LSB ((uint8_t *)&result)[1] = f.read(); ((uint8_t *)&result)[2] = f.read(); ((uint8_t *)&result)[3] = f.read(); // MSB return result; } Hope this was all useful, and saves someone else their afternoon and evening 🙂 <3