// MCUFRIEND UNO shields have microSD on pins 10, 11, 12, 13 // The official 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 v1.0.1 of the library with the Arduino Library Manager. // edit the src/SdFatConfig.h file to #define ENABLE_SOFTWARE_SPI_CLASS 1 // // copy all your BMP files to the root directory on the microSD with your PC // (or another directory) #include // f.k. for Arduino-1.5.2 #define USE_SDFAT #include // Use the SdFat library SdFatSoftSpi<12, 11, 13> SD; //Bit-Bang on the Shield pins #include // Hardware-specific library #include MCUFRIEND_kbv tft; #define SD_CS 10 #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_CS); if (!good) { Serial.print(F("cannot start SD")); while (1); } root = SD.open(namebuf); pathlen = strlen(namebuf); } void loop() { tft.setRotation(1); //Added in to use horizontal rotation char *nm = namebuf + pathlen; File f = root.openNextFile(); uint8_t ret; uint32_t start; if (f != NULL) { #ifdef USE_SDFAT f.getName(nm, 32 - pathlen); #else strcpy(nm, (char *)f.name()); #endif 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 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); }