Site icon AranaCorp

Usando o Shield TFT e o seu módulo de cartão SD

O Shield TFT é normalmente fornecido com um módulo de cartão SD para armazenamento de dados ou imagens. LCDs com ecrã táctil para exibir imagens e criar interfaces gráficas de utilizador. Neste tutorial utilizamos o escudo Kuman TFT 2.8″ (muito semelhante ao escudo 3.5″) e veremos como interagir com o cartão microSD.

N.B.: Mesmo que o escudo seja compatível com a placa Arduino Mega, o módulo SD não pode ser utilizado directamente.

Material

Esquema

O escudo é colocado directamente sobre uma prancha Arduino UNO ou Mega prancha. O escudo utiliza quase todos os pinos do Arduino UNO. Certifique-se de que não utiliza os mesmos para outros módulos. O módulo de cartão SD do escudo TFT utiliza o bus SPI e selecciona o pino 10.

Código

Para utilizar o objecto do escudo TFT e, em particular, a comunicação com o cartão SD, utilizamos as bibliotecas SD.h. Neste exemplo, iremos recuperar a lista de ficheiros contidos no cartão SD e exibi-la no ecrã LCD

//Libraries
#include <SD.h>//https:
#include <Adafruit_GFX.h>//https://github.com/adafruit/Adafruit-GFX-Library
#include <MCUFRIEND_kbv.h>//https://github.com/prenticedavid/MCUFRIEND_kbv
#include <TouchScreen.h> //https://github.com/adafruit/Adafruit_TouchScreen
//Constants
#define SD_CS 10
#define BLACK 0
#define GREY 21845
#define BLUE 31
#define RED 63488
#define GREEN 2016
#define DARKGREEN 1472
#define CYAN 2047
#define MAGENTA 63519
#define YELLOW 65504
#define GOLD 56768
#define WHITE 65535
//Touch screen configuration
#define MINPRESSURE 200
#define MAXPRESSURE 1000
// ALL Touch panels and wiring is DIFFERENT
// copy-paste results from TouchScreen_Calibr_native.ino
//3.5 Parameters
//const int XP = 8, XM = A2, YP = A3, YM = 9; //320x480 ID=0x9486
//const int TS_LEFT = 144, TS_RT = 887, TS_TOP = 936, TS_BOT = 87;
//2.8 Parameters
const int XP = 8, XM = A2, YP = A3, YM = 9; //240x320 ID=0x9341
const int TS_LEFT = 907, TS_RT = 120, TS_TOP = 74, TS_BOT = 913;
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);
TSPoint p;
bool down;
int pixel_x, pixel_y;     //Touch_getXY() updates global vars
//Variables
int currentPage  = 0, oldPage = -1;
int nbFiles = 0;
String fileList[10];
//Objects
MCUFRIEND_kbv tft;
// Button calibration
Adafruit_GFX_Button page1_btn, page2_btn;
int margin = 5;
int btnWidth = 100;
int btnHeight = 40;
int btnY = 200;
void setup() {
 //Init Serial USB
 Serial.begin(9600);
 Serial.println(F("Initialize System"));
 //Init tft screen
 uint16_t ID = tft.readID();
 if (ID == 0xD3D3) ID = 0x9486;  //for 3.5" TFT LCD Shield , 0x9341 for 2.8" TFT LCD Shield
 tft.begin(ID);
 tft.setRotation(1);//0-PORTRAIT 1-PAYSAGE 2-REVERSE PORTRAIT 3-REVERSE PAYSAGE
 //Uncomment if you are using SD
 if (!SD.begin(SD_CS)) {
   Serial.println(F("initialization failed!"));
   return;
 }
 currentPage = 0; // Indicates that we are at Home Screen
}
void loop() {
 down = Touch_getXY();
 switch (currentPage) {
   case 0:
     if (currentPage != oldPage) {
       Serial.println(F("Draw list"));
       drawList();
     }
     if (down) {
       currentPage = 1;
     }
     break;
   case 1:
     if (currentPage != oldPage) {
       tft.fillScreen(BLACK);
       delay(200);
       oldPage = currentPage;
     }
     currentPage = 0;
     break;
 }
}
void drawList() { /* function drawHomePage */
 getFilenames();
 tft.setRotation(1);
 tft.fillScreen(BLACK);
 //Title
 tft.setCursor(0, 10);
 tft.setTextSize(3);
 tft.setTextColor(WHITE, BLACK);
 tft.print("File list"); // Prints the string on the screen
 tft.drawLine(0, 32, 319, 32, DARKGREEN); // Draws the red line
 //text
 tft.setTextSize(2);
 tft.setTextColor(RED, BLACK);
 for (int i = 0; i < nbFiles; i++) {
   tft.setCursor(10, 40 + 20 * i);
   tft.print(fileList[i]);
 }
 oldPage = currentPage;
}
/************************************************************************************
    UTILITY FUNCTION
*************************************************************************************/
bool Touch_getXY(void)
{
 p = ts.getPoint();
 pinMode(YP, OUTPUT);      //restore shared pins
 pinMode(XM, OUTPUT);
 digitalWrite(YP, HIGH);
 digitalWrite(XM, HIGH);
 bool pressed = (p.z > MINPRESSURE && p.z < MAXPRESSURE);
 if (pressed) {
   if (tft.width() <= tft.height()) { //Portrait
     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());
   } else {
     pixel_x = map(p.y, TS_TOP, TS_BOT, 0, tft.width());
     pixel_y = map(p.x, TS_RT, TS_LEFT, 0, tft.height());
   }
 }
 return pressed;
}
File root;
void getFilenames(void ) { /* function printFilenames */
 ////find files in SD card
 root = SD.open("/");
 int i = 0;
 while (true) {
   File entry =  root.openNextFile();
   if (! entry) {
     break;// no more files
   }
   fileList[i] = entry.name();
   i++;
   Serial.println(entry.name());
   entry.close();
 }
 nbFiles = i;
}

Bónus: Mostrar imagens BMP do cartão SD

O principal objectivo do módulo MicroSD no escudo TFT é armazenar imagens para exibição no ecrã. Se não tiver uma imagem bitmap à mão, pode descarregar a que utilizamos neste exemplo.

Para exibir uma imagem bitmap, precisamos de utilizar uma função especial que pode ser reutilizada no seu código tal como está.

#define BUFFPIXEL 20           //Drawing speed, 20 is meant to be the best but you can use 60 altough it takes a lot of uno's RAM         
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();
 progmemPrint(PSTR("Loading image '"));
 Serial.print(filename);
 Serial.println('\'');
 // Open requested file on SD card
 if ((bmpFile = SD.open(filename)) == NULL) {
   progmemPrintln(PSTR("File not found"));
   return;
 }
 // Parse BMP header
 if(read16(bmpFile) == 0x4D42) { // BMP signature
   progmemPrint(PSTR("File size: ")); Serial.println(read32(bmpFile));
   (void)read32(bmpFile); // Read & ignore creator bytes
   bmpImageoffset = read32(bmpFile); // Start of image data
   progmemPrint(PSTR("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
   // Read DIB header
   progmemPrint(PSTR("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
     progmemPrint(PSTR("Bit Depth: ")); Serial.println(bmpDepth);
     if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed
       goodBmp = true; // Supported BMP format -- proceed!
       progmemPrint(PSTR("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);
       } 
       progmemPrint(PSTR("Loaded in "));
       Serial.print(millis() - startTime);
       Serial.println(" ms");
     } // end goodBmp
   }
 }
 bmpFile.close();
 if(!goodBmp) progmemPrintln(PSTR("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;
}
// Copy string from flash to serial port
// Source string MUST be inside a PSTR() declaration!
void progmemPrint(const char *str) {
 char c;
 while(c = pgm_read_byte(str++)) Serial.print(c);
}
// Same as above, with trailing newline
void progmemPrintln(const char *str) {
 progmemPrint(str);
 Serial.println();
}

Criaremos um botão para cada ficheiro e, utilizando a função anterior, mostraremos as imagens contidas no cartão SD quando premirmos o botão correspondente.

//Libraries
#include <SD.h>//https:
#include <Adafruit_GFX.h>//https://github.com/adafruit/Adafruit-GFX-Library
#include <MCUFRIEND_kbv.h>//https://github.com/prenticedavid/MCUFRIEND_kbv
#include <TouchScreen.h> //https://github.com/adafruit/Adafruit_TouchScreen
//Constants
#define SD_CS 10
#define BLACK 0
#define GREY 21845
#define BLUE 31
#define RED 63488
#define GREEN 2016
#define DARKGREEN 1472
#define CYAN 2047
#define MAGENTA 63519
#define YELLOW 65504
#define GOLD 56768
#define WHITE 65535
//Touch screen configuration
#define MINPRESSURE 200
#define MAXPRESSURE 1000
// ALL Touch panels and wiring is DIFFERENT
// copy-paste results from TouchScreen_Calibr_native.ino
//3.5 Parameters
//const int XP = 8, XM = A2, YP = A3, YM = 9; //320x480 ID=0x9486
//const int TS_LEFT = 144, TS_RT = 887, TS_TOP = 936, TS_BOT = 87;
//2.8 Parameters
const int XP = 8, XM = A2, YP = A3, YM = 9; //240x320 ID=0x9341
const int TS_LEFT = 907, TS_RT = 120, TS_TOP = 74, TS_BOT = 913;
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);
TSPoint p;
bool down;
int pixel_x, pixel_y;     //Touch_getXY() updates global vars
//Variables
int currentPage  = 0, oldPage = -1;
int nbFiles = 0;
String fileList[10];
const int charBuff=50;
char charCopy[50];
//Objects
MCUFRIEND_kbv tft;
// Button calibration
Adafruit_GFX_Button fileBtn[10];
int margin = 5;
int btnWidth = 100;
int btnHeight = 40;
int btnY = 200;
void setup() {
 //Init Serial USB
 Serial.begin(9600);
 Serial.println(F("Initialize System"));
 //Init tft screen
 uint16_t ID = tft.readID();
 if (ID == 0xD3D3) ID = 0x9486;  //for 3.5" TFT LCD Shield , 0x9341 for 2.8" TFT LCD Shield
 tft.begin(ID);
 tft.setRotation(1);//0-PORTRAIT 1-PAYSAGE 2-REVERSE PORTRAIT 3-REVERSE PAYSAGE
 //Uncomment if you are using SD
 if (!SD.begin(SD_CS)) {
   Serial.println(F("initialization failed!"));
   return;
 }
 currentPage = 0; // Indicates that we are at Home Screen
}
void loop() {
 down = Touch_getXY();
 switch (currentPage) {
   case 0:
     if (currentPage != oldPage) {
       Serial.println(F("Draw list"));
       drawList();
     }
     oldPage = currentPage;
     for(int i=1;i<nbFiles;i++){
       fileBtn[i].press(down && fileBtn[i].contains(pixel_x, pixel_y));
       if (fileBtn[i].justReleased()) fileBtn[i].drawButton();
       if (fileBtn[i].justPressed()) {
         fileBtn[i].drawButton(true);
         fileList[i].toCharArray(charCopy,charBuff);
         bmpDraw(charCopy, 0, 0);
         currentPage = 1;
       }
     }
     
     
     break;
   case 1:
     oldPage=currentPage;
     if (down) {
       currentPage = 0;
     }
     break;
 }
}
void drawList() { /* function drawHomePage */
 getFilenames();
 tft.setRotation(1);
 tft.fillScreen(BLACK);
 //Title
 tft.setCursor(0, 10);
 tft.setTextSize(3);
 tft.setTextColor(WHITE, BLACK);
 tft.print("File list"); // Prints the string on the screen
 tft.drawLine(0, 32, 319, 32, DARKGREEN); // Draws the red line
 //text
 tft.setTextSize(2);
 tft.setTextColor(RED, BLACK);
 for (int i = 1; i < nbFiles; i++) {
   //tft.setCursor(10, 40 + 20 * i);
   //tft.print(fileList[i]);
   // Button
   fileList[i].toCharArray(charCopy,charBuff);
   fileBtn[i].initButton(&tft,  tft.width() / 2. , 45+ i*(20+5), 2 * 100, 20, WHITE, GREEN, BLACK, charCopy, 2);
   fileBtn[i].drawButton(false);
 }
}
/************************************************************************************
    UTILITY FUNCTION
*************************************************************************************/
bool Touch_getXY(void)
{
 p = ts.getPoint();
 pinMode(YP, OUTPUT);      //restore shared pins
 pinMode(XM, OUTPUT);
 digitalWrite(YP, HIGH);
 digitalWrite(XM, HIGH);
 bool pressed = (p.z > MINPRESSURE && p.z < MAXPRESSURE);
 if (pressed) {
   if (tft.width() <= tft.height()) { //Portrait
     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());
   } else {
     pixel_x = map(p.y, TS_TOP, TS_BOT, 0, tft.width());
     pixel_y = map(p.x, TS_RT, TS_LEFT, 0, tft.height());
   }
 }
 return pressed;
}
File root;
void getFilenames(void ) { /* function printFilenames */
 ////find files in SD card
 root = SD.open("/");
 int i = 0;
 while (true) {
   File entry =  root.openNextFile();
   if (! entry) {
     break;// no more files
   }
   fileList[i] = entry.name();
   i++;
   Serial.println(entry.name());
   entry.close();
 }
 nbFiles = i;
 root.close();
}
#define BUFFPIXEL 20           //Drawing speed, 20 is meant to be the best but you can use 60 altough it takes a lot of uno's RAM         
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();
 progmemPrint(PSTR("Loading image '"));
 Serial.print(filename);
 Serial.println('\'');
 // Open requested file on SD card
 if ((bmpFile = SD.open(filename)) == NULL) {
   progmemPrintln(PSTR("File not found"));
   return;
 }
 // Parse BMP header
 if(read16(bmpFile) == 0x4D42) { // BMP signature
   progmemPrint(PSTR("File size: ")); Serial.println(read32(bmpFile));
   (void)read32(bmpFile); // Read & ignore creator bytes
   bmpImageoffset = read32(bmpFile); // Start of image data
   progmemPrint(PSTR("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
   // Read DIB header
   progmemPrint(PSTR("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
     progmemPrint(PSTR("Bit Depth: ")); Serial.println(bmpDepth);
     if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed
       goodBmp = true; // Supported BMP format -- proceed!
       progmemPrint(PSTR("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);
       } 
       progmemPrint(PSTR("Loaded in "));
       Serial.print(millis() - startTime);
       Serial.println(" ms");
     } // end goodBmp
   }
 }
 bmpFile.close();
 if(!goodBmp) progmemPrintln(PSTR("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;
}
// Copy string from flash to serial port
// Source string MUST be inside a PSTR() declaration!
void progmemPrint(const char *str) {
 char c;
 while(c = pgm_read_byte(str++)) Serial.print(c);
}
// Same as above, with trailing newline
void progmemPrintln(const char *str) {
 progmemPrint(str);
 Serial.println();
}

Uma vez carregado o código, aparecerá um menu com um botão para cada ficheiro no cartão SD. Se premir um botão, a imagem bitmap correspondente será exibida no ecrã. Se premir novamente o ecrã, regressará ao menu principal.

Aplicações

Fontes

Exit mobile version