ILI9341 Screen Dump to SD Card on Teensy 3.6

Status
Not open for further replies.
Where can I find an example sketch to dump the displayed contents of the ILI9341 to an SD card?

Can the screen display contents be preserved while loading and executing the dump sketch or is the screen cleared upon loading the new sketch?

My idea is to be able to dump screen displays for later documenting through post processing some bitmap format, possibly .bmp using GIMP or some other linux graphics program. Ideas?

Maybe a reversal of the example program, ILI9341_t3-master/examples/spitftbitmap.ino is in order.
 
Last edited:
You may have to do it in the same sketch - it seems the display is reset to begin?
KurtE has a recent screen buffer version of this driver that fits in T_3.6 RAM. Screen writes go to RAM then to the display. If you found that perhaps you could just pull the RAM off to the SD card?
 
As the others mentioned, the different version of the ili9341 libraries (ili9341_t3 and mine ili9341_t3n) have the readPixel, which retrieves the current pixel location and returns the current 16 bit color. Or the readRect, where you give it a rectangle and an array to retrieve the colors in. This simply returns the colors in the rectangle one after another... With my Screen buffer code (modeled after ili9341_t3dma version of library), I don't think I exported a method yet to retrieve the frame buffer location, but it would not be hard to do so...

How you store this data on an SD card? If it were me I would probably look at the spitftbitmap example program that comes with some of these libraries, which reads in a bitmap and displays it and write a version that does the reverse...

If you don't need/want it to be an actual bitmap that can be displayed on other displays than you don't necessarily have to write it in the actual format of a bitmap... But at least that example would show you how to access an SD Card as well as display
 
ILI9341 Screen Dump to SD card BMP file on Teensy 3.6 Problems

I hope you can help with my project's problems..

Project: Dump ILI9341 TFT Display contents to a BMP format disk file on
a microSD card using the Teensy 3.6 on-board uSD socket.

Problem: How to properly extract the pixels from the TFT display using the ILI9341.h library functions and properly format, then write the RGB pixels to the file. Current problem is getting the pixel data properly formatted and written to the uSD BMP file. So far I have succeeded in writing good BMPFILEHEADER and BMPINFOHEADER records (14 + 40 bytes), failing at getting pixel data written. Overall file size is correct length for the TFT screen dump.

I have spent many hours to get to this point. My approach was to develop in stages:
-Log appropriate messages in the IDE System Monitor window using the Serial.print functions.
-Create rudimentary TFT display with push butons and a boarder frame line. Button reads not implemented yet, but methodology already tested and confirmed, thanks to examples provided.
-Extract and display the T3.6 serial number and MAC address (I now, that's not the correct name for it).
-Get the SD card (on board slot) file creation and file write routines implemented.
-Open an output file, display the actual file name on TFT.
-Start to build the BMP file.
-Format & Write the bitmap file and info headers (Big effort, but concquered it).
-Attempt to extract the TFT display pixels and format and write to the BMP SD card File.

Testing cycle was to create the BMP file on the uSD card then "sneakerdisk" the resulting file to the
Linux PC for display with a bitmap (BMP) viewer (Image Viewer and GIMP - equal results), then create appropriate HEX dumps for further analysis.

Evolution of verification of my assumptions:
-ARM Cortex M4 (ie the Teensy3.6) and the Intel 64 bit processor running Linux both have 16bit Little
Endian formats, a good thing (being the same, that is). At first this caused confusion when viewing the dumps
- reading across the dump right byte of word followed by left byte of word, next byte repeat the same actions, a dizzy experience, at best.
-Had problems with writing the bitmap header. It seems the uint32_t long int needs to start on a 32 bit boundary in the target buffer. Well, the 2 ID Bytes are followed by file size(in 32 bit) resulted in garbage
2 byte insertion before the 32 bit word. Not acceptable for the BMP format. I then tried a union of a structure and a byte array hoping that would cure it, but that did not work. I then finally succeeded by breaking down the 3 bit filesize word by shifting 8 bits at a time into the target buffer byte array position. The info header was easier, the union and structure approach worked. Stuff the structure elements, then copy from the byte array of the union to the target byte stream to be written. This resulted in a proper bitmap info header.

Where my real problem lies is in properly extracting the pixels from the TFT display using the ILI9341.h library functions and properly format, then write the RGB pixels to the file. Result is total garbage, a lot of zeroes with some garbage pixel data spread about in the file (you need to display the TFTDMP10.BMP file using your choice of BMP file viewer to see the garbage results).

Attached zip file contains
- This problem report file (Problem.txt)
- ScreenToSDbmp.ino - souce sketch
- TFTDMP10.BMP - Target output bitmap file
- TFTDMP10.BMP.log - Sysmon window messages from Serial.print()
- TFTDMP10.head.txt - Frnt end of hexdump (od -x TFTDMP10.BMP | head )
- TFTDMP10hexdump.txt - Hex dump of .BMP ( od -x > TFTDMP10.BMP )
- TFTDMP10_screensnapshot.jpg - photo of screen and the teensy3.6
- ScreenToSDbmp_compilerMessageLog - verbose mode --- File too big so it would not upload (5.2MB).

Development Platform:
Ubuntu 16.04 on Intel(R) Core(TM) i7-5600U CPU @ 2.60GHz
16GB Ram
3TB HD resources
Arduino IDE 1.8.1
Teensyduino 1.35
Target Hardware Teensy 3.6, ILI9341 TFT display

Any help will be greatly appreciated.

Thanks,
Steve.

Code:
/* ScreenToSDbmp
 *  Dump the ILI9341 TFT screen to an uSD .bmp file.
 *  Sideline features:
 *  - Fetch & display Teensy 3.6 Serial Number and MAC address.
 *  - Display pushbuttons
 *  - Display SD file name
 *  Author: Steven T. Stolen / K7CCR
 *  Version List:
 *  0.9.0 Initial creation
 *  
 *  Hardware:
 *  - Teensy 3.6 running at 180mHz
 *  - ILI9341 Color 320x240 TFT Touchscreen Display
 *  Software:
 *  - Arduino IDE v3.6 Linux64 version
 *  - TeensyDuino v1.35
 *  Media:
 *  - MicroSD 4GB FAT16, 8GB FAT32
 *  Wanted:
 *  - SD routines that support larger than 8.3 dos type filenames
 *  - Thumbdrive type interface to xfer screen dump to PC rather
 *  than "sneakerdisk: method. 
 *  - Implement push button graphics program application.
 *  Purpose:
 *  - Provide bitmap file for PC post processing, ie use for
 *  documenting the project, users manuals, etc.
 *  - Front-end package for further application developments, a
 *  "core" package
 *  Credits:
 *  -JoaoLopesF/SPFD5408 for _GFX_Button() usage giving me some working
 *  concepts for a foundation. See GitHub 
 *     SPFD5408/examples/spfd5408_calibrate/
 *     spfd5408_calibrate.ino vers 0.9.0
 * - FrankBoesing for the TeensyMAC library on Github.
 *   https://github.com/FrankBoesing/TeensyMAC
 *
 * What it does...
 * ILI9341 setup and displays:
 * - box frame
 * - 3 push buttons
 * - displays t3.6 serial nr & MAC Address
 * Serial monitor window:
 * - // - displays t3.6 serial nr & MAC Address
 * 3-Color LED
 * Rotate blinks - RED, GREEN, BLUE
 */

#include <SD.h>
#include <SD_t3.h>
#include <TeensyMAC.h>
#include "SPI.h"
#include "ILI9341_t3.h"
#include "font_NunitoBold.h"
#include "font_Arial.h"

#define DEBUG

#define DisplaySerial Serial

const boolean DO_DEBUG = true;
const uint32_t SERIAL_BAUD_RATE = 115200;

// TFT pins
const uint8_t TFT_DC = 9;
const uint8_t TFT_CS = 10;
const uint8_t MIC_PIN = 14;
const uint8_t BACKLIGHT_PIN = 23;

// LED Pins on 3-color LED
// Common Anode
// Selected these pins for default PWM
const uint8_t LED_RED = 34;
const uint8_t LED_GREEN = 35;
const uint8_t LED_BLUE = 36;
const char PROGRAM_NAME[] = "ScreenToSDbmp";
char SERIALNR[16];
char MACLINE[22];

// Dimensions
const uint16_t width  = ILI9341_TFTWIDTH;
const uint16_t height = ILI9341_TFTHEIGHT;
uint16_t x = 0;
uint16_t y = 0;

// Use hardware SPI (#13, #12, #11) and the above for CS/DC
ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC);

// Define the button Class objects (3 buttons)
#define BUTTONS 3
#define BUTTON_CLEAR 0
#define BUTTON_SHOW 1
#define BUTTON_RESET 2

Adafruit_GFX_Button buttons[BUTTONS];
uint16_t buttons_y = 0;

const int chipSelect = BUILTIN_SDCARD;  // for Teensy 3.6
File bmpFile;
 
void setup() {
  //Setup Serial Monitor with ArduinoIDE
  Serial.begin(115200);
  while (!Serial) ; //Wait for system monitor
  Serial.println(PROGRAM_NAME);

  // See if the card is present and can be initialized
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    return; //Byebye!
  }
  else Serial.println("uSD Card Initialized.");

  // Fetch & display T3.6 serial nr and MAC address
  sprintf(SERIALNR, "Serial: %lu\n",teensySerial());
  Serial.printf(SERIALNR);
  sprintf(MACLINE, "MAC: 0x%012llX\n", teensyMAC());
  Serial.printf(MACLINE);

  // Set up the TFT display
  tft.begin();
  tft.setRotation( 2 );
  tft.fillScreen(ILI9341_BLACK);
  tft.setFont(Nunito_9_Bold);

  // Blink the LED a few times, rotating through the colors
  initializeLED();  
  for (uint8_t i = 0; i < 2; i++) {
    rotateLED();
  }

  initializeButtons();  // Draw some buttons
  dumpScreenToSD();
}

void loop() {
  // put your main code here, to run repeatedly:
}

void initializeButtons() {
  uint16_t w = 70;
  uint16_t h = 20;
  uint8_t spacing_x = 5;
  uint8_t textsize = 1;

  x = 40;
  y = height -20;
  char buttonlabels[3][10] = { "DUMP ", "SHOW  ", "RESET " };
  uint16_t buttoncolors[15] = { ILI9341_RED, ILI9341_BLUE, 
                                ILI9341_GREEN };

  // button inits
  for (uint8_t b=0; b<3; b++) {
    buttons[b].initButton(&tft,
        x+b*(w+spacing_x), y,
        w, h, ILI9341_WHITE, buttoncolors[b], ILI9341_WHITE,
        buttonlabels[b], textsize);
  }
  
  // Save the y position to avoid draws ---- ????
  // I guess that since it is declared globally
  // that it keeps these actions static and not continuously
  // refreshed with redraws?
  buttons_y = y;

  delay(1000);

  // Lets draw the border rectangle
  tft.drawRect(0,0,width-1,height-1,ILI9341_MAGENTA);

  // Draw the serial nr
  tft.setCursor(4,4);
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(1);
  tft.println(SERIALNR);
  // Draw the MAC Address
  tft.setCursor(4,16);
  tft.println(MACLINE);
  // Draw the program name
  tft.setFont(Nunito_12_Bold);  // Note: Font size change from 9
  tft.setCursor(4,28);
  tft.print("Program: ");
  tft.println(PROGRAM_NAME);
  tft.setFont(Nunito_9_Bold);  // Note: Font size changed back
  
  // Lets draw some buttons
  for (uint8_t i=0; i<3; i++) {
    buttons[i].drawButton();
  }
}

void initializeLED(void) {
   // LED Setup
  // 3-color common anode LED
  // off when high, on when low
  pinMode(LED_RED, OUTPUT);
  pinMode(LED_GREEN, OUTPUT);
  pinMode(LED_BLUE, OUTPUT);

  // Put the LED in off state
  digitalWrite(LED_RED, HIGH);
  digitalWrite(LED_GREEN, HIGH);
  digitalWrite(LED_BLUE, HIGH);
 
}

void rotateLED(void) {
    // put your main code here, to run repeatedly:
  digitalWrite(LED_RED, LOW); // on
  delay(500);
  digitalWrite(LED_RED, HIGH); // off
  delay(500);
  
  digitalWrite(LED_GREEN, LOW);
  delay(500);
  digitalWrite(LED_GREEN, HIGH);
  delay(500);
  
  digitalWrite(LED_BLUE, LOW);  
  delay(500);
  digitalWrite(LED_BLUE, HIGH);
  delay(1000);
}

void dumpScreenToSD(void) {
/*  
 *   - Analyze TFT Display, collect size details
 *   - For DEBUG,
 *     - Format file ID and find next available name
 *   - Draw file name to display (relates screen dump
 *     to created file)
 *   - Open File
 *   - Format file records and write to SD file.
 *   - Don't forget to close the file.
 */
  //-------------Local Declarations
  char filename[] = "TFTDMP00.BMP";

  unsigned char bmpfileheader[14] = 
      {'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0};
//  uint8_t bmpinfoheader[40] =
//      {40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 24,0};

  uint8_t *linebuf = NULL;
  uint32_t filesize = 54 + 3 * width * height;
  uint16_t linesize;
  
  uint16_t colorPixel;
  uint8_t r, g, b;
  //uint8_t bmppad[3] = {0,0,0};
  uint8_t pads;

#define  BMP_SIGNATURE_WORD  0x4D42

//---------------xBITMAPFILEHEADER  
  struct xBITMAPFILEHEADER
  {
    uint16_t  bfType;       // signature - 'BM'
    uint32_t  bfSize;       // file size in bytes
    uint16_t  bfReserved1;  // 0
    uint16_t  bfReserved2;  // 0
    uint32_t  bfOffBits;    // offset to bitmap
  };
  
  union fh_data {
    struct xBITMAPFILEHEADER fh;
    uint8_t fhData[sizeof(xBITMAPFILEHEADER)];
  } fhFrame;

  union fhKludge {
    uint32_t bfSize;
    uint8_t kludge[4];
  } kludgeFrame;

//---------------xBITMAPINFOHEADER  
  struct xBITMAPINFOHEADER
  {
    uint32_t  biSize;           // size of this struct
    int32_t   biWidth;          // bmap width in pixels
    int32_t   biHeight;         // bmap height in pixels
    uint16_t  biPlanes;         // numplanes - always 1
    uint16_t  biBitCount;       // bits per pixel
    uint32_t  biCompression;    // compression flag
    uint32_t  biSizeImage;      // image size in bytes
    int32_t   biXPelsPerMeter;  // horz resolution
    int32_t   biYPelsPerMeter;  // vert resolution
    uint32_t  biClrUsed;        // 0 -> color table size
    uint32_t  biClrImportant;   // important color count
  };

  union ih_data {
    struct xBITMAPINFOHEADER ih;
    uint8_t ihData[sizeof(xBITMAPINFOHEADER)];
  } ihFrame;

  // Set up line buffer
  if (linebuf) free(linebuf);  //make sure linebuf not allocated yet
  linebuf = (uint8_t *)malloc(3 * width);
  linesize = 3 * width;
  memset(linebuf, 0, linesize);

  // Format File Name - determine if unique (so we can open)
  // Form: TFTDMP99.BMP then
  // Open File
  for (uint8_t i=0; i<100; i++) {
    filename[6] = i / 10 + '0';
    filename[7] = i % 10 + '0';
    if (!SD.exists(filename)) {
      // ony open a new file if it does not exist
      bmpFile = SD.open (filename, FILE_WRITE);
      break;
    }
  }

  Serial.println("just passed SD.open");
    
  if (! bmpFile) {
    Serial.print ("Could not create file: ");
    Serial.println (filename);
    return;
  }
  Serial.print ("Opened new file: ");
  Serial.println(filename);
  // Format and draw file name to display
  tft.setFont(Nunito_12_Bold);  // Note: Font size change from 9
  tft.setCursor(4,44);
  tft.print("SDfile: ");
  tft.println(filename);
  tft.setFont(Nunito_9_Bold);  // Note: Font size changed back

  // calculate padding
  pads = (4 - (width * 3) % 4) % 4;
  Serial.print("Linebuf padding #bytes/record: ");
  Serial.println(pads, DEC);

  //********FORMAT FILE HEADER AND WRITE TO SD***********

#ifdef DEFINE
  Serial.print("File size: ");
  Serial.println(filesize);
  Serial.print("Line size: ");
  Serial.println(linesize);
  Serial.print("Height: ");
  Serial.println(height);
  Serial.print("Width: ");
  Serial.println(width);     
#endif 

  //initialize and write the file header
  // Disfunctional structure stuffing - 32bit alignment issue.
  fhFrame.fh.bfType       = (uint16_t) BMP_SIGNATURE_WORD;
  fhFrame.fh.bfSize       = filesize;
  fhFrame.fh.bfReserved1  = (uint16_t) 0;
  fhFrame.fh.bfReserved2  = (uint16_t) 0;
  fhFrame.fh.bfOffBits    = (uint32_t) 54;
  // Only part used is the bfSize.

  /*  A kludge approach to stuffing the bfSize, 16bit word,
   *  into a byte array, bmpfileheader[2..3]. Note, this is 
   *  the only field in the xBITMAPFILEHEADER that is not of
   *  fixed value for this style of display bitmap. Major 
   *  problem with the above xBITMAPFILEHEADER is that the first 32 
   *  bit word in a structure must be on a 32 bit boundary. The 
   *  first element is a 16 bit word followed by a 32 bit word.
   *  The compiler packs 16 bits of garbage between the two words
   *  to insure that the first 32 bit word is on its 32 bit 
   *  boundary. The rest of the structure shifted by 16 bits because 
   *  of this effect. See size proof. Desired = 14 byte header.
   *  Actual is 16 byte structure.
   */
   
  //load up bfsize in kludge
//  for (uint8_t i=0; i<4; i++) {
//    kludgeFrame.bfSize = fhFrame.fh.bfSize;
//  }
  // load up bfSize into bmpfileheader[2..5] byte positions
//  for (uint8_t i=0; i<4; i++) {
//    bmpfileheader[i+2] = kludgeFrame.kludge[i];
//  }

  // Prep the file header for write to SD
//  for (uint8_t i=0; i<14; i++) {
//    //bitmap file header is 14 bytes  
//    linebuf[i] = bmpfileheader[i];
//  }



  //
  bmpfileheader[2] = (filesize);
  bmpfileheader[3] = (filesize>>8);
  bmpfileheader[4] = (filesize>>16);
  bmpfileheader[5] = (filesize>>24);
  bmpFile.write(bmpfileheader, sizeof(bmpfileheader));

#ifdef DEBUG   
  // now, display the bmpfileheader
  Serial.println("bmpfileheader good dump (14 bytes):");
  for (uint8_t i=0; i<14; i++) {
    Serial.print(bmpfileheader[i],HEX);
    Serial.print(" ");
  }
  Serial.println(" ");


  //Dump the bad fileheader to sys mon
  Serial.println(" ");
  Serial.println("The bad xBITMAPFILEHEADER: ");
  for (uint8_t i=0; i<14; i++) {
    linebuf[i] = fhFrame.fhData[i];
    Serial.print(linebuf[i], HEX);
    Serial.print(" ");
  }
  Serial.println(" ");

  uint8_t fileHeaderSize = sizeof(xBITMAPFILEHEADER);
  Serial.print("Size of xBITMAPFILEHEADER: ");
  Serial.println(fileHeaderSize);
  Serial.println(" ");
#endif

  // Here we put the write of the file header
  // in linebuf to SD
  ////bmpFile.write(linebuf, 14);
  memset(linebuf, 0, 16); // Zero the used portion of the linebuf
  
  //*******FORMAT INFO HEADER AND WRITE TO SD*******
  
  ihFrame.ih.biSize           = sizeof(xBITMAPINFOHEADER);
  ihFrame.ih.biWidth          = width;
  ihFrame.ih.biHeight         = height;
  ihFrame.ih.biPlanes         = (uint16_t) 1;
  ihFrame.ih.biBitCount       = (uint16_t) 24;
  ihFrame.ih.biCompression    = (uint32_t) 0;
  ihFrame.ih.biSizeImage      = width * height;
  ihFrame.ih.biXPelsPerMeter  = (uint32_t) 0;
  ihFrame.ih.biYPelsPerMeter  = (uint32_t) 0;
  ihFrame.ih.biClrUsed        = (uint32_t) 0;
  ihFrame.ih.biClrImportant   = (uint32_t) 0;
  
  // Copy the INFO Buffer to the linebuf
  for (uint8_t i=0; i<40; i++) {
    //bitmap info header is 40 bytes  
    linebuf[i] = ihFrame.ihData[i];
  }

  //here we put the write of the info header
  bmpFile.write(linebuf, 40);

#ifdef DEBUG   
  //Dump the infoheader to sys mon, clear the linebuf as we go.
  Serial.println("xBITMAPINFOHEADER: ");
  for (uint8_t i=0; i<40; i++) {
    Serial.print(linebuf[i], HEX);
    Serial.print(" ");
    if((i+1)%8==0) Serial.println(" "); //After 8 byte dump, 
                                    //start a new line.
  }

  Serial.println(" ");
  Serial.println("Done printing header dumps.");
#endif
  memset(linebuf,0,40);  //clean up buffer
  
  //************SCREEN DUMP TO SD FILE ***************
  // Pixel dump to file here
  delay(100);
  // Build BMP file records from display & write to file
  Serial.println(" ");
  Serial.println("Start Writing Screenlines");
  for(int16_t i = height-1; i >= 0; i--) {
    Serial.print(i, DEC);
    Serial.print(" ");
    if (i % 10 == 0) Serial.println("");
    
    // do stuff & write a line from bottom up
    for(uint16_t j = 0; j < linesize; j+=3) {
      //Fetch pixel colors & stuff BGR color order to linebuf
      colorPixel = tft.readPixel(j,i);
      tft.color565toRGB(colorPixel,r,g,b);
      linebuf[j  ] = b;
      linebuf[j+1] = g;
      linebuf[j+2] = r;
    }
    // write the linebuf
    bmpFile.write(linebuf, linesize);
  }
  
  // All records should be written now.
  // Close File
  bmpFile.close();
  // Housekeeping
  free(linebuf);
  Serial.println(" ");
  Serial.print("Closed File: ");
  Serial.println(filename);
}

See the attached files for file dumps, resulting BMP file, etc.

Thanks for your patience. I am new to forum entries so getting the source code and the full problem defined to show didn't work very well in the first cut.
 

Attachments

  • TFTDMP10.BMP
    225.1 KB · Views: 132
  • TFTDMP10_head.txt
    434 bytes · Views: 116
  • TFTDMP10hexdump.txt
    50.7 KB · Views: 118
  • ScreenToSDbmp_compilerMessage.txt
    186.6 KB · Views: 113
  • TFTDMP10.BMP.txt
    1.7 KB · Views: 109
Last edited:
It works - T3.6 dumping ILI9341 screen contents to .bmp file with the onboard SD

I finally succeeded.

The method I was trying to use was to walk the screen (bottom to top for BMP file format) walking each line with a readPixel() followed by color565toRGB(), and finally writing out the line to the SD card blue, green, then red (order is another BMP file format requirement) for each pixel. Somewhere in that fetch of the pixel from the tft display I was getting mostly zeros, then some parts of real data but not in any discernable format.

My solution was to try doing la readRect() with a height of 1 pixel and the colorPixel array of length of the line width of the display. The rest I already had in place needing minor tweaks to walk the vertical from the botom, capturing the rectangle (one row of pixel data), converting to RGB as before, then writing the file.

Here's the working code:
Code:
/* ScreenToSDbmp
 *  Dump the ILI9341 TFT screen to an uSD .bmp file.
 *  Sideline features:
 *  - Fetch & display Teensy 3.6 Serial Number and MAC address.
 *  - Display pushbuttons
 *  - Display SD file name
 *  Author: Steven T. Stolen / K7CCR
 *  Version List:
 *  0.9.0 Initial creation
 *  
 *  Hardware:
 *  - Teensy 3.6 running at 180mHz
 *  - ILI9341 Color 320x240 TFT Touchscreen Display
 *  Software:
 *  - Arduino IDE v3.6 Linux64 version
 *  - TeensyDuino v1.35
 *  Media:
 *  - MicroSD 4GB FAT16, 8GB FAT32
 *  Wanted:
 *  - SD routines that support larger than 8.3 dos type filenames
 *  - Thumbdrive type interface to xfer screen dump to PC rather
 *  than "sneakerdisk: method. 
 *  - Implement push button graphics program application.
 *  Purpose:
 *  - Provide bitmap file for PC post processing, ie use for
 *  documenting the project, users manuals, etc.
 *  - Front-end package for further application developments, a
 *  "core" package
 *  Credits:
 *  -JoaoLopesF/SPFD5408 for _GFX_Button() usage giving me some working
 *  concepts for a foundation. See GitHub 
 *     SPFD5408/examples/spfd5408_calibrate/
 *     spfd5408_calibrate.ino vers 0.9.0
 * - FrankBoesing for the TeensyMAC library on Github.
 *   https://github.com/FrankBoesing/TeensyMAC
 *
 * What it does...
 * ILI9341 setup and displays:
 * - box frame
 * - 3 push buttons
 * - displays t3.6 serial nr & MAC Address
 * Serial monitor window:
 * - // - displays t3.6 serial nr & MAC Address
 * 3-Color LED
 * Rotate blinks - RED, GREEN, BLUE
 */

#include <SD.h>
#include <SD_t3.h>
#include <TeensyMAC.h>
#include "SPI.h"
#include "ILI9341_t3.h"
#include "font_NunitoBold.h"
#include "font_Arial.h"

#define DEBUG

#define DisplaySerial Serial

const boolean DO_DEBUG = true;
const uint32_t SERIAL_BAUD_RATE = 115200;

// TFT pins
const uint8_t TFT_DC = 9;
const uint8_t TFT_CS = 10;
const uint8_t MIC_PIN = 14;
const uint8_t BACKLIGHT_PIN = 23;

uint16_t colorPixel;
uint8_t r, g, b;

// LED Pins on 3-color LED
// Common Anode
// Selected these pins for default PWM
const uint8_t LED_RED = 34;
const uint8_t LED_GREEN = 35;
const uint8_t LED_BLUE = 36;
const char PROGRAM_NAME[] = "ScreenToSDbmp";
char SERIALNR[16];
char MACLINE[22];

// Dimensions
const uint16_t width  = ILI9341_TFTWIDTH;
const uint16_t height = ILI9341_TFTHEIGHT;
uint16_t x = 0;
uint16_t y = 0;
const uint16_t linesize = 3 * width;
uint8_t linebuf[linesize];
const uint32_t filesize = 54 + 3 * width * height;
// note - 56 is to make the file size mod 4 (adds 2 bytes for pad)

// try pixel array for readRect()
uint16_t pixelColorArray[3 * width];

// Use hardware SPI (#13, #12, #11) and the above for CS/DC
ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC);

// Define the button Class objects (3 buttons)
#define BUTTONS 3
#define BUTTON_CLEAR 0
#define BUTTON_SHOW 1
#define BUTTON_RESET 2

Adafruit_GFX_Button buttons[BUTTONS];
uint16_t buttons_y = 0;

const int chipSelect = BUILTIN_SDCARD;  // for Teensy 3.6
File bmpFile;
 
void setup() {
  //Setup Serial Monitor with ArduinoIDE
  Serial.begin(115200);
  while (!Serial) ; //Wait for system monitor
  Serial.println(PROGRAM_NAME);

  // See if the card is present and can be initialized
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    return; //Byebye!
  }
  else Serial.println("uSD Card Initialized.");

  // Fetch & display T3.6 serial nr and MAC address
  sprintf(SERIALNR, "Serial: %lu\n",teensySerial());
  Serial.printf(SERIALNR);
  sprintf(MACLINE, "MAC: 0x%012llX\n", teensyMAC());
  Serial.printf(MACLINE);

  // Set up the TFT display
  tft.begin();
  tft.setRotation( 2 );
  tft.fillScreen(ILI9341_LIGHTGREY);
  tft.setFont(Nunito_9_Bold);

  // Blink the LED a few times, rotating through the colors
   initializeLED();  
   for (uint8_t i = 0; i < 2; i++) {
     rotateLED();
                                                                       }

  initializeButtons();
  
  // Lets draw the border rectangle
  tft.drawRect(0,0,width-1,height-1,ILI9341_MAGENTA);

  // Draw the serial nr
  tft.setCursor(4,4);
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(1);
  tft.println(SERIALNR);
  // Draw the MAC Address
  tft.setCursor(4,16);
  tft.println(MACLINE);
  // Draw the program name
  tft.setFont(Nunito_12_Bold);  // Note: Font size change from 9
  tft.setCursor(4,28);
  tft.print("Program: ");
  tft.println(PROGRAM_NAME);
  tft.setFont(Nunito_9_Bold);  // Note: Font size changed back
  
  // Draw the buttons
  for (uint8_t i=0; i<3; i++) {
    buttons[i].drawButton();
  }

  // Let's flood a rectangle
  Serial.print("Stuffing rectangle with ILI9341_ORANGE, 0xFD20: 0x");
  Serial.println(ILI9341_ORANGE,HEX);
  tft.fillRect(0,60,40,40,ILI9341_ORANGE);
  delay(100);

  for (int8_t i=0; i<10; i++) {
    y = 60;
    colorPixel = tft.readPixel(i,y);
    Serial.print("colorPixel Read back = 0x");
    Serial.println(colorPixel, HEX);
    delay(200);
  
    tft.color565toRGB(colorPixel,r,g,b);
  
    Serial.print("Red = 0x");
    Serial.print(r, HEX);
    Serial.print(" in decimal: ");
    Serial.println(r, DEC);
  
    Serial.print("Grn = 0x");
    Serial.print(g, HEX);
    Serial.print(" in decimal: ");
    Serial.println(g, DEC);
  
    Serial.print("Blu = 0x");
    Serial.print(b, HEX);
    Serial.print(" in decimal: ");
    Serial.println(b, DEC);
    Serial.println(" ");
  } 

  dumpScreenToSD();
}

void loop() {
  // put your main code here, to run repeatedly:
}

void initializeButtons() {
  uint16_t w = 70;
  uint16_t h = 20;
  uint8_t spacing_x = 5;
  uint8_t textsize = 1;

  x = 40;
  y = height -20;
  char buttonlabels[3][10] = { "DUMP ", "SHOW  ", "RESET " };
  uint16_t buttoncolors[15] = { ILI9341_RED, ILI9341_BLUE, 
                                ILI9341_GREEN };

  // button inits
  for (uint8_t b=0; b<3; b++) {
    buttons[b].initButton(&tft,
        x+b*(w+spacing_x), y,
        w, h, ILI9341_WHITE, buttoncolors[b], ILI9341_WHITE,
        buttonlabels[b], textsize);
  }
  
  // Save the y position to avoid draws ---- ????
  // I guess that since it is declared globally
  // that it keeps these actions static and not continuously
  // refreshed with redraws?
  buttons_y = y;

  delay(1000);
}

void initializeLED(void) {
   // LED Setup
  // 3-color common anode LED
  // off when high, on when low
  pinMode(LED_RED, OUTPUT);
  pinMode(LED_GREEN, OUTPUT);
  pinMode(LED_BLUE, OUTPUT);

  // Put the LED in off state
  digitalWrite(LED_RED, HIGH);
  digitalWrite(LED_GREEN, HIGH);
  digitalWrite(LED_BLUE, HIGH);
 
}

void rotateLED(void) {
    // put your main code here, to run repeatedly:
  digitalWrite(LED_RED, LOW); // on
  delay(500);
  digitalWrite(LED_RED, HIGH); // off
  delay(500);
  
  digitalWrite(LED_GREEN, LOW);
  delay(500);
  digitalWrite(LED_GREEN, HIGH);
  delay(500);
  
  digitalWrite(LED_BLUE, LOW);  
  delay(500);
  digitalWrite(LED_BLUE, HIGH);
  delay(1000);
}

void dumpScreenToSD(void) {
/*  
 *   - Analyze TFT Display, collect size details
 *   - For DEBUG,
 *     - Format file ID and find next available name
 *   - Draw file name to display (relates screen dump
 *     to created file)
 *   - Open File
 *   - Format file records and write to SD file.
 *   - Don't forget to close the file.
 */
  //-------------Local Declarations
  char filename[] = "DMPTFT00.BMP";

  unsigned char bmpfileheader[14] = 
      {'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0};
//  uint8_t bmpinfoheader[40] =
//      {40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 24,0};

  
#ifdef DEBUG
  uint16_t linebufcount = 0; //count of linebuf records written
#endif

#define  BMP_SIGNATURE_WORD  0x4D42

//-----xBITMAPFILEHEADER  
// kept for reference
//  struct xBITMAPFILEHEADER
//  {
//    uint16_t  bfType;       // signature - 'BM'
//    uint32_t  bfSize;       // file size in bytes
//    uint16_t  bfReserved1;  // 0
//    uint16_t  bfReserved2;  // 0
//    uint32_t  bfOffBits;    // offset to bitmap
//  };
  
//  union fh_data {
//    struct xBITMAPFILEHEADER fh;
//    uint8_t fhData[sizeof(xBITMAPFILEHEADER)];
//  } fhFrame;

//  union fhKludge {
//    uint32_t bfSize;
//    uint8_t kludge[4];
//  } kludgeFrame;
//-----xBITMAPFILEHEADER--end of reference

//---------------xBITMAPINFOHEADER  
  struct xBITMAPINFOHEADER
  {
    uint32_t  biSize;           // size of this struct
    int32_t   biWidth;          // bmap width in pixels
    int32_t   biHeight;         // bmap height in pixels
    uint16_t  biPlanes;         // numplanes - always 1
    uint16_t  biBitCount;       // bits per pixel
    uint32_t  biCompression;    // compression flag
    uint32_t  biSizeImage;      // image size in bytes
    int32_t   biXPelsPerMeter;  // horz resolution
    int32_t   biYPelsPerMeter;  // vert resolution
    uint32_t  biClrUsed;        // 0 -> color table size
    uint32_t  biClrImportant;   // important color count
  };

  union ih_data {
    struct xBITMAPINFOHEADER ih;
    uint8_t ihData[sizeof(xBITMAPINFOHEADER)];
  } ihFrame;

  // Clear the line buffer, linebuf is a fixed array length.
  memset(linebuf, 0, linesize);

  // Format File Name - determine if unique (so we can open)
  // Form: TFTDMP99.BMP then
  // Open File
  for (uint8_t i=0; i<100; i++) {
    filename[6] = i / 10 + '0';
    filename[7] = i % 10 + '0';
    if (!SD.exists(filename)) {
      // ony open a new file if it does not exist
       bmpFile = SD.open (filename, FILE_WRITE);
      break;
    }
  }

  Serial.println("just passed SD.open");
    
  if (! bmpFile) {
    Serial.print ("Could not create file: ");
    Serial.println (filename);
     return;
  }
  Serial.print ("Opened new file: ");
  Serial.println(filename);
  // Format and draw file name to display
  tft.setFont(Nunito_12_Bold);  // Note: Font size change from 9
  tft.setCursor(4,44);
  tft.print("SDfile: ");
  tft.println(filename);
  tft.setFont(Nunito_9_Bold);  // Note: Font size changed back

  //********FORMAT FILE HEADER AND WRITE TO SD***********

#ifdef DEFINE
  Serial.print("File size: ");
  Serial.println(filesize);
  Serial.print("Line size: ");
  Serial.println(linesize);
  Serial.print("Height: ");
  Serial.println(height);
  Serial.print("Width: ");
  Serial.println(width);     
#endif 
  //
  bmpfileheader[2] = (filesize);
  bmpfileheader[3] = (filesize>>8);
  bmpfileheader[4] = (filesize>>16);
  bmpfileheader[5] = (filesize>>24);
   bmpFile.write(bmpfileheader, sizeof(bmpfileheader));

#ifdef DEBUG   
  // now, display the bmpfileheader
  Serial.println("bmpfileheader (14 bytes):");
  for (uint8_t i=0; i<14; i++) {
    Serial.print(bmpfileheader[i],HEX);
    Serial.print(" ");
  }
  Serial.println(" ");
#endif

  // Here we put the write of the file header
  // in linebuf to SD
  // bmpFile.write(linebuf, 14);
  //memset(linebuf, 0, 16); // Zero the used portion of the linebuf
  
  //*******FORMAT INFO HEADER AND WRITE TO SD*******
  
  ihFrame.ih.biSize           = sizeof(xBITMAPINFOHEADER);
  ihFrame.ih.biWidth          = width;
  ihFrame.ih.biHeight         = height;
  ihFrame.ih.biPlanes         = (uint16_t) 1;
  ihFrame.ih.biBitCount       = (uint16_t) 24;
  ihFrame.ih.biCompression    = (uint32_t) 0;
  ihFrame.ih.biSizeImage      = width * height;
  ihFrame.ih.biXPelsPerMeter  = (uint32_t) 0;
  ihFrame.ih.biYPelsPerMeter  = (uint32_t) 0;
  ihFrame.ih.biClrUsed        = (uint32_t) 0;
  ihFrame.ih.biClrImportant   = (uint32_t) 0;
  
  // Copy the INFO Buffer to the linebuf
  for (uint8_t i=0; i<40; i++) {
    //bitmap info header is 40 bytes  
    linebuf[i] = ihFrame.ihData[i];
  }

  //here we put the write of the info header
   bmpFile.write(linebuf, 40);

#ifdef DEBUG   
  //Dump the infoheader to sys mon
  Serial.println("xBITMAPINFOHEADER: ");
  for (uint8_t i=0; i<40; i++) {
    Serial.print(linebuf[i], HEX);
    Serial.print(" ");
    if((i+1)%8==0) Serial.println(" "); //After 8 byte dump, 
                                        //start a new line.
  }

  Serial.println(" ");
  Serial.println("Done printing header dumps.");
#endif
 // memset(linebuf,0,40);  //clean up buffer
  
  //************SCREEN DUMP TO SD FILE ***************
  // Pixel dump to file here
  delay(100);
  // Build BMP file records from display & write to file
  for(int16_t i = height-1; i >= 0; i--) {
    // Here we write the pixels to the SD. 
    // We fetch the pixel, trnslate to rgb, stuff the
    // line buf with the colors swapped to b,g,r order
    // as we walk the linebuf.

    // fetch whole line - rectangle height 1 pixel
    tft.readRect(0, i, width, 1, pixelColorArray); 
    
    // do stuff & write a line from bottom of screen up to top
    // as per normal BMP order
    for(int16_t j = 0; j < width; j++) {
      //Fetch pixel colors & stuff BGR color order to linebuf
      
      tft.color565toRGB(pixelColorArray[j],r,g,b);
      linebuf[j*3]      = b;
      linebuf[j*3 + 1]  = g;
      linebuf[j*3 + 2]  = r;
      
#ifdef DEBUG
      if(j==0 && i==60) {
        Serial.print("colorPixel at 0,60 = 0x");
        Serial.println(pixelColorArray[j], HEX);
        //delay(200);
     
        Serial.print("Red = 0x");
        Serial.print(r, HEX);
        Serial.print(" in decimal: ");
        Serial.println(r, DEC);
  
        Serial.print("Grn = 0x");
        Serial.print(g, HEX);
        Serial.print(" in decimal: ");
        Serial.println(g, DEC);
  
        Serial.print("Blu = 0x");
        Serial.print(b, HEX);
        Serial.print(" in decimal: ");
        Serial.println(b, DEC);
        Serial.println(" ");
      }
#endif
    }
    // write the linebuf
     bmpFile.write(linebuf, linesize);
#ifdef DEBUG
    // do a partial dump of the first linebuf write to Serial
    //linebufcount++; // bump the buffer line count
    if ((i > 55) && (i < 66)) { // line nr range
      Serial.print("Partial pixel dump of line ");
      Serial.print(i, DEC);
      Serial.println(" written:");
      for (uint8_t nix = 0; nix<15; nix++) { //first 5 pixels
        Serial.print(linebuf[nix], HEX);
        Serial.print(" ");
      }
      Serial.println(" ");
    }
#endif
  }
  
  // All records should be written now.
  // Close File
   bmpFile.close();
  // Housekeeping
  Serial.println(" ");
  Serial.print("Closed File: ");
  Serial.println(filename);
}

I also have attached the resulting .BMP file as well as a serial monitor log file of the run (results of all the Serial.print/println calls). My hopes are that someone knowledgeable with the innards of the ILI9341.ccp / .h library files may shed some insight on my previous post as to wy my original attempt did not succeed.

Loads of frustration, midnight oil, coffee, and finally success. Wish there was some good documentation on the ILI9341 library.
 

Attachments

  • DMPTFT05.bmp
    225.1 KB · Views: 130
  • DMPTFT05BMPlog.txt
    2.4 KB · Views: 154
Status
Not open for further replies.
Back
Top