π

Elegoo Display and Stolen Code

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

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

Solve to post *

jcx.life