From 1cf2305e4d6d20292796ce43fb9ccc7531bd4f5e Mon Sep 17 00:00:00 2001 From: Bodmer Date: Fri, 23 Nov 2018 03:09:07 +0000 Subject: [PATCH] Add capability to read TFT SDA bi-directional pin Tested on an ESP32 with ST7789V display SDA read is very slow on an ESP8266 (to be investigated) Added new setup #define TFT_SDA_READ Add new ESP32 and ESP8266 compatible in example/Generic folder to test capture of TFT screen --- Extensions/Touch.cpp | 2 +- TFT_eSPI.cpp | 164 +++++- TFT_eSPI.h | 14 +- User_Setup.h | 5 + User_Setups/Setup18_ST7789.h | 6 + .../TFT_Screen_Capture/TFT_Screen_Capture.ino | 207 +++++++ .../TFT_Screen_Capture/processing_sketch.ino | 535 ++++++++++++++++++ .../TFT_Screen_Capture/screenServer.ino | 196 +++++++ 8 files changed, 1117 insertions(+), 12 deletions(-) create mode 100644 examples/Generic/TFT_Screen_Capture/TFT_Screen_Capture.ino create mode 100644 examples/Generic/TFT_Screen_Capture/processing_sketch.ino create mode 100644 examples/Generic/TFT_Screen_Capture/screenServer.ino diff --git a/Extensions/Touch.cpp b/Extensions/Touch.cpp index 87fa182..1b0192d 100644 --- a/Extensions/Touch.cpp +++ b/Extensions/Touch.cpp @@ -12,7 +12,7 @@ /*************************************************************************************** ** Function name: getTouchRaw -** Description: read raw touch position. Return false if not pressed. +** Description: read raw touch position. Always returns true. ***************************************************************************************/ uint8_t TFT_eSPI::getTouchRaw(uint16_t *x, uint16_t *y){ uint16_t tmp; diff --git a/TFT_eSPI.cpp b/TFT_eSPI.cpp index 45e8219..86029f9 100644 --- a/TFT_eSPI.cpp +++ b/TFT_eSPI.cpp @@ -253,6 +253,10 @@ void TFT_eSPI::init(uint8_t tc) wrpinmask = (uint32_t) digitalPinToBitMask(TFT_WR); #endif + #ifdef TFT_SCLK + sclkpinmask = (uint32_t) digitalPinToBitMask(TFT_SCLK); + #endif + #ifdef TFT_SPI_OVERLAP // Overlap mode SD0=MISO, SD1=MOSI, CLK=SCLK must use D3 as CS // pins(int8_t sck, int8_t miso, int8_t mosi, int8_t ss); @@ -620,35 +624,68 @@ uint16_t TFT_eSPI::readPixel(int32_t x0, int32_t y0) spi_begin_read(); + #ifdef ST7789_DRIVER + writecommand(ST7789_COLMOD); // Switch from 16 bit colour to 18 bit (required) + writedata(0x66); + #endif + readAddrWindow(x0, y0, x0, y0); // Sets CS low + #ifdef TFT_SDA_READ + // To be investigated: spi_end_read() is VERY slow on the ESP8266 here + spi_end_read(); // Releasing the HAL mutex is mandatory for the ESP32 + SPI.end(); // Disconnect the SPI peripheral to release the pins + digitalWrite(TFT_SCLK, HIGH); // Set clock pin high + pinMode(TFT_SCLK, OUTPUT); // Set the SCLK pin to output + pinMode(TFT_MOSI, INPUT_PULLUP); // Set the MOSI pin to input + #endif + // Dummy read to throw away don't care value - tft_Write_8(0); + tft_Read_8(0); // Read the 3 RGB bytes, colour is actually only in the top 6 bits of each byte // as the TFT stores colours as 18 bits #if !defined (ILI9488_DRIVER) + uint8_t r = tft_Read_8(0); uint8_t g = tft_Read_8(0); uint8_t b = tft_Read_8(0); + #else + // The 6 colour bits are in MS 6 bits of each byte, but the ILI9488 needs an extra clock pulse // so bits appear shifted right 1 bit, so mask the middle 6 bits then shift 1 place left uint8_t r = (tft_Read_8(0)&0x7E)<<1; uint8_t g = (tft_Read_8(0)&0x7E)<<1; uint8_t b = (tft_Read_8(0)&0x7E)<<1; + #endif CS_H; +#ifdef TFT_SDA_READ + #ifdef ESP32 + SPI.begin(TFT_SCLK, TFT_MISO, TFT_MOSI, -1); + #else + #ifdef TFT_SPI_OVERLAP + SPI.pins(6, 7, 8, 0); + #else + SPI.begin(); + #endif + #endif + #ifdef ST7789_DRIVER + writecommand(ST7789_COLMOD); + writedata(0x55); + #endif +#else spi_end_read(); - +#endif + return color565(r, g, b); #endif } - /*************************************************************************************** ** Function name: read byte - supports class functions ** Description: Read a byte from ESP32 8 bit data port @@ -756,10 +793,24 @@ void TFT_eSPI::readRect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint16_t spi_begin_read(); + #ifdef ST7789_DRIVER + writecommand(ST7789_COLMOD); // Switch from 16 bit colour to 18 bit (required) + writedata(0x66); + #endif + readAddrWindow(x, y, x + w - 1, y + h - 1); // Sets CS low + #ifdef TFT_SDA_READ + // To be investigated: spi_end_read() is VERY slow on the ESP8266 here + spi_end_read(); // Releasing the HAL mutex is mandatory for the ESP32 + SPI.end(); // Disconnect the SPI peripheral to release the pins + digitalWrite(TFT_SCLK, HIGH); // Set clock pin high + pinMode(TFT_SCLK, OUTPUT); // Set the SCLK pin to output + pinMode(TFT_MOSI, INPUT_PULLUP); // Set the MOSI pin to input + #endif + // Dummy read to throw away don't care value - tft_Write_8(0); + tft_Read_8(0); // Read window pixel 24 bit RGB values uint32_t len = w * h; @@ -768,15 +819,19 @@ void TFT_eSPI::readRect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint16_t // Read the 3 RGB bytes, colour is actually only in the top 6 bits of each byte // as the TFT stores colours as 18 bits #if !defined (ILI9488_DRIVER) + uint8_t r = tft_Read_8(0); uint8_t g = tft_Read_8(0); uint8_t b = tft_Read_8(0); + #else + // The 6 colour bits are in LS 6 bits of each byte but we do not include the extra clock pulse // so we use a trick and mask the middle 6 bits of the byte, then only shift 1 place left uint8_t r = (tft_Read_8(0)&0x7E)<<1; uint8_t g = (tft_Read_8(0)&0x7E)<<1; uint8_t b = (tft_Read_8(0)&0x7E)<<1; + #endif // Swapped colour byte order for compatibility with pushRect() @@ -785,10 +840,64 @@ void TFT_eSPI::readRect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint16_t CS_H; +#ifdef TFT_SDA_READ + #ifdef ESP32 + SPI.begin(TFT_SCLK, TFT_MISO, TFT_MOSI, -1); + #else + #ifdef TFT_SPI_OVERLAP + SPI.pins(6, 7, 8, 0); + #else + SPI.begin(); + #endif + #endif + #ifdef ST7789_DRIVER + writecommand(ST7789_COLMOD); + writedata(0x55); + #endif +#else spi_end_read(); #endif + +#endif } +/*************************************************************************************** +** Function name: tft_Read_8 +** Description: Software SPI to read bidirectional SDA line +***************************************************************************************/ +// For the ST7789 the tft_Read_8() macro is replaced by a function +#ifdef TFT_SDA_READ + #ifdef ESP32 + #define SCLK_L GPIO.out_w1tc = (1 << TFT_SCLK) + #define SCLK_H GPIO.out_w1ts = (1 << TFT_SCLK) + #else // ESP8266 + #define SCLK_L GPOC=sclkpinmask + #define SCLK_H GPOS=sclkpinmask + #endif +uint8_t TFT_eSPI::tft_Read_8(uint8_t dummy) +{ + uint8_t ret = 0; + uint32_t reg = 0; + + for (uint8_t i = 0; i < 8; i++) { // read results + ret <<= 1; + #ifdef ESP32 // bit bangs at around 1MHz + SCLK_L; + //SCLK_L; + reg = gpio_input_get(); // SDA must connect to ESP32 pin in range 0-31 + SCLK_H; + if (reg&(1<>>> NOTE: NOT ALL TFTs SUPPORT READING THE CGRAM (pixel) MEMORY <<<< + + ######################################################################### + ###### DON'T FORGET TO UPDATE THE User_Setup.h FILE IN THE LIBRARY ###### + ######################################################################### +*/ + +// Created by: Bodmer 5/3/17 +// Updated by: Bodmer 10/3/17 +// Updated by: Bodmer 23/11/18 to support SDA reads and the ESP32 +// Version: 0.07 + +// MIT licence applies, all text above must be included in derivative works + +#include // Hardware-specific library +#include + +TFT_eSPI tft = TFT_eSPI(); // Invoke custom library + +unsigned long targetTime = 0; +byte red = 0x1F; +byte green = 0; +byte blue = 0; +byte state = 0; +unsigned int colour = red << 11; // Colour order is RGB 5+6+5 bits each + +void setup(void) { + Serial.begin(921600); // Set to a high rate for fast image transfer to a PC + + tft.init(); + tft.setRotation(0); + tft.fillScreen(TFT_BLACK); + + randomSeed(analogRead(A0)); + + targetTime = millis() + 1000; +} + +#define RGB_TEST false // true produces a simple RGB color test screen + +void loop() { + + if (targetTime < millis()) { + if (!RGB_TEST) + { + targetTime = millis() + 1500; // Wait a minimum of 1.5s + + tft.setRotation(random(4)); + rainbow_fill(); // Fill the screen with rainbow colours + + tft.setTextColor(TFT_BLACK); // Text background is not defined so it is transparent + tft.setTextDatum(TC_DATUM); // Top Centre datum + int xpos = tft.width() / 2; // Centre of screen + + tft.setTextFont(0); // Select font 0 which is the Adafruit font + tft.drawString("Original Adafruit font!", xpos, 5); + + // The new larger fonts do not need to use the .setCursor call, coords are embedded + tft.setTextColor(TFT_BLACK); // Do not plot the background colour + + // Overlay the black text on top of the rainbow plot (the advantage of not drawing the backgorund colour!) + tft.drawString("Font size 2", xpos, 14, 2); // Draw text centre at position xpos, 14 using font 2 + tft.drawString("Font size 4", xpos, 30, 4); // Draw text centre at position xpos, 30 using font 4 + tft.drawString("12.34", xpos, 54, 6); // Draw text centre at position xpos, 54 using font 6 + + tft.drawString("12.34 is in font size 6", xpos, 92, 2); // Draw text centre at position xpos, 92 using font 2 + // Note the x position is the top of the font! + + // draw a floating point number + float pi = 3.1415926; // Value to print + int precision = 3; // Number of digits after decimal point + + int ypos = 110; // y position + + tft.setTextDatum(TR_DATUM); // Top Right datum so text butts neatly to xpos (right justified) + + tft.drawFloat(pi, precision, xpos, ypos, 2); // Draw rounded number and return new xpos delta for next print position + + tft.setTextDatum(TL_DATUM); // Top Left datum so text butts neatly to xpos (left justified) + + tft.drawString(" is pi", xpos, ypos, 2); + + tft.setTextSize(1); // We are using a font size multiplier of 1 + tft.setTextDatum(TC_DATUM); // Top Centre datum + tft.setTextColor(TFT_BLACK); // Set text colour to black, no background (so transparent) + + tft.drawString("Transparent...", xpos, 125, 4); // Font 4 + + tft.setTextColor(TFT_WHITE, TFT_BLACK); // Set text colour to white and background to black + tft.drawString("White on black", xpos, 150, 4); // Font 4 + + tft.setTextColor(TFT_GREEN, TFT_BLACK); // This time we will use green text on a black background + + tft.setTextFont(2); // Select font 2, now we do not need to specify the font in drawString() + + // An easier way to position text and blank old text is to set the datum and use width padding + tft.setTextDatum(BC_DATUM); // Bottom centre for text datum + tft.setTextPadding(tft.width() + 1); // Pad text to full screen width + 1 spare for +/-1 position rounding + + tft.drawString("Ode to a Small Lump of Green Putty", xpos, 230 - 32); + tft.drawString("I Found in My Armpit One Midsummer", xpos, 230 - 16); + tft.drawString("Morning", xpos, 230); + + tft.setTextDatum(TL_DATUM); // Reset to top left for text datum + tft.setTextPadding(0); // Reset text padding to 0 pixels + + // Now call the screen server to send a copy of the TFT screen to the PC running the Processing client sketch + screenServer(); + } + else + { + tft.fillScreen(TFT_BLACK); + tft.fillRect( 0, 0, 16, 16, TFT_RED); + tft.fillRect(16, 0, 16, 16, TFT_GREEN); + tft.fillRect(32, 0, 16, 16, TFT_BLUE); + screenServer(); + } + } +} + +// Fill screen with a rainbow pattern +void rainbow_fill() +{ + // The colours and state are not initialised so the start colour changes each time the funtion is called + int rotation = tft.getRotation(); + tft.setRotation(random(4)); + for (int i = tft.height() - 1; i >= 0; i--) { + // This is a "state machine" that ramps up/down the colour brightnesses in sequence + switch (state) { + case 0: + green ++; + if (green == 64) { + green = 63; + state = 1; + } + break; + case 1: + red--; + if (red == 255) { + red = 0; + state = 2; + } + break; + case 2: + blue ++; + if (blue == 32) { + blue = 31; + state = 3; + } + break; + case 3: + green --; + if (green == 255) { + green = 0; + state = 4; + } + break; + case 4: + red ++; + if (red == 32) { + red = 31; + state = 5; + } + break; + case 5: + blue --; + if (blue == 255) { + blue = 0; + state = 0; + } + break; + } + colour = red << 11 | green << 5 | blue; + // Draw a line 1 pixel wide in the selected colour + tft.drawFastHLine(0, i, tft.width(), colour); // tft.width() returns the pixel width of the display + } + tft.setRotation(rotation); +} diff --git a/examples/Generic/TFT_Screen_Capture/processing_sketch.ino b/examples/Generic/TFT_Screen_Capture/processing_sketch.ino new file mode 100644 index 0000000..3e71f41 --- /dev/null +++ b/examples/Generic/TFT_Screen_Capture/processing_sketch.ino @@ -0,0 +1,535 @@ +// This is a copy of the processing sketch that can be used to capture the images +// Copy the sketch below and remove the /* and */ at the beginning and end. + +// The sketch runs in Processing version 3.3 on a PC, it can be downloaded here: +// https://processing.org/download/ + +/* + +// This is a Processing sketch, see https://processing.org/ to download the IDE + +// The sketch is a client that requests TFT screenshots from an Arduino board. +// The Arduino must call a screenshot server function to respond with pixels. + +// It has been created to work with the TFT_eSPI library here: +// https://github.com/Bodmer/TFT_eSPI + +// The sketch must only be run when the designated serial port is available and enumerated +// otherwise the screenshot window may freeze and that process will need to be terminated +// This is a limitation of the Processing environment and not the sketch. +// If anyone knows how to determine if a serial port is available at start up the PM me +// on (Bodmer) the Arduino forum. + +// The block below contains variables that the user may need to change for a particular setup +// As a minimum set the serial port and baud rate must be defined. The capture window is +// automatically resized for landscape, portrait and different TFT resolutions. + +// Captured images are stored in the sketch folder, use the Processing IDE "Sketch" menu +// option "Show Sketch Folder" or press Ctrl+K + +// Created by: Bodmer 5/3/17 +// Updated by: Bodmer 12/3/17 +// Version: 0.07 + +// MIT licence applies, all text above must be included in derivative works + + +// ########################################################################################### +// # These are the values to change for a particular setup # +// # +int serial_port = 0; // Use enumerated value from list provided when sketch is run # +// # +// On an Arduino Due Programming Port use a baud rate of:115200) # +// On an Arduino Due Native USB Port use a baud rate of any value # +int serial_baud_rate = 921600; // # +// # +// Change the image file type saved here, comment out all but one # +//String image_type = ".jpg"; // # +String image_type = ".png"; // Lossless compression # +//String image_type = ".bmp"; // # +//String image_type = ".tif"; // # +// # +boolean save_border = true; // Save the image with a border # +int border = 5; // Border pixel width # +boolean fade = false; // Fade out image after saving # +// # +int max_images = 100; // Maximum of numbered file images before over-writing files # +// # +int max_allowed = 1000; // Maximum number of save images allowed before a restart # +// # +// # End of the values to change for a particular setup # +// ########################################################################################### + +// These are default values, this sketch obtains the actual values from the Arduino board +int tft_width = 480; // default TFT width (automatic - sent by Arduino) +int tft_height = 480; // default TFT height (automatic - sent by Arduino) +int color_bytes = 2; // 2 for 16 bit, 3 for three RGB bytes (automatic - sent by Arduino) + +import processing.serial.*; + +Serial serial; // Create an instance called serial + +int serialCount = 0; // Count of colour bytes arriving + +// Stage window graded background colours +color bgcolor1 = color(0, 100, 104); // Arduino IDE style background color 1 +color bgcolor2 = color(77, 183, 187); // Arduino IDE style background color 2 +//color bgcolor2 = color(255, 255, 255); // White + +// TFT image frame greyscale value (dark grey) +color frameColor = 42; + +color buttonStopped = color(255, 0, 0); +color buttonRunning = color(128, 204, 206); +color buttonDimmed = color(180, 0, 0); +boolean dimmed = false; +boolean running = true; +boolean mouseClick = false; + +int[] rgb = new int[3]; // Buffer for the colour bytes +int indexRed = 0; // Colour byte index in the array +int indexGreen = 1; +int indexBlue = 2; + +int n = 0; + +int x_offset = (500 - tft_width) /2; // Image offsets in the window +int y_offset = 20; + +int xpos = 0, ypos = 0; // Current pixel position + +int beginTime = 0; +int pixelWaitTime = 1000; // Maximum 1000ms wait for image pixels to arrive +int lastPixelTime = 0; // Time that "image send" command was sent + +int requestTime = 0; +int requestCount = 0; + +int state = 0; // State machine current state + +int progress_bar = 0; // Console progress bar dot count +int pixel_count = 0; // Number of pixels read for 1 screen +float percentage = 0; // Percentage of pixels received + +int saved_image_count = 0; // Stats - number of images processed +int bad_image_count = 0; // Stats - number of images that had lost pixels +String filename = ""; + +int drawLoopCount = 0; // Used for the fade out + +void setup() { + + size(500, 540); // Stage size, can handle 480 pixels wide screen + noStroke(); // No border on the next thing drawn + noSmooth(); // No anti-aliasing to avoid adjacent pixel colour merging + + // Graded background and title + drawWindow(); + + frameRate(2000); // High frame rate so draw() loops fast + + // Print a list of the available serial ports + println("-----------------------"); + println("Available Serial Ports:"); + println("-----------------------"); + printArray(Serial.list()); + println("-----------------------"); + + print("Port currently used: ["); + print(serial_port); + println("]"); + + String portName = Serial.list()[serial_port]; + + serial = new Serial(this, portName, serial_baud_rate); + + state = 99; +} + +void draw() { + + if (mouseClick) buttonClicked(); + + switch(state) { + + case 0: // Init varaibles, send start request + if (running) { + tint(0, 0, 0, 255); + flushBuffer(); + println(""); + print("Ready: "); + + xpos = 0; + ypos = 0; + serialCount = 0; + progress_bar = 0; + pixel_count = 0; + percentage = 0; + drawLoopCount = frameCount; + lastPixelTime = millis() + 1000; + + state = 1; + } else { + if (millis() > beginTime) { + beginTime = millis() + 500; + dimmed = !dimmed; + if (dimmed) drawButton(buttonDimmed); + else drawButton(buttonStopped); + } + } + break; + + case 1: // Console message, give server some time + print("requesting image "); + serial.write("S"); + delay(10); + beginTime = millis(); + requestTime = millis() + 1000; + requestCount = 1; + state = 2; + break; + + case 2: // Get size and set start time for rendering duration report + if (millis() > requestTime) { + requestCount++; + print("*"); + serial.clear(); + serial.write("S"); + if (requestCount > 32) { + requestCount = 0; + System.err.println(" - no response!"); + state = 0; + } + requestTime = millis() + 1000; + } + if ( getSize() == true ) { // Go to next state when we have the size and bits per pixel + getFilename(); + flushBuffer(); // Precaution in case image header size increases in later versions + lastPixelTime = millis() + 1000; + beginTime = millis(); + state = 3; + } + break; + + case 3: // Request pixels and render returned RGB values + state = renderPixels(); // State will change when all pixels are rendered + + // Request more pixels, changing the number requested allows the average transfer rate to be controlled + // The pixel transfer rate is dependant on four things: + // 1. The frame rate defined in this Processing sketch in setup() + // 2. The baud rate of the serial link (~10 bit periods per byte) + // 3. The number of request bytes 'R' sent in the lines below + // 4. The number of pixels sent in a burst by the server sketch (defined via NPIXELS) + + //serial.write("RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"); // 32 x NPIXELS more + serial.write("RRRRRRRRRRRRRRRR"); // 16 x NPIXELS more + //serial.write("RRRRRRRR"); // 8 x NPIXELS more + //serial.write("RRRR"); // 4 x NPIXELS more + //serial.write("RR"); // 2 x NPIXELS more + //serial.write("R"); // 1 x NPIXELS more + if (!running) state = 4; + break; + + case 4: // Pixel receive time-out, flush serial buffer + flushBuffer(); + state = 6; + break; + + case 5: // Save the image to the sketch folder (Ctrl+K to access) + saveScreenshot(); + saved_image_count++; + println("Saved image count = " + saved_image_count); + if (bad_image_count > 0) System.err.println(" Bad image count = " + bad_image_count); + drawLoopCount = frameCount; // Reset value ready for counting in step 6 + state = 6; + break; + + case 6: // Fade the old image if enabled + if ( fadedImage() == true ) state = 0; // Go to next state when image has faded + break; + + case 99: // Draw image viewer window + drawWindow(); + delay(50); // Delay here seems to be required for the IDE console to get ready + state = 0; + break; + + default: + println(""); + System.err.println("Error state reached - check sketch!"); + break; + } +} + +void drawWindow() +{ + // Graded background in Arduino colours + for (int i = 0; i < height - 25; i++) { + float inter = map(i, 0, height - 25, 0, 1); + color c = lerpColor(bgcolor1, bgcolor2, inter); + stroke(c); + line(0, i, 500, i); + } + fill(bgcolor2); + rect( 0, height-25, width-1, 24); + textAlign(CENTER); + textSize(20); + fill(0); + text("Bodmer's TFT image viewer", width/2, height-6); + + if (running) drawButton(buttonRunning); + else drawButton(buttonStopped); +} + +void flushBuffer() +{ + //println("Clearing serial pipe after a time-out"); + int clearTime = millis() + 50; + while ( millis() < clearTime ) serial.clear(); +} + +boolean getSize() +{ + if ( serial.available() > 6 ) { + println(); + char code = (char)serial.read(); + if (code == 'W') { + tft_width = serial.read()<<8 | serial.read(); + } + code = (char)serial.read(); + if (code == 'H') { + tft_height = serial.read()<<8 | serial.read(); + } + code = (char)serial.read(); + if (code == 'Y') { + int bits_per_pixel = (char)serial.read(); + if (bits_per_pixel == 24) color_bytes = 3; + else color_bytes = 2; + } + code = (char)serial.read(); + if (code == '?') { + drawWindow(); + + x_offset = (500 - tft_width) /2; + tint(0, 0, 0, 255); + noStroke(); + fill(frameColor); + rect((width - tft_width)/2 - border, y_offset - border, tft_width + 2 * border, tft_height + 2 * border); + return true; + } + } + return false; +} + +void saveScreenshot() +{ + println(); + if (saved_image_count < max_allowed) + { + if (filename == "") filename = "tft_screen_" + (n++); + filename = filename + image_type; + println("Saving image as \"" + filename + "\""); + if (save_border) + { + PImage partialSave = get(x_offset - border, y_offset - border, tft_width + 2*border, tft_height + 2*border); + partialSave.save(filename); + } else { + PImage partialSave = get(x_offset, y_offset, tft_width, tft_height); + partialSave.save(filename); + } + + if (n>=max_images) n = 0; + } + else + { + System.err.println(max_allowed + " saved image count exceeded, restart the sketch"); + } +} + +void getFilename() +{ + int readTime = millis() + 20; + int inByte = 0; + filename = ""; + while ( serial.available() > 0 && millis() < readTime && inByte != '.') + { + inByte = serial.read(); + if (inByte == ' ') inByte = '_'; + if ( unicodeCheck(inByte) ) filename += (char)inByte; + } + + inByte = serial.read(); + if (inByte == '@') filename += "_" + timeCode(); + else if (inByte == '#') filename += "_" + saved_image_count%100; + else if (inByte == '%') filename += "_" + millis(); + else if (inByte != '*') filename = ""; + + inByte = serial.read(); + if (inByte == 'j') image_type =".jpg"; + else if (inByte == 'b') image_type =".bmp"; + else if (inByte == 'p') image_type =".png"; + else if (inByte == 't') image_type =".tif"; +} + +boolean unicodeCheck(int unicode) +{ + if ( unicode >= '0' && unicode <= '9' ) return true; + if ( (unicode >= 'A' && unicode <= 'Z' ) || (unicode >= 'a' && unicode <= 'z')) return true; + if ( unicode == '_' || unicode == '/' ) return true; + return false; +} + +String timeCode() +{ + String timeCode = (int)year() + "_" + (int)month() + "_" + (int)day() + "_"; + timeCode += (int)hour() + "_" + (int)minute() + "_" + (int)second(); + return timeCode; +} + +int renderPixels() +{ + if ( serial.available() > 0 ) { + + // Add the latest byte from the serial port to array: + while (serial.available()>0) + { + rgb[serialCount++] = serial.read(); + + // If we have 3 colour bytes: + if ( serialCount >= color_bytes ) { + serialCount = 0; + pixel_count++; + if (color_bytes == 3) + { + stroke(rgb[indexRed], rgb[indexGreen], rgb[indexBlue], 1000); + } else + { // Can cater for various byte orders + //stroke( (rgb[0] & 0x1F)<<3, (rgb[0] & 0xE0)>>3 | (rgb[1] & 0x07)<<5, (rgb[1] & 0xF8)); + //stroke( (rgb[1] & 0x1F)<<3, (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[0] & 0xF8)); + stroke( (rgb[0] & 0xF8), (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[1] & 0x1F)<<3); + //stroke( (rgb[1] & 0xF8), (rgb[0] & 0xE0)>>3 | (rgb[1] & 0x07)<<5, (rgb[0] & 0x1F)<<3); + } + // We get some pixel merge aliasing if smooth() is defined, so draw pixel twice + point(xpos + x_offset, ypos + y_offset); + //point(xpos + x_offset, ypos + y_offset); + + lastPixelTime = millis(); + xpos++; + if (xpos >= tft_width) { + xpos = 0; + progressBar(); + ypos++; + if (ypos>=tft_height) { + ypos = 0; + if ((int)percentage <100) { + while (progress_bar++ < 64) print(" "); + percent(100); + } + println("Image fetch time = " + (millis()-beginTime)/1000.0 + " s"); + return 5; + } + } + } + } + } else + { + if (millis() > (lastPixelTime + pixelWaitTime)) + { + println(""); + System.err.println(pixelWaitTime + "ms time-out for pixels exceeded..."); + if (pixel_count > 0) { + bad_image_count++; + System.err.print("Pixels missing = " + (tft_width * tft_height - pixel_count)); + System.err.println(", corrupted image not saved"); + System.err.println("Good image count = " + saved_image_count); + System.err.println(" Bad image count = " + bad_image_count); + } + return 4; + } + } + return 3; +} + +void progressBar() +{ + progress_bar++; + print("."); + if (progress_bar >63) + { + progress_bar = 0; + percentage = 0.5 + 100 * pixel_count/(0.001 + tft_width * tft_height); + percent(percentage); + } +} + +void percent(float percentage) +{ + if (percentage > 100) percentage = 100; + println(" [ " + (int)percentage + "% ]"); + textAlign(LEFT); + textSize(16); + noStroke(); + fill(bgcolor2); + rect(10, height - 25, 70, 20); + fill(0); + text(" [ " + (int)percentage + "% ]", 10, height-8); +} + +boolean fadedImage() +{ + int opacity = frameCount - drawLoopCount; // So we get increasing fade + if (fade) + { + tint(255, opacity); + //image(tft_img, x_offset, y_offset); + noStroke(); + fill(50, 50, 50, opacity); + rect( (width - tft_width)/2, y_offset, tft_width, tft_height); + delay(10); + } + if (opacity > 50) // End fade after 50 cycles + { + return true; + } + return false; +} + +void drawButton(color buttonColor) +{ + stroke(0); + fill(buttonColor); + rect(500 - 100, 540 - 26, 80, 24); + textAlign(CENTER); + textSize(20); + fill(0); + if (running) text(" Pause ", 500 - 60, height-7); + else text(" Run ", 500 - 60, height-7); +} + +void buttonClicked() +{ + mouseClick = false; + if (running) { + running = false; + drawButton(buttonStopped); + System.err.println(""); + System.err.println("Stopped - click 'Run' button: "); + //noStroke(); + //fill(50); + //rect( (width - tft_width)/2, y_offset, tft_width, tft_height); + beginTime = millis() + 500; + dimmed = false; + state = 4; + } else { + running = true; + drawButton(buttonRunning); + } +} + +void mousePressed() { + if (mouseX > (500 - 100) && mouseX < (500 - 20) && mouseY > (540 - 26) && mouseY < (540 - 2)) { + mouseClick = true; + } +} + +*/ diff --git a/examples/Generic/TFT_Screen_Capture/screenServer.ino b/examples/Generic/TFT_Screen_Capture/screenServer.ino new file mode 100644 index 0000000..2924947 --- /dev/null +++ b/examples/Generic/TFT_Screen_Capture/screenServer.ino @@ -0,0 +1,196 @@ +// Reads a screen image off the TFT and send it to a processing client sketch +// over the serial port. Use a high baud rate, e.g. for an ESP8266: +// Serial.begin(921600); + +// At 921600 baud a 320 x 240 image with 16 bit colour transfers can be sent to the +// PC client in ~1.67s and 24 bit colour in ~2.5s which is close to the theoretical +// minimum transfer time. + +// This sketch has been created to work with the TFT_eSPI library here: +// https://github.com/Bodmer/TFT_eSPI + +// Created by: Bodmer 27/1/17 +// Updated by: Bodmer 10/3/17 +// Updated by: Bodmer 23/11/18 to support SDA reads and the ESP32 +// Version: 0.08 + +// MIT licence applies, all text above must be included in derivative works + +//==================================================================================== +// Definitions +//==================================================================================== +#define PIXEL_TIMEOUT 100 // 100ms Time-out between pixel requests +#define START_TIMEOUT 10000 // 10s Maximum time to wait at start transfer + +#define BITS_PER_PIXEL 16 // 24 for RGB colour format, 16 for 565 colour format + +// File names must be alpha-numeric characters (0-9, a-z, A-Z) or "/" underscore "_" +// other ascii characters are stripped out by client, including / generates +// sub-directories +#define DEFAULT_FILENAME "tft_screenshots/screenshot" // In case none is specified +#define FILE_TYPE "png" // jpg, bmp, png, tif are valid + +// Filename extension +// '#' = add incrementing number, '@' = add timestamp, '%' add millis() timestamp, +// '*' = add nothing +// '@' and '%' will generate new unique filenames, so beware of cluttering up your +// hard drive with lots of images! The PC client sketch is set to limit the number of +// saved images to 1000 and will then prompt for a restart. +#define FILE_EXT '@' + +// Number of pixels to send in a burst (minimum of 1), no benefit above 8 +// NPIXELS values and render times: +// NPIXELS 1 = use readPixel() = >5s and 16 bit pixels only +// NPIXELS >1 using rectRead() 2 = 1.75s, 4 = 1.68s, 8 = 1.67s +#define NPIXELS 8 // Must be integer division of both TFT width and TFT height + +//==================================================================================== +// Screen server call with no filename +//==================================================================================== +// Start a screen dump server (serial or network) - no filename specified +boolean screenServer(void) +{ + // With no filename the screenshot will be saved with a default name e.g. tft_screen_#.xxx + // where # is a number 0-9 and xxx is a file type specified below + return screenServer(DEFAULT_FILENAME); +} + +//==================================================================================== +// Screen server call with filename +//==================================================================================== +// Start a screen dump server (serial or network) - filename specified +boolean screenServer(String filename) +{ + delay(0); // Equivalent to yield() for ESP8266; + + boolean result = serialScreenServer(filename); // Screenshot serial port server + //boolean result = wifiScreenServer(filename); // Screenshot WiFi UDP port server (WIP) + + delay(0); // Equivalent to yield() + + //Serial.println(); + //if (result) Serial.println(F("Screen dump passed :-)")); + //else Serial.println(F("Screen dump failed :-(")); + + return result; +} + +//==================================================================================== +// Serial server function that sends the data to the client +//==================================================================================== +boolean serialScreenServer(String filename) +{ + // Precautionary receive buffer garbage flush for 50ms + uint32_t clearTime = millis() + 50; + while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266; + + boolean wait = true; + uint32_t lastCmdTime = millis(); // Initialise start of command time-out + + // Wait for the starting flag with a start time-out + while (wait) + { + delay(0); // Equivalent to yield() for ESP8266; + // Check serial buffer + if (Serial.available() > 0) { + // Read the command byte + uint8_t cmd = Serial.read(); + // If it is 'S' (start command) then clear the serial buffer for 100ms and stop waiting + if ( cmd == 'S' ) { + // Precautionary receive buffer garbage flush for 50ms + clearTime = millis() + 50; + while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266; + + wait = false; // No need to wait anymore + lastCmdTime = millis(); // Set last received command time + + // Send screen size etc using a simple header with delimiters for client checks + sendParameters(filename); + } + } + else + { + // Check for time-out + if ( millis() > lastCmdTime + START_TIMEOUT) return false; + } + } + + uint8_t color[3 * NPIXELS]; // RGB and 565 format color buffer for N pixels + + // Send all the pixels on the whole screen + for ( uint32_t y = 0; y < tft.height(); y++) + { + // Increment x by NPIXELS as we send NPIXELS for every byte received + for ( uint32_t x = 0; x < tft.width(); x += NPIXELS) + { + delay(0); // Equivalent to yield() for ESP8266; + + // Wait here for serial data to arrive or a time-out elapses + while ( Serial.available() == 0 ) + { + if ( millis() > lastCmdTime + PIXEL_TIMEOUT) return false; + delay(0); // Equivalent to yield() for ESP8266; + } + + // Serial data must be available to get here, read 1 byte and + // respond with N pixels, i.e. N x 3 RGB bytes or N x 2 565 format bytes + if ( Serial.read() == 'X' ) { + // X command byte means abort, so clear the buffer and return + clearTime = millis() + 50; + while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266; + return false; + } + // Save arrival time of the read command (for later time-out check) + lastCmdTime = millis(); + +#if defined BITS_PER_PIXEL && BITS_PER_PIXEL >= 24 && NPIXELS > 1 + // Fetch N RGB pixels from x,y and put in buffer + tft.readRectRGB(x, y, NPIXELS, 1, color); + // Send buffer to client + Serial.write(color, 3 * NPIXELS); // Write all pixels in the buffer +#else + // Fetch N 565 format pixels from x,y and put in buffer + if (NPIXELS > 1) tft.readRect(x, y, NPIXELS, 1, (uint16_t *)color); + else + { + uint16_t c = tft.readPixel(x, y); + color[0] = c>>8; + color[1] = c & 0xFF; // Swap bytes + } + // Send buffer to client + Serial.write(color, 2 * NPIXELS); // Write all pixels in the buffer +#endif + } + } + + Serial.flush(); // Make sure all pixel bytes have been despatched + + return true; +} + +//==================================================================================== +// Send screen size etc using a simple header with delimiters for client checks +//==================================================================================== +void sendParameters(String filename) +{ + Serial.write('W'); // Width + Serial.write(tft.width() >> 8); + Serial.write(tft.width() & 0xFF); + + Serial.write('H'); // Height + Serial.write(tft.height() >> 8); + Serial.write(tft.height() & 0xFF); + + Serial.write('Y'); // Bits per pixel (16 or 24) + if (NPIXELS > 1) Serial.write(BITS_PER_PIXEL); + else Serial.write(16); // readPixel() only provides 16 bit values + + Serial.write('?'); // Filename next + Serial.print(filename); + + Serial.write('.'); // End of filename marker + + Serial.write(FILE_EXT); // Filename extension identifier + + Serial.write(*FILE_TYPE); // First character defines file type j,b,p,t +}