Playing around with yet another version of ILI9341_t3 library (ILI9341_t3n)

:eek: - Looks like I have not migrated the SPIN stuff to the master branch :0 -

In the mean time you might try the SPIN branch - WIP-SPIN-1-CLASS

I probably should have archived the earlier master of ILI9341_t3n to use with older SPI implementations...

Does not like that either. BTW i'm using Arduino 1.6.12 and Teensyduino 1.32.
Going to install latest of both.


Code:
In file included from D:\Arduino\hardware\teensy\avr\libraries\ILI9341_t3n/ILI9341_t3n.h:75:0,

                 from C:\Users\Stephen\Box Sync\arduino-1.06\ILI9341\ILI9341_Kurt_Async\ILI9341_Kurt_Async.ino:2:

D:\Arduino\hardware\teensy\avr\libraries\SPIN/SPIN.h: In member function 'bool SPINClass::pinIsMOSI(uint8_t)':

D:\Arduino\hardware\teensy\avr\libraries\SPIN/SPIN.h:56:55: error: 'class SPIClass' has no member named 'pinIsMOSI'

     inline bool pinIsMOSI(uint8_t pin)  {return spi().pinIsMOSI(pin);}

                                                       ^

D:\Arduino\hardware\teensy\avr\libraries\SPIN/SPIN.h: In member function 'bool SPINClass::pinIsMISO(uint8_t)':

D:\Arduino\hardware\teensy\avr\libraries\SPIN/SPIN.h:57:54: error: 'class SPIClass' has no member named 'pinIsMISO'

     inline bool pinIsMISO(uint8_t pin) {return spi().pinIsMISO(pin);}

                                                      ^

D:\Arduino\hardware\teensy\avr\libraries\SPIN/SPIN.h: In member function 'bool SPINClass::pinIsSCK(uint8_t)':

D:\Arduino\hardware\teensy\avr\libraries\SPIN/SPIN.h:58:53: error: 'class SPIClass' has no member named 'pinIsSCK'

     inline bool pinIsSCK(uint8_t pin) {return spi().pinIsSCK(pin);}

                                                     ^

Error compiling for board Teensy 3.6.
 
Last edited:
Sounds like you need the version before Teensyduino 1.37... There is a branch in SPIN for it (Not master)

Actually looks like my default branch (master) for the ILI9341_t3n is setup for this old version... Was in the process of renaming master to a save version and then rename Async support branch to master, which depends on the newer version of SPIN.
 
Got it working on Teensyduino 1.39, or at-least I can get it to compile.
It does not like me calling the Frame buffer, i'm probably doing something wrong now... :/

Think i'm at the point of opening a bottle and getting drunk for the night lol.

Code:
#include "SPI.h"
#include <ILI9341_t3n.h>
#define TFT_DC      9
#define TFT_CS      10
#define TFT_RST     8  // 255 = unused, connect to 3.3V
#define TFT_MOSI    11
#define TFT_SCLK    13
#define TFT_MISO    12

ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);
uint16_t tft_frame_buffer[ILI9341_TFTWIDTH * ILI9341_TFTHEIGHT];



void setup() {

  tft.begin();
  tft.setFrameBuffer(tft_frame_buffer);
  tft.setRotation(1);
  tft.fillScreen(ILI9341_BLACK);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setTextSize(2);
  tft.println("Waiting for Arduino Serial Monitor...");
  tft.updateScreen();
  Serial.begin(9600);
  while (!Serial) ; // wait for Arduino Serial Monitor
  Serial.println("ILI9341 Test!");
  tft.println("Connected!");
  tft.updateScreen();

}


void loop(void) {


}

Code:
C:\Users\Stephen\AppData\Local\Temp\arduino_build_407557\sketch\ILI9341_Kurt_Async.ino.cpp.o: In function `setup':

C:\Users\Stephen\AppData\Local\Temp\arduino_modified_sketch_559641/ILI9341_Kurt_Async.ino:18: undefined reference to `ILI9341_t3n::setFrameBuffer(unsigned short*)'

collect2.exe: error: ld returned 1 exit status

Error compiling for board Teensy 3.6.
 
Yep, going back to main for now, few things I want to work on.

KurtE, is the frame buffer and DMA transfers tied togeather or can we just send commands with DMA. I've seen in the past where large amounts of time are spent just sending commands to the screen, can that be mitigated with DMA Transfers of commands or is the Frame Buffer required? The two big enemies to me are CPU time and Screen Flicker when it comes to the ILI9341.
 
Back on the master ILI9341_T3n, got my screen updating pretty well, numbers are still a PITA when your values get large enough to move things around. Need to figure out how to implement drawing text/numbers from right to left instead of just left to right.

KurtE, i'm going to start playing with touch, will the future addition of the Frame Buffer mess with touch if both share the same pins?

Edit..
Also played with powering the LED Backlight directly from 3.32V. Seems to draw around 78.3mA, if the screen is similar to whats used over at Buydisplay then it has 4 Led's each rated at 20mA @ 3.2V.

Edit2. Updated measured Backlight voltage and current. Crap jumper wires are CRAP!!!!!
 
Last edited:
Sounds good, I still want to cleanup for old versus new versions of the Teensyduino... I know I could probably roll into one version that checks Teensyduino version. Or maybe SPI version and work on both... Probably should, just need to get motivated

Text output: Not sure when you say right to left, if you are talking about things like output in Hebrew or Arabic or ... Or are we simply talking about right justify text?

I have no idea of how much support there is in any of the library for right to left text.

For Right justified numbers, in the past I have done things like, output a number right justified, example for 16 bit numbers assuming <= 65535
Code:
void outputNumberRightJutified(uint6_t num) {
  if (num < 10)             tft.print("    ");
  else if (num < 100)     tft.print("   ");
  else if (num < 1000)   tft.print("  ");
  else if (num < 10000) tft.print(" ");
  tft.print(num, DEC);
}
Again this is a brute force version...

As for Touch and Frame buffer. Again depends on things like:
If you are trying to run the frame buffer update Async, then you should not do any other SPI on the same SPI buss. However as long as the Update is not currently in progress, you should be able to do query to touch device.
This is assuming SPI interface from your description, and that the Touch controller has different chip select pin.
 
Ok, maybe im expecting something that is not implemented.... The below code outputs the same time for drawtimelast regardless of it being before or after the tft.updateScreen();
IR confused :(

EDIT... Ok, added tft.useFrameBuffer(true); and got it from 30.27mS to 6.7mS before the update call but oddly its now 51.5mS vs 30.27mS before I added the buffer after calling update. Both seem to take a little while to setup the buffer.

EDIT2... Yep just confirmed it takes more time(+21mS) to complete the loop using frame buffer then not using it.

EDIT3... Just poking some sections, it takes 635uS to do the WattHours section of the code with the frame buffer and about 2.8mS using no frame buffer.

EDIT4... Another interesting thing. Overclocking to 240Mhz with an SPI of 60Mhz halves the times, the increase in cpu speed only slightly lowers the time it takes to do the operations. Frame buffered only saw about a 20% reduction in time needed to setup the buffer before transmission.

Code:
#include "SPI.h"
#include <ILI9341_t3n.h>


#define TFT_DC      9
#define TFT_CS      10
#define TFT_RST     8  // 255 = unused, connect to 3.3V
#define TFT_MOSI    11
#define TFT_SCLK    13
#define TFT_MISO    12

ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);

float WattHours = 0.000;


void setup() {
  

  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(ILI9341_BLACK);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setTextSize(2);
  tft.println("Waiting for Arduino Serial Monitor...");
  tft.updateScreen();
  Serial.begin(9600);
  while (!Serial) ; // wait for Arduino Serial Monitor
  Serial.println("ILI9341 Test!");
  tft.println("Connected!");
  tft.updateScreen();

    delay(5000);
    tft.fillScreen(ILI9341_BLACK);

 
}
 elapsedMillis drawtimer;
 elapsedMicros drawtime;
 uint32_t drawtimelast;


void loop(void) {


 if(drawtimer > 100){
  drawtime = 0;
  tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
  tft.setTextSize(3);     //Upper Left Side
  tft.setCursor(3, 3);
  tft.print("Q1 ");
  tft.print("4.095");
  tft.setCursor(3, 27);
  tft.print("Q2 ");
  tft.print("4.095");
  tft.setCursor(3, 51);
  tft.print("Q3 ");
  tft.print("4.095");
  tft.setCursor(3, 75);
  tft.print("Q4 ");
  tft.print("4.095");
  tft.setCursor(3, 99);
  tft.print("Q5 ");
  tft.print("4.095");
    tft.setTextSize(4);     //Upper Right Side
    tft.setCursor(175, 3);
    tft.print("81.92");
    tft.setCursor(295, 3);    
    tft.print("V");
    tft.setCursor(150, 36);    
    tft.print("300.00");
    tft.setCursor(295, 36);    
    tft.print("W");
    tft.setCursor(175, 69);
    tft.print("20.48");    
    tft.setCursor(295, 69);    
    tft.print("A");
      tft.setTextSize(2);     //Lower Right Side
      tft.setCursor(3, 125);
      tft.print("DAC0 ");
      tft.print("100");
      tft.print(" %");      
      tft.setCursor(143, 125);
      tft.print("DAC1 ");
      tft.print("100");
      tft.print(" %"); 
      tft.setCursor(3, 145);
      tft.print("FAN ");
      tft.setCursor(63, 145);
      tft.print("100");
      tft.print(" %");      
      tft.setCursor(143, 145);
      tft.print("FAN ");
      tft.setCursor(203, 145);
      tft.print("9999");
      tft.print(" RPM");
      tft.setTextSize(3);
      tft.setCursor(3, 180);   //cursor set here unless one of the if is true.      
      if(WattHours < 10){
      tft.print("   "); }
      else if(WattHours < 100){
      tft.print("  ");        }
      else if (WattHours < 1000){      
      tft.print(" ");           }
      tft.print(WattHours, 3);
      WattHours = WattHours + 1.101;    
      tft.print(" WH");
      tft.setCursor(3, 210);
      tft.print("24H 60M 60S ");
      tft.setTextSize(2);      
      tft.print(drawtimelast);
  drawtimelast = drawtime;
    
  tft.updateScreen();
  drawtimer = 0;
//  drawtimelast = drawtime;
 }

  
}


Edit4 latest code i'm playing with. Added frame buffer and CPU/FBUS/SPI readout to serial.

Code:
#include "SPI.h"
#include <ILI9341_t3n.h>


#define TFT_DC      9
#define TFT_CS      10
#define TFT_RST     8  // 255 = unused, connect to 3.3V
#define TFT_MOSI    11
#define TFT_SCLK    13
#define TFT_MISO    12

ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);

float WattHours = 0.000;


void setup() {
  

  tft.begin();
//  tft.useFrameBuffer(true);
  tft.setRotation(1);
  tft.fillScreen(ILI9341_BLACK);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setTextSize(2);
  tft.println("Waiting for Arduino Serial Monitor...");
  tft.updateScreen();
  Serial.begin(9600);
  while (!Serial) ; // wait for Arduino Serial Monitor
  Serial.println("ILI9341 Test!");
  Serial.print("F_CPU ");
  Serial.print(F_CPU);
  Serial.println(" MHZ");
  Serial.print("F_BUS ");
  Serial.print(F_BUS);
  Serial.println(" MHZ");
  Serial.print("SPICLOCK ");
  Serial.print(F_BUS / 2);
  Serial.println(" MHZ");
 
  tft.println("Connected!");
  tft.updateScreen();

    delay(5000);
    tft.fillScreen(ILI9341_BLACK);

 
}
 elapsedMillis drawtimer;
 elapsedMicros drawtime;
 uint32_t drawtimelast;


void loop(void) {


 if(drawtimer > 100){
  drawtime = 0;
  tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
  tft.setTextSize(3);     //Upper Left Side
  tft.setCursor(3, 3);
  tft.print("Q1 ");
  tft.print("4.095");
  tft.setCursor(3, 27);
  tft.print("Q2 ");
  tft.print("4.095");
  tft.setCursor(3, 51);
  tft.print("Q3 ");
  tft.print("4.095");
  tft.setCursor(3, 75);
  tft.print("Q4 ");
  tft.print("4.095");
  tft.setCursor(3, 99);
  tft.print("Q5 ");
  tft.print("4.095");
    tft.setTextSize(4);     //Upper Right Side
    tft.setCursor(175, 3);
    tft.print("81.92");
    tft.setCursor(295, 3);    
    tft.print("V");
    tft.setCursor(150, 36);    
    tft.print("300.00");
    tft.setCursor(295, 36);    
    tft.print("W");
    tft.setCursor(175, 69);
    tft.print("20.48");    
    tft.setCursor(295, 69);    
    tft.print("A");
      tft.setTextSize(2);     //Lower Right Side
      tft.setCursor(3, 125);
      tft.print("DAC0 ");
      tft.print("100");
      tft.print(" %");      
      tft.setCursor(143, 125);
      tft.print("DAC1 ");
      tft.print("100");
      tft.print(" %"); 
      tft.setCursor(3, 145);
      tft.print("FAN ");
      tft.setCursor(63, 145);
      tft.print("100");
      tft.print(" %");      
      tft.setCursor(143, 145);
      tft.print("FAN ");
      tft.setCursor(203, 145);
      tft.print("9999");
      tft.print(" RPM");
      tft.setTextSize(3);

      tft.setCursor(3, 180);   //cursor set here unless one of the if is true.      
      if(WattHours < 10){
      tft.print("   "); }
      else if(WattHours < 100){
      tft.print("  ");        }
      else if (WattHours < 1000){      
      tft.print(" ");           }
      tft.print(WattHours, 3);
      WattHours = WattHours + 1.101;    
      tft.print(" WH");

      tft.setCursor(3, 210);
      tft.print("24H 60M 60S ");
      tft.setTextSize(2);      
      tft.print(drawtimelast);
      
//  drawtimelast = drawtime;
//  drawtime = 0;    
  tft.updateScreen();
  drawtimer = 0;
  drawtimelast = drawtime;
 }

  
}
 
Last edited:
Here is a more optimized version of my code from this weekend, it removes a lot of the redrawing of things that wont normally change. This saves about 10mS when not using the frame buffer, it only saves like 3mS with the frame buffer, I believe because much of the time used with the frame buffer is sending the data to the screen. Which makes sense since doubling the SPI from 30 to 60Mhz halved the amount of time the frame buffer needed to complete but sadly added an odd screen flicker, almost like the LED's were flickering. Hoping Kurt will have time to help me with DMA at some point.

Ive also added the XPT2046 Touch library and it sends the touch data to the screen and to the attached PC. The worse time I've seen for the touch section is about 84uS. I may stop sending to the PC and just start displaying min/max/current times for the 2 time outputs I'm using since its possible the serial communications are affecting the times.


Code:
#include "SPI.h"
#include <ILI9341_t3n.h>
#include <XPT2046_Touchscreen.h>

#define TFT_DC      9
#define TFT_CS      10
#define TFT_RST     8  // 255 = unused, connect to 3.3V
#define TFT_MOSI    11
#define TFT_SCLK    13
#define TFT_MISO    12

#define CS_PIN  4   //Touch CS
#define TIRQ_PIN  2  //Touch Interupt pin

ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);
XPT2046_Touchscreen ts(CS_PIN, TIRQ_PIN);  // Param 2 - Touch IRQ Pin - interrupt enabled polling


float WattHours = 0.000;


void setup() {
  

  tft.begin();
  ts.begin();
  tft.useFrameBuffer(true);
  tft.setRotation(1);
  tft.fillScreen(ILI9341_BLACK);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setTextSize(2);
  tft.println("Waiting for Arduino Serial Monitor...");
  tft.updateScreen();
  Serial.begin(9600);
  while (!Serial) ; // wait for Arduino Serial Monitor
  Serial.println("ILI9341 Test!");
  Serial.print("F_CPU ");
  Serial.print(F_CPU);
  Serial.println(" MHZ");
  Serial.print("F_BUS ");
  Serial.print(F_BUS);
  Serial.println(" MHZ");
  Serial.print("SPICLOCK ");
  Serial.print(F_BUS / 2);
  Serial.println(" MHZ");
 
  tft.println("Connected!");
  tft.updateScreen();

    delay(5000);
    tft.fillScreen(ILI9341_BLACK);

 
}
 elapsedMillis drawtimer;
 elapsedMillis TouchTimer;
 elapsedMicros drawtime;
 elapsedMicros touchtime;
 uint32_t drawtimelast;
 uint32_t touchtimelast;
 byte debugmenu = 0;
 uint16_t posx = 0;
 uint16_t posy = 0;


void loop(void) {

if(debugmenu == 0){
  tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
  tft.setTextSize(3);     //Upper Left Side
  tft.setCursor(3, 3);
  tft.print("Q1");
  tft.setCursor(3, 27);  
  tft.print("Q2");
  tft.setCursor(3, 51);  
  tft.print("Q3");
  tft.setCursor(3, 75);  
  tft.print("Q4");
  tft.setCursor(3, 99);  
  tft.print("Q5");

    tft.setTextSize(4);     //Upper Right Side
    tft.setCursor(295, 3);    
    tft.print("V");
    tft.setCursor(295, 36);    
    tft.print("W");
    tft.setCursor(295, 69);    
    tft.print("A");
    
      tft.setTextSize(2);     //Lower Right Side
      tft.setCursor(3, 125);
      tft.print("DAC0");
      tft.setCursor(110, 125);
      tft.print("%");    
      tft.setCursor(143, 125);
      tft.print("DAC1");
      tft.setCursor(251, 125);
      tft.print("%"); 

      tft.setCursor(3, 145);
      tft.print("FAN");
      tft.setCursor(110, 145);
      tft.print("%");
      tft.setCursor(143, 145);
      tft.print("FAN");
      tft.setCursor(251, 145);      
      tft.print("RPM");
      
      tft.setTextSize(3);
      tft.setCursor(165, 180);
      tft.print("WH");

      tft.setCursor(39, 210);
      tft.print("H");
      tft.setCursor(103, 210);
      tft.print("M");
      tft.setCursor(167, 210);
      tft.print("S");

      tft.setTextSize(2);
      tft.setCursor(224, 180);            
      tft.print("T");
      tft.setCursor(224, 200);            
      tft.print("X");
      tft.setCursor(224, 220);            
      tft.print("Y");


      
  debugmenu = 1;
}



 if(drawtimer > 100){
  drawtime = 0;
  tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
  tft.setTextSize(3);     //Upper Left Side
  tft.setCursor(50, 3);
  tft.print("4.095");     //Q1
  tft.setCursor(50, 27);
  tft.print("4.095");     //Q2
  tft.setCursor(50, 51);
  tft.print("4.095");     //Q3
  tft.setCursor(50, 75);
  tft.print("4.095");     //Q4
  tft.setCursor(50, 99);
  tft.print("4.095");     //Q5
  drawtimelast = drawtime;  
    tft.setTextSize(4);     //Upper Right Side
    tft.setCursor(175, 3);
    tft.print("81.92");    //V
    tft.setCursor(150, 36);    
    tft.print("300.00");   //W
    tft.setCursor(175, 69);
    tft.print("20.48");    //A
      tft.setTextSize(2);     //Lower Right Side
      tft.setCursor(63, 125);
      tft.print("100");    //DAC0    
      tft.setCursor(203, 125);
      tft.print("100");    //DAC1
      tft.setCursor(63, 145);
      tft.print("100");    //FAN
      tft.setCursor(203, 145);
      tft.print("9999");   //RPM
//drawtime = 0;
      tft.setTextSize(3);
      tft.setCursor(3, 180);   //cursor set here unless one of the if is true.      
      if(WattHours < 10){
      tft.print("   "); }
      else if(WattHours < 100){
      tft.print("  ");        }
      else if (WattHours < 1000){      
      tft.print(" ");           }
      tft.print(WattHours, 3);
      WattHours = WattHours + 0.100;    
//drawtimelast = drawtime;
      tft.setCursor(3, 210);
      tft.print("24");
      tft.setCursor(67, 210);      
      tft.print("60");
      tft.setCursor(131, 210);      
      tft.print("60");
      tft.setTextSize(2);
      
      tft.setCursor(240, 180);            
      tft.print(drawtimelast);
      tft.setCursor(240, 200);            
      tft.print(posx);
      tft.print(" ");
      tft.setCursor(240, 220);            
      tft.print(posy);      
      tft.print(" ");
            
//  drawtimelast = drawtime;
//  drawtime = 0;    
  tft.updateScreen();
  drawtimer = 0;
//  drawtimelast = drawtime;
 }

  if ((ts.touched()) && (TouchTimer > 100)) {
    touchtime = 0;
    TS_Point p = ts.getPoint();
    posx = p.x;
    posy = p.y;
    touchtimelast = touchtime;
    Serial.print("Pressure = ");
    Serial.print(p.z);
    Serial.print(", x = ");
    Serial.print(p.x);
    Serial.print(", y = ");
    Serial.print(p.y);
    Serial.print(", time = ");
    Serial.print(touchtimelast); 
    Serial.println("uS");       

    TouchTimer = 0;
  }



  
}
 
Its ALIVE!!!!!!!!! Reread post 95 and started poking it with a stick.

Ok, after a few hours of banging my keyboard and giving my Teensy funny looks I got Async working. I had several gotcha's, one being I was calling tft.updateScreen(); in several places that would cause the Teensy to freeze in it tracks. Another is that the touch screen appears to be causing the Async to go nuts and lock up the Teensy. Not sure if its noise triggering the interrupt or what.

Loop time is roughly 4.226mS, which is much better then it was. Will have to find ways to improve it if possible.

Edit :( It seems to be unstable, it will randomly stop and have to be restarted. Latest run was about 810 seconds.

Code:
#include "SPI.h"
#include <ILI9341_t3n.h>
//#include <XPT2046_Touchscreen.h>

#define TFT_DC      9
#define TFT_CS      10
#define TFT_RST     8  // 255 = unused, connect to 3.3V
#define TFT_MOSI    11
#define TFT_SCLK    13
#define TFT_MISO    12

#define CS_PIN  4   //Touch CS
#define TIRQ_PIN  2  //Touch Interupt pin

//uint16_t tft_frame_buffer[ILI9341_TFTWIDTH * ILI9341_TFTHEIGHT];

DMAMEM uint16_t ili9341_frame_buffer[ILI9341_TFTWIDTH*ILI9341_TFTHEIGHT];

ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO, &SPIN);
//XPT2046_Touchscreen ts(CS_PIN, TIRQ_PIN);  // Param 2 - Touch IRQ Pin - interrupt enabled polling


float WattHours = 0.000;


void setup() {
  

  tft.begin();
//  ts.begin();
  tft.setFrameBuffer(ili9341_frame_buffer);
  tft.useFrameBuffer(true);  
  tft.setRotation(1);
  tft.fillScreen(ILI9341_BLACK);   
  tft.setTextColor(ILI9341_YELLOW);
  tft.setTextSize(2);
  tft.println("Waiting for Arduino Serial Monitor...");
//  tft.updateScreen();
  tft.updateScreenAsync();
  Serial.begin(9600);
  while (!Serial) ; // wait for Arduino Serial Monitor
  Serial.println("ILI9341 Test!");
  Serial.print("F_CPU ");
  Serial.print(F_CPU);
  Serial.println(" MHZ");
  Serial.print("F_BUS ");
  Serial.print(F_BUS);
  Serial.println(" MHZ");
  Serial.print("SPICLOCK ");
  Serial.print(F_BUS / 2);
  Serial.println(" MHZ");
 
  tft.println("Connected!");
  tft.updateScreenAsync();  
//  tft.updateScreen();

    delay(5000);
    tft.fillScreen(ILI9341_BLACK);
//    tft.updateScreenAsync(); 
 
}
 elapsedMillis drawtimer;
 elapsedMillis TouchTimer;
 elapsedMicros drawtime;
 elapsedMicros touchtime;
 uint32_t drawtimelast;
 uint32_t touchtimelast;
 byte debugmenu = 0;
 uint16_t posx = 0;
 uint16_t posy = 0;


void loop(void) {

if(debugmenu == 0){
  tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
  tft.setTextSize(3);     //Upper Left Side
  tft.setCursor(3, 3);
  tft.print("Q1");
  tft.setCursor(3, 27);  
  tft.print("Q2");
  tft.setCursor(3, 51);  
  tft.print("Q3");
  tft.setCursor(3, 75);  
  tft.print("Q4");
  tft.setCursor(3, 99);  
  tft.print("Q5");

    tft.setTextSize(4);     //Upper Right Side
    tft.setCursor(295, 3);    
    tft.print("V");
    tft.setCursor(295, 36);    
    tft.print("W");
    tft.setCursor(295, 69);    
    tft.print("A");
    
      tft.setTextSize(2);     //Lower Right Side
      tft.setCursor(3, 125);
      tft.print("DAC0");
      tft.setCursor(110, 125);
      tft.print("%");    
      tft.setCursor(143, 125);
      tft.print("DAC1");
      tft.setCursor(251, 125);
      tft.print("%"); 

      tft.setCursor(3, 145);
      tft.print("FAN");
      tft.setCursor(110, 145);
      tft.print("%");
      tft.setCursor(143, 145);
      tft.print("FAN");
      tft.setCursor(251, 145);      
      tft.print("RPM");
      
      tft.setTextSize(3);
      tft.setCursor(165, 180);
      tft.print("WH");

      tft.setCursor(39, 210);
      tft.print("H");
      tft.setCursor(103, 210);
      tft.print("M");
      tft.setCursor(167, 210);
      tft.print("S");

      tft.setTextSize(2);
      tft.setCursor(224, 180);            
      tft.print("T");
      tft.setCursor(224, 200);            
      tft.print("X");
      tft.setCursor(224, 220);            
      tft.print("Y");
  tft.updateScreenAsync();

 TouchTimer = 0;      
  debugmenu = 1;
  drawtimer = 0;
}



 if(drawtimer > 100){
  drawtime = 0;
  tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
  tft.setTextSize(3);     //Upper Left Side
  tft.setCursor(50, 3);
  tft.print("4.095");     //Q1
  tft.setCursor(50, 27);
  tft.print("4.095");     //Q2
  tft.setCursor(50, 51);
  tft.print("4.095");     //Q3
  tft.setCursor(50, 75);
  tft.print("4.095");     //Q4
  tft.setCursor(50, 99);
  tft.print("4.095");     //Q5
//  drawtimelast = drawtime;  
    tft.setTextSize(4);     //Upper Right Side
    tft.setCursor(175, 3);
    tft.print("81.92");    //V
    tft.setCursor(150, 36);    
    tft.print("300.00");   //W
    tft.setCursor(175, 69);
    tft.print("20.48");    //A
      tft.setTextSize(2);     //Lower Right Side
      tft.setCursor(63, 125);
      tft.print("100");    //DAC0    
      tft.setCursor(203, 125);
      tft.print("100");    //DAC1
      tft.setCursor(63, 145);
      tft.print("100");    //FAN
      tft.setCursor(203, 145);
      tft.print("9999");   //RPM
//drawtime = 0;
      tft.setTextSize(3);
      tft.setCursor(3, 180);   //cursor set here unless one of the if is true.      
      if(WattHours < 10){
      tft.print("   "); }
      else if(WattHours < 100){
      tft.print("  ");        }
      else if (WattHours < 1000){      
      tft.print(" ");           }
      tft.print(WattHours, 3);
      WattHours = WattHours + 0.100;    
//drawtimelast = drawtime;
      tft.setCursor(3, 210);
      tft.print("24");
      tft.setCursor(67, 210);      
      tft.print("60");
      tft.setCursor(131, 210);      
      tft.print("60");
      tft.setTextSize(2);
      
      tft.setCursor(240, 180);            
      tft.print(drawtimelast);
      tft.setCursor(240, 200);            
      tft.print(posx);
      tft.print(" ");
      tft.setCursor(240, 220);            
      tft.print(posy);      
      tft.print(" ");
            
//  drawtimelast = drawtime;
//  drawtime = 0;  
     tft.updateScreenAsync();
//  tft.updateScreen();
  drawtimer = 0;
  drawtimelast = drawtime;
 }
/*
  if ((ts.touched()) && (TouchTimer > 1000)) {
    touchtime = 0;
    TS_Point p = ts.getPoint();
    posx = p.x;
    posy = p.y;
    touchtimelast = touchtime;
    Serial.print("Pressure = ");
    Serial.print(p.z);
    Serial.print(", x = ");
    Serial.print(p.x);
    Serial.print(", y = ");
    Serial.print(p.y);
    Serial.print(", time = ");
    Serial.print(touchtimelast); 
    Serial.println("uS");       

    TouchTimer = 0;
  }
*/


  
}
 
Just catching this - saw touch. With DMA xfer the touchscreen should be on another SPI ideally? With interrupt if the DMA is off you can then call the Touch test and use the same SPI bus for that.

I haven't looked at this in a long time and not the DMA - using the INT pin should work - but as soon as touch is detected with INT it will immediately hit the bus to get the points.

It might be nice - when INT is in use - to return the value of this through a new interface?:: isrWake. Then you could check that value ( always true when no INT ) - and when true a TOUCH interrupt was detected - and the next .update will poll SPI for touch data.
 
Right now I have Touch all commented out, trying to figure out why its unstable, I left it running and did some other things and found it locked up again, this time it got to 1053 seconds.

Edit.. And just stopped at 935 Seconds, oddly the Watt Hours value I'm using as a timer is incrementing strange. The tenths of a second is going but im seing hundredths and thousands also, but they should not be changing from zero.....

Going to set it to a lower CPU rate and leave it for the night, it locks up almost instantly if I overclock to 240Mhz.
 
Last edited:
I saw it was commented. You could stop the drawing and test with TOUCH only and then know the bus is healthy - without int pin it will continuously poll on each update - when touched points data could go out serial?
 
The Touch and ILI9341 are healthy when not using the DMA Async. I tried increasing the Touchtimer to 1000 Seconds and it still froze a few second in so i'm wondering if the library is doing something to the SPI while DMA is running, I noticed a few times early on that I was receiving touch data over serial, which should only occur if the IRQ pin is firing, which it should not since I was not touching the screen. Worst case I move touch back to another SPI like I originally planned, for now I want to keep touch off and focus on the DMA Async and why it keeps freezing.

I'm not sure why the thing just stops when touch is disabled, I leave it running and come back and its stuck, left it running at 144Mhz and it was stuck around 3400 seconds. At 180Mhz it sticks around the 900-1100 Second mark. I just tried allocating most of the memory and seeing if it stopped sooner, that had no effect. I increased the refresh rate from 10hz to 12.5hz, which oddly seems to have increased the amount of time needed before it freezes......

Removed the XPT connections to the SPI going to the LCD, seems to have helped, going to leave it running while i'm at work and see if its still going it 8-9 hours.
 
Do you have a simple test, like what is running in #110, that I should try to see if it reproduces? Hopefully using the PJRC touch screen? i.e. Those I have...
 
Do you have a simple test, like what is running in #110, that I should try to see if it reproduces? Hopefully using the PJRC touch screen? i.e. Those I have...

The code in #110 should work provided you use the Async branch and make a few changes. I tried creating an issue on Github but screwed it up because of the way they have us posting code. And they wont let me edit my original post.....

I'm hoping that the screen is still running when I get home today, if so that means its some kind of noise issue. Which would be weird since the none Async version ran for hours at 48Mhz SPI with no issue. Yet the Async DMA is falling on its face at 30Mhz.
 
Came home, found it at 4063 seconds. If I try to use any of the 3 overclock levels it at the most gets 1 screen update after serial monitor opens before locking up. (192, 216 and 240Mhz)

Running a none async version with 240Mhz OC to see if it will lock up after a while.

Kurt does the DMA get feedback from the screen or is it a 1 way transfer?
 
Sorry, I have been outside all day playing with Tractors, box blade, ... (Working on trails...)

I am seeing lockups as well. Will investigate.

I do see some issues in the test code...

That is suppose you do an updateAsync()...

Then right after that you issues some other tft.draw... command. The problem will be the Async draw may not be done and as such you are writing to the same pixels you are updating to screen. So can get partial updates... And obviously should not try to start an updateAsync if another one is active.

Also doing anything with SPI while the Async update is active will likely screw up SPI as the DMA is setup... The code is in a beginTransaction at the time. When the DMA completes it then does the endTransaction.

There is a functions you can call to see if a dma Transaction is active or not. likewise wait for them. But occasionally I have seen these fail... Not sure if there is a compile issue as the code is comparing to variable that is volatile... Will look some more.

In the CPP file there is a #define DEBUG_ASYNC_UPDATE that is commented out that prints lots of debug stuff.

The dma stuff only goes to the screen, we are not looking at what is coming back from MISO pin.
 
Totally alright Kurt, everyone has a life :)

So far its up to 8000 Seconds using the normal Non Async code. Going to let it run overnight.
Ya, updateAsync() was one of the first issues I ran into, it took me a few tries to figure out why it kept freezing due to things I left in. I had a few old tft.updateScreen(); commands I forgot to remove, those also caused it to lock up. Im using the elapsedMillis drawtimer; to avoid it but its possible that for whatever reason a transfer takes much longer and they collide. From what I can tell at 30Mhz it should take about 41mS to update the full buffer to the screen.
 
Ran all night using tft.updateScreen(); , I just tried using the #define DEBUG_ASYNC_UPDATE with 240Mhz, I get 2 dumps from it and it freezes, it never even makes it to displaying numbers. Using it at 180Mhz lasted a little longer.... 1-50 seconds at 500mS updates. 2 printouts below.

If I change SPI to 15Mhz it does not last any longer then 30Mhz. It all seems somewhat random, it does not freeze in the same spot with multiple tests at different clocks for CPU and SPI. It also only lasts a few seconds with the CPU at 180, FBUS at 90Mhz and SPI at 45Mhz.

Going to run it at 120Mhz/30Mhz SPI all day and see what it does. It does not seem to be SPI reliant except when it takes to long to update and the next update collides with the existing one. Something is tripping the DMA up since it does not seem to matter if its updating at 10Hz or 2Hz when its at 180Mhz it will randomly freeze within the first minute.



240Mhz tft.updateScreenAsync();
Code:
DMA dump TCDs 2
20016c20 40009040:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0010 
20016b20 20016b40:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0010 
20016b60 20016b80:1fffca00 1010002 2 ffff3800 4002c034 64000000 20016bc0 64000010 
20016ba0 20016bc0:20009200 1010002 2 ffff3800 4002c034 64000000 20016b40 6400001a 
20016be0 20016c00:1fff0200 1010002 2 fffffffe 4002c034 10000 20016b40 10010 
ILI9341 Test!
F_CPU 240000000 MHZ
F_BUS 60000000 MHZ
SPICLOCK 30000000 MHZ
DMA dump TCDs 2
20016c20 40009040:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0090 
20016b20 20016b40:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0010 
20016b60 20016b80:1fffca00 1010002 2 ffff3800 4002c034 64000000 20016bc0 64000010 
20016ba0 20016bc0:20009200 1010002 2 ffff3800 4002c034 64000000 20016b40 6400001a 
20016be0 20016c00:1fff0200 1010002 2 fffffffe 4002c034 10000 20016b40 10010


180Mhz 500mS tft.updateScreenAsync();
Code:
DMA dump TCDs 2
20016c20 40009040:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0010 
20016b20 20016b40:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0010 
20016b60 20016b80:1fffca00 1010002 2 ffff3800 4002c034 64000000 20016bc0 64000010 
20016ba0 20016bc0:20009200 1010002 2 ffff3800 4002c034 64000000 20016b40 6400001a 
20016be0 20016c00:1fff0200 1010002 2 fffffffe 4002c034 10000 20016b40 10010 
ILI9341 Test!
F_CPU 180000000 MHZ
F_BUS 60000000 MHZ
SPICLOCK 30000000 MHZ
DMA dump TCDs 2
20016c20 40009040:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0090 
20016b20 20016b40:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0010 
20016b60 20016b80:1fffca00 1010002 2 ffff3800 4002c034 64000000 20016bc0 64000010 
20016ba0 20016bc0:20009200 1010002 2 ffff3800 4002c034 64000000 20016b40 6400001a 
20016be0 20016c00:1fff0200 1010002 2 fffffffe 4002c034 10000 20016b40 10010 
DMA dump TCDs 2
20016c20 40009040:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0090 
20016b20 20016b40:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0010 
20016b60 20016b80:1fffca00 1010002 2 ffff3800 4002c034 64000000 20016bc0 64000010 
20016ba0 20016bc0:20009200 1010002 2 ffff3800 4002c034 64000000 20016b40 6400001a 
20016be0 20016c00:1fff0200 1010002 2 fffffffe 4002c034 10000 20016b40 10010 
DMA dump TCDs 2
20016c20 40009040:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0090 
20016b20 20016b40:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0010 
20016b60 20016b80:1fffca00 1010002 2 ffff3800 4002c034 64000000 20016bc0 64000010 
20016ba0 20016bc0:20009200 1010002 2 ffff3800 4002c034 64000000 20016b40 6400001a 
20016be0 20016c00:1fff0200 1010002 2 fffffffe 4002c034 10000 20016b40 10010 
DMA dump TCDs 2
20016c20 40009040:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0090 
20016b20 20016b40:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0010 
20016b60 20016b80:1fffca00 1010002 2 ffff3800 4002c034 64000000 20016bc0 64000010 
20016ba0 20016bc0:20009200 1010002 2 ffff3800 4002c034 64000000 20016b40 6400001a 
20016be0 20016c00:1fff0200 1010002 2 fffffffe 4002c034 10000 20016b40 10010 
DMA dump TCDs 2
20016c20 40009040:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0090 
20016b20 20016b40:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0010 
20016b60 20016b80:1fffca00 1010002 2 ffff3800 4002c034 64000000 20016bc0 64000010 
20016ba0 20016bc0:20009200 1010002 2 ffff3800 4002c034 64000000 20016b40 6400001a 
20016be0 20016c00:1fff0200 1010002 2 fffffffe 4002c034 10000 20016b40 10010 
DMA dump TCDs 2
20016c20 40009040:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0090 
20016b20 20016b40:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0010 
20016b60 20016b80:1fffca00 1010002 2 ffff3800 4002c034 64000000 20016bc0 64000010 
20016ba0 20016bc0:20009200 1010002 2 ffff3800 4002c034 64000000 20016b40 6400001a 
20016be0 20016c00:1fff0200 1010002 2 fffffffe 4002c034 10000 20016b40 10010 
DMA dump TCDs 2
20016c20 40009040:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0090 
20016b20 20016b40:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0010 
20016b60 20016b80:1fffca00 1010002 2 ffff3800 4002c034 64000000 20016bc0 64000010 
20016ba0 20016bc0:20009200 1010002 2 ffff3800 4002c034 64000000 20016b40 6400001a 
20016be0 20016c00:1fff0200 1010002 2 fffffffe 4002c034 10000 20016b40 10010 
DMA dump TCDs 2
20016c20 40009040:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0090 
20016b20 20016b40:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0010 
20016b60 20016b80:1fffca00 1010002 2 ffff3800 4002c034 64000000 20016bc0 64000010 
20016ba0 20016bc0:20009200 1010002 2 ffff3800 4002c034 64000000 20016b40 6400001a 
20016be0 20016c00:1fff0200 1010002 2 fffffffe 4002c034 10000 20016b40 10010 
DMA dump TCDs 2
20016c20 40009040:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0090 
20016b20 20016b40:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0010 
20016b60 20016b80:1fffca00 1010002 2 ffff3800 4002c034 64000000 20016bc0 64000010 
20016ba0 20016bc0:20009200 1010002 2 ffff3800 4002c034 64000000 20016b40 6400001a 
20016be0 20016c00:1fff0200 1010002 2 fffffffe 4002c034 10000 20016b40 10010 
DMA dump TCDs 2
20016c20 40009040:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0090 
20016b20 20016b40:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0010 
20016b60 20016b80:1fffca00 1010002 2 ffff3800 4002c034 64000000 20016bc0 64000010 
20016ba0 20016bc0:20009200 1010002 2 ffff3800 4002c034 64000000 20016b40 6400001a 
20016be0 20016c00:1fff0200 1010002 2 fffffffe 4002c034 10000 20016b40 10010 
DMA dump TCDs 2
20016c20 40009040:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0090 
20016b20 20016b40:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0010 
20016b60 20016b80:1fffca00 1010002 2 ffff3800 4002c034 64000000 20016bc0 64000010 
20016ba0 20016bc0:20009200 1010002 2 ffff3800 4002c034 64000000 20016b40 6400001a 
20016be0 20016c00:1fff0200 1010002 2 fffffffe 4002c034 10000 20016b40 10010 
DMA dump TCDs 2
20016c20 40009040:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0090 
20016b20 20016b40:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0010 
20016b60 20016b80:1fffca00 1010002 2 ffff3800 4002c034 64000000 20016bc0 64000010 
20016ba0 20016bc0:20009200 1010002 2 ffff3800 4002c034 64000000 20016b40 6400001a 
20016be0 20016c00:1fff0200 1010002 2 fffffffe 4002c034 10000 20016b40 10010 
DMA dump TCDs 2
20016c20 40009040:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0090 
20016b20 20016b40:1fff0202 1010002 2 ffff3802 4002c034 63ff0000 20016b80 63ff0010 
20016b60 20016b80:1fffca00 1010002 2 ffff3800 4002c034 64000000 20016bc0 64000010 
20016ba0 20016bc0:20009200 1010002 2 ffff3800 4002c034 64000000 20016b40 6400001a 
20016be0 20016c00:1fff0200 1010002 2 fffffffe 4002c034 10000 20016b40 10010
 
I will play around some more today. Have more trail work to do...

When I tried it yesterday with the debug printing on, your updates appeared to be working fine for at least 10 minutes... (I stopped it then to try other things)... I think there is a timing issue, where some state is getting set or cleared in appropriately... I tried fixing one such issue that is most likely not it... Where I:
Would do something like: <Start dma> followed by setting variable saying dma is active.
The dma interrupt: Clears the variable... But what happens if the interrupt happens before the code returns to initially set that variable... Should not happen, but better to set first and then start dma...

Change did not help your case. Next up. I am going to hook up logic analyzer, add some digitalWriteFast calls to key functions and see if anything looks obvious.
 
:D - Hopefully a fix!

Try Sync to update on github...

It appears like the code was hanging in the end DMA interrupt handler. Issue was that when it got here we wanted to push a NULL command onto the stack as to release the CS pins if it is hardware CS and also remove the CONT state...

Problem was if the SPI queue was full when we did this it screwed up... So wait for queue not full before push... Your slightly modified program is now running on my board at 180... So far for 445...

Most of my mods are for IO pin differences. But did add in a few calls to wait for display to complete before doing any more updates...

Code:
#include "SPI.h"
#include <ILI9341_t3n.h>
//#include <XPT2046_Touchscreen.h>

#define KURTS_FLEXI
#ifdef KURTS_FLEXI
#define TFT_DC 22
#define TFT_CS 15
#define TFT_RST -1
#define TFT_SCLK 14
#define TFT_MISO 12
#define TFT_MOSI 7
#define DEBUG_PIN 13

#define CS_PIN  8   //Touch CS
#define TIRQ_PIN  3  //Touch Interupt pin
#else
#define TFT_DC      9
#define TFT_CS      10
#define TFT_RST     8  // 255 = unused, connect to 3.3V
#define TFT_MOSI    11
#define TFT_SCLK    13
#define TFT_MISO    12
#define CS_PIN  4   //Touch CS
#define TIRQ_PIN  2  //Touch Interupt pin
#endif


//uint16_t tft_frame_buffer[ILI9341_TFTWIDTH * ILI9341_TFTHEIGHT];

DMAMEM uint16_t ili9341_frame_buffer[ILI9341_TFTWIDTH * ILI9341_TFTHEIGHT];

ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO, &SPIN);
//XPT2046_Touchscreen ts(CS_PIN, TIRQ_PIN);  // Param 2 - Touch IRQ Pin - interrupt enabled polling


float WattHours = 0.000;


void setup() {


  tft.begin();
  //  ts.begin();
  tft.setFrameBuffer(ili9341_frame_buffer);
  tft.useFrameBuffer(true);
  tft.setRotation(1);
  tft.fillScreen(ILI9341_BLACK);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setTextSize(2);
  tft.println("Waiting for Arduino Serial Monitor...");
  //  tft.updateScreen();
  tft.updateScreenAsync();
  Serial.begin(9600);
  while (!Serial) ; // wait for Arduino Serial Monitor
  Serial.println("ILI9341 Test!");
  Serial.print("F_CPU ");
  Serial.print(F_CPU);
  Serial.println(" MHZ");
  Serial.print("F_BUS ");
  Serial.print(F_BUS);
  Serial.println(" MHZ");
  Serial.print("SPICLOCK ");
  Serial.print(F_BUS / 2);
  Serial.println(" MHZ");

  tft.waitUpdateAsyncComplete() ;
  tft.println("Connected!");
  tft.updateScreenAsync();
  //  tft.updateScreen();
  Serial.println("Before Delay");
  delay(1000);
  Serial.println("After Delay");
  tft.waitUpdateAsyncComplete() ;
  tft.fillScreen(ILI9341_BLACK);
  Serial.println("Before Update async");
  tft.updateScreenAsync();
  Serial.println("End Setup");
}
elapsedMillis drawtimer;
elapsedMillis TouchTimer;
elapsedMicros drawtime;
elapsedMicros touchtime;
uint32_t drawtimelast;
uint32_t touchtimelast;
byte debugmenu = 0;
uint16_t posx = 0;
uint16_t posy = 0;


void loop(void) {

  if (debugmenu == 0) {
    Serial.println("Before Wait Async");
    tft.waitUpdateAsyncComplete() ;
    //tft.tft.waitUpdateAsyncComplete();  // Don't change stuff until any async update has completed
    tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
    tft.setTextSize(3);     //Upper Left Side
    tft.setCursor(3, 3);
    tft.print("Q1");
    tft.setCursor(3, 27);
    tft.print("Q2");
    tft.setCursor(3, 51);
    tft.print("Q3");
    tft.setCursor(3, 75);
    tft.print("Q4");
    tft.setCursor(3, 99);
    tft.print("Q5");

    tft.setTextSize(4);     //Upper Right Side
    tft.setCursor(295, 3);
    tft.print("V");
    tft.setCursor(295, 36);
    tft.print("W");
    tft.setCursor(295, 69);
    tft.print("A");

    tft.setTextSize(2);     //Lower Right Side
    tft.setCursor(3, 125);
    tft.print("DAC0");
    tft.setCursor(110, 125);
    tft.print("%");
    tft.setCursor(143, 125);
    tft.print("DAC1");
    tft.setCursor(251, 125);
    tft.print("%");

    tft.setCursor(3, 145);
    tft.print("FAN");
    tft.setCursor(110, 145);
    tft.print("%");
    tft.setCursor(143, 145);
    tft.print("FAN");
    tft.setCursor(251, 145);
    tft.print("RPM");

    tft.setTextSize(3);
    tft.setCursor(165, 180);
    tft.print("WH");

    tft.setCursor(39, 210);
    tft.print("H");
    tft.setCursor(103, 210);
    tft.print("M");
    tft.setCursor(167, 210);
    tft.print("S");

    tft.setTextSize(2);
    tft.setCursor(224, 180);
    tft.print("T");
    tft.setCursor(224, 200);
    tft.print("X");
    tft.setCursor(224, 220);
    tft.print("Y");
    Serial.println("Before update async menu");
    tft.updateScreenAsync();
    //tft.updateScreen();

    TouchTimer = 0;
    debugmenu = 1;
    drawtimer = 0;
  }



  if (drawtimer > 100) {
    drawtime = 0;
    tft.waitUpdateAsyncComplete() ;
    tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
    tft.setTextSize(3);     //Upper Left Side
    tft.setCursor(50, 3);
    tft.print("4.095");     //Q1
    tft.setCursor(50, 27);
    tft.print("4.095");     //Q2
    tft.setCursor(50, 51);
    tft.print("4.095");     //Q3
    tft.setCursor(50, 75);
    tft.print("4.095");     //Q4
    tft.setCursor(50, 99);
    tft.print("4.095");     //Q5
    //  drawtimelast = drawtime;
    tft.setTextSize(4);     //Upper Right Side
    tft.setCursor(175, 3);
    tft.print("81.92");    //V
    tft.setCursor(150, 36);
    tft.print("300.00");   //W
    tft.setCursor(175, 69);
    tft.print("20.48");    //A
    tft.setTextSize(2);     //Lower Right Side
    tft.setCursor(63, 125);
    tft.print("100");    //DAC0
    tft.setCursor(203, 125);
    tft.print("100");    //DAC1
    tft.setCursor(63, 145);
    tft.print("100");    //FAN
    tft.setCursor(203, 145);
    tft.print("9999");   //RPM
    //drawtime = 0;
    tft.setTextSize(3);
    tft.setCursor(3, 180);   //cursor set here unless one of the if is true.
    if (WattHours < 10) {
      tft.print("   ");
    }
    else if (WattHours < 100) {
      tft.print("  ");
    }
    else if (WattHours < 1000) {
      tft.print(" ");
    }
    tft.print(WattHours, 3);
    WattHours = WattHours + 0.100;
    //drawtimelast = drawtime;
    tft.setCursor(3, 210);
    tft.print("24");
    tft.setCursor(67, 210);
    tft.print("60");
    tft.setCursor(131, 210);
    tft.print("60");
    tft.setTextSize(2);

    tft.setCursor(240, 180);
    tft.print(drawtimelast);
    tft.setCursor(240, 200);
    tft.print(posx);
    tft.print(" ");
    tft.setCursor(240, 220);
    tft.print(posy);
    tft.print(" ");

    //  drawtimelast = drawtime;
    //  drawtime = 0;
    tft.updateScreenAsync();
    //  tft.updateScreen();
    drawtimer = 0;
    drawtimelast = drawtime;
  }
  /*
    if ((ts.touched()) && (TouchTimer > 1000)) {
      touchtime = 0;
      TS_Point p = ts.getPoint();
      posx = p.x;
      posy = p.y;
      touchtimelast = touchtime;
      Serial.print("Pressure = ");
      Serial.print(p.z);
      Serial.print(", x = ");
      Serial.print(p.x);
      Serial.print(", y = ");
      Serial.print(p.y);
      Serial.print(", time = ");
      Serial.print(touchtimelast);
      Serial.println("uS");

      TouchTimer = 0;
    }
  */
}
Hopefully it works for you... Sorry about that.

Edit: Still running, counter is now over 4142...
 
Last edited:
Nice! I actually came home and it was still running from dis-morning, its over 35K seconds and still going.
Have you noticed the odd counting of the Watt hours Float, after a short while it starts producing what I think is rounding errors.

Replaced Serial.print(F_BUS / 2); with Serial.print(ILI9341_SPICLOCK); ,gives much better results. Its funny when you do something simple like that and then spend 30 minutes scratching your head trying to figure out why #define ILI9341_SPICLOCK 60000000 isn't doing anything lol.


:D Its on FIRE!!!!!
Code:
ILI9341 Test!
F_CPU 240000000 MHZ
F_BUS 120000000 MHZ
SPICLOCK 60000000 MHZ



Here is the modified Async .cpp i'm using. Without those it wont compile using the latest Teensy Loader (1.39) and the &SPIN call inside
Code:
ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO, &SPIN);
.

ILI9341_t3n.cpp Async
Code:
// https://github.com/PaulStoffregen/ILI9341_t3n
// http://forum.pjrc.com/threads/26305-Highly-optimized-ILI9341-(320x240-TFT-color-display)-library

/***************************************************
  This is our library for the Adafruit ILI9341 Breakout and Shield
  ----> http://www.adafruit.com/products/1651

  Check out the links above for our tutorials and wiring diagrams
  These displays use SPI to communicate, 4 or 5 pins are required to
  interface (RST is optional)
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 ****************************************************/
// <SoftEgg>

//Additional graphics routines by Tim Trzepacz, SoftEgg LLC added December 2015
//(And then accidentally deleted and rewritten March 2016. Oops!)
//Gradient support
//----------------
//		fillRectVGradient	- fills area with vertical gradient
//		fillRectHGradient	- fills area with horizontal gradient
//		fillScreenVGradient - fills screen with vertical gradient
// 	fillScreenHGradient - fills screen with horizontal gradient

//Additional Color Support
//------------------------
//		color565toRGB		- converts 565 format 16 bit color to RGB
//		color565toRGB14		- converts 16 bit 565 format color to 14 bit RGB (2 bits clear for math and sign)
//		RGB14tocolor565		- converts 14 bit RGB back to 16 bit 565 format color

//Low Memory Bitmap Support
//-------------------------
// 		writeRect8BPP - 	write 8 bit per pixel paletted bitmap
// 		writeRect4BPP - 	write 4 bit per pixel paletted bitmap
// 		writeRect2BPP - 	write 2 bit per pixel paletted bitmap
// 		writeRect1BPP - 	write 1 bit per pixel paletted bitmap

//TODO: transparent bitmap writing routines for sprites

//String Pixel Length support
//---------------------------
//		strPixelLen			- gets pixel length of given ASCII string

// <\SoftEgg>

#include "ILI9341_t3n.h"
#include <SPIN.h>
#include <SPI.h>  

//#define DEBUG_ASYNC_UPDATE  // Enable to print out dma info
//#define DEBUG_ASYNC_LEDS	// Enable to use digitalWrites to Debug
#ifdef DEBUG_ASYNC_LEDS
#define DEBUG_PIN_1 0
#define DEBUG_PIN_2 1
#define DEBUG_PIN_3 4
#endif

#ifdef ENABLE_ILI9341_FRAMEBUFFER
DMASetting 	ILI9341_t3n::_dmasettings[4];
DMAChannel 	ILI9341_t3n::_dmatx;

DMAChannel 	_dmatx;
ILI9341_t3n *ILI9341_t3n::_dmaActiveDisplay = 0;
volatile uint8_t  	ILI9341_t3n::_dma_state = 0;  // Use pointer to this as a way to get back to object...
volatile uint32_t	ILI9341_t3n::_dma_frame_count = 0;	// Can return a frame count...

void ILI9341_t3n::dmaInterrupt(void) {
	//Serial.println("DMA Interrupt");
	///  digitalWriteFast(1,!digitalReadFast(1));
#ifdef DEBUG_ASYNC_LEDS
	digitalWriteFast(DEBUG_PIN_2, HIGH);
#endif
	_dma_frame_count++;
	_dmatx.clearInterrupt();

	// See if we are in continuous mode or not..
	if ((_dma_state & ILI9341_DMA_CONT) == 0) {
		// We are in single refresh mode or the user has called cancel so
		// Lets try to release the CS pin
		_dmaActiveDisplay->_pspin->waitFifoNotFull();
		_dmaActiveDisplay->writecommand_last(ILI9341_NOP);
		_dmaActiveDisplay->endSPITransaction();
		_dma_state &= ~ILI9341_DMA_ACTIVE;
		_dmaActiveDisplay = 0;	// We don't have a display active any more... 
	}
#ifdef DEBUG_ASYNC_LEDS
	digitalWriteFast(DEBUG_PIN_2, LOW);
#endif
}

#endif
// Teensy 3.1 can only generate 30 MHz SPI when running at 120 MHz (overclock)

#define WIDTH  ILI9341_TFTWIDTH
#define HEIGHT ILI9341_TFTHEIGHT

// Constructor when using hardware ILI9241_KINETISK__pspi->  Faster, but must use SPI pins
// specific to each board type (e.g. 11,13 for Uno, 51,52 for Mega, etc.)
ILI9341_t3n::ILI9341_t3n(uint8_t cs, uint8_t dc, uint8_t rst, 
	uint8_t mosi, uint8_t sclk, uint8_t miso, SPINClass *pspin )
{
	_cs   = cs;
	_dc   = dc;
	_rst  = rst;
	_mosi = mosi;
	_sclk = sclk;
	_miso = miso;
	_width    = WIDTH;
	_height   = HEIGHT;
	_pspin	  = pspin; 
#ifdef KINETISK	
	_pkinetisk_spi = _pspin->kinetisk_spi();
#else
	_pkinetisl_spi = _pspin->kinetisl_spi();
#endif		

	rotation  = 0;
	cursor_y  = cursor_x    = 0;
	textsize  = 1;
	textcolor = textbgcolor = 0xFFFF;
	wrap      = true;
	font      = NULL;
	setClipRect();
	setOrigin();

	// Added to see how much impact actually using non hardware CS pin might be
    _cspinmask = 0;
    _csport = NULL;

	#ifdef ENABLE_ILI9341_FRAMEBUFFER
    _pfbtft = NULL;	
    _use_fbtft = 0;						// Are we in frame buffer mode?
	_we_allocated_buffer = 0;
    #endif

}

//=======================================================================
// Add optinal support for using frame buffer to speed up complex outputs
//=======================================================================
#define CBALLOC (ILI9341_TFTHEIGHT*ILI9341_TFTWIDTH*2)
void ILI9341_t3n::setFrameBuffer(uint16_t *frame_buffer) 
{
	#ifdef ENABLE_ILI9341_FRAMEBUFFER
	_pfbtft = frame_buffer;
	if (_pfbtft != NULL) {
		memset(_pfbtft, 0, ILI9341_TFTHEIGHT*ILI9341_TFTWIDTH*2);
	}

	#endif	
}

uint8_t ILI9341_t3n::useFrameBuffer(boolean b)		// use the frame buffer?  First call will allocate
{
	#ifdef ENABLE_ILI9341_FRAMEBUFFER
	if (b) {
		// First see if we need to allocate buffer
		if (_pfbtft == NULL) {
			_pfbtft = (uint16_t *)malloc(CBALLOC);
			if (_pfbtft == NULL)
				return 0;	// failed 
			memset(_pfbtft, 0, CBALLOC);	
			_we_allocated_buffer = 1;
		}
		_use_fbtft = 1;
	} else 
		_use_fbtft = 0;

	return _use_fbtft;	
	#else
	return 0;
	#endif
}

void ILI9341_t3n::freeFrameBuffer(void)						// explicit call to release the buffer
{
	#ifdef ENABLE_ILI9341_FRAMEBUFFER
	if (_we_allocated_buffer && (_pfbtft != NULL)) {
		_pfbtft = NULL;
		_use_fbtft = 0;	// make sure the use is turned off
		_we_allocated_buffer = 0;
	}
	#endif
}
void ILI9341_t3n::updateScreen(void)					// call to say update the screen now.
{
	// Not sure if better here to check flag or check existence of buffer.
	// Will go by buffer as maybe can do interesting things?
	#ifdef ENABLE_ILI9341_FRAMEBUFFER
	if (_use_fbtft) {
		beginSPITransaction();
		if (_standard) {
			// Doing full window. 
			setAddr(0, 0, _width-1, _height-1);
			writecommand_cont(ILI9341_RAMWR);

			// BUGBUG doing as one shot.  Not sure if should or not or do like
			// main code and break up into transactions...
			uint16_t *pfbtft_end = &_pfbtft[(ILI9341_TFTWIDTH*ILI9341_TFTHEIGHT)-1];	// setup 
			uint16_t *pftbft = _pfbtft;

			// Quick write out the data;
			while (pftbft < pfbtft_end) {
				writedata16_cont(*pftbft++);
			}
			writedata16_last(*pftbft);
		} else {
			// setup just to output the clip rectangle area. 
			setAddr(_displayclipx1, _displayclipy1, _displayclipx2-1, _displayclipy2-1);
			writecommand_cont(ILI9341_RAMWR);

			// BUGBUG doing as one shot.  Not sure if should or not or do like
			// main code and break up into transactions...
			uint16_t * pfbPixel_row = &_pfbtft[ _displayclipy1*_width + _displayclipx1];
			for (uint16_t y = _displayclipy1; y < _displayclipy2; y++) {
				uint16_t * pfbPixel = pfbPixel_row;
				for (uint16_t x = _displayclipx1; x < (_displayclipx2-1); x++) {
					writedata16_cont(*pfbPixel++);
				}
				if (y < (_displayclipy2-1))
					writedata16_cont(*pfbPixel);
				else	
					writedata16_last(*pfbPixel);
				pfbPixel_row += _width;	// setup for the next row. 
			}
		}
		endSPITransaction();
	}
	#endif
}			 

#ifdef DEBUG_ASYNC_UPDATE
void dumpDMA_TCD(DMABaseClass *dmabc)
{
	Serial.printf("%x %x:", (uint32_t)dmabc, (uint32_t)dmabc->TCD);

	const uint32_t *p = (const uint32_t *)dmabc->TCD;

	for (uint8_t i=0; i<8; i++) {
		Serial.printf("%x ", *p++);
	}
	Serial.println();
}
#endif

void	ILI9341_t3n::initDMASettings(void) 
{
//	Serial.printf("initDMASettings called %d\n", ili9341_t3n_dma_has_been_init);
	if (_dma_state) {  // should test for init, but...
		return;	// we already init this. 
	}

	//Serial.println("InitDMASettings");

	uint8_t dmaTXevent = _pspin->dmaTXEvent();

	// BUGBUG:: check for -1 as wont work on SPI2 on T3.5
//	uint16_t *fbtft_start_dma_addr = _pfbtft;
	uint32_t count_words_write = (CBALLOC/SCREEN_DMA_NUM_SETTINGS)/2; // Note I know the divide will give whole number
	
	//Serial.printf("CWW: %d %d %d\n", CBALLOC, SCREEN_DMA_NUM_SETTINGS, count_words_write);
	// Now lets setup DMA access to this memory... 
	_dmasettings[0].sourceBuffer(&_pfbtft[1], (count_words_write-1)*2);
	_dmasettings[0].destination(_pkinetisk_spi->PUSHR);

	// Hack to reset the destination to only output 2 bytes.
	_dmasettings[0].TCD->ATTR_DST = 1;
	_dmasettings[0].replaceSettingsOnCompletion(_dmasettings[1]);

	_dmasettings[1].sourceBuffer(&_pfbtft[count_words_write], count_words_write*2);
	_dmasettings[1].destination(_pkinetisk_spi->PUSHR);
	_dmasettings[1].TCD->ATTR_DST = 1;
	_dmasettings[1].replaceSettingsOnCompletion(_dmasettings[2]);

	_dmasettings[2].sourceBuffer(&_pfbtft[count_words_write*2], count_words_write*2);
	_dmasettings[2].destination(_pkinetisk_spi->PUSHR);
	_dmasettings[2].TCD->ATTR_DST = 1;
	_dmasettings[2].replaceSettingsOnCompletion(_dmasettings[3]);

	// Sort of hack - but wrap around to output the first word again. 
	_dmasettings[3].sourceBuffer(_pfbtft, 2);
	_dmasettings[3].destination(_pkinetisk_spi->PUSHR);
	_dmasettings[3].TCD->ATTR_DST = 1;
	_dmasettings[3].replaceSettingsOnCompletion(_dmasettings[0]);

	// Setup DMA main object
	//Serial.println("Setup _dmatx");
	_dmatx.begin(true);
	_dmatx.triggerAtHardwareEvent(dmaTXevent);
	_dmatx = _dmasettings[0];
	_dmatx.attachInterrupt(dmaInterrupt);
	_dma_state = ILI9341_DMA_INIT;  // Should be first thing set!
}


bool ILI9341_t3n::updateScreenAsync(bool update_cont)					// call to say update the screen now.
{
	// Not sure if better here to check flag or check existence of buffer.
	// Will go by buffer as maybe can do interesting things?
	// BUGBUG:: only handles full screen so bail on the rest of it...
	#ifdef ENABLE_ILI9341_FRAMEBUFFER
	if (!_use_fbtft) return false;

#ifdef DEBUG_ASYNC_LEDS
	digitalWriteFast(DEBUG_PIN_1, HIGH);
#endif
	// Init DMA settings. 
	initDMASettings();

	// Don't start one if already active.
	if (_dma_state & ILI9341_DMA_ACTIVE) {
	#ifdef DEBUG_ASYNC_LEDS
		digitalWriteFast(DEBUG_PIN_1, LOW);
	#endif
		return false;
	}

	if (update_cont) {
		// Try to link in #3 into the chain
		_dmasettings[2].replaceSettingsOnCompletion(_dmasettings[3]);
		_dmasettings[2].TCD->CSR &= ~(DMA_TCD_CSR_INTMAJOR | DMA_TCD_CSR_DREQ);  // Don't interrupt on this one... 
		_dmasettings[3].interruptAtCompletion();
		_dmasettings[3].TCD->CSR &= ~(DMA_TCD_CSR_DREQ);  // Don't disable on this one  
		_dma_state |= ILI9341_DMA_CONT;
	} else {
		// In this case we will only run through once...
		_dmasettings[2].replaceSettingsOnCompletion(_dmasettings[0]);
		_dmasettings[2].interruptAtCompletion();
		_dmasettings[2].disableOnCompletion();
		_dma_state &= ~ILI9341_DMA_CONT;
	}


#ifdef DEBUG_ASYNC_UPDATE
	Serial.printf("DMA dump TCDs %d\n", _dmatx.channel);
	dumpDMA_TCD(&_dmatx);
	dumpDMA_TCD(&_dmasettings[0]);
	dumpDMA_TCD(&_dmasettings[1]);
	dumpDMA_TCD(&_dmasettings[2]);
	dumpDMA_TCD(&_dmasettings[3]);
#endif
	beginSPITransaction();

	// Doing full window. 
	setAddr(0, 0, _width-1, _height-1);
	writecommand_cont(ILI9341_RAMWR);

	// Write the first Word out before enter DMA as to setup the proper CS/DC/Continue flaugs
	writedata16_cont(*_pfbtft);
	// now lets start up the DMA
//	volatile uint16_t  biter = _dmatx.TCD->BITER;
	//DMA_CDNE_CDNE(_dmatx.channel);
//	_dmatx = _dmasettings[0];
//	_dmatx.TCD->BITER = biter;
	_dma_frame_count = 0;  // Set frame count back to zero. 
	_dmaActiveDisplay = this;
	_dma_state |= ILI9341_DMA_ACTIVE;
	_pkinetisk_spi->RSER |= SPI_RSER_TFFF_DIRS |	 SPI_RSER_TFFF_RE;	 // Set DMA Interrupt Request Select and Enable register
	_pkinetisk_spi->MCR &= ~SPI_MCR_HALT;  //Start transfers.
	_dmatx.enable();
#ifdef DEBUG_ASYNC_LEDS
	digitalWriteFast(DEBUG_PIN_1, LOW);
#endif
	return true;
    #else
    return false;     // no frame buffer so will never start... 
	#endif

}			 

void ILI9341_t3n::endUpdateAsync() {
	// make sure it is on
	#ifdef ENABLE_ILI9341_FRAMEBUFFER
	if (_dma_state & ILI9341_DMA_CONT) {
		_dma_state &= ~ILI9341_DMA_CONT; // Turn of the continueous mode
		_dmasettings[3].disableOnCompletion();
	}
	#endif
}
	
void ILI9341_t3n::waitUpdateAsyncComplete(void) 
{
	#ifdef ENABLE_ILI9341_FRAMEBUFFER
#ifdef DEBUG_ASYNC_LEDS
	digitalWriteFast(DEBUG_PIN_3, HIGH);
#endif

	while ((_dma_state & ILI9341_DMA_ACTIVE)) {
		// asm volatile("wfi");
	};
#ifdef DEBUG_ASYNC_LEDS
	digitalWriteFast(DEBUG_PIN_3, LOW);
#endif
	#endif	
}

//=======================================================================


void ILI9341_t3n::setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
	beginSPITransaction();
	setAddr(x0, y0, x1, y1);
	writecommand_last(ILI9341_RAMWR); // write to RAM
	endSPITransaction();
}

void ILI9341_t3n::pushColor(uint16_t color)
{
	beginSPITransaction();
	writedata16_last(color);
	endSPITransaction();
}

void ILI9341_t3n::drawPixel(int16_t x, int16_t y, uint16_t color) {
	x += _originx;
	y += _originy;
	if((x < _displayclipx1) ||(x >= _displayclipx2) || (y < _displayclipy1) || (y >= _displayclipy2)) return;

	#ifdef ENABLE_ILI9341_FRAMEBUFFER
	if (_use_fbtft) {
		_pfbtft[y*_width + x] = color;

	} else 
	#endif
	{
		beginSPITransaction();
		setAddr(x, y, x, y);
		writecommand_cont(ILI9341_RAMWR);
		writedata16_last(color);
		endSPITransaction();
	}
}

void ILI9341_t3n::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color)
{
	x+=_originx;
	y+=_originy;
	// Rectangular clipping
	if((x < _displayclipx1) || (x >= _displayclipx2) || (y >= _displayclipy2)) return;
	if(y < _displayclipy1) { h = h - (_displayclipy1 - y); y = _displayclipy1;}
	if((y+h-1) >= _displayclipy2) h = _displayclipy2-y;
	if(h<1) return;

	#ifdef ENABLE_ILI9341_FRAMEBUFFER
	if (_use_fbtft) {
		uint16_t * pfbPixel = &_pfbtft[ y*_width + x];
		while (h--) {
			*pfbPixel = color;
			pfbPixel += _width;
		}
	} else 
	#endif
	{
		beginSPITransaction();
		setAddr(x, y, x, y+h-1);
		writecommand_cont(ILI9341_RAMWR);
		while (h-- > 1) {
			writedata16_cont(color);
		}
		writedata16_last(color);
		endSPITransaction();
	}
}

void ILI9341_t3n::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color)
{
	x+=_originx;
	y+=_originy;

	// Rectangular clipping
	if((y < _displayclipy1) || (x >= _displayclipx2) || (y >= _displayclipy2)) return;
	if(x<_displayclipx1) { w = w - (_displayclipx1 - x); x = _displayclipx1; }
	if((x+w-1) >= _displayclipx2)  w = _displayclipx2-x;
	if (w<1) return;

	#ifdef ENABLE_ILI9341_FRAMEBUFFER
	if (_use_fbtft) {
		if ((x&1) || (w&1)) {
			uint16_t * pfbPixel = &_pfbtft[ y*_width + x];
			while (w--) {
				*pfbPixel++ = color;
			}
		} else {
			// X is even and so is w, try 32 bit writes..
			uint32_t color32 = (color << 16) | color;
			uint32_t * pfbPixel = (uint32_t*)((uint16_t*)&_pfbtft[ y*_width + x]);
			while (w) {
				*pfbPixel++ = color32;
				w -= 2;
			}
		}
	} else 
	#endif
	{
		beginSPITransaction();
		setAddr(x, y, x+w-1, y);
		writecommand_cont(ILI9341_RAMWR);
		while (w-- > 1) {
			writedata16_cont(color);
		}
		writedata16_last(color);
		endSPITransaction();
	}
}

void ILI9341_t3n::fillScreen(uint16_t color)
{
	#ifdef ENABLE_ILI9341_FRAMEBUFFER
	if (_use_fbtft && _standard) {
		// Speed up lifted from Franks DMA code... _standard is if no offsets and rects..
		uint32_t color32 = (color << 16) | color;

		uint32_t *pfbPixel = (uint32_t *)_pfbtft;
		uint32_t *pfbtft_end = (uint32_t *)((uint16_t *)&_pfbtft[(ILI9341_TFTWIDTH * ILI9341_TFTHEIGHT)]); // setup
		while (pfbPixel < pfbtft_end) {
			*pfbPixel++ = color32; *pfbPixel++ = color32; *pfbPixel++ = color32;*pfbPixel++ = color32;
			*pfbPixel++ = color32; *pfbPixel++ = color32; *pfbPixel++ = color32;*pfbPixel++ = color32;
			*pfbPixel++ = color32; *pfbPixel++ = color32; *pfbPixel++ = color32;*pfbPixel++ = color32;
			*pfbPixel++ = color32; *pfbPixel++ = color32; *pfbPixel++ = color32;*pfbPixel++ = color32;
			*pfbPixel++ = color32; *pfbPixel++ = color32; *pfbPixel++ = color32;*pfbPixel++ = color32;
			*pfbPixel++ = color32; *pfbPixel++ = color32; *pfbPixel++ = color32;*pfbPixel++ = color32;
			*pfbPixel++ = color32; *pfbPixel++ = color32; *pfbPixel++ = color32;*pfbPixel++ = color32;
			*pfbPixel++ = color32; *pfbPixel++ = color32; *pfbPixel++ = color32;*pfbPixel++ = color32;
		}

	} else 
	#endif
	{
		fillRect(0, 0, _width, _height, color);
	}
}

// fill a rectangle
void ILI9341_t3n::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color)
{
	x+=_originx;
	y+=_originy;

	// Rectangular clipping (drawChar w/big text requires this)
	if((x >= _displayclipx2) || (y >= _displayclipy2)) return;
	if (((x+w) <= _displayclipx1) || ((y+h) <= _displayclipy1)) return;
	if(x < _displayclipx1) {	w -= (_displayclipx1-x); x = _displayclipx1; 	}
	if(y < _displayclipy1) {	h -= (_displayclipy1 - y); y = _displayclipy1; 	}
	if((x + w - 1) >= _displayclipx2)  w = _displayclipx2  - x;
	if((y + h - 1) >= _displayclipy2) h = _displayclipy2 - y;

	#ifdef ENABLE_ILI9341_FRAMEBUFFER
	if (_use_fbtft) {
		if ((x&1) || (w&1)) {
			uint16_t * pfbPixel_row = &_pfbtft[ y*_width + x];
			for (;h>0; h--) {
				uint16_t * pfbPixel = pfbPixel_row;
				for (int i = 0 ;i < w; i++) {
					*pfbPixel++ = color;
				}
				pfbPixel_row += _width;
			}
		} else {
			// Horizontal is even number so try 32 bit writes instead
			uint32_t color32 = (color << 16) | color;
			uint32_t * pfbPixel_row = (uint32_t *)((uint16_t*)&_pfbtft[ y*_width + x]);
			w = w/2;	// only iterate half the times
			for (;h>0; h--) {
				uint32_t * pfbPixel = pfbPixel_row;
				for (int i = 0 ;i < w; i++) {
					*pfbPixel++ = color32;
				}
				pfbPixel_row += (_width/2);
			}
		}
	} else 
	#endif
	{

		// TODO: this can result in a very long transaction time
		// should break this into multiple transactions, even though
		// it'll cost more overhead, so we don't stall other SPI libs
		beginSPITransaction();
		setAddr(x, y, x+w-1, y+h-1);
		writecommand_cont(ILI9341_RAMWR);
		for(y=h; y>0; y--) {
			for(x=w; x>1; x--) {
				writedata16_cont(color);
			}
			writedata16_last(color);
			if (y > 1 && (y & 1)) {
				endSPITransaction();
				beginSPITransaction();
			}
		}
		endSPITransaction();
	}
}

// fillRectVGradient	- fills area with vertical gradient
void ILI9341_t3n::fillRectVGradient(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color1, uint16_t color2)
{
	x+=_originx;
	y+=_originy;

	// Rectangular clipping 
	if((x >= _displayclipx2) || (y >= _displayclipy2)) return;
	if(x < _displayclipx1) {	w -= (_displayclipx1-x); x = _displayclipx1; 	}
	if(y < _displayclipy1) {	h -= (_displayclipy1 - y); y = _displayclipy1; 	}
	if((x + w - 1) >= _displayclipx2)  w = _displayclipx2  - x;
	if((y + h - 1) >= _displayclipy2) h = _displayclipy2 - y;
	
	int16_t r1, g1, b1, r2, g2, b2, dr, dg, db, r, g, b;
	color565toRGB14(color1,r1,g1,b1);
	color565toRGB14(color2,r2,g2,b2);
	dr=(r2-r1)/h; dg=(g2-g1)/h; db=(b2-b1)/h;
	r=r1;g=g1;b=b1;	

	#ifdef ENABLE_ILI9341_FRAMEBUFFER
	if (_use_fbtft) {
		if ((x&1) || (w&1)) {
			uint16_t * pfbPixel_row = &_pfbtft[ y*_width + x];
			for (;h>0; h--) {
				uint16_t color = RGB14tocolor565(r,g,b);
				uint16_t * pfbPixel = pfbPixel_row;
				for (int i = 0 ;i < w; i++) {
					*pfbPixel++ = color;
				}
				r+=dr;g+=dg; b+=db;
				pfbPixel_row += _width;
			}
		} else {
			// Horizontal is even number so try 32 bit writes instead
			uint32_t * pfbPixel_row = (uint32_t *)((uint16_t*)&_pfbtft[ y*_width + x]);
			w = w/2;	// only iterate half the times
			for (;h>0; h--) {
				uint32_t * pfbPixel = pfbPixel_row;
				uint16_t color = RGB14tocolor565(r,g,b);
				uint32_t color32 = (color << 16) | color;
				for (int i = 0 ;i < w; i++) {
					*pfbPixel++ = color32;
				}
				pfbPixel_row += (_width/2);
				r+=dr;g+=dg; b+=db;
			}
		}
	} else 
	#endif
	{		
		beginSPITransaction();
		setAddr(x, y, x+w-1, y+h-1);
		writecommand_cont(ILI9341_RAMWR);
		for(y=h; y>0; y--) {
			uint16_t color = RGB14tocolor565(r,g,b);

			for(x=w; x>1; x--) {
				writedata16_cont(color);
			}
			writedata16_last(color);
			if (y > 1 && (y & 1)) {
				endSPITransaction();
				beginSPITransaction();
			}
			r+=dr;g+=dg; b+=db;
		}
		endSPITransaction();
	}
}

// fillRectHGradient	- fills area with horizontal gradient
void ILI9341_t3n::fillRectHGradient(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color1, uint16_t color2)
{
	x+=_originx;
	y+=_originy;

	// Rectangular clipping 
	if((x >= _displayclipx2) || (y >= _displayclipy2)) return;
	if(x < _displayclipx1) {	w -= (_displayclipx1-x); x = _displayclipx1; 	}
	if(y < _displayclipy1) {	h -= (_displayclipy1 - y); y = _displayclipy1; 	}
	if((x + w - 1) >= _displayclipx2)  w = _displayclipx2  - x;
	if((y + h - 1) >= _displayclipy2) h = _displayclipy2 - y;
	
	int16_t r1, g1, b1, r2, g2, b2, dr, dg, db, r, g, b;
	uint16_t color;
	color565toRGB14(color1,r1,g1,b1);
	color565toRGB14(color2,r2,g2,b2);
	dr=(r2-r1)/w; dg=(g2-g1)/w; db=(b2-b1)/w;
	r=r1;g=g1;b=b1;	
	#ifdef ENABLE_ILI9341_FRAMEBUFFER
	if (_use_fbtft) {
		uint16_t * pfbPixel_row = &_pfbtft[ y*_width + x];
		for (;h>0; h--) {
			uint16_t * pfbPixel = pfbPixel_row;
			for (int i = 0 ;i < w; i++) {
				*pfbPixel++ = RGB14tocolor565(r,g,b);
				r+=dr;g+=dg; b+=db;
			}
			pfbPixel_row += _width;
			r=r1;g=g1;b=b1;
		}
	} else 
	#endif
	{
		beginSPITransaction();
		setAddr(x, y, x+w-1, y+h-1);
		writecommand_cont(ILI9341_RAMWR);
		for(y=h; y>0; y--) {
			for(x=w; x>1; x--) {
				color = RGB14tocolor565(r,g,b);
				writedata16_cont(color);
				r+=dr;g+=dg; b+=db;
			}
			color = RGB14tocolor565(r,g,b);
			writedata16_last(color);
			if (y > 1 && (y & 1)) {
				endSPITransaction();
				beginSPITransaction();
			}
			r=r1;g=g1;b=b1;
		}
		endSPITransaction();
	}
}

// fillScreenVGradient - fills screen with vertical gradient
void ILI9341_t3n::fillScreenVGradient(uint16_t color1, uint16_t color2)
{
	fillRectVGradient(0,0,_width,_height,color1,color2);
}

// fillScreenHGradient - fills screen with horizontal gradient
void ILI9341_t3n::fillScreenHGradient(uint16_t color1, uint16_t color2)
{
	fillRectHGradient(0,0,_width,_height,color1,color2);
}


#define MADCTL_MY  0x80
#define MADCTL_MX  0x40
#define MADCTL_MV  0x20
#define MADCTL_ML  0x10
#define MADCTL_RGB 0x00
#define MADCTL_BGR 0x08
#define MADCTL_MH  0x04

void ILI9341_t3n::setRotation(uint8_t m)
{
	rotation = m % 4; // can't be higher than 3
	beginSPITransaction();
	writecommand_cont(ILI9341_MADCTL);
	switch (rotation) {
	case 0:
		writedata8_last(MADCTL_MX | MADCTL_BGR);
		_width  = ILI9341_TFTWIDTH;
		_height = ILI9341_TFTHEIGHT;
		break;
	case 1:
		writedata8_last(MADCTL_MV | MADCTL_BGR);
		_width  = ILI9341_TFTHEIGHT;
		_height = ILI9341_TFTWIDTH;
		break;
	case 2:
		writedata8_last(MADCTL_MY | MADCTL_BGR);
		_width  = ILI9341_TFTWIDTH;
		_height = ILI9341_TFTHEIGHT;
		break;
	case 3:
		writedata8_last(MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR);
		_width  = ILI9341_TFTHEIGHT;
		_height = ILI9341_TFTWIDTH;
		break;
	}
	endSPITransaction();
	setClipRect();
	setOrigin();
	
	cursor_x = 0;
	cursor_y = 0;
}

void ILI9341_t3n::setScroll(uint16_t offset)
{
	beginSPITransaction();
	writecommand_cont(ILI9341_VSCRSADD);
	writedata16_last(offset);
	endSPITransaction();
}

void ILI9341_t3n::invertDisplay(boolean i)
{
	beginSPITransaction();
	writecommand_last(i ? ILI9341_INVON : ILI9341_INVOFF);
	endSPITransaction();
}










/*
uint8_t ILI9341_t3n::readdata(void)
{
  uint8_t r;
       // Try to work directly with SPI registers...
       // First wait until output queue is empty
        uint16_t wTimeout = 0xffff;
        while (((_pkinetisk_spi->SR) & (15 << 12)) && (--wTimeout)) ; // wait until empty
        
//       	_pkinetisk_spi->MCR |= SPI_MCR_CLR_RXF; // discard any received data
//		_pkinetisk_spi->SR = SPI_SR_TCF;
        
        // Transfer a 0 out... 
        writedata8_cont(0);   
        
        // Now wait until completed. 
        wTimeout = 0xffff;
        while (((_pkinetisk_spi->SR) & (15 << 12)) && (--wTimeout)) ; // wait until empty
        r = _pkinetisk_spi->POPR;  // get the received byte... should check for it first...
    return r;
}
 */

uint8_t ILI9341_t3n::readcommand8(uint8_t c, uint8_t index)
{
    // Bail if not valid miso
    if (_miso == 0xff) return 0;

 #ifdef KINETISK
    uint16_t wTimeout = 0xffff;
    uint8_t r=0;


    beginSPITransaction();
	if (_pspin->sizeFIFO() >= 4) {
	    while (((_pkinetisk_spi->SR) & (15 << 12)) && (--wTimeout)) ; // wait until empty

	    // Make sure the last frame has been sent...
	    _pkinetisk_spi->SR = SPI_SR_TCF;   // dlear it out;
	    wTimeout = 0xffff;
	    while (!((_pkinetisk_spi->SR) & SPI_SR_TCF) && (--wTimeout)) ; // wait until it says the last frame completed

	    // clear out any current received bytes
	    wTimeout = 0x10;    // should not go more than 4...
	    while ((((_pkinetisk_spi->SR) >> 4) & 0xf) && (--wTimeout))  {
	        r = _pkinetisk_spi->POPR;
	    }

	    //writecommand(0xD9); // sekret command
		_pkinetisk_spi->PUSHR = 0xD9 | (pcs_command << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT;
	//	while (((_pkinetisk_spi->SR) & (15 << 12)) > (3 << 12)) ; // wait if FIFO full

	    // writedata(0x10 + index);
		_pkinetisk_spi->PUSHR = (0x10 + index) | (pcs_data << 16) | SPI_PUSHR_CTAS(0);
	//	while (((_pkinetisk_spi->SR) & (15 << 12)) > (3 << 12)) ; // wait if FIFO full
	    // writecommand(c);
	   	_pkinetisk_spi->PUSHR = c | (pcs_command << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT;
	//	while (((_pkinetisk_spi->SR) & (15 << 12)) > (3 << 12)) ; // wait if FIFO full

	    // readdata
		_pkinetisk_spi->PUSHR = 0 | (pcs_data << 16) | SPI_PUSHR_CTAS(0);
	//	while (((_pkinetisk_spi->SR) & (15 << 12)) > (3 << 12)) ; // wait if FIFO full

	    // Now wait until completed.
	    wTimeout = 0xffff;
	    while (((_pkinetisk_spi->SR) & (15 << 12)) && (--wTimeout)) ; // wait until empty

	    // Make sure the last frame has been sent...
	    _pkinetisk_spi->SR = SPI_SR_TCF;   // dlear it out;
	    wTimeout = 0xffff;
	    while (!((_pkinetisk_spi->SR) & SPI_SR_TCF) && (--wTimeout)) ; // wait until it says the last frame completed

	    wTimeout = 0x10;    // should not go more than 4...
	    // lets get all of the values on the FIFO
	    while ((((_pkinetisk_spi->SR) >> 4) & 0xf) && (--wTimeout))  {
	        r = _pkinetisk_spi->POPR;
	    }
	} else {
		while (((_pkinetisk_spi->SR) & (15 << 12)) && (--wTimeout)) ; // wait until empty

	    // Make sure the last frame has been sent...
	    _pkinetisk_spi->SR = SPI_SR_TCF;   // dlear it out;
	    wTimeout = 0xffff;
	    while (!((_pkinetisk_spi->SR) & SPI_SR_TCF) && (--wTimeout)) ; // wait until it says the last frame completed

	    // clear out any current received bytes
	    wTimeout = 0x10;    // should not go more than 4...
	    while ((((_pkinetisk_spi->SR) >> 4) & 0xf) && (--wTimeout))  {
	        r = _pkinetisk_spi->POPR;
	    }

	    //writecommand(0xD9); // sekret command
		_pkinetisk_spi->PUSHR = 0xD9 | (pcs_command << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT;
		while (((_pkinetisk_spi->SR) & (15 << 12)) > (0 << 12)) ; // wait if FIFO full

	    // writedata(0x10 + index);
		_pkinetisk_spi->PUSHR = (0x10 + index) | (pcs_data << 16) | SPI_PUSHR_CTAS(0);
		while (((_pkinetisk_spi->SR) & (15 << 12)) > (0 << 12)) ; // wait if FIFO full

		// See if there are any return values to pop 
	    while (((_pkinetisk_spi->SR) >> 4) & 0xf)  {
	        r = _pkinetisk_spi->POPR;
	    }

	    // writecommand(c);
	   	_pkinetisk_spi->PUSHR = c | (pcs_command << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT;
		while (((_pkinetisk_spi->SR) & (15 << 12)) > (0 << 12)) ; // wait if FIFO full

		// See if there are any return values to pop 
	    while (((_pkinetisk_spi->SR) >> 4) & 0xf)  {
	        r = _pkinetisk_spi->POPR;
	    }

	    // readdata
		_pkinetisk_spi->PUSHR = 0 | (pcs_data << 16) | SPI_PUSHR_CTAS(0);
		while (((_pkinetisk_spi->SR) & (15 << 12)) > (0 << 12)) ; // wait if FIFO full

		// See if there are any return values to pop 
	    while (((_pkinetisk_spi->SR) >> 4) & 0xf)  {
	        r = _pkinetisk_spi->POPR;
	    }
	    // Now wait until completed.
	    wTimeout = 0xffff;
	    while (((_pkinetisk_spi->SR) & (15 << 12)) && (--wTimeout)) ; // wait until empty

	    // Make sure the last frame has been sent...
	    _pkinetisk_spi->SR = SPI_SR_TCF;   // dlear it out;
	    wTimeout = 0xffff;
	    while (!((_pkinetisk_spi->SR) & SPI_SR_TCF) && (--wTimeout)) ; // wait until it says the last frame completed

	    wTimeout = 0x10;    // should not go more than 4...
	    // lets get all of the values on the FIFO
	    while ((((_pkinetisk_spi->SR) >> 4) & 0xf) && (--wTimeout))  {
	        r = _pkinetisk_spi->POPR;
	    }

	}  
    endSPITransaction();
    return r;  // get the received byte... should check for it first...
#else
	beginSPITransaction();
	writecommand_cont(0xD9);
	writedata8_cont(0x10 + index);

	writecommand_cont(c);
	writedata8_cont(0);
	uint8_t r = waitTransmitCompleteReturnLast();
	endSPITransaction();
	return r;

#endif   
}

// Read Pixel at x,y and get back 16-bit packed color
#define READ_PIXEL_PUSH_BYTE 0x3f
uint16_t ILI9341_t3n::readPixel(int16_t x, int16_t y)
{
#ifdef KINETISK	
	//BUGBUG:: Should add some validation of X and Y
	// Now if we are in buffer mode can return real fast
	#ifdef ENABLE_ILI9341_FRAMEBUFFER
	if (_use_fbtft) {
		x+=_originx;
		y+=_originy;

		return _pfbtft[y*_width + x] ;
	}
	#endif	

   if (_miso == 0xff) return 0xffff;	// bail if not valid miso

	// First pass for other SPI busses use readRect to handle the read... 
	if (_pspin->sizeFIFO() < 4) {
		uint16_t colors;
		readRect(x, y, 1, 1, &colors);
		return colors;
	}

	uint8_t dummy __attribute__((unused));
	uint8_t r,g,b;

	beginSPITransaction(ILI9341_SPICLOCK_READ);

	// Update our origin. 
	x+=_originx;
	y+=_originy;

	setAddr(x, y, x, y);
	writecommand_cont(ILI9341_RAMRD); // read from RAM
	_pspin->waitTransmitComplete();

	// Push 4 bytes over SPI
	_pkinetisk_spi->PUSHR = 0 | (pcs_data << 16) | SPI_PUSHR_CTAS(0)| SPI_PUSHR_CONT;
	_pspin->waitFifoEmpty();    // wait for both queues to be empty.

	_pkinetisk_spi->PUSHR = 0 | (pcs_data << 16) | SPI_PUSHR_CTAS(0)| SPI_PUSHR_CONT;
	_pkinetisk_spi->PUSHR = 0 | (pcs_data << 16) | SPI_PUSHR_CTAS(0)| SPI_PUSHR_CONT;
	_pkinetisk_spi->PUSHR = 0 | (pcs_data << 16) | SPI_PUSHR_CTAS(0)| SPI_PUSHR_EOQ;

	// Wait for End of Queue
	while ((_pkinetisk_spi->SR & SPI_SR_EOQF) == 0) ;
	_pkinetisk_spi->SR = SPI_SR_EOQF;  // make sure it is clear

	// Read Pixel Data
	dummy = _pkinetisk_spi->POPR;	// Read a DUMMY byte of GRAM
	r = _pkinetisk_spi->POPR;		// Read a RED byte of GRAM
	g = _pkinetisk_spi->POPR;		// Read a GREEN byte of GRAM
	b = _pkinetisk_spi->POPR;		// Read a BLUE byte of GRAM

	endSPITransaction();
	return color565(r,g,b);
#else
	// Kinetisk
	uint16_t colors;
	readRect(x, y, 1, 1, &colors);
	return colors;
#endif	
}

// Now lets see if we can read in multiple pixels
#ifdef KINETISK
void ILI9341_t3n::readRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t *pcolors)
{
	// Use our Origin. 
	x+=_originx;
	y+=_originy;
	//BUGBUG:: Should add some validation of X and Y

	#ifdef ENABLE_ILI9341_FRAMEBUFFER
	if (_use_fbtft) {
		uint16_t * pfbPixel_row = &_pfbtft[ y*_width + x];
		for (;h>0; h--) {
			uint16_t * pfbPixel = pfbPixel_row;
			for (int i = 0 ;i < w; i++) {
				*pcolors++ = *pfbPixel++;
			}
			pfbPixel_row += _width;
		}
		return;	
	}
	#endif	

   if (_miso == 0xff) return;		// bail if not valid miso

	uint8_t rgb[3];               // RGB bytes received from the display
	uint8_t rgbIdx = 0;
	uint32_t txCount = w * h * 3; // number of bytes we will transmit to the display
	uint32_t rxCount = txCount;   // number of bytes we will receive back from the display

	beginSPITransaction(ILI9341_SPICLOCK_READ);

	setAddr(x, y, x+w-1, y+h-1);
	writecommand_cont(ILI9341_RAMRD); // read from RAM

	// transmit a DUMMY byte before the color bytes
	_pkinetisk_spi->PUSHR = 0 | (pcs_data << 16) | SPI_PUSHR_CTAS(0)| SPI_PUSHR_CONT;

	// skip values returned by the queued up transfers and the current in-flight transfer
	uint32_t sr = _pkinetisk_spi->SR;
	uint8_t skipCount = ((sr >> 4) & 0xF) + ((sr >> 12) & 0xF) + 1;

	while (txCount || rxCount) {
		// transmit another byte if possible
		if (txCount && ((_pkinetisk_spi->SR & 0xF000) >> 12) < _pspin->sizeFIFO()) {
			txCount--;
			if (txCount) {
				_pkinetisk_spi->PUSHR = READ_PIXEL_PUSH_BYTE | (pcs_data << 16) | SPI_PUSHR_CTAS(0)| SPI_PUSHR_CONT;
			} else {
				_pkinetisk_spi->PUSHR = READ_PIXEL_PUSH_BYTE | (pcs_data << 16) | SPI_PUSHR_CTAS(0)| SPI_PUSHR_EOQ;
			}
		}

		// receive another byte if possible, and either skip it or store the color
		if (rxCount && (_pkinetisk_spi->SR & 0xF0)) {
			rgb[rgbIdx] = _pkinetisk_spi->POPR;

			if (skipCount) {
				skipCount--;
			} else {
				rxCount--;
				rgbIdx++;
				if (rgbIdx == 3) {
					rgbIdx = 0;
					*pcolors++ = color565(rgb[0], rgb[1], rgb[2]);
				}
			}
		}
	}

	// wait for End of Queue
	while ((_pkinetisk_spi->SR & SPI_SR_EOQF) == 0) ;
	_pkinetisk_spi->SR = SPI_SR_EOQF;  // make sure it is clear
	endSPITransaction();

}
#else

// Teensy LC version
void ILI9341_t3n::readRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t *pcolors)
{
	// Use our Origin. 
	x+=_originx;
	y+=_originy;
	//BUGBUG:: Should add some validation of X and Y

   if (_miso == 0xff) return;		// bail if not valid miso

	uint8_t rgb[3];               // RGB bytes received from the display
	uint8_t rgbIdx = 0;
	uint32_t txCount = w * h * 3; // number of bytes we will transmit to the display
	uint32_t rxCount = txCount;   // number of bytes we will receive back from the display

	beginSPITransaction(ILI9341_SPICLOCK_READ);

	setAddr(x, y, x+w-1, y+h-1);
	writecommand_cont(ILI9341_RAMRD); // read from RAM

	// transmit a DUMMY byte before the color bytes
	writedata8_cont(0);

	// Wait until that one returns, Could do a little better and double buffer but this is easer for now.
	waitTransmitComplete();

	// Since double buffer setup lets try keeping read/write in sync
#define RRECT_TIMEOUT 0xffff	
#undef 	READ_PIXEL_PUSH_BYTE
#define READ_PIXEL_PUSH_BYTE 0 // try with zero to see... 	
	uint16_t timeout_countdown = RRECT_TIMEOUT;
	uint16_t dl_in;
	// Write out first byte:

	while (!(_pkinetisl_spi->S & SPI_S_SPTEF)) ; // Not worried that this can completely hang?
	_pkinetisl_spi->DL = READ_PIXEL_PUSH_BYTE;

	while (rxCount && timeout_countdown) {
		// Now wait until we can output something
		dl_in = 0xffff;
		if (rxCount > 1) {
			while (!(_pkinetisl_spi->S & SPI_S_SPTEF)) ; // Not worried that this can completely hang?
			if (_pkinetisl_spi->S & SPI_S_SPRF)
				dl_in = _pkinetisl_spi->DL;  
			_pkinetisl_spi->DL = READ_PIXEL_PUSH_BYTE;
		}

		// Now wait until there is a byte available to receive
		while ((dl_in != 0xffff) && !(_pkinetisl_spi->S & SPI_S_SPRF) && --timeout_countdown) ;
		if (timeout_countdown) {   // Make sure we did not get here because of timeout 
			rxCount--;
			rgb[rgbIdx] = (dl_in != 0xffff)? dl_in : _pkinetisl_spi->DL;
			rgbIdx++;
			if (rgbIdx == 3) {
				rgbIdx = 0;
				*pcolors++ = color565(rgb[0], rgb[1], rgb[2]);
			}
			timeout_countdown = timeout_countdown;
		}
	}

	// Debug code. 
/*	if (timeout_countdown == 0) {
		Serial.print("RRect Timeout ");
		Serial.println(rxCount, DEC);
	} */
	endSPITransaction();
}
#endif		

// Now lets see if we can writemultiple pixels
void ILI9341_t3n::writeRect(int16_t x, int16_t y, int16_t w, int16_t h, const uint16_t *pcolors)
{

	x+=_originx;
	y+=_originy;
	uint16_t x_clip_left = 0;  // How many entries at start of colors to skip at start of row
	uint16_t x_clip_right = 0;    // how many color entries to skip at end of row for clipping
	// Rectangular clipping 

	// See if the whole thing out of bounds...
	if((x >= _displayclipx2) || (y >= _displayclipy2)) return;
	if (((x+w) <= _displayclipx1) || ((y+h) <= _displayclipy1)) return;

	// In these cases you can not do simple clipping, as we need to synchronize the colors array with the
	// We can clip the height as when we get to the last visible we don't have to go any farther. 
	// also maybe starting y as we will advance the color array. 
 	if(y < _displayclipy1) {
 		int dy = (_displayclipy1 - y);
 		h -= dy; 
 		pcolors += (dy*w); // Advance color array to 
 		y = _displayclipy1; 	
 	}

	if((y + h - 1) >= _displayclipy2) h = _displayclipy2 - y;

	// For X see how many items in color array to skip at start of row and likewise end of row 
	if(x < _displayclipx1) {
		x_clip_left = _displayclipx1-x; 
		w -= x_clip_left; 
		x = _displayclipx1; 	
	}
	if((x + w - 1) >= _displayclipx2) {
		x_clip_right = w;
		w = _displayclipx2  - x;
		x_clip_right -= w; 
	} 

	#ifdef ENABLE_ILI9341_FRAMEBUFFER
	if (_use_fbtft) {
		uint16_t * pfbPixel_row = &_pfbtft[ y*_width + x];
		for (;h>0; h--) {
			uint16_t * pfbPixel = pfbPixel_row;
			pcolors += x_clip_left;
			for (int i = 0 ;i < w; i++) {
				*pfbPixel++ = *pcolors++;
			}
			pfbPixel_row += _width;
			pcolors += x_clip_right;

		}
		return;	
	}
	#endif

   	beginSPITransaction();
	setAddr(x, y, x+w-1, y+h-1);
	writecommand_cont(ILI9341_RAMWR);
	for(y=h; y>0; y--) {
		pcolors += x_clip_left;
		for(x=w; x>1; x--) {
			writedata16_cont(*pcolors++);
		}
		writedata16_last(*pcolors++);
		pcolors += x_clip_right;
	}
	endSPITransaction();
}

// writeRect8BPP - 	write 8 bit per pixel paletted bitmap
//					bitmap data in array at pixels, one byte per pixel
//					color palette data in array at palette
void ILI9341_t3n::writeRect8BPP(int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t *pixels, const uint16_t * palette )
{
	//Serial.printf("\nWR8: %d %d %d %d %x\n", x, y, w, h, (uint32_t)pixels);
	x+=_originx;
	y+=_originy;

	uint16_t x_clip_left = 0;  // How many entries at start of colors to skip at start of row
	uint16_t x_clip_right = 0;    // how many color entries to skip at end of row for clipping
	// Rectangular clipping 

	// See if the whole thing out of bounds...
	if((x >= _displayclipx2) || (y >= _displayclipy2)) return;
	if (((x+w) <= _displayclipx1) || ((y+h) <= _displayclipy1)) return;

	// In these cases you can not do simple clipping, as we need to synchronize the colors array with the
	// We can clip the height as when we get to the last visible we don't have to go any farther. 
	// also maybe starting y as we will advance the color array. 
 	if(y < _displayclipy1) {
 		int dy = (_displayclipy1 - y);
 		h -= dy; 
 		pixels += (dy*w); // Advance color array to 
 		y = _displayclipy1; 	
 	}

	if((y + h - 1) >= _displayclipy2) h = _displayclipy2 - y;

	// For X see how many items in color array to skip at start of row and likewise end of row 
	if(x < _displayclipx1) {
		x_clip_left = _displayclipx1-x; 
		w -= x_clip_left; 
		x = _displayclipx1; 	
	}
	if((x + w - 1) >= _displayclipx2) {
		x_clip_right = w;
		w = _displayclipx2  - x;
		x_clip_right -= w; 
	} 
	//Serial.printf("WR8C: %d %d %d %d %x- %d %d\n", x, y, w, h, (uint32_t)pixels, x_clip_right, x_clip_left);
	#ifdef ENABLE_ILI9341_FRAMEBUFFER
	if (_use_fbtft) {
		uint16_t * pfbPixel_row = &_pfbtft[ y*_width + x];
		for (;h>0; h--) {
			pixels += x_clip_left;
			uint16_t * pfbPixel = pfbPixel_row;
			for (int i = 0 ;i < w; i++) {
				*pfbPixel++ = palette[*pixels++];
			}
			pixels += x_clip_right;
			pfbPixel_row += _width;
		}
		return;	
	}
	#endif

	beginSPITransaction();
	setAddr(x, y, x+w-1, y+h-1);
	writecommand_cont(ILI9341_RAMWR);
	for(y=h; y>0; y--) {
		pixels += x_clip_left;
		//Serial.printf("%x: ", (uint32_t)pixels);
		for(x=w; x>1; x--) {
			//Serial.print(*pixels, DEC);
			writedata16_cont(palette[*pixels++]);
		}
		//Serial.println(*pixels, DEC);
		writedata16_last(palette[*pixels++]);
		pixels += x_clip_right;
	}
	endSPITransaction();
}

// writeRect4BPP - 	write 4 bit per pixel paletted bitmap
//					bitmap data in array at pixels, 4 bits per pixel
//					color palette data in array at palette
//					width must be at least 2 pixels
void ILI9341_t3n::writeRect4BPP(int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t *pixels, const uint16_t * palette )
{
	// Simply call through our helper
	writeRectNBPP(x, y, w, h,  4, pixels, palette );
}

// writeRect2BPP - 	write 2 bit per pixel paletted bitmap
//					bitmap data in array at pixels, 4 bits per pixel
//					color palette data in array at palette
//					width must be at least 4 pixels
void ILI9341_t3n::writeRect2BPP(int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t *pixels, const uint16_t * palette )
{
	// Simply call through our helper
	writeRectNBPP(x, y, w, h,  2, pixels, palette );

}

///============================================================================
// writeRect1BPP - 	write 1 bit per pixel paletted bitmap
//					bitmap data in array at pixels, 4 bits per pixel
//					color palette data in array at palette
//					width must be at least 8 pixels
void ILI9341_t3n::writeRect1BPP(int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t *pixels, const uint16_t * palette )
{
	// Simply call through our helper
	writeRectNBPP(x, y, w, h,  1, pixels, palette );
}



///============================================================================
// writeRectNBPP - 	write N(1, 2, 4, 8) bit per pixel paletted bitmap
//					bitmap data in array at pixels
//  Currently writeRect1BPP, writeRect2BPP, writeRect4BPP use this to do all of the work. 
void ILI9341_t3n::writeRectNBPP(int16_t x, int16_t y, int16_t w, int16_t h,  uint8_t bits_per_pixel, 
		const uint8_t *pixels, const uint16_t * palette )
{
	//Serial.printf("\nWR8: %d %d %d %d %x\n", x, y, w, h, (uint32_t)pixels);
	x+=_originx;
	y+=_originy;
	uint8_t pixels_per_byte = 8/ bits_per_pixel;
	uint16_t count_of_bytes_per_row = (w + pixels_per_byte -1)/pixels_per_byte;		// Round up to handle non multiples
	uint8_t row_shift_init = 8 - bits_per_pixel;				// We shift down 6 bits by default 
	uint8_t pixel_bit_mask = (1 << bits_per_pixel) - 1; 		// get mask to use below
	// Rectangular clipping 

	// See if the whole thing out of bounds...
	if((x >= _displayclipx2) || (y >= _displayclipy2)) return;
	if (((x+w) <= _displayclipx1) || ((y+h) <= _displayclipy1)) return;

	// In these cases you can not do simple clipping, as we need to synchronize the colors array with the
	// We can clip the height as when we get to the last visible we don't have to go any farther. 
	// also maybe starting y as we will advance the color array. 
	// Again assume multiple of 8 for width
 	if(y < _displayclipy1) {
 		int dy = (_displayclipy1 - y);
 		h -= dy; 
 		pixels += dy * count_of_bytes_per_row;
 		y = _displayclipy1; 	
 	}

	if((y + h - 1) >= _displayclipy2) h = _displayclipy2 - y;

	// For X see how many items in color array to skip at start of row and likewise end of row 
	if(x < _displayclipx1) {
		uint16_t x_clip_left = _displayclipx1-x; 
		w -= x_clip_left; 
		x = _displayclipx1; 
		// Now lets update pixels to the rigth offset and mask
		uint8_t x_clip_left_bytes_incr = x_clip_left / pixels_per_byte;
		pixels += x_clip_left_bytes_incr;
		row_shift_init = 8 - (x_clip_left - (x_clip_left_bytes_incr * pixels_per_byte) + 1) * bits_per_pixel; 	
	}

	if((x + w - 1) >= _displayclipx2) {
		w = _displayclipx2  - x;
	} 

	const uint8_t * pixels_row_start = pixels;  // remember our starting position offset into row

	#ifdef ENABLE_ILI9341_FRAMEBUFFER
	if (_use_fbtft) {
		uint16_t * pfbPixel_row = &_pfbtft[ y*_width + x];
		for (;h>0; h--) {
			uint16_t * pfbPixel = pfbPixel_row;
			pixels = pixels_row_start;				// setup for this row
			uint8_t pixel_shift = row_shift_init;			// Setup mask

			for (int i = 0 ; i < w; i++) {
				*pfbPixel++ = palette[((*pixels)>>pixel_shift) & pixel_bit_mask];
				if (!pixel_shift) {
					pixel_shift = 8 - bits_per_pixel;	//setup next mask
					pixels++;
				} else {
					pixel_shift -= bits_per_pixel;
				}
			}
			pfbPixel_row += _width;
			pixels_row_start += count_of_bytes_per_row;
		}
		return;	

	}
	#endif

	beginSPITransaction();
	setAddr(x, y, x+w-1, y+h-1);
	writecommand_cont(ILI9341_RAMWR);
	for (;h>0; h--) {
		pixels = pixels_row_start;				// setup for this row
		uint8_t pixel_shift = row_shift_init;			// Setup mask

		for (int i = 0 ;i < w; i++) {
			writedata16_cont(palette[((*pixels)>>pixel_shift) & pixel_bit_mask]);
			if (!pixel_shift) {
				pixel_shift = 8 - bits_per_pixel;	//setup next mask
				pixels++;
			} else {
				pixel_shift -= bits_per_pixel;
			}
		}
		pixels_row_start += count_of_bytes_per_row;
	}
	writecommand_last(ILI9341_NOP);
	endSPITransaction();
}

static const uint8_t init_commands[] = {
	4, 0xEF, 0x03, 0x80, 0x02,
	4, 0xCF, 0x00, 0XC1, 0X30,
	5, 0xED, 0x64, 0x03, 0X12, 0X81,
	4, 0xE8, 0x85, 0x00, 0x78,
	6, 0xCB, 0x39, 0x2C, 0x00, 0x34, 0x02,
	2, 0xF7, 0x20,
	3, 0xEA, 0x00, 0x00,
	2, ILI9341_PWCTR1, 0x23, // Power control
	2, ILI9341_PWCTR2, 0x10, // Power control
	3, ILI9341_VMCTR1, 0x3e, 0x28, // VCM control
	2, ILI9341_VMCTR2, 0x86, // VCM control2
	2, ILI9341_MADCTL, 0x48, // Memory Access Control
	2, ILI9341_PIXFMT, 0x55,
	3, ILI9341_FRMCTR1, 0x00, 0x18,
	4, ILI9341_DFUNCTR, 0x08, 0x82, 0x27, // Display Function Control
	2, 0xF2, 0x00, // Gamma Function Disable
	2, ILI9341_GAMMASET, 0x01, // Gamma curve selected
	16, ILI9341_GMCTRP1, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08,
		0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00, // Set Gamma
	16, ILI9341_GMCTRN1, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07,
		0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F, // Set Gamma
	0
};

void ILI9341_t3n::begin(void)
{
    // verify SPI pins are valid;
	// allow user to say use current ones...
#ifdef DEBUG_ASYNC_UPDATE
	Serial.printf("????? _dmasettings[0] %x %x\n", (uint32_t)&_dmasettings[0], (uint32_t)_dmasettings[0].TCD); Serial.flush();
#endif
	if ((_mosi != 255) || (_miso != 255) || (_sclk != 255)) {
		if (!(_pspin->pinIsMOSI(_mosi)) || !(_pspin->pinIsMISO(_miso)) || !(_pspin->pinIsSCK(_sclk))) {
			#ifdef SPIN1_OBJECT_CREATED			
			if (SPIN1.pinIsMOSI(_mosi) && SPIN1.pinIsMISO(_miso) && SPIN1.pinIsSCK(_sclk)) {
				_pspin = &SPIN1;
#ifdef KINETISK
				_pkinetisk_spi = _pspin->kinetisk_spi();
#else
				_pkinetisl_spi = _pspin->kinetisl_spi();
#endif					
				//Serial.println("ILI9341_t3n: SPIN1 automatically selected");
			} else {
				#ifdef SPIN2_OBJECT_CREATED			
				if (SPIN2.pinIsMOSI(_mosi) && SPIN2.pinIsMISO(_miso) && SPIN2.pinIsSCK(_sclk)) {
					_pspin = &SPIN2;
					_pkinetisk_spi = _pspin->kinetisk_spi();
					//Serial.println("ILI9341_t3n: SPIN2 automatically selected");
				} else {
				#endif
			#endif
					uint8_t mosi_sck_bad = false;
					Serial.print("ILI9341_t3n: Error not valid SPI pins:");
					if(!(_pspin->pinIsMOSI(_mosi)))  {
						Serial.print(" MOSI");
						mosi_sck_bad = true;
					}
					if (!_pspin->pinIsSCK(_sclk)) {
						Serial.print(" SCLK");
						mosi_sck_bad = true;
					}

					// Maybe allow us to limp with only MISO bad
					if(!(_pspin->pinIsMISO(_miso))) {
						Serial.print(" MISO");
						_miso = 0xff;	// set miso to 255 as flag it is bad
					}
					Serial.println();
					
					if (mosi_sck_bad) {
	    				return; // not valid pins...
					}
				#ifdef SPIN2_OBJECT_CREATED			
	    		}
	    		#endif
			#ifdef SPIN1_OBJECT_CREATED			
			}
			#endif

		}
		//Serial.printf("MOSI:%d MISO:%d SCK:%d\n\r", _mosi, _miso, _sclk);			
        _pspin->setMOSI(_mosi);
        if (_miso != 0xff) _pspin->setMISO(_miso);
        _pspin->setSCK(_sclk);
	}

	_pspin->begin();
#ifdef KINETISK
	if (_pspin->pinIsChipSelect(_cs, _dc)) {
		pcs_data = _pspin->setCS(_cs);
		pcs_command = pcs_data | _pspin->setCS(_dc);
	} else {
		// See if at least DC is on chipselect pin, if so try to limp along...
		if (_pspin->pinIsChipSelect(_dc)) {
			pcs_data = 0;
			pcs_command = pcs_data | _pspin->setCS(_dc);
			pinMode(_cs, OUTPUT);
			_csport    = portOutputRegister(digitalPinToPort(_cs));
			_cspinmask = digitalPinToBitMask(_cs);
			*_csport |= _cspinmask;
		} else {
			pcs_data = 0;
			pcs_command = 0;
  			Serial.println("ILI9341_t3n: Error not DC is not valid hardware CS pin");
			return;
		}
	}
#else
	// TLC
	pcs_data = 0;
	pcs_command = 0;
	pinMode(_cs, OUTPUT);
	_csport    = portOutputRegister(digitalPinToPort(_cs));
	_cspinmask = digitalPinToBitMask(_cs);
	*_csport |= _cspinmask;
	pinMode(_dc, OUTPUT);
	_dcport    = portOutputRegister(digitalPinToPort(_dc));
	_dcpinmask = digitalPinToBitMask(_dc);
	*_dcport |= _dcpinmask;
	_dcpinAsserted = 0;
#endif	

	// toggle RST low to reset
	if (_rst < 255) {
		pinMode(_rst, OUTPUT);
		digitalWrite(_rst, HIGH);
		delay(5);
		digitalWrite(_rst, LOW);
		delay(20);
		digitalWrite(_rst, HIGH);
		delay(150);
	}
/*
	uint8_t x = readcommand8(ILI9341_RDMODE);
	Serial.print("\nDisplay Power Mode: 0x"); Serial.println(x, HEX);
	x = readcommand8(ILI9341_RDMADCTL);
	Serial.print("\nMADCTL Mode: 0x"); Serial.println(x, HEX);
	x = readcommand8(ILI9341_RDPIXFMT);
	Serial.print("\nPixel Format: 0x"); Serial.println(x, HEX);
	x = readcommand8(ILI9341_RDIMGFMT);
	Serial.print("\nImage Format: 0x"); Serial.println(x, HEX);
	x = readcommand8(ILI9341_RDSELFDIAG);
	Serial.print("\nSelf Diagnostic: 0x"); Serial.println(x, HEX);
*/	
	beginSPITransaction();
	const uint8_t *addr = init_commands;
	while (1) {
		uint8_t count = *addr++;
		if (count-- == 0) break;
		writecommand_cont(*addr++);
		while (count-- > 0) {
			writedata8_cont(*addr++);
		}
	}
	writecommand_last(ILI9341_SLPOUT);    // Exit Sleep
	endSPITransaction();
	delay(120); 		
	beginSPITransaction();
	writecommand_last(ILI9341_DISPON);    // Display on
	endSPITransaction();

#ifdef DEBUG_ASYNC_LEDS
	pinMode(DEBUG_PIN_1, OUTPUT);
	pinMode(DEBUG_PIN_2, OUTPUT);
	pinMode(DEBUG_PIN_3, OUTPUT);
#endif
}




/*
This is the core graphics library for all our displays, providing a common
set of graphics primitives (points, lines, circles, etc.).  It needs to be
paired with a hardware-specific library for each display device we carry
(to handle the lower-level functions).

Adafruit invests time and resources providing this open source code, please
support Adafruit & open-source hardware by purchasing products from Adafruit!

Copyright (c) 2013 Adafruit Industries.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

- Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/

//#include "glcdfont.c"
extern "C" const unsigned char glcdfont[];

// Draw a circle outline
void ILI9341_t3n::drawCircle(int16_t x0, int16_t y0, int16_t r,
    uint16_t color) {
  int16_t f = 1 - r;
  int16_t ddF_x = 1;
  int16_t ddF_y = -2 * r;
  int16_t x = 0;
  int16_t y = r;

  drawPixel(x0  , y0+r, color);
  drawPixel(x0  , y0-r, color);
  drawPixel(x0+r, y0  , color);
  drawPixel(x0-r, y0  , color);

  while (x<y) {
    if (f >= 0) {
      y--;
      ddF_y += 2;
      f += ddF_y;
    }
    x++;
    ddF_x += 2;
    f += ddF_x;

    drawPixel(x0 + x, y0 + y, color);
    drawPixel(x0 - x, y0 + y, color);
    drawPixel(x0 + x, y0 - y, color);
    drawPixel(x0 - x, y0 - y, color);
    drawPixel(x0 + y, y0 + x, color);
    drawPixel(x0 - y, y0 + x, color);
    drawPixel(x0 + y, y0 - x, color);
    drawPixel(x0 - y, y0 - x, color);
  }
}

void ILI9341_t3n::drawCircleHelper( int16_t x0, int16_t y0,
               int16_t r, uint8_t cornername, uint16_t color) {
  int16_t f     = 1 - r;
  int16_t ddF_x = 1;
  int16_t ddF_y = -2 * r;
  int16_t x     = 0;
  int16_t y     = r;

  while (x<y) {
    if (f >= 0) {
      y--;
      ddF_y += 2;
      f     += ddF_y;
    }
    x++;
    ddF_x += 2;
    f     += ddF_x;
    if (cornername & 0x4) {
      drawPixel(x0 + x, y0 + y, color);
      drawPixel(x0 + y, y0 + x, color);
    }
    if (cornername & 0x2) {
      drawPixel(x0 + x, y0 - y, color);
      drawPixel(x0 + y, y0 - x, color);
    }
    if (cornername & 0x8) {
      drawPixel(x0 - y, y0 + x, color);
      drawPixel(x0 - x, y0 + y, color);
    }
    if (cornername & 0x1) {
      drawPixel(x0 - y, y0 - x, color);
      drawPixel(x0 - x, y0 - y, color);
    }
  }
}

void ILI9341_t3n::fillCircle(int16_t x0, int16_t y0, int16_t r,
			      uint16_t color) {
  drawFastVLine(x0, y0-r, 2*r+1, color);
  fillCircleHelper(x0, y0, r, 3, 0, color);
}

// Used to do circles and roundrects
void ILI9341_t3n::fillCircleHelper(int16_t x0, int16_t y0, int16_t r,
    uint8_t cornername, int16_t delta, uint16_t color) {

  int16_t f     = 1 - r;
  int16_t ddF_x = 1;
  int16_t ddF_y = -2 * r;
  int16_t x     = 0;
  int16_t y     = r;

  while (x<y) {
    if (f >= 0) {
      y--;
      ddF_y += 2;
      f     += ddF_y;
    }
    x++;
    ddF_x += 2;
    f     += ddF_x;

    if (cornername & 0x1) {
      drawFastVLine(x0+x, y0-y, 2*y+1+delta, color);
      drawFastVLine(x0+y, y0-x, 2*x+1+delta, color);
    }
    if (cornername & 0x2) {
      drawFastVLine(x0-x, y0-y, 2*y+1+delta, color);
      drawFastVLine(x0-y, y0-x, 2*x+1+delta, color);
    }
  }
}


// Bresenham's algorithm - thx wikpedia
void ILI9341_t3n::drawLine(int16_t x0, int16_t y0,
	int16_t x1, int16_t y1, uint16_t color)
{
	if (y0 == y1) {
		if (x1 > x0) {
			drawFastHLine(x0, y0, x1 - x0 + 1, color);
		} else if (x1 < x0) {
			drawFastHLine(x1, y0, x0 - x1 + 1, color);
		} else {
			drawPixel(x0, y0, color);
		}
		return;
	} else if (x0 == x1) {
		if (y1 > y0) {
			drawFastVLine(x0, y0, y1 - y0 + 1, color);
		} else {
			drawFastVLine(x0, y1, y0 - y1 + 1, color);
		}
		return;
	}

	bool steep = abs(y1 - y0) > abs(x1 - x0);
	if (steep) {
		swap(x0, y0);
		swap(x1, y1);
	}
	if (x0 > x1) {
		swap(x0, x1);
		swap(y0, y1);
	}

	int16_t dx, dy;
	dx = x1 - x0;
	dy = abs(y1 - y0);

	int16_t err = dx / 2;
	int16_t ystep;

	if (y0 < y1) {
		ystep = 1;
	} else {
		ystep = -1;
	}

	beginSPITransaction();
	int16_t xbegin = x0;
	if (steep) {
		for (; x0<=x1; x0++) {
			err -= dy;
			if (err < 0) {
				int16_t len = x0 - xbegin;
				if (len) {
					VLine(y0, xbegin, len + 1, color);
				} else {
					Pixel(y0, x0, color);
				}
				xbegin = x0 + 1;
				y0 += ystep;
				err += dx;
			}
		}
		if (x0 > xbegin + 1) {
			VLine(y0, xbegin, x0 - xbegin, color);
		}

	} else {
		for (; x0<=x1; x0++) {
			err -= dy;
			if (err < 0) {
				int16_t len = x0 - xbegin;
				if (len) {
					HLine(xbegin, y0, len + 1, color);
				} else {
					Pixel(x0, y0, color);
				}
				xbegin = x0 + 1;
				y0 += ystep;
				err += dx;
			}
		}
		if (x0 > xbegin + 1) {
			HLine(xbegin, y0, x0 - xbegin, color);
		}
	}
	writecommand_last(ILI9341_NOP);
	endSPITransaction();
}

// Draw a rectangle
void ILI9341_t3n::drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color)
{
	#ifdef ENABLE_ILI9341_FRAMEBUFFER
	if (_use_fbtft) {
		drawFastHLine(x, y, w, color);
		drawFastHLine(x, y+h-1, w, color);
		drawFastVLine(x, y, h, color);
		drawFastVLine(x+w-1, y, h, color);
	} else 
	#endif
	{
	    beginSPITransaction();
		HLine(x, y, w, color);
		HLine(x, y+h-1, w, color);
		VLine(x, y, h, color);
		VLine(x+w-1, y, h, color);
		writecommand_last(ILI9341_NOP);
		endSPITransaction();
	}
}

// Draw a rounded rectangle
void ILI9341_t3n::drawRoundRect(int16_t x, int16_t y, int16_t w,
  int16_t h, int16_t r, uint16_t color) {
  // smarter version
  drawFastHLine(x+r  , y    , w-2*r, color); // Top
  drawFastHLine(x+r  , y+h-1, w-2*r, color); // Bottom
  drawFastVLine(x    , y+r  , h-2*r, color); // Left
  drawFastVLine(x+w-1, y+r  , h-2*r, color); // Right
  // draw four corners
  drawCircleHelper(x+r    , y+r    , r, 1, color);
  drawCircleHelper(x+w-r-1, y+r    , r, 2, color);
  drawCircleHelper(x+w-r-1, y+h-r-1, r, 4, color);
  drawCircleHelper(x+r    , y+h-r-1, r, 8, color);
}

// Fill a rounded rectangle
void ILI9341_t3n::fillRoundRect(int16_t x, int16_t y, int16_t w,
				 int16_t h, int16_t r, uint16_t color) {
  // smarter version
  fillRect(x+r, y, w-2*r, h, color);

  // draw four corners
  fillCircleHelper(x+w-r-1, y+r, r, 1, h-2*r-1, color);
  fillCircleHelper(x+r    , y+r, r, 2, h-2*r-1, color);
}

// Draw a triangle
void ILI9341_t3n::drawTriangle(int16_t x0, int16_t y0,
				int16_t x1, int16_t y1,
				int16_t x2, int16_t y2, uint16_t color) {
  drawLine(x0, y0, x1, y1, color);
  drawLine(x1, y1, x2, y2, color);
  drawLine(x2, y2, x0, y0, color);
}

// Fill a triangle
void ILI9341_t3n::fillTriangle ( int16_t x0, int16_t y0,
				  int16_t x1, int16_t y1,
				  int16_t x2, int16_t y2, uint16_t color) {

  int16_t a, b, y, last;

  // Sort coordinates by Y order (y2 >= y1 >= y0)
  if (y0 > y1) {
    swap(y0, y1); swap(x0, x1);
  }
  if (y1 > y2) {
    swap(y2, y1); swap(x2, x1);
  }
  if (y0 > y1) {
    swap(y0, y1); swap(x0, x1);
  }

  if(y0 == y2) { // Handle awkward all-on-same-line case as its own thing
    a = b = x0;
    if(x1 < a)      a = x1;
    else if(x1 > b) b = x1;
    if(x2 < a)      a = x2;
    else if(x2 > b) b = x2;
    drawFastHLine(a, y0, b-a+1, color);
    return;
  }

  int16_t
    dx01 = x1 - x0,
    dy01 = y1 - y0,
    dx02 = x2 - x0,
    dy02 = y2 - y0,
    dx12 = x2 - x1,
    dy12 = y2 - y1,
    sa   = 0,
    sb   = 0;

  // For upper part of triangle, find scanline crossings for segments
  // 0-1 and 0-2.  If y1=y2 (flat-bottomed triangle), the scanline y1
  // is included here (and second loop will be skipped, avoiding a /0
  // error there), otherwise scanline y1 is skipped here and handled
  // in the second loop...which also avoids a /0 error here if y0=y1
  // (flat-topped triangle).
  if(y1 == y2) last = y1;   // Include y1 scanline
  else         last = y1-1; // Skip it

  for(y=y0; y<=last; y++) {
    a   = x0 + sa / dy01;
    b   = x0 + sb / dy02;
    sa += dx01;
    sb += dx02;
    /* longhand:
    a = x0 + (x1 - x0) * (y - y0) / (y1 - y0);
    b = x0 + (x2 - x0) * (y - y0) / (y2 - y0);
    */
    if(a > b) swap(a,b);
    drawFastHLine(a, y, b-a+1, color);
  }

  // For lower part of triangle, find scanline crossings for segments
  // 0-2 and 1-2.  This loop is skipped if y1=y2.
  sa = dx12 * (y - y1);
  sb = dx02 * (y - y0);
  for(; y<=y2; y++) {
    a   = x1 + sa / dy12;
    b   = x0 + sb / dy02;
    sa += dx12;
    sb += dx02;
    /* longhand:
    a = x1 + (x2 - x1) * (y - y1) / (y2 - y1);
    b = x0 + (x2 - x0) * (y - y0) / (y2 - y0);
    */
    if(a > b) swap(a,b);
    drawFastHLine(a, y, b-a+1, color);
  }
}

void ILI9341_t3n::drawBitmap(int16_t x, int16_t y,
			      const uint8_t *bitmap, int16_t w, int16_t h,
			      uint16_t color) {

  int16_t i, j, byteWidth = (w + 7) / 8;

  for(j=0; j<h; j++) {
    for(i=0; i<w; i++ ) {
      if(pgm_read_byte(bitmap + j * byteWidth + i / 8) & (128 >> (i & 7))) {
	drawPixel(x+i, y+j, color);
      }
    }
  }
}

size_t ILI9341_t3n::write(uint8_t c)
{
	if (font) {
		if (c == '\n') {
			cursor_y += font->line_space; // Fix linefeed. Added by T.T., SoftEgg
			cursor_x = 0;
		} else {
			drawFontChar(c);
		}
	} else {
		if (c == '\n') {
			cursor_y += textsize*8;
			cursor_x  = 0;
		} else if (c == '\r') {
			// skip em
		} else {
			drawChar(cursor_x, cursor_y, c, textcolor, textbgcolor, textsize);
			cursor_x += textsize*6;
			if (wrap && (cursor_x > (_width - textsize*6))) {
				cursor_y += textsize*8;
				cursor_x = 0;
			}
		}
	}
	return 1;
}

// Draw a character
void ILI9341_t3n::drawChar(int16_t x, int16_t y, unsigned char c,
			    uint16_t fgcolor, uint16_t bgcolor, uint8_t size)
{
	if((x >= _width)            || // Clip right
	   (y >= _height)           || // Clip bottom
	   ((x + 6 * size - 1) < 0) || // Clip left  TODO: is this correct?
	   ((y + 8 * size - 1) < 0))   // Clip top   TODO: is this correct?
		return;

	if (fgcolor == bgcolor) {
		// This transparent approach is only about 20% faster
		if (size == 1) {
			uint8_t mask = 0x01;
			int16_t xoff, yoff;
			for (yoff=0; yoff < 8; yoff++) {
				uint8_t line = 0;
				for (xoff=0; xoff < 5; xoff++) {
					if (glcdfont[c * 5 + xoff] & mask) line |= 1;
					line <<= 1;
				}
				line >>= 1;
				xoff = 0;
				while (line) {
					if (line == 0x1F) {
						drawFastHLine(x + xoff, y + yoff, 5, fgcolor);
						break;
					} else if (line == 0x1E) {
						drawFastHLine(x + xoff, y + yoff, 4, fgcolor);
						break;
					} else if ((line & 0x1C) == 0x1C) {
						drawFastHLine(x + xoff, y + yoff, 3, fgcolor);
						line <<= 4;
						xoff += 4;
					} else if ((line & 0x18) == 0x18) {
						drawFastHLine(x + xoff, y + yoff, 2, fgcolor);
						line <<= 3;
						xoff += 3;
					} else if ((line & 0x10) == 0x10) {
						drawPixel(x + xoff, y + yoff, fgcolor);
						line <<= 2;
						xoff += 2;
					} else {
						line <<= 1;
						xoff += 1;
					}
				}
				mask = mask << 1;
			}
		} else {
			uint8_t mask = 0x01;
			int16_t xoff, yoff;
			for (yoff=0; yoff < 8; yoff++) {
				uint8_t line = 0;
				for (xoff=0; xoff < 5; xoff++) {
					if (glcdfont[c * 5 + xoff] & mask) line |= 1;
					line <<= 1;
				}
				line >>= 1;
				xoff = 0;
				while (line) {
					if (line == 0x1F) {
						fillRect(x + xoff * size, y + yoff * size,
							5 * size, size, fgcolor);
						break;
					} else if (line == 0x1E) {
						fillRect(x + xoff * size, y + yoff * size,
							4 * size, size, fgcolor);
						break;
					} else if ((line & 0x1C) == 0x1C) {
						fillRect(x + xoff * size, y + yoff * size,
							3 * size, size, fgcolor);
						line <<= 4;
						xoff += 4;
					} else if ((line & 0x18) == 0x18) {
						fillRect(x + xoff * size, y + yoff * size,
							2 * size, size, fgcolor);
						line <<= 3;
						xoff += 3;
					} else if ((line & 0x10) == 0x10) {
						fillRect(x + xoff * size, y + yoff * size,
							size, size, fgcolor);
						line <<= 2;
						xoff += 2;
					} else {
						line <<= 1;
						xoff += 1;
					}
				}
				mask = mask << 1;
			}
		}
	} else {
		// This solid background approach is about 5 time faster
		uint8_t xc, yc;
		uint8_t xr, yr;
		uint8_t mask = 0x01;
		uint16_t color;

		// We need to offset by the origin.
		x+=_originx;
		y+=_originy;
		int16_t x_char_start = x;  // remember our X where we start outputting...

		if((x >= _displayclipx2)            || // Clip right
			 (y >= _displayclipy2)           || // Clip bottom
			 ((x + 6 * size - 1) < _displayclipx1) || // Clip left  TODO: this is not correct
			 ((y + 8 * size - 1) < _displayclipy1))   // Clip top   TODO: this is not correct
			return;


		#ifdef ENABLE_ILI9341_FRAMEBUFFER
		if (_use_fbtft) {

			uint16_t * pfbPixel_row = &_pfbtft[ y*_width + x];
			for (yc=0; (yc < 8) && (y < _displayclipy2); yc++) {
				for (yr=0; (yr < size) && (y < _displayclipy2); yr++) {
					x = x_char_start; 		// get our first x position...
					if (y >= _displayclipy1) {
						uint16_t * pfbPixel = pfbPixel_row;
						for (xc=0; xc < 5; xc++) {
							if (glcdfont[c * 5 + xc] & mask) {
								color = fgcolor;
							} else {
								color = bgcolor;
							}
							for (xr=0; xr < size; xr++) {
								if ((x >= _displayclipx1) && (x < _displayclipx2)) {
									*pfbPixel = color;
								}
								pfbPixel++;
								x++;
							}
						}
						for (xr=0; xr < size; xr++) {
							if ((x >= _displayclipx1) && (x < _displayclipx2)) {
								*pfbPixel = bgcolor;
							}
							pfbPixel++;
							x++;
						}
					}
					pfbPixel_row += _width; // setup pointer to 
					y++;
				}
				mask = mask << 1;
			}

		} else 
		#endif
		{
			// need to build actual pixel rectangle we will output into.
			int16_t y_char_top = y;	// remember the y
			int16_t w =  6 * size;
			int16_t h = 8 * size;

			if(x < _displayclipx1) {	w -= (_displayclipx1-x); x = _displayclipx1; 	}
			if((x + w - 1) >= _displayclipx2)  w = _displayclipx2  - x;
			if(y < _displayclipy1) {	h -= (_displayclipy1 - y); y = _displayclipy1; 	}
			if((y + h - 1) >= _displayclipy2) h = _displayclipy2 - y;

			beginSPITransaction();
			setAddr(x, y, x + w -1, y + h - 1);

			y = y_char_top;	// restore the actual y.
			writecommand_cont(ILI9341_RAMWR);
			for (yc=0; (yc < 8) && (y < _displayclipy2); yc++) {
				for (yr=0; (yr < size) && (y < _displayclipy2); yr++) {
					x = x_char_start; 		// get our first x position...
					if (y >= _displayclipy1) {
						for (xc=0; xc < 5; xc++) {
							if (glcdfont[c * 5 + xc] & mask) {
								color = fgcolor;
							} else {
								color = bgcolor;
							}
							for (xr=0; xr < size; xr++) {
								if ((x >= _displayclipx1) && (x < _displayclipx2)) {
									writedata16_cont(color);
								}
								x++;
							}
						}
						for (xr=0; xr < size; xr++) {
							if ((x >= _displayclipx1) && (x < _displayclipx2)) {
								writedata16_cont(bgcolor);
							}
							x++;
						}
					}
					y++;
				}
				mask = mask << 1;
			}
			writecommand_last(ILI9341_NOP);
			endSPITransaction();
		}
	}
}

static uint32_t fetchbit(const uint8_t *p, uint32_t index)
{
	if (p[index >> 3] & (1 << (7 - (index & 7)))) return 1;
	return 0;
}

static uint32_t fetchbits_unsigned(const uint8_t *p, uint32_t index, uint32_t required)
{
	uint32_t val = 0;
	do {
		uint8_t b = p[index >> 3];
		uint32_t avail = 8 - (index & 7);
		if (avail <= required) {
			val <<= avail;
			val |= b & ((1 << avail) - 1);
			index += avail;
			required -= avail;
		} else {
			b >>= avail - required;
			val <<= required;
			val |= b & ((1 << required) - 1);
			break;
		}
	} while (required);
	return val;
}

static uint32_t fetchbits_signed(const uint8_t *p, uint32_t index, uint32_t required)
{
	uint32_t val = fetchbits_unsigned(p, index, required);
	if (val & (1 << (required - 1))) {
		return (int32_t)val - (1 << required);
	}
	return (int32_t)val;
}


void ILI9341_t3n::drawFontChar(unsigned int c)
{
	uint32_t bitoffset;
	const uint8_t *data;

	//Serial.printf("drawFontChar(%c) %d\n", c, c);

	if (c >= font->index1_first && c <= font->index1_last) {
		bitoffset = c - font->index1_first;
		bitoffset *= font->bits_index;
	} else if (c >= font->index2_first && c <= font->index2_last) {
		bitoffset = c - font->index2_first + font->index1_last - font->index1_first + 1;
		bitoffset *= font->bits_index;
	} else if (font->unicode) {
		return; // TODO: implement sparse unicode
	} else {
		return;
	}
	//Serial.printf("  index =  %d\n", fetchbits_unsigned(font->index, bitoffset, font->bits_index));
	data = font->data + fetchbits_unsigned(font->index, bitoffset, font->bits_index);

	uint32_t encoding = fetchbits_unsigned(data, 0, 3);
	if (encoding != 0) return;
	uint32_t width = fetchbits_unsigned(data, 3, font->bits_width);
	bitoffset = font->bits_width + 3;
	uint32_t height = fetchbits_unsigned(data, bitoffset, font->bits_height);
	bitoffset += font->bits_height;
	//Serial.printf("  size =   %d,%d\n", width, height);
	//Serial.printf("  line space = %d\n", font->line_space);

	int32_t xoffset = fetchbits_signed(data, bitoffset, font->bits_xoffset);
	bitoffset += font->bits_xoffset;
	int32_t yoffset = fetchbits_signed(data, bitoffset, font->bits_yoffset);
	bitoffset += font->bits_yoffset;
	//Serial.printf("  offset = %d,%d\n", xoffset, yoffset);

	uint32_t delta = fetchbits_unsigned(data, bitoffset, font->bits_delta);
	bitoffset += font->bits_delta;
	//Serial.printf("  delta =  %d\n", delta);

	//Serial.printf("  cursor = %d,%d\n", cursor_x, cursor_y);

	 //horizontally, we draw every pixel, or none at all
	if (cursor_x < 0) cursor_x = 0;
	int32_t origin_x = cursor_x + xoffset;
	if (origin_x < 0) {
		cursor_x -= xoffset;
		origin_x = 0;
	}
	if (origin_x + (int)width > _width) {
		if (!wrap) return;
		origin_x = 0;
		if (xoffset >= 0) {
			cursor_x = 0;
		} else {
			cursor_x = -xoffset;
		}
		cursor_y += font->line_space;
	}
	if (cursor_y >= _height) return;

	// vertically, the top and/or bottom can be clipped
	int32_t origin_y = cursor_y + font->cap_height - height - yoffset;
	//Serial.printf("  origin = %d,%d\n", origin_x, origin_y);

	// TODO: compute top skip and number of lines
	int32_t linecount = height;
	//uint32_t loopcount = 0;
	int32_t y = origin_y;
	bool opaque = (textbgcolor != textcolor);


	// Going to try a fast Opaque method which works similar to drawChar, which is near the speed of writerect
	if (!opaque) {
		while (linecount > 0) {
			//Serial.printf("    linecount = %d\n", linecount);
			uint32_t n = 1;
			if (fetchbit(data, bitoffset++) != 0) {
				n = fetchbits_unsigned(data, bitoffset, 3) + 2;
				bitoffset += 3;
			}
			uint32_t x = 0;
			do {
				int32_t xsize = width - x;
				if (xsize > 32) xsize = 32;
				uint32_t bits = fetchbits_unsigned(data, bitoffset, xsize);
				//Serial.printf("    multi line %d %d %x\n", n, x, bits);
				drawFontBits(opaque, bits, xsize, origin_x + x, y, n);
				bitoffset += xsize;
				x += xsize;
			} while (x < width);


			y += n;
			linecount -= n;
			//if (++loopcount > 100) {
				//Serial.println("     abort draw loop");
				//break;
			//}
		}
	} else {
		// Now opaque mode... 
		// Now write out background color for the number of rows above the above the character
		// figure out bounding rectangle... 
		// In this mode we need to update to use the offset and bounding rectangles as we are doing it it direct.
		// also update the Origin 
		int cursor_x_origin = cursor_x + _originx;
		int cursor_y_origin = cursor_y + _originy;
		origin_x += _originx;
		origin_y += _originy;



		int start_x = (origin_x < cursor_x_origin) ? origin_x : cursor_x_origin; 	
		if (start_x < 0) start_x = 0;
		
		int start_y = (origin_y < cursor_y_origin) ? origin_y : cursor_y_origin; 
		if (start_y < 0) start_y = 0;
		int end_x = cursor_x_origin + delta; 
		if ((origin_x + (int)width) > end_x)
			end_x = origin_x + (int)width;
		if (end_x >= _displayclipx2)  end_x = _displayclipx2;	
		int end_y = cursor_y_origin + font->line_space; 
		if ((origin_y + (int)height) > end_y)
			end_y = origin_y + (int)height;
		if (end_y >= _displayclipy2) end_y = _displayclipy2;	
		end_x--;	// setup to last one we draw
		end_y--;
		int start_x_min = (start_x >= _displayclipx1) ? start_x : _displayclipx1;
		int start_y_min = (start_y >= _displayclipy1) ? start_y : _displayclipy1;

		// See if anything is in the display area.
		if((end_x < _displayclipx1) ||(start_x >= _displayclipx2) || (end_y < _displayclipy1) || (start_y >= _displayclipy2)) {
			cursor_x += delta;	// could use goto or another indent level...
		 	return;
		}
/*
		Serial.printf("drawFontChar(%c) %d\n", c, c);
		Serial.printf("  size =   %d,%d\n", width, height);
		Serial.printf("  line space = %d\n", font->line_space);
		Serial.printf("  offset = %d,%d\n", xoffset, yoffset);
		Serial.printf("  delta =  %d\n", delta);
		Serial.printf("  cursor = %d,%d\n", cursor_x, cursor_y);
		Serial.printf("  origin = %d,%d\n", origin_x, origin_y);

		Serial.printf("  Bounding: (%d, %d)-(%d, %d)\n", start_x, start_y, end_x, end_y);
		Serial.printf("  mins (%d %d),\n", start_x_min, start_y_min);
*/
		#ifdef ENABLE_ILI9341_FRAMEBUFFER
		if (_use_fbtft) {
			uint16_t * pfbPixel_row = &_pfbtft[ start_y*_width + start_x];
			uint16_t * pfbPixel;
			int screen_y = start_y;
			int screen_x;

			while (screen_y < origin_y) {
				pfbPixel = pfbPixel_row;
				// only output if this line is within the clipping region.
				if ((screen_y >= _displayclipy1) && (screen_y < _displayclipy2)) {
					for (screen_x = start_x; screen_x <= end_x; screen_x++) {
						if (screen_x >= _displayclipx1) {
							*pfbPixel = textbgcolor;
						}
						pfbPixel++;
					}
				}
				screen_y++;
				pfbPixel_row += _width;
			}

			// Now lets process each of the data lines. 
			screen_y = origin_y;

			while (linecount > 0) {
				//Serial.printf("    linecount = %d\n", linecount);
				uint32_t b = fetchbit(data, bitoffset++);
				uint32_t n;
				if (b == 0) {
					//Serial.println("Single");
					n = 1;
				} else {
					//Serial.println("Multi");
					n = fetchbits_unsigned(data, bitoffset, 3) + 2;
					bitoffset += 3;
				}
				uint32_t bitoffset_row_start = bitoffset;
				while (n--) {
					pfbPixel = pfbPixel_row;
					if ((screen_y >= _displayclipy1) && (screen_y < _displayclipy2)) {
						bitoffset = bitoffset_row_start;	// we will work through these bits maybe multiple times

						for (screen_x = start_x; screen_x < origin_x; screen_x++) {
							if (screen_x >= _displayclipx1) {
								*pfbPixel = textbgcolor;
							} // make sure not clipped
							pfbPixel++;
						}
					}

					screen_x = origin_x;
					uint32_t x = 0;
					do {
						uint32_t xsize = width - x;
						if (xsize > 32) xsize = 32;
						uint32_t bits = fetchbits_unsigned(data, bitoffset, xsize);
						uint32_t bit_mask = 1 << (xsize-1);
						//Serial.printf(" %d %d %x %x\n", x, xsize, bits, bit_mask);
						if ((screen_y >= _displayclipy1) && (screen_y < _displayclipy2)) {
							while (bit_mask && (screen_x <= end_x)) {
								if ((screen_x >= _displayclipx1) && (screen_x < _displayclipx2)) {
									*pfbPixel = (bits & bit_mask) ? textcolor : textbgcolor;
								}
								pfbPixel++;	
								bit_mask = bit_mask >> 1;
								screen_x++;	// increment our pixel position. 
							}
						}
							bitoffset += xsize;
						x += xsize;
					} while (x < width);
					if ((screen_y >= _displayclipy1) && (screen_y < _displayclipy2)) {
						// output bg color and right hand side
						while (screen_x++ <= end_x) {
							*pfbPixel++ = textbgcolor;
						}
					}			 
		 			screen_y++;
					pfbPixel_row += _width;
					linecount--;
				}
			}

			// clear below character
	 		while (screen_y++ <= end_y) {
				if ((screen_y >= _displayclipy1) && (screen_y < _displayclipy2)) {
					pfbPixel = pfbPixel_row;
					for (screen_x = start_x; screen_x <= end_x; screen_x++) {
						if (screen_x >= _displayclipx1) {
							*pfbPixel = textbgcolor;
						}
						pfbPixel++;
					}
				}
				pfbPixel_row += _width;
			}

		} else 
		#endif
		{
			beginSPITransaction();
			//Serial.printf("SetAddr %d %d %d %d\n", start_x_min, start_y_min, end_x, end_y);
			// output rectangle we are updating... We have already clipped end_x/y, but not yet start_x/y
			setAddr( start_x_min, start_y_min, end_x, end_y);
			writecommand_cont(ILI9341_RAMWR);
			int screen_y = start_y_min;
			int screen_x;
			while (screen_y < origin_y) {
				for (screen_x = start_x_min; screen_x <= end_x; screen_x++) {
					writedata16_cont(textbgcolor);
				}
				screen_y++;
			}

			// Now lets process each of the data lines. 
			screen_y = origin_y;
			while (linecount > 0) {
				//Serial.printf("    linecount = %d\n", linecount);
				uint32_t b = fetchbit(data, bitoffset++);
				uint32_t n;
				if (b == 0) {
					//Serial.println("    Single");
					n = 1;
				} else {
					//Serial.println("    Multi");
					n = fetchbits_unsigned(data, bitoffset, 3) + 2;
					bitoffset += 3;
				}
				uint32_t bitoffset_row_start = bitoffset;
				while (n--) {
					// do some clipping here. 
					bitoffset = bitoffset_row_start;	// we will work through these bits maybe multiple times
					// We need to handle case where some of the bits may not be visible, but we still need to
					// read through them
					//Serial.printf("y:%d  %d %d %d %d\n", screen_y, start_x, origin_x, _displayclipx1, _displayclipx2);
					if ((screen_y >= _displayclipy1) && (screen_y < _displayclipy2)) {
						for (screen_x = start_x; screen_x < origin_x; screen_x++) {
							if ((screen_x >= _displayclipx1) && (screen_x < _displayclipx2)) {
								//Serial.write('-');
								writedata16_cont(textbgcolor);
							}
						}
					}	
					uint32_t x = 0;
					screen_x = origin_x;
					do {
						uint32_t xsize = width - x;
						if (xsize > 32) xsize = 32;
						uint32_t bits = fetchbits_unsigned(data, bitoffset, xsize);
						uint32_t bit_mask = 1 << (xsize-1);
						//Serial.printf("     %d %d %x %x - ", x, xsize, bits, bit_mask);
						if ((screen_y >= _displayclipy1) && (screen_y < _displayclipy2)) {
							while (bit_mask) {
								if ((screen_x >= _displayclipx1) && (screen_x < _displayclipx2)) {
									writedata16_cont((bits & bit_mask) ? textcolor : textbgcolor);
									//Serial.write((bits & bit_mask) ? '*' : '.');
								}
								bit_mask = bit_mask >> 1;
								screen_x++ ; // Current actual screen X
							}
							//Serial.println();
							bitoffset += xsize;
						}
						x += xsize;
					} while (x < width) ;
					if ((screen_y >= _displayclipy1) && (screen_y < _displayclipy2)) {
						// output bg color and right hand side
						while (screen_x++ <= end_x) {
							writedata16_cont(textbgcolor);
							//Serial.write('+');
						}
						//Serial.println();
					}
		 			screen_y++;
					linecount--;
				}
			}

			// clear below character - note reusing xcreen_x for this
			screen_x = (end_y + 1 - screen_y) * (end_x + 1 - start_x_min); // How many bytes we need to still output
			//Serial.printf("Clear Below: %d\n", screen_x);
			while (screen_x-- > 1) {
				writedata16_cont(textbgcolor);
			}
			writedata16_last(textbgcolor);
			endSPITransaction();
		}

	}
	// Increment to setup for the next character.
	cursor_x += delta;

}

//strPixelLen			- gets pixel length of given ASCII string
int16_t ILI9341_t3n::strPixelLen(char * str)
{
//	//Serial.printf("strPixelLen %s\n", str);
	if (!str) return(0);
	uint16_t len=0, maxlen=0;
	while (*str)
	{
		if (*str=='\n')
		{
			if ( len > maxlen )
			{
				maxlen=len;
				len=0;
			}
		}
		else
		{
			if (!font)
			{
				len+=textsize*6;
			}
			else
			{

				uint32_t bitoffset;
				const uint8_t *data;
				uint16_t c = *str;

//				//Serial.printf("char %c(%d)\n", c,c);

				if (c >= font->index1_first && c <= font->index1_last) {
					bitoffset = c - font->index1_first;
					bitoffset *= font->bits_index;
				} else if (c >= font->index2_first && c <= font->index2_last) {
					bitoffset = c - font->index2_first + font->index1_last - font->index1_first + 1;
					bitoffset *= font->bits_index;
				} else if (font->unicode) {
					continue;
				} else {
					continue;
				}
				//Serial.printf("  index =  %d\n", fetchbits_unsigned(font->index, bitoffset, font->bits_index));
				data = font->data + fetchbits_unsigned(font->index, bitoffset, font->bits_index);

				uint32_t encoding = fetchbits_unsigned(data, 0, 3);
				if (encoding != 0) continue;
//				uint32_t width = fetchbits_unsigned(data, 3, font->bits_width);
//				//Serial.printf("  width =  %d\n", width);
				bitoffset = font->bits_width + 3;
				bitoffset += font->bits_height;

//				int32_t xoffset = fetchbits_signed(data, bitoffset, font->bits_xoffset);
//				//Serial.printf("  xoffset =  %d\n", xoffset);
				bitoffset += font->bits_xoffset;
				bitoffset += font->bits_yoffset;

				uint32_t delta = fetchbits_unsigned(data, bitoffset, font->bits_delta);
				bitoffset += font->bits_delta;
//				//Serial.printf("  delta =  %d\n", delta);

				len += delta;//+width-xoffset;
//				//Serial.printf("  len =  %d\n", len);
				if ( len > maxlen )
				{
					maxlen=len;
//					//Serial.printf("  maxlen =  %d\n", maxlen);
				}
			
			}
		}
		str++;
	}
//	//Serial.printf("Return  maxlen =  %d\n", maxlen);
	return( maxlen );
}
void ILI9341_t3n::drawFontBits(bool opaque, uint32_t bits, uint32_t numbits, int32_t x, int32_t y, uint32_t repeat)
{
	if (bits == 0) {
		if (opaque) {
			fillRect(x, y, numbits, repeat, textbgcolor);
		}
	} else {
		int32_t x1 = x;
		uint32_t n = numbits;
		int w;
		int bgw;

		w = 0;
		bgw = 0;

		do {
			n--;
			if (bits & (1 << n)) {
				if (bgw>0) {
					if (opaque) {
						fillRect(x1 - bgw, y, bgw, repeat, textbgcolor);
					}
					bgw=0;
				}
				w++;
			} else {
				if (w>0) {
					fillRect(x1 - w, y, w, repeat, textcolor);
					w = 0;
				}
				bgw++;
			}
			x1++;
		} while (n > 0);

		if (w > 0) {
			fillRect(x1 - w, y, w, repeat, textcolor);
		}

		if (bgw > 0) {
			if (opaque) {
				fillRect(x1 - bgw, y, bgw, repeat, textbgcolor);
			}
		}
	}
}

void ILI9341_t3n::setCursor(int16_t x, int16_t y) {
	if (x < 0) x = 0;
	else if (x >= _width) x = _width - 1;
	cursor_x = x;
	if (y < 0) y = 0;
	else if (y >= _height) y = _height - 1;
	cursor_y = y;
}
void ILI9341_t3n::getCursor(int16_t *x, int16_t *y) {
  *x = cursor_x;
  *y = cursor_y;
}

void ILI9341_t3n::setTextSize(uint8_t s) {
  textsize = (s > 0) ? s : 1;
}

uint8_t ILI9341_t3n::getTextSize() {
	return textsize;
}

void ILI9341_t3n::setTextColor(uint16_t c) {
  // For 'transparent' background, we'll set the bg
  // to the same as fg instead of using a flag
  textcolor = textbgcolor = c;
}

void ILI9341_t3n::setTextColor(uint16_t c, uint16_t b) {
  textcolor   = c;
  textbgcolor = b;
}

void ILI9341_t3n::setTextWrap(boolean w) {
  wrap = w;
}

boolean ILI9341_t3n::getTextWrap()
{
	return wrap;
}

uint8_t ILI9341_t3n::getRotation(void) {
  return rotation;
}

void ILI9341_t3n::sleep(bool enable) {
	beginSPITransaction();
	if (enable) {
		writecommand_cont(ILI9341_DISPOFF);		
		writecommand_last(ILI9341_SLPIN);	
		  endSPITransaction();
	} else {
		writecommand_cont(ILI9341_DISPON);
		writecommand_last(ILI9341_SLPOUT);
		endSPITransaction();
		delay(5);
	}
}

void Adafruit_GFX_Button::initButton(ILI9341_t3n *gfx,
	int16_t x, int16_t y, uint8_t w, uint8_t h,
	uint16_t outline, uint16_t fill, uint16_t textcolor,
	const char *label, uint8_t textsize)
{
	_x = x;
	_y = y;
	_w = w;
	_h = h;
	_outlinecolor = outline;
	_fillcolor = fill;
	_textcolor = textcolor;
	_textsize = textsize;
	_gfx = gfx;
	strncpy(_label, label, 9);
	_label[9] = 0;
}

void Adafruit_GFX_Button::drawButton(bool inverted)
{
	uint16_t fill, outline, text;

	if (! inverted) {
		fill = _fillcolor;
		outline = _outlinecolor;
		text = _textcolor;
	} else {
		fill =  _textcolor;
		outline = _outlinecolor;
		text = _fillcolor;
	}
	_gfx->fillRoundRect(_x - (_w/2), _y - (_h/2), _w, _h, min(_w,_h)/4, fill);
	_gfx->drawRoundRect(_x - (_w/2), _y - (_h/2), _w, _h, min(_w,_h)/4, outline);
	_gfx->setCursor(_x - strlen(_label)*3*_textsize, _y-4*_textsize);
	_gfx->setTextColor(text);
	_gfx->setTextSize(_textsize);
	_gfx->print(_label);
}

bool Adafruit_GFX_Button::contains(int16_t x, int16_t y)
{
	if ((x < (_x - _w/2)) || (x > (_x + _w/2))) return false;
	if ((y < (_y - _h/2)) || (y > (_y + _h/2))) return false;
	return true;
}
 
KurtE, seems to be working pretty good, I removed the tft.waitUpdateAsyncComplete(); from the drawtimer loop and it ran all night without locking up. Thank you for creating this library and sticking with it:)


Now that its working i'm looking for ways to make it faster. Anyone know of a way to make my second example even faster?

The below code section takes up to 384uS at 240MHz to complete.
Code:
      tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
      tft.setTextSize(3);
      tft.setCursor(3, 180);   //cursor set here unless one of the if is true.      
      if(WattHours < 10){
      tft.print("    "); }
      else if(WattHours < 100){
      tft.print("   ");        }
      else if (WattHours < 1000){      
      tft.print("  ");           }
      else if (WattHours < 10000){      
      tft.print(" ");           }          
      tft.print(WattHours, 3);

This version using fillRect takes up to 220uS at 240MHz to complete. In the not so distant past this was the fastest way to go about it.
Code:
      tft.setTextColor(ILI9341_WHITE);
      tft.fillRect(3, 180, 162, 24, ILI9341_BLACK);
      tft.setTextSize(3);   
      if(WattHours < 10){
      tft.setCursor(75, 180);       }
      else if(WattHours < 100){
      tft.setCursor(57, 180);       }
      else if (WattHours < 1000){      
      tft.setCursor(39, 180);       }
      else if (WattHours < 10000){      
      tft.setCursor(21, 180);       } 
      else {tft.setCursor(3, 180);  }         
      tft.print(WattHours, 3);
 
Couple of things. Some of these may help, some may not.

Without frame buffer, doing Opaque text was lot faster than transparent so you were helped by using:
tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);

FillRect - Now depending on the above, why fill the whole rectangle when you are going to overwrite everything to the right of the new text cursor.

FillRect - Remember the previous outputs starting setCursor and only when necessary fill rect from the old cursor start to the new cursor spot.
That is if the new numbers number of digits is >= the previous numbers number of digits, you don't need to do a fill...
 
Back
Top