From 406aac051e222df46d6a506125ac9656675ad282 Mon Sep 17 00:00:00 2001 From: Bodmer Date: Fri, 10 Mar 2017 19:16:31 +0000 Subject: [PATCH] Update to screenshot sketches + others Minor tweaks and re-arrangement of User and Custom font references between header files --- TFT_eSPI.h | 32 +- Tools/Screenshot_client/ILI9341_example.png | Bin 0 -> 5170 bytes Tools/Screenshot_client/Screenshot_client.pde | 441 +++++++++------- Tools/Screenshot_client/example_1.png | Bin 13401 -> 0 bytes Tools/Screenshot_client/example_2.png | Bin 9997 -> 0 bytes User_Setup_Select.h | 22 + .../TFT_Screen_Capture/TFT_Screen_Capture.ino | 117 +++-- .../TFT_Screen_Capture/processing_sketch.ino | 470 +++++++++++------- .../TFT_Screen_Capture/screenServer.ino | 132 +++-- 9 files changed, 728 insertions(+), 486 deletions(-) create mode 100644 Tools/Screenshot_client/ILI9341_example.png delete mode 100644 Tools/Screenshot_client/example_1.png delete mode 100644 Tools/Screenshot_client/example_2.png diff --git a/TFT_eSPI.h b/TFT_eSPI.h index d24e292..3e23ea7 100644 --- a/TFT_eSPI.h +++ b/TFT_eSPI.h @@ -19,20 +19,10 @@ #ifndef _TFT_eSPIH_ #define _TFT_eSPIH_ -// Include header file that defines the fonts loaded and the pins to be used +// Include header file that defines the fonts loaded, the TFT drivers +// available and the pins to be used #include -// Load the right driver definitions <<<<<<<<<<<<<<<<<<<<< ADD NEW DRIVERS TO THE LIST HERE <<<<<<<<<<<<<<<<<<<<<<< -#if defined (ILI9341_DRIVER) - #include -#elif defined (ST7735_DRIVER) - #include -#elif defined (ILI9163_DRIVER) - #include -#elif defined (S6D02A1_DRIVER) - #include -#endif - // If the frequency is not defined, set a default #ifndef SPI_FREQUENCY #define SPI_FREQUENCY 20000000 @@ -107,14 +97,10 @@ #include -// New custom fonts -#include // CF_OL24 -#include // CF_OL32 -#include // CF_RT24 -#include // CF_S24 -#include // CF_Y32 +// Call up any user custom fonts +#include -// Free fonts +// Original Adafruit_GFX "Free Fonts" #include // TT1 #include // FF1 or FM9 @@ -179,9 +165,6 @@ #include // FF47 or FSBI18 #include // FF48 or FSBI24 -// Swap any type -template static inline void -swap(T& a, T& b) { T t = a; a = b; b = t; } //These enumerate the text plotting alignment (reference datum point) #define TL_DATUM 0 // Top left (default) @@ -222,6 +205,11 @@ swap(T& a, T& b) { T t = a; a = b; b = t; } #define TFT_GREENYELLOW 0xAFE5 /* 173, 255, 47 */ #define TFT_PINK 0xF81F + +// Swap any type +template static inline void +swap(T& a, T& b) { T t = a; a = b; b = t; } + // This is a structure to conveniently hold infomation on the default fonts // Stores pointer to font character image address table, width table and height diff --git a/Tools/Screenshot_client/ILI9341_example.png b/Tools/Screenshot_client/ILI9341_example.png new file mode 100644 index 0000000000000000000000000000000000000000..25369e68396663915b3a300d61956023b1fcc4f3 GIT binary patch literal 5170 zcmY*d2|SeFyBEn8X+f6k`&uGdvJ5dLjU|MnUs*!-WsGfXC9(_#`N@#2#xg?oL0X1E zM2oc%W{iC@mZ>J@zQeu$d+%pH@0{~I?>XP+Jli?Xds3{gTkvv=aI>(m@LmC%*|M-4 zx(f9FaIymb;GKdT7M7F0ub7$Kh?ra}(%3pRklR}q!FoLYv9Uq?;~%Rj#%7Wz;F0Es zNIDqQv)0e02t4LAJ1o6FgvmetnDagN|M{f~%4rt-w)zaOTBi|~mr)or3XbJ*wmTK$FQWq!&O7ZXmYTvlR?KBrW8~WFi=I;Se>JKk2CM z&8>}!U~92b|35c9?o1qQB>ziyTeg46DoIH;=|d!1iPIR@+aEzB_zXAg3~?88#8id( z<_BGVEI%?u_T_k(&#P3&b!4U143AzkCS)ccq&Nx<44tZ=)QCj`S_&>6q9$zal_}33 zhI(%!?xLAClv`hoYjZx*M-SrHB;LvCeJA*RbEC~CthkNgp86#%M{4&Cwr|r(wZKL5 zwV8^(V;W}mJewLFoM)%Snr3h;H-FZELyj7q2NV1LxzNj}CT19aSdvnl z04Tc+c|KgugDaqyn=5UQgSPt6^_yWz)t{18`0~ELGLSQk!xkTry1ba~Vse44k4`?Y zSFGTHy@nI{LCWlX^vnDpa0Lsj`zYvwyw5f!L1U2}u$e&8gTWqk0k3qlRF*c!IY=e`-GJd!Gn^!^6bE?br>Jcf$WoE!$d7N2ef_N{%@RNK$AfeK@hf@mQaN z*EEP-&?A(BrPVpg8E1lWUmgn#&iVhRnu+sWv1)PWa=#acH9Epvj-?|bVy@T+`tWLc zdPV~oase>E9e=|)j?Yn$JZf>M(g@Nz#Lm3pt$A{e+9B$m*nuV75)LfkdteDDGu%Kh zA>7@sYRMWax&0j{=ibUvYDdQ*&n81&o|2_7gy!Zd1?%L}pJ zvb>QlAHr%#(vmPnF~5*bQ=e9U?VR4X;Nu8vH)l|g-4my>u3XL3nqL;zw}l4vy*b8C zefc$^U!e0$%$U08#>5y|l5n(?ZdgA${|22)Vu9%=infe&RFrez>5WXt#URwtlnc(2 zuHL6Dobv8Dfs|nb;_c1IqbAfn2`TlTEqBj*$z9iK_%6J_HI*Ke*7)u|Oc!wqOejuj zeM3dTmV@NgQX2`Z$?mO}^Y1w2`Vd zP?zchlnUaAyEc!X6gsQU>IPZ=CA;FZ<|gS+bs|A_Qt_MvqI(mkK5eUc7*Fr~Un4AMxenzxZQ*YC6T&J4?V~O6{IZnp zeLb!-e#O7krD82p*j_kDC(3wv{mgAfZb`zrPcnViXdd%9?tJiT%S^$d7O1%4Q?pOR zTHH*8CH@eAzy<6x2bY(Stm8JU*+vEwKYIB1t||<0snR5oWXB+SmMN(3)mM}&MDhG zn_18F;QKY9Z$+^pv0QjbG}-I(3yd|4$*w9}Fx`K_Fa;m6vsY-xKBt~g zY)IWUzr6c|IRP%nTs7uLxBHEWuTGWMf4eC&RM_zJnTJS^8?Cv+rNh1jdZcLd-@2o+ zloz0WP37tB7dHd)NSn$-LU_g>Al7x5)>!{zSEKX9Q0bW?ZQ77g4QRe^r1k!dmICR$ zGd&HmW4A#vKr&S$8q&C-AAhJ=9WVNzFSF$q#gz`2)C}xo{?J8_TD4JT2~Lfk;Ct{Y zRS*ETVe&gbgL6IUpM_7Jm2dk9!idXKdiPgSI?`{ho_%G9s?m{GeF#s7?sGvwm8Fxt z%L@hLIn(%HA0XSZm@-?;5a`mK*KhtY#K=OMN+W?JtKDDA#7{J0r{&E*4i#x~?!f5h z09QGzXIw6*dGMq>B~Pci?`Ssd7=T^%WjY>qycvI9yZI^B>9QvRE5WHW@Y1%?9M5h&?RJ&W(w7plbWOA@)6P(-<$mDBsuD&Z7&rd#ygJgyIeCPR!M~*rH0>-czsY|_Aorz~@OU29gk~|E4-tvGNBzY#w=y%Dh!RCA0-wai7Uomo zKAHX+@zgGh$c(Qi10Z2aCi13RoM@X0PvJh9C790@cCCegfKU(rY23FsY8NCjqhkL4 z*!+8Vo8MCry<@uy{y3#lRLmr-@GKziiLJ>04jngKZOCl6R%`YM@bQS?=}%ALp^rTv zX$33Zua6*UGQoGkOHO;Qc04i+fp~CViic@EVHlirWZi5-#s$Wstw;P`YkXV(CSpy< zR4BE=X4cYQ*2lE4$9UwTwFJ>GKPyB7BN7 zYB-QIiv~FhdovWu2ow*ma)dUfUA=r--g8621(=fZVDMLK(xg`vDLr9=IQ*_X4% z-*x{Lck6$`M%pdle^{2*c1#~I3y&1H1uR4&959L_=u`%E!Ep7IA_Zge{(;~6F*JLB zA(cSq>4~{%NpP!>aT^Z9#r$XbNENO0(DDv zMzr3T@aozyzKjn|xDvgHm6g0zcCN;wB2XcBNfpn}iJ)05_4S4{Ep?h&HbKM|Hr9sP zU9#c?A&w3^@9&7+J=8}}3?;s6o7^I_C5~S7miSh`Uej8^y5sss6=PsGglkL7+V)f6 z2TG{XEE-vFb7yjjKKE!yU1x07<5GUzDS!QV7Fc&q23CK}>2}=*x0JI>nnUbt3+yR; zU)Ux$p?*B|EW}+0J?hRaFUv@>XBr|n1Lu?-7>>%DVW>JO4QPl;QglYMbeP!70s(0f zYtAPE2_0u4Pq3Q~Rdor*aacD?i`&I3gI}i{n*jj<1(AmNmu5+@<&cRY*C~)HAU`_; zt2b6q?I^5C3D$L)zVoIF3Pa_@P3SL83Nm%92qlu*hHiECBVhU8bxFcujZx}@b^#iO zXU?$_cR_m8xm#WsLRUjs`bfBC;H3wlrCpcwqOuIqL)skvZK;1%uLM-QmKLaDo)1%^ zoqbx~$s1%>^nl}t&8Nl(C|sMSgI+*dRqn+X;GfT`d+7!M%!g>yB(y!zP8cRc;YRz^`C9BD4k zC497PzZw%-%6*fxiI+4SVp`Y5(mjPxDg|jN4UTJ8qpbq{r;>ZqZ)hWEMhx0lhqFas zi(=(m?z&;&%r)}4{OI>i1d~-q!CXjG-HxiGR<6^JmRz=C`Ko`Z;`phTvsZt6Gr^3xP} z%GNJbiQwrDW84)+onwfWmBZSmIV$)m!{fJ#XfaZ4m;G*NXVWy=9UjLRo~|>pAq2UbzraXMn%2k@8-Svc!f?Xr82~#tUJt(Wn!onw--ma61p8s?n z>g+W7^)W)`Dc}AJN@(q?doY*l4m`JY-J~hl>Y2;gTH=gic=I!J$c6gH~EGoFuKF0Sz z_SdGj{zO0BPv3v&kS&KgH|0m`@?0|Ri-}H|10rTtoa4VCo0vLuum7h@>u@O1_SjcB zp#4AHM2x_FD<@I=bmFe*829VSn)E7jYV3FuYps&+zM50aEgny|vEW1lr~FLVK><5` z!0pWdjsY+Mq6cjAZ;XyV@VR${Bix$+I5jO354DJqX)oY@*)w-yd#+y_mCKv6r~^#`>OyTf05JFK<#5 z)Z5=J4NHFS?ej=hGovEWgbj_m6T7LbPPxz86CPTWFOGy-Xa3B^A;N4+2Bz#qku+y2 zA4+I2YTCtYwm%B~N-3lQLV1Z@D}If>kxt<`gx5sIwsmS&VreRrA4aj^czxL+PStA8 zMHbmFsubzEd&`%vX-^*G2dySOrec15?r6ob>HWR1Q1ep7n$R|8*Ev&VP3W+TFt&J9 zjfxT_@eYgYAfTae$O?j6Z<*r5tNSdIFjE$&n-9* zBL+XR`GQvEI7CK^WWH1z(`+Es=JQ8ig?(0j^+v5VI|i^jlJ?bKgk)8Qb-V~E-*=jwtrK_EJkGOON~pu2jDFwycj$%=SJ-IWUo zb)gRNu)||=g?t9=lLf++h+V$cgpc^C2!`?5X#dmjQN;!NO^_aM-5XUJc#^Wtw@~K4 z%Rw~hw2`HEYLp1|Q45=YOsmLg!jbJ5Y$CZ%a*<~AWOi(|9qXzi&asVNskOrQL_wi} zU2X2GFUDh~G*E?a(tFKtm+A*a4QQj*1UaM5{22+9)Oku~v17K*wc$Olqy2lkCzX@~ z=4+K;v_W=8s$xLDD7>U?ffv>NBrpiZK|a*IVJL>bbP<*4J(w&Qe^TqXzkb$Z6FaM1$b zzDd2t7&zW5*VdzxsFgMZARDZJODLpHIx{z_i}PGWypcKp4`K>9XYvD4Eq6B|6`0qL z*WW&`N%=@H5GuiX3n5yF1U@yVmR7H6HV82DA*pD?H1r(S3jED2`?(F_^);C2@EfH7 zrYtP8X;y%({5dsM92kTzBomN2YuQx6cf8}4M754Rcr75)k3 beginTime) { + System.err.println(" - no response!"); + state = 0; + } + if ( getSize() == true ) { // Go to next state when we have the size and bits per pixel + flushBuffer(); // Precaution in case image header size increases in later versions + 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) - 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 >= 3 ) { - serialCount = 0; - pixel_count++; - stroke(rgb[indexRed], rgb[indexGreen], rgb[indexBlue],1000); - // We seem to get some pixel transparency so draw twice - point(xpos + x_offset, ypos + y_offset); - point(xpos + x_offset, ypos + y_offset); - lastPixelTime = millis(); - xpos++; - if (xpos >= tft_width) { - xpos = 0; - print("."); - progress_bar++; - if (progress_bar >31) - { - progress_bar = 0; - percentage = 0.5 + 100 * pixel_count/(0.001 + tft_width * tft_height); - if (percentage > 100) percentage = 100; - println(" [ " + (int)percentage + "% ]"); - } - ypos++; - if (ypos>=tft_height) { - ypos = 0; - println("Image fetch time = " + (millis()-beginTime)/1000.0 + " s"); - state = 5; - } - } - } - } - } else - { - if (millis() > (lastPixelTime + pixelWaitTime)) - { - println(""); - System.err.println("No response, trying again..."); - state = 4; - } - } - // Request 32pixels - serial.write("RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"); + //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 break; - case 4: // Time-out, flush serial buffer + case 4: // Pixel receive time-out, flush serial buffer println(); - //println("Clearing serial pipe after a time-out"); - int clearTime = millis() + 50; - while ( millis() < clearTime ) - { - serial.read(); - } + flushBuffer(); state = 6; break; - case 5: // Save the image tot he sketch folder - println(); - String filename = "tft_screen_" + n + 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); - } - - n = n + 1; - if (n>=max_images) n = 0; - drawLoopCount = 0; // Reset value ready for counting in step 6 + 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 - int opacity = drawLoopCount; // So we get increasing fade - if (drawLoopCount > 50) // End fade after 50 cycles - { - opacity = 255; - state = 0; - } - delay(10); - if (fade) - { - tint(255, opacity); - image(tft_img, x_offset, y_offset); - } - + if ( fadedImage() == true ) state = 0; // Go to next state when image has faded break; case 99: // Draw image viewer window - textAlign(CENTER); - textSize(20); - background(bgcolor); - image(img, 0, 0); - - fill(0); - text("Bodmer's TFT image viewer", width/2, height-10); - - stroke(0, 0, 0); - - rect(x_offset - border, y_offset - border, tft_width - 1 + 2*border, tft_height - 1 + 2*border); - - fill(255); - rect(x_offset, y_offset, tft_width-1, tft_height-1); - + drawWindow(); + delay(50); // Delay here seems to be required for the IDE console to get ready state = 0; break; @@ -287,4 +215,185 @@ void draw() { 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); +} + +void flushBuffer() +{ + //println("Clearing serial pipe after a time-out"); + int clearTime = millis() + 50; + while ( millis() < clearTime ) serial.read(); +} + +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; +} + +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 + { + stroke( (rgb[1] & 0x1F)<<3, (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[0] & 0xF8)); + //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); +} + +void saveScreenshot() +{ + println(); + String filename = "tft_screen_" + n + 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); + } + + n = n + 1; + if (n>=max_images) n = 0; +} + +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; } \ No newline at end of file diff --git a/Tools/Screenshot_client/example_1.png b/Tools/Screenshot_client/example_1.png deleted file mode 100644 index 3a29e3c16c7cd98b6e8185c2da1b041e6c763bda..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13401 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 diff --git a/User_Setup_Select.h b/User_Setup_Select.h index 7161f24..b0b2d7c 100644 --- a/User_Setup_Select.h +++ b/User_Setup_Select.h @@ -24,3 +24,25 @@ //#include // Setup file configured for my S6D02A1 //#include // Setup file template for copying/editting + + + + + +///////////////////////////////////////////////////////////////////////////////////// +// // +// DON'T TINKER WITH ANY OF THE FOLLOWING LINES, THESE ADD THE TFT DRIVERS // +// THEY ARE HERE FOR BODMER'S CONVENIENCE! // +// // +///////////////////////////////////////////////////////////////////////////////////// + +// Load the right driver definition - do not tinker here ! +#if defined (ILI9341_DRIVER) + #include +#elif defined (ST7735_DRIVER) + #include +#elif defined (ILI9163_DRIVER) + #include +#elif defined (S6D02A1_DRIVER) + #include +#endif diff --git a/examples/ILI9341/TFT_Screen_Capture/TFT_Screen_Capture.ino b/examples/ILI9341/TFT_Screen_Capture/TFT_Screen_Capture.ino index ceed295..d791b0f 100644 --- a/examples/ILI9341/TFT_Screen_Capture/TFT_Screen_Capture.ino +++ b/examples/ILI9341/TFT_Screen_Capture/TFT_Screen_Capture.ino @@ -1,24 +1,41 @@ /* - This example draws rainbow colours on the screen, adds text in various - fast rendering fonts and then sends the TFT screen to a PC over the serial - port to a processing sketch. + This sketch has been written to test the Processing screenshot client. - This sketch uses the GLCD, 2, 4, 6 fonts. + It has been created to work with the TFT_eSPI library here: + https://github.com/Bodmer/TFT_eSPI + + It sends screenshots to a PC running a Processing client sketch. + + The Processing IDE that will run the client sketch can be downloaded + here: https://processing.org/ + + The Processing sketch needed is contained within a tab attached to this + Arduino sketch. Cut and copy that tab into the Processing IDE and run. + + This sketch uses the GLCD, 2, 4, 6 fonts only. Make sure all the display driver and pin comnenctions are correct by editting the User_Setup.h file in the TFT_eSPI library folder. + Maximum recommended SPI clock rate is 27MHz when reading pixels, 40MHz + seems to be OK with ILI9341 displays but this is above the manufacturers + specifed maximum clock rate. + ######################################################################### ###### 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 +// Version: 0.06 + +// 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 with default width and height - -//TFT_eSPI tft = TFT_eSPI(240, 320); // Could invoke custom library declaring width and height +TFT_eSPI tft = TFT_eSPI(); // Invoke custom library with default width and height unsigned long targetTime = 0; byte red = 31; @@ -34,72 +51,76 @@ void setup(void) { tft.setRotation(0); tft.fillScreen(TFT_BLACK); + randomSeed(analogRead(A0)); + targetTime = millis() + 1000; } void loop() { if (targetTime < millis()) { - targetTime = millis() + 4000; - + targetTime = millis() + 1500; // Wait a minimum of 1.5s + + tft.setRotation(random(4)); rainbow_fill(); // Fill the screen with rainbow colours - // The standard AdaFruit font still works as before - tft.setTextColor(TFT_BLACK); // Background is not defined so it is transparent - - tft.setCursor (60, 5); + 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.print("Original Adafruit font!"); - - //tft.drawString("Original Adafruit font!",60,5,1); + 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.drawCentreString("Font size 2", 120, 14, 2); // Draw text centre at position 120, 14 using font 2 - tft.drawCentreString("Font size 4", 120, 30, 4); // Draw text centre at position 120, 30 using font 4 - tft.drawCentreString("12.34", 120, 54, 6); // Draw text centre at position 120, 54 using font 6 + 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.drawCentreString("12.34 is in font size 6", 120, 92, 2); // Draw text centre at position 120, 92 using font 2 + 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.14159; // Value to print - int precision = 3; // Number of digits after decimal point - int xpos = 90; // x position + float pi = 3.1415926; // Value to print + int precision = 3; // Number of digits after decimal point + int ypos = 110; // y position - int font = 2; // font number 2 - xpos += tft.drawFloat(pi, precision, xpos, ypos, font); // Draw rounded number and return new xpos delta for next print position - tft.drawString(" is pi", xpos, ypos, font); // Continue printing from new x position - tft.setTextSize(1); // We are using a size multiplier of 1 + 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.setCursor(36, 150, 4); // Set cursor to x = 36, y = 150 and use font 4 - tft.println("Transparent..."); // As we use println, the cursor moves to the next line + tft.drawString("Transparent...", xpos, 125, 4); // Font 4 - tft.setCursor(30, 175); // Set cursor to x = 30, y = 175 - tft.setTextColor(TFT_WHITE, TFT_BLACK); // Set text colour to white and background to black - tft.println(" White on black "); - - tft.setTextFont(4); // Select font 4 without moving cursor - tft.setCursor(50, 210); // Set cursor to x = 50, y = 210 without changing the font - tft.setTextColor(TFT_WHITE); - // By using #TFT print we can use all the formatting features like printing HEX - tft.print(57005, HEX); // Cursor does no move to next line - tft.println(48879, HEX); // print and move cursor to next line + 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 + + 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(241); // Pad text to full screen wdth (240 pixels + 1 spare for +/-1 position rounding) - tft.drawString("Ode to a Small Lump of Green Putty", 120, 300 - 32); - tft.drawString("I Found in My Armpit One Midsummer", 120, 300 - 16); - tft.drawString("Morning", 120, 300); + 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(); } } @@ -108,8 +129,9 @@ void loop() { void rainbow_fill() { // The colours and state are not initialised so the start colour changes each time the funtion is called - - for (int i = 319; i >= 0; i--) { + 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: @@ -159,6 +181,7 @@ void rainbow_fill() // Draw a line 1 pixel wide in the selected colour tft.drawFastHLine(0, i, tft.width(), colour); // in this example tft.width() returns the pixel width of the display } + tft.setRotation(rotation); } diff --git a/examples/ILI9341/TFT_Screen_Capture/processing_sketch.ino b/examples/ILI9341/TFT_Screen_Capture/processing_sketch.ino index 4fafcbd..f904c12 100644 --- a/examples/ILI9341/TFT_Screen_Capture/processing_sketch.ino +++ b/examples/ILI9341/TFT_Screen_Capture/processing_sketch.ino @@ -1,56 +1,51 @@ -// Below is a copy of the processing sketch that can be used to capture the images -// The sketch beow is NOT and Arduino IDE sketch! +// 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. -// Copy the sketch content below and remove the /* and */ at the beginning and end. -// The sketch runs in Processing version 3.3, it can be downloaded here: +// The sketch runs in Processing version 3.3 on a PC, it can be downloaded here: // https://processing.org/download/ -// When the sketch is loaded in Processing, save it as "Screenshot_Client", and click -// the run triangle. Then check the serial port list in the console report, edit the -// Processing sketch to use the right port by changing the port number allocated in -// "int serial_port = X;" at line 26 (see line 43 below) - -// The Arduino IDE and Processing will share a serial port, make sure only one -// program tries to use the port at any time. Processing may "freeze" otherwise. - -/* <<<<<<<<<<<<<<<<<<<<<<<<< REMOVE THIS LINE <<<<<<<<<<<<<<<<<<<<<<<<< +/* // 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. +// 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 library provides a member function that reads the RGB values of screen pixels -// and an example TFT_Screen_Capture +// 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. -// Captured images are stored in the Processing sketch folder, use "Sketch" menu option -// "Show Sketch Folder" or press Ctrl+K in the Processing IDE. +// 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. -// Created by: Bodmer 27/1/17 +// 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 10/3/17 +// Version: 0.06 // MIT licence applies, all text above must be included in derivative works -import processing.serial.*; - -Serial serial; // Create an instance called serial // ########################################################################################### // # These are the values to change for a particular setup # // # int serial_port = 0; // Use enumerated value from list provided when sketch is run # -int serial_baud_rate = 921600; // Maximum tested is 921600 # // # -int tft_width = 240; // TFT width in portrait orientation # -int tft_height = 320; // TFT height # -//int tft_width = 320; // TFT width in landscape orientation # -//int tft_height = 240; // TFT height # +// 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"; // # +String image_type = ".png"; // Lossless compression # //String image_type = ".bmp"; // # //String image_type = ".tif"; // # // # @@ -63,62 +58,62 @@ int max_images = 10; // Maximum of numbered saved images before over-writing fil // # End of the values to change for a particular setup # // ########################################################################################### -int serialCount = 0; // Count of colour bytes arriving +// 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) -int bgcolor = 255; // Background color +import processing.serial.*; -PImage img, tft_img; +Serial serial; // Create an instance called serial -color light_blue = color(50, 128, 255); +int serialCount = 0; // Count of colour bytes arriving -int[] rgb = new int[6]; // Buffer for the RGB colour bytes +// 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; + +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; -long end = 10; // Whether we've heard from the microcontroller - -int n = 0; // Whether we've heard from the microcontroller - -boolean got_image = false; +int n = 0; int x_offset = (500 - tft_width) /2; // Image offsets in the window -int y_offset = 20; // -int xpos, ypos; // Pixel position +int y_offset = 20; + +int xpos = 0, ypos = 0; // Current pixel position int beginTime = 0; -int pixelWaitTime = 100; // Wait a maximum of 100ms gap for image pixels to arrive +int pixelWaitTime = 1000; // Maximum 1000ms wait for image pixels to arrive int lastPixelTime = 0; // Time that "image send" command was sent int state = 0; // State machine current state -int progress_bar = 0; -int pixel_count = 0; -float percentage = 0; +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 drawLoopCount = 0; +int saved_image_count = 0; // Stats - number of images processed +int bad_image_count = 0; // Stats - number of images that had lost pixels + +int drawLoopCount = 0; // Used for the fade out void setup() { - size(500, 540); // Stage size, could handle 480 pixel scrren + 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 - img = createImage(500, 540, ARGB); - for (int i = 0; i < img.pixels.length; i++) { - float a = map(i, 0, img.pixels.length, 255, 0); - img.pixels[i] = color(0, 153, 204, a); - } + // Graded background and title + drawWindow(); - tft_img = createImage(tft_width, tft_height, ARGB); - for (int i = 0; i < tft_img.pixels.length; i++) { - tft_img.pixels[i] = color(0, 0, 0, 255); - } - - frameRate(5000); // High frame rate so draw() loops fast - - xpos = 0; - ypos = 0; + frameRate(2000); // High frame rate so draw() loops fast // Print a list of the available serial ports println("-----------------------"); @@ -133,164 +128,93 @@ void setup() { String portName = Serial.list()[serial_port]; - delay(1000); - serial = new Serial(this, portName, serial_baud_rate); state = 99; } void draw() { - drawLoopCount++; switch(state) { case 0: // Init varaibles, send start request - tint(255, 255); - textAlign(CENTER); - textSize(20); - + tint(0, 0, 0, 255); + println(); + flushBuffer(); println(""); - //println("Clearing pipe..."); - beginTime = millis() + 200; - while ( millis() < beginTime ) - { - serial.read(); - } - println("Ready to receive image"); - serial.write("S"); + print("Ready: "); + xpos = 0; ypos = 0; serialCount = 0; progress_bar = 0; pixel_count = 0; percentage = 0; - drawLoopCount = 0; + drawLoopCount = frameCount; lastPixelTime = millis() + 1000; + state = 1; break; case 1: // Console message, give server some time - println("Requesting image"); - delay(10); + print("requesting image "); + serial.write("S"); + delay(10); + beginTime = millis() + 1000; state = 2; break; - case 2: // Get size and set start time for render time report - // To do: Read image size info, currently hard coded - beginTime = millis(); - state = 3; - break; - - case 3: // Request pixels and reder them - if ( serial.available() > 0 ) { - - // Add the latest byte from the serial port to array: - while (serial.available()>0) - { - rgb[serialCount] = serial.read(); - serialCount++; - - // If we have 3 colour bytes: - if (serialCount >= 3 ) { - serialCount = 0; - pixel_count++; - stroke(rgb[indexRed], rgb[indexGreen], rgb[indexBlue]); - point(xpos + x_offset, ypos + y_offset); - lastPixelTime = millis(); - xpos++; - if (xpos >= tft_width) { - xpos = 0; - print("."); - progress_bar++; - if (progress_bar >31) - { - progress_bar = 0; - percentage = 0.5 + 100 * pixel_count/(0.001 + tft_width * tft_height); - if (percentage > 100) percentage = 100; - println(" [ " + (int)percentage + "% ]"); - } - ypos++; - if (ypos>=tft_height) { - ypos = 0; - println("Image fetch time = " + (millis()-beginTime)/1000.0 + " s"); - state = 5; - } - } - } - } - } else - { - - if (millis() > (lastPixelTime + pixelWaitTime)) - { - println(""); - System.err.println("No response, trying again..."); - state = 4; - } else - { - // Request 64 more pixels (ESP8266 buffer size) - serial.write("RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"); - serial.write("RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"); - } + case 2: // Get size and set start time for rendering duration report + if (millis() > beginTime) { + System.err.println(" - no response!"); + state = 0; + } + if ( getSize() == true ) { // Go to next state when we have the size and bits per pixel + flushBuffer(); // Precaution in case image header size increases in later versions + beginTime = millis(); + state = 3; } break; - case 4: // Time-out, flush serial buffer + 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 + break; + + case 4: // Pixel receive time-out, flush serial buffer println(); - //println("Clearing serial pipe after a time-out"); - int clearTime = millis() + 50; - while ( millis() < clearTime ) - { - serial.read(); - } - state = 0; + flushBuffer(); + state = 6; break; - case 5: // Save the image tot he sketch folder - println(); - String filename = "tft_screen_" + n + image_type; - println("Saving image as \"" + filename); // Does not execute - 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); - } - - n = n + 1; - if (n>9) n = 0; - drawLoopCount = 0; // Reset value ready for counting in step 6 + 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 - delay(10); - if (fade) - { - tint(255, drawLoopCount); - image(tft_img, x_offset, y_offset); - } - if (drawLoopCount > 50) state = 0; // Wait for fade to end + if ( fadedImage() == true ) state = 0; // Go to next state when image has faded break; case 99: // Draw image viewer window - textAlign(CENTER); - textSize(20); - background(bgcolor); - image(img, 0, 0); - - fill(0); - text("Bodmer's TFT image viewer", width/2, height-10); - - stroke(0, 0, 0); - - rect(x_offset - border, y_offset - border, tft_width - 1 + 2*border, tft_height - 1 + 2*border); - - fill(100); - rect(x_offset, y_offset, tft_width-1, tft_height-1); - + drawWindow(); + delay(50); // Delay here seems to be required for the IDE console to get ready state = 0; break; @@ -301,4 +225,186 @@ void draw() { } } -*/ // <<<<<<<<<<<<<<<<<<<<<<<<< REMOVE THIS LINE <<<<<<<<<<<<<<<<<<<<<<<<< +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); +} + +void flushBuffer() +{ + //println("Clearing serial pipe after a time-out"); + int clearTime = millis() + 50; + while ( millis() < clearTime ) serial.read(); +} + +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; +} + +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 + { + stroke( (rgb[1] & 0x1F)<<3, (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[0] & 0xF8)); + //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); +} + +void saveScreenshot() +{ + println(); + String filename = "tft_screen_" + n + 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); + } + + n = n + 1; + if (n>=max_images) n = 0; +} + +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; +} + + +*/ diff --git a/examples/ILI9341/TFT_Screen_Capture/screenServer.ino b/examples/ILI9341/TFT_Screen_Capture/screenServer.ino index 911750b..4c9ecbf 100644 --- a/examples/ILI9341/TFT_Screen_Capture/screenServer.ino +++ b/examples/ILI9341/TFT_Screen_Capture/screenServer.ino @@ -1,68 +1,62 @@ -// TFT screenshot server +// 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); -// This is a sketch support tab containing function calls to read a screen image -// off a TFT and send it to a processing client sketch over the serial port. - -// See the processing_sketch tab, it contains a copy of the processing sketch. - -// Use a high baud rate, for an ESP8266: -/* - Serial.begin(921600); -*/ -// 240 x 320 images take about 3.5s to transfer at 921600 baud(minimum is ~2.5s) +// 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 +// Version: 0.06 -// The MIT permissive free software license applies, include all text above in -// derivatives. +// MIT licence applies, all text above must be included in derivative works -#define BAUD_RATE 250000 // Maximum Arduino IDE Serial Monitor rate -#define DUMP_BAUD_RATE 921600 // Rate used for screen dumps by ESP8266 -#define PIXEL_TIMEOUT 100 // 100ms Time-out between pixel requests -#define START_TIMEOUT 10000 // 10s Maximum time to wait at start transfer +#define BAUD_RATE 250000 // Maximum Serial Monitor rate for other messages +#define DUMP_BAUD_RATE 921600 // Rate used for screen dumps + +#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 + +// Number of pixels to send in a burst (minimum of 1), no benefit above 8 +// NPIXELS values and render times: 1 = 5.0s, 2 = 1.75s, 4 = 1.68s, 8 = 1.67s +#define NPIXELS 8 // Must be integer division of both TFT width and TFT height + // Start a screen dump server (serial or network) boolean screenServer(void) { Serial.end(); // Stop the serial port (clears buffers too) Serial.begin(DUMP_BAUD_RATE); // Force baud rate to be high - yield(); - + delay(0); // Equivalent to yield() for ESP8266; + boolean result = serialScreenServer(); // Screenshot serial port server - //boolean result = wifiDump(); // Screenshot WiFi UDP port server (WIP) - + //boolean result = wifiScreenServer(); // Screenshot WiFi UDP port server (WIP) + Serial.end(); // Stop the serial port (clears buffers too) Serial.begin(BAUD_RATE); // Return baud rate to normal - yield(); + delay(0); // Equivalent to yield() for ESP8266; //Serial.println(); //if (result) Serial.println(F("Screen dump passed :-)")); //else Serial.println(F("Screen dump failed :-(")); - + return result; } // Screenshot serial port server (Processing sketch acts as client) boolean serialScreenServer(void) { - // Serial commands from client: - // 'S' to start the transfer process (To do: reply with width + height) - // 'R' or any character except 'X' to request pixel - // 'X' to abort and return immediately to caller - // Returned boolean values: - // true = image despatched OK - // false = time-out or abort command received // Precautionary receive buffer garbage flush for 50ms uint32_t clearTime = millis() + 50; - while ( millis() < clearTime ) { - Serial.read(); - yield(); - } + 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 @@ -70,7 +64,7 @@ boolean serialScreenServer(void) // Wait for the starting flag with a start time-out while (wait) { - yield(); + delay(0); // Equivalent to yield() for ESP8266; // Check serial buffer if (Serial.available() > 0) { // Read the command byte @@ -79,21 +73,21 @@ boolean serialScreenServer(void) if ( cmd == 'S' ) { // Precautionary receive buffer garbage flush for 50ms clearTime = millis() + 50; - while ( millis() < clearTime ) { - Serial.read(); - yield(); - } + 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, not supported by processing sketch yet - //Serial.write('W'); - //Serial.write(tft.width() >> 8); - //Serial.write(tft.width() & 0xFF); - //Serial.write('H'); - //Serial.write(tft.height() >> 8); - //Serial.write(tft.height() & 0xFF); - //Serial.write('Y'); + // Send screen size using a simple header with delimiters for client checks + Serial.write('W'); + Serial.write(tft.width() >> 8); + Serial.write(tft.width() & 0xFF); + Serial.write('H'); + Serial.write(tft.height() >> 8); + Serial.write(tft.height() & 0xFF); + Serial.write('Y'); + Serial.write(BITS_PER_PIXEL); + Serial.write('?'); } } else @@ -103,49 +97,49 @@ boolean serialScreenServer(void) } } - uint8_t color[3]; // RGB color buffer for 1 pixel - - // Send all the pixels on the whole screen (typically 5 seconds at 921600 baud) + 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 2 as we send 2 pixels for every byte received - for ( uint32_t x = 0; x < tft.width(); x += 1) + // Increment x by NPIXELS as we send NPIXELS for every byte received + for ( uint32_t x = 0; x < tft.width(); x += NPIXELS) { - yield(); + delay(0); // Equivalent to yield() for ESP8266; // Wait here for serial data to arrive or a time-out elapses while ( Serial.available() == 0 ) { - yield; 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 + // 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(); + 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(); - // Fetch data for N pixels starting at x,y - tft.readRectRGB(x, y, 1, 1, color); - // Send values to client - Serial.write(color[0]); // Pixel 1 red - Serial.write(color[1]); // Pixel 1 green - Serial.write(color[2]); // Pixel 1 blue - //Serial.write(color[3]); // Pixel 2 red - //Serial.write(color[4]); // Pixel 2 green - //Serial.write(color[5]); // Pixel 2 blue +#if defined BITS_PER_PIXEL && BITS_PER_PIXEL >= 24 + // 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 + tft.readRect(x, y, NPIXELS, 1, (uint16_t *)color); + // Send buffer to client + Serial.write(color, 2 * NPIXELS); // Write all pixels in the buffer +#endif } } - - // Receive buffer excess command flush for 50ms - clearTime = millis() + 50; - while ( millis() < clearTime ) Serial.read(); + + Serial.flush(); // Make sure all pixel bytes have been despatched return true; }