Frame buffer for Adafruit_GFX based displays

vjmuzik

Well-known member
I've been using the builtin Adafruit_GFX classes to implement a frame buffer in my projects, I finally decided to make a simple helper class that makes it a little bit easier to accomplish and maybe less confusing for others to implement as well. The reason why you would want to use a frame buffer is to speed up your code if you are drawing a lot of objects to the display, normally every time you go to draw something it makes use of the SPI bus. Over a long span of commands this can drastically slow your program, this lets you quickly draw to the buffer in memory instead then you can send your drawing once it's ready all at one time to the display. I've only been able to test it with ST7735 displays, there shouldn't be a reason why it wouldn't work with other Adafruit_GFX displays, but someone else would have to test that as I don't have any others at the moment. Some more information on how to use it can be found on the GitHub page as well as a really simple example that shows it's not difficult to make use of.

Here's the GitHub link to it: https://github.com/vjmuzik/Adafruit_GFX_Buffer
 
Hallo fiends

I'm using a ST7735 color TFT 1.8 inch RGB display and have a problem with the framebuffer.
If the framebuffer is disabled then the display is ok. If the framebuffer is switched on, the display is wrong.

The originally code was written for a smaller display 160 x 80 pixel. Where is my problem.


without framebuffer
20201103-110031.jpg


with framebuffer
20201103-110119.jpg


ST7735_t3.h
Code:
/***************************************************
  This is a library for the Adafruit 1.8" SPI display.
  This library works with the Adafruit 1.8" TFT Breakout w/SD card
  ----> http://www.adafruit.com/products/358
  as well as Adafruit raw 1.8" TFT display
  ----> http://www.adafruit.com/products/618

  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
 ****************************************************/

#ifndef __ST7735_t3_H_
#define __ST7735_t3_H_

#include "Arduino.h"
#include <SPI.h>
#include <Adafruit_GFX.h>

#ifndef DISABLE_ST77XX_FRAMEBUFFER
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
#define ENABLE_ST77XX_FRAMEBUFFER
#elif defined(__IMXRT1062__)
#define ENABLE_ST77XX_FRAMEBUFFER
#endif
// Lets allow the user to define if they want T3.2 to enable frame buffer.
// it will only work on subset of displays due to memory
#define ENABLE_ST77XX_FRAMEBUFFER_T32
#if defined(__MK20DX256__) && defined(ENABLE_ST77XX_FRAMEBUFFER_T32)
#define ENABLE_ST77XX_FRAMEBUFFER
#endif
#endif


#define ST7735_SPICLOCK 24000000
//#define ST7735_SPICLOCK 16000000

// some flags for initR() :(
#define INITR_GREENTAB 0x0
#define INITR_REDTAB   0x1
#define INITR_BLACKTAB 0x2

#define INITR_18GREENTAB    INITR_GREENTAB
#define INITR_18REDTAB      INITR_REDTAB
#define INITR_18BLACKTAB    INITR_BLACKTAB
#define INITR_144GREENTAB   0x1
#define INITR_144GREENTAB_OFFSET   0x4
#define INITR_MINI160x80  0x05

#define INIT_ST7789_TABCOLOR 42  // Not used except as a indicator to the code... 

#define ST7735_TFTWIDTH  128
#define ST7735_TFTWIDTH_80     80 // for mini
// for 1.44" display
#define ST7735_TFTHEIGHT_144 128
// for 1.8" display and mini
#define ST7735_TFTHEIGHT_160  160 // for 1.8" and mini display

#define ST7735_NOP     0x00
#define ST7735_SWRESET 0x01
#define ST7735_RDDID   0x04
#define ST7735_RDDST   0x09

#define ST7735_SLPIN   0x10
#define ST7735_SLPOUT  0x11
#define ST7735_PTLON   0x12
#define ST7735_NORON   0x13

#define ST7735_INVOFF  0x20
#define ST7735_INVON   0x21
#define ST7735_DISPOFF 0x28
#define ST7735_DISPON  0x29
#define ST7735_CASET   0x2A
#define ST7735_RASET   0x2B
#define ST7735_RAMWR   0x2C
#define ST7735_RAMRD   0x2E

#define ST7735_PTLAR   0x30
#define ST7735_COLMOD  0x3A
#define ST7735_MADCTL  0x36

#define ST7735_FRMCTR1 0xB1
#define ST7735_FRMCTR2 0xB2
#define ST7735_FRMCTR3 0xB3
#define ST7735_INVCTR  0xB4
#define ST7735_DISSET5 0xB6

#define ST7735_PWCTR1  0xC0
#define ST7735_PWCTR2  0xC1
#define ST7735_PWCTR3  0xC2
#define ST7735_PWCTR4  0xC3
#define ST7735_PWCTR5  0xC4
#define ST7735_VMCTR1  0xC5

#define ST7735_RDID1   0xDA
#define ST7735_RDID2   0xDB
#define ST7735_RDID3   0xDC
#define ST7735_RDID4   0xDD

#define ST7735_PWCTR6  0xFC

#define ST7735_GMCTRP1 0xE0
#define ST7735_GMCTRN1 0xE1

// Color definitions
#define ST7735_BLACK   0x0000
#define ST7735_BLUE    0x001F
#define ST7735_RED     0xF800
#define ST7735_GREEN   0x07E0
#define ST7735_CYAN    0x07FF
#define ST7735_MAGENTA 0xF81F
#define ST7735_YELLOW  0xFFE0
#define ST7735_WHITE   0xFFFF

// Also define them in a non specific ST77XX specific name
#define ST77XX_BLACK      0x0000
#define ST77XX_WHITE      0xFFFF
#define ST77XX_RED        0xF800
#define ST77XX_GREEN      0x07E0
#define ST77XX_BLUE       0x001F
#define ST77XX_CYAN       0x07FF
#define ST77XX_MAGENTA    0xF81F
#define ST77XX_YELLOW     0xFFE0
#define ST77XX_ORANGE     0xFA00
#define ST77XX_DARKRED    0xA000
#define ST77XX_DARKGREY   0x2222


#if defined(__IMXRT1062__)  // Teensy 4.x
// Also define these in lower memory so as to make sure they are not cached...
// try work around DMA memory cached.  So have a couple of buffers we copy frame buffer into
// as to move it out of the memory that is cached...
#define ST77XX_DMA_BUFFER_SIZE 512
typedef struct {
  DMASetting      _dmasettings[2];
  DMAChannel      _dmatx;
  uint16_t        _dma_buffer1[ST77XX_DMA_BUFFER_SIZE] __attribute__ ((aligned(4)));
  uint16_t        _dma_buffer2[ST77XX_DMA_BUFFER_SIZE] __attribute__ ((aligned(4)));  
} ST7735DMA_Data;
#endif


class ST7735_t3 : public Adafruit_GFX {

 public:

  ST7735_t3(uint8_t CS, uint8_t RS, uint8_t SID, uint8_t SCLK, uint8_t RST = -1);
  ST7735_t3(uint8_t CS, uint8_t RS, uint8_t RST = -1);

  void     initB(void),                             // for ST7735B displays
           initR(uint8_t options = INITR_GREENTAB), // for ST7735R
           setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1),
           pushColor(uint16_t color, boolean last_pixel=false),
           fillScreen(uint16_t color),
           drawPixel(int16_t x, int16_t y, uint16_t color),
           drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color),
           drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color),
           fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);
  virtual void setRotation(uint8_t r);
  void     invertDisplay(boolean i);
  void     setRowColStart(uint16_t x, uint16_t y);
  uint16_t  rowStart() {return _rowstart;}
  uint16_t  colStart() {return _colstart;}

  void setAddr(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
    __attribute__((always_inline)) {
        writecommand(ST7735_CASET); // Column addr set
        writedata16(x0+_xstart);   // XSTART 
        writedata16(x1+_xstart);   // XEND
        writecommand(ST7735_RASET); // Row addr set
        writedata16(y0+_ystart);   // YSTART
        writedata16(y1+_ystart);   // YEND
  }

  void sendCommand(uint8_t commandByte, const uint8_t *dataBytes, uint8_t numDataBytes);



  // Pass 8-bit (each) R,G,B, get back 16-bit packed color
  inline uint16_t Color565(uint8_t r, uint8_t g, uint8_t b) {
           return ((b & 0xF8) << 8) | ((g & 0xFC) << 3) | (r >> 3);
  }
  void setBitrate(uint32_t n);

  /* These are not for current use, 8-bit protocol only!
  uint8_t  readdata(void),
           readcommand8(uint8_t);
  uint16_t readcommand16(uint8_t);
  uint32_t readcommand32(uint8_t);
  void     dummyclock(void);
  */
  // Useful methods added from ili9341_t3 
  void writeRect(int16_t x, int16_t y, int16_t w, int16_t h, const uint16_t *pcolors);

// Frame buffer support
#ifdef ENABLE_ST77XX_FRAMEBUFFER
  enum {ST77XX_DMA_INIT=0x01, ST77XX_DMA_CONT=0x02, ST77XX_DMA_FINISH=0x04,ST77XX_DMA_ACTIVE=0x80};

  // added support to use optional Frame buffer
  void  setFrameBuffer(uint16_t *frame_buffer);
  uint8_t useFrameBuffer(boolean b);    // use the frame buffer?  First call will allocate
  void  freeFrameBuffer(void);      // explicit call to release the buffer
  void  updateScreen(void);       // call to say update the screen now. 
  bool  updateScreenAsync(bool update_cont = false);  // call to say update the screen optinoally turn into continuous mode. 
  void  waitUpdateAsyncComplete(void);
  void  endUpdateAsync();      // Turn of the continueous mode fla
  void  dumpDMASettings();
  uint16_t *getFrameBuffer() {return _pfbtft;}
  uint32_t frameCount() {return _dma_frame_count; }
  boolean asyncUpdateActive(void)  {return (_dma_state & ST77XX_DMA_ACTIVE);}
  void  initDMASettings(void);
  #else
  // added support to use optional Frame buffer
  void  setFrameBuffer(uint16_t *frame_buffer) {return;}
  uint8_t useFrameBuffer(boolean b) {return 0;};    // use the frame buffer?  First call will allocate
  void  freeFrameBuffer(void) {return;}      // explicit call to release the buffer
  void  updateScreen(void) {return;}       // call to say update the screen now. 
  bool  updateScreenAsync(bool update_cont = false) {return false;}  // call to say update the screen optinoally turn into continuous mode. 
  void  waitUpdateAsyncComplete(void) {return;}
  void  endUpdateAsync() {return;}      // Turn of the continueous mode fla
  void  dumpDMASettings() {return;}

  uint32_t frameCount() {return 0; }
  uint16_t *getFrameBuffer() {return NULL;}
  boolean asyncUpdateActive(void)  {return false;}
  #endif


 protected:
  uint8_t  tabcolor;

  void     spiwrite(uint8_t),
           spiwrite16(uint16_t d),
           writecommand(uint8_t c),
           writecommand_last(uint8_t c),
           writedata(uint8_t d),
           writedata_last(uint8_t d),
           writedata16(uint16_t d),
           writedata16_last(uint16_t d),
           commandList(const uint8_t *addr),
           commonInit(const uint8_t *cmdList, uint8_t mode=SPI_MODE0);
//uint8_t  spiread(void);

  boolean  hwSPI=true;


  uint16_t _colstart, _rowstart, _xstart, _ystart, _rot, _screenHeight, _screenWidth;
  SPISettings _spiSettings;
#if defined(__MK66FX1M0__)
  uint8_t  _cs, _rs, _rst, _sid, _sclk;
  uint8_t pcs_data, pcs_command;
  uint32_t ctar;
  volatile uint8_t *datapin, *clkpin, *cspin, *rspin;

  SPIClass *_pspi = nullptr;
  uint8_t   _spi_num = 0;          // Which buss is this spi on? 
  KINETISK_SPI_t *_pkinetisk_spi = nullptr;
  SPIClass::SPI_Hardware_t *_spi_hardware = nullptr;
  void waitTransmitComplete(void);
  void waitTransmitComplete(uint32_t mcr);
  uint32_t _fifo_full_test = 0;

  inline void beginSPITransaction() {
    if (_pspi) _pspi->beginTransaction(_spiSettings);
    if (cspin) *cspin = 0;
  }

  inline void endSPITransaction()
  {
    if (cspin) *cspin = 1;
    if (_pspi) _pspi->endTransaction();  
  }


#endif
#if defined(__IMXRT1062__)  // Teensy 4.x
  SPIClass *_pspi = nullptr;
  uint8_t   _spi_num = 0;          // Which buss is this spi on? 
  IMXRT_LPSPI_t *_pimxrt_spi = nullptr;
  SPIClass::SPI_Hardware_t *_spi_hardware;
  uint8_t _pending_rx_count = 0;
  uint32_t _spi_tcr_current = 0; 


  void DIRECT_WRITE_LOW(volatile uint32_t * base, uint32_t mask)  __attribute__((always_inline)) {
    *(base+34) = mask;
  }
  void DIRECT_WRITE_HIGH(volatile uint32_t * base, uint32_t mask)  __attribute__((always_inline)) {
    *(base+33) = mask;
  }
  
  #define TCR_MASK  (LPSPI_TCR_PCS(3) | LPSPI_TCR_FRAMESZ(31) | LPSPI_TCR_CONT | LPSPI_TCR_RXMSK )

  void maybeUpdateTCR(uint32_t requested_tcr_state) {
  if ((_spi_tcr_current & TCR_MASK) != requested_tcr_state) {
      bool dc_state_change = (_spi_tcr_current & LPSPI_TCR_PCS(3)) != (requested_tcr_state & LPSPI_TCR_PCS(3));
      _spi_tcr_current = (_spi_tcr_current & ~TCR_MASK) | requested_tcr_state ;
      // only output when Transfer queue is empty.
      if (!dc_state_change || !_dcpinmask) {
        while ((_pimxrt_spi->FSR & 0x1f) )  ;
        _pimxrt_spi->TCR = _spi_tcr_current;  // update the TCR

      } else {
        waitTransmitComplete();
        if (requested_tcr_state & LPSPI_TCR_PCS(3)) DIRECT_WRITE_HIGH(_dcport, _dcpinmask);
        else DIRECT_WRITE_LOW(_dcport, _dcpinmask);
        _pimxrt_spi->TCR = _spi_tcr_current & ~(LPSPI_TCR_PCS(3) | LPSPI_TCR_CONT); // go ahead and update TCR anyway?  

      }
    }
  }

  inline void beginSPITransaction() {
    if (hwSPI) _pspi->beginTransaction(_spiSettings);
    if (_csport)DIRECT_WRITE_LOW(_csport, _cspinmask);
  }

  inline void endSPITransaction() {
    if (_csport)DIRECT_WRITE_HIGH(_csport, _cspinmask);
    if (hwSPI) _pspi->endTransaction();  
  }

 
  void waitFifoNotFull(void) {
    uint32_t tmp __attribute__((unused));
    do {
        if ((_pimxrt_spi->RSR & LPSPI_RSR_RXEMPTY) == 0)  {
            tmp = _pimxrt_spi->RDR;  // Read any pending RX bytes in
            if (_pending_rx_count) _pending_rx_count--; //decrement count of bytes still levt
        }
    } while ((_pimxrt_spi->SR & LPSPI_SR_TDF) == 0) ;
 }
 void waitTransmitComplete(void)  {
    uint32_t tmp __attribute__((unused));
//    digitalWriteFast(2, HIGH);

    while (_pending_rx_count) {
        if ((_pimxrt_spi->RSR & LPSPI_RSR_RXEMPTY) == 0)  {
            tmp = _pimxrt_spi->RDR;  // Read any pending RX bytes in
            _pending_rx_count--; //decrement count of bytes still levt
        }
    }
    _pimxrt_spi->CR = LPSPI_CR_MEN | LPSPI_CR_RRF;       // Clear RX FIFO
//    digitalWriteFast(2, LOW);
}


  uint8_t  _cs, _rs, _rst, _sid, _sclk;

  uint32_t _cspinmask;
  volatile uint32_t *_csport;
  uint32_t _dcpinmask;
  volatile uint32_t *_dcport;
  uint32_t _mosipinmask;
  volatile uint32_t *_mosiport;
  uint32_t _sckpinmask;
  volatile uint32_t *_sckport;
  

  uint32_t ctar;
#endif

#ifdef ENABLE_ST77XX_FRAMEBUFFER
    // Add support for optional frame buffer
  uint16_t  *_pfbtft;           // Optional Frame buffer 
  uint8_t   _use_fbtft;         // Are we in frame buffer mode?
  uint16_t  *_we_allocated_buffer;      // We allocated the buffer; 
  uint32_t  _count_pixels;       // How big is the display in total pixels...

  // Add DMA support. 
  // Note: We have enough memory to have more than one, so could have multiple active devices (one per SPI BUS)
  //     All three devices have 3 SPI buss so hard coded
  static  ST7735_t3     *_dmaActiveDisplay[3];  // Use pointer to this as a way to get back to object...
  volatile uint8_t      _dma_state;         // DMA status
  volatile uint32_t     _dma_frame_count;   // Can return a frame count...

  #if defined(__MK66FX1M0__) 
  // T3.6 use Scatter/gather with chain to do transfer
  static DMASetting   _dmasettings[3][4];
  DMAChannel   _dmatx;
  uint8_t      _cnt_dma_settings;   // how many do we need for this display?
  #elif defined(__IMXRT1062__)  // Teensy 4.x
  static ST7735DMA_Data _dma_data[3];   // one structure for each SPI buss... 
  // try work around DMA memory cached.  So have a couple of buffers we copy frame buffer into
  // as to move it out of the memory that is cached...
  volatile    uint32_t _dma_pixel_index = 0;
  volatile uint16_t _dma_sub_frame_count = 0; // Can return a frame count...
  uint16_t          _dma_buffer_size;   // the actual size we are using <= DMA_BUFFER_SIZE;
  uint16_t          _dma_cnt_sub_frames_per_frame;  
  uint32_t      _spi_fcr_save;    // save away previous FCR register value

  #elif defined(__MK64FX512__)
  // T3.5 - had issues scatter/gather so do just use channels/interrupts
  // and update and continue
  uint8_t _cspinmask;
  volatile uint8_t *_csport = nullptr;
  DMAChannel   _dmatx;
  DMAChannel   _dmarx;
  uint32_t   _dma_count_remaining;
  uint16_t   _dma_write_size_words;
  #elif defined(__MK20DX256__)
  // For first pass maybe emulate T3.5 on SPI...
  uint8_t _cspinmask;
  volatile uint8_t *_csport = nullptr;
  DMAChannel   _dmatx;
  DMAChannel   _dmarx;
  uint16_t   _dma_count_remaining;
  uint16_t   _dma_write_size_words;

  #endif  
  static void dmaInterrupt(void);
  static void dmaInterrupt1(void);
  static void dmaInterrupt2(void);
  void process_dma_interrupt(void);
#endif

};

#endif


ST7735Display.h
Code:
#define sclk 27
#define mosi 26
#define cs 2
#define dc 3
#define rst 9
#define DISPLAYTIMEOUT 700

#include <Adafruit_GFX.h>
#include "ST7735_t3.h" // Local copy from TD1.48 that works for 0.96" IPS 160x80 display

#include "Fonts/Org_01.h"
#include "Yeysk16pt7b.h"
#include "Fonts/FreeSansBold18pt7b.h"
#include "Fonts/FreeSans12pt7b.h"
#include "Fonts/FreeSans9pt7b.h"
#include "Fonts/FreeSansOblique24pt7b.h"
#include "Fonts/FreeSansBoldOblique24pt7b.h"

#define PULSE 1
#define VAR_TRI 2
#define FILTER_ENV 3
#define AMP_ENV 4

ST7735_t3 tft = ST7735_t3(cs, dc, mosi, sclk, rst);

String currentParameter = "";
String currentValue = "";
float currentFloatValue = 0.0;
String currentPgmNum = "";
String currentPatchName = "";
String newPatchName = "";
char * currentSettingsOption = "";
char * currentSettingsValue = "";
uint32_t currentSettingsPart = SETTINGS;
uint32_t paramType = PARAMETER;

boolean MIDIClkSignal = false;
uint32_t peakCount = 0;
uint16_t prevLen = 0;

uint32_t colour[NO_OF_VOICES] = {ST7735_BLUE, ST7735_BLUE, ST7735_BLUE, ST7735_BLUE, ST7735_BLUE, ST7735_BLUE, ST7735_BLUE, ST7735_BLUE, ST7735_BLUE, ST7735_BLUE, ST7735_BLUE, ST7735_BLUE};

unsigned long timer = 0;

void startTimer() {
  if (state == PARAMETER)
  {
    timer = millis();
  }
}

FLASHMEM void renderBootUpPage()
{
  tft.fillScreen(ST7735_BLACK);
  tft.drawRect(42, 30, 46, 11, ST7735_WHITE);
  tft.fillRect(88, 30, 61, 11, ST7735_WHITE);
  tft.setCursor(45, 31);
  tft.setFont(&Org_01);
  tft.setTextSize(1);
  tft.setTextColor(ST7735_WHITE);
  tft.println("ELECTRO");
  tft.setTextColor(ST7735_BLACK);
  tft.setCursor(91, 37);
  tft.println("TECHNIQUE");
  tft.setTextColor(ST7735_YELLOW);
  tft.setFont(&Yeysk16pt7b);
  tft.setCursor(5, 70);
  tft.setTextSize(1);
  tft.println("TSynth");
  tft.setTextColor(ST7735_RED);
  tft.setFont(&FreeSans9pt7b);
  tft.setCursor(110, 95);
  tft.println(VERSION);
}

FLASHMEM void renderPeak() {
  if (vuMeter && peak.available()) {
    uint16_t len = 0;
    if (peakCount > 1) {
      len = (int)(peak.read() * 75.0f);
      prevLen = len;
      peakCount = 0;
    } else {
      len = prevLen;
      peakCount++;
    }
    tft.drawFastVLine(158, 103 - len, len ,  len > 72 ? ST77XX_RED : ST77XX_GREEN);
    tft.drawFastVLine(159, 103 - len, len ,  len > 72 ? ST77XX_RED : ST77XX_GREEN);
  }
}

FLASHMEM void renderCurrentPatchPage() {
  tft.fillScreen(ST7735_BLACK);
  tft.setFont(&FreeSansBold18pt7b);
  tft.setCursor(5, 53);
  tft.setTextColor(ST7735_YELLOW);
  tft.setTextSize(1);
  tft.println(currentPgmNum);

  tft.setTextColor(ST7735_BLACK);
  tft.setFont(&Org_01);

  if (MIDIClkSignal) {
    tft.fillRect(100, 27, 14, 7, ST77XX_ORANGE);
    tft.setCursor(101, 32);
    tft.println("CK");
  }
  renderPeak();

  //      1 2 3 4 5 6 7 8 9 10 11 12
  //    1 B B B B B B B B B B B B
  //    2 B B B B B B Y Y Y Y Y Y
  //    3 B B B B O O O O Y Y Y Y
  //    4 B B B R O O O R R Y Y Y

  //V4
  if (voices[3].voiceOn == 1 && unison && notesOn == 4) {
    colour[3] = ST77XX_DARKRED;
  } else if (voices[3].voiceOn == 1 && unison && notesOn > 1 && colour[3] != ST77XX_DARKRED) {
    colour[3] = ST7735_BLUE;
  } else if (voices[3].voiceOn == 0) {
    colour[3] = ST7735_BLUE;
  }

  //V5-6
  if (voices[4].voiceOn == 1 && unison && notesOn > 2) {
    colour[4] = ST77XX_ORANGE;
    colour[5] = ST77XX_ORANGE;
  } else if (voices[4].voiceOn == 0) {
    colour[4] = ST7735_BLUE;
    colour[5] = ST7735_BLUE;
  }

  //V7
  if (voices[6].voiceOn == 1 && unison && notesOn > 2) {
    colour[6] = ST77XX_ORANGE;
  } else if (voices[6].voiceOn == 1 && unison && notesOn == 2 && colour[6] != ST77XX_ORANGE) {
    colour[6] = ST7735_YELLOW;
  } else if (voices[6].voiceOn == 0) {
    colour[6] = ST7735_BLUE;
  }

  //V8
  if (voices[7].voiceOn == 1 && unison && notesOn == 4) {
    colour[7] = ST77XX_DARKRED;
  } else if (voices[7].voiceOn == 1 && unison && notesOn == 3 && colour[7] != ST77XX_DARKRED) {
    colour[7] = ST77XX_ORANGE;
  } else if (voices[7].voiceOn == 1 && unison && notesOn == 2 && colour[7] != ST77XX_DARKRED && colour[7] != ST77XX_ORANGE) {
    colour[7] = ST7735_YELLOW;
  } else if (voices[7].voiceOn == 0) {
    colour[7] = ST7735_BLUE;
  }

  //V9
  if (voices[8].voiceOn == 1 && unison && notesOn == 4) {
    colour[8] = ST77XX_DARKRED;
  } else if (voices[8].voiceOn == 1 && unison && (notesOn == 2 || notesOn == 3) && colour[8] != ST77XX_DARKRED) {
    colour[8] = ST7735_YELLOW;
  } else if (voices[8].voiceOn == 0) {
    colour[8] = ST7735_BLUE;
  }

  //V10-12
  if (voices[9].voiceOn == 1 && unison && notesOn > 1) {
    colour[9] = ST7735_YELLOW;
    colour[10] = ST7735_YELLOW;
    colour[11] = ST7735_YELLOW;
  } else if (voices[9].voiceOn == 0) {
    colour[9] = ST7735_BLUE;
    colour[10] = ST7735_BLUE;
    colour[11] = ST7735_BLUE;
  }

  if (voices[0].voiceOn == 1)   tft.fillRect(117, 27, 8, 8, ST7735_BLUE); else tft.drawRect(117, 27, 8, 8, ST7735_BLUE);
  if (voices[1].voiceOn == 1)   tft.fillRect(127, 27, 8, 8, ST7735_BLUE); else tft.drawRect(127, 27, 8, 8, ST7735_BLUE);
  if (voices[2].voiceOn == 1)   tft.fillRect(137, 27, 8, 8, ST7735_BLUE); else tft.drawRect(137, 27, 8, 8, ST7735_BLUE);

  if (voices[3].voiceOn == 1)   tft.fillRect(147, 27, 8, 8, colour[3]); else tft.drawRect(147, 27, 8, 8, ST7735_BLUE);
  if (voices[4].voiceOn == 1)   tft.fillRect(117, 37, 8, 8, colour[4]); else tft.drawRect(117, 37, 8, 8, ST7735_BLUE);
  if (voices[5].voiceOn == 1)   tft.fillRect(127, 37, 8, 8, colour[5]); else tft.drawRect(127, 37, 8, 8, ST7735_BLUE);

  if (voices[6].voiceOn == 1)   tft.fillRect(137, 37, 8, 8, colour[6]); else tft.drawRect(137, 37, 8, 8, ST7735_BLUE);
  if (voices[7].voiceOn == 1)   tft.fillRect(147, 37, 8, 8, colour[7]); else tft.drawRect(147, 37, 8, 8, ST7735_BLUE);
  if (voices[8].voiceOn == 1)   tft.fillRect(117, 47, 8, 8, colour[8]); else tft.drawRect(117, 47, 8, 8, ST7735_BLUE);

  if (voices[9].voiceOn == 1)   tft.fillRect(127, 47, 8, 8, colour[9]); else tft.drawRect(127, 47, 8, 8, ST7735_BLUE);
  if (voices[10].voiceOn == 1)  tft.fillRect(137, 47, 8, 8, colour[10]); else tft.drawRect(137, 47, 8, 8, ST7735_BLUE);
  if (voices[11].voiceOn == 1)  tft.fillRect(147, 47, 8, 8, colour[11]); else tft.drawRect(147, 47, 8, 8, ST7735_BLUE);

  tft.drawFastHLine(10, 63, tft.width() - 20, ST7735_RED);
  tft.setFont(&FreeSans12pt7b);
  tft.setTextColor(ST7735_YELLOW);
  tft.setCursor(1, 90);
  tft.setTextColor(ST7735_WHITE);
  tft.println(currentPatchName);
}

FLASHMEM void renderPulseWidth(float value) {
  tft.drawFastHLine(108, 74, 15 + (value * 13), ST7735_CYAN);
  tft.drawFastVLine(123 + (value * 13), 74, 20, ST7735_CYAN);
  tft.drawFastHLine(123 + (value * 13), 94, 16 - (value * 13), ST7735_CYAN);
  if (value < 0) {
    tft.drawFastVLine(108, 74, 21, ST7735_CYAN);
  } else {
    tft.drawFastVLine(138, 74, 21, ST7735_CYAN);
  }
}

FLASHMEM void renderVarTriangle(float value) {
  tft.drawLine(110, 94, 123 + (value * 13), 74, ST7735_CYAN);
  tft.drawLine(123 + (value * 13), 74, 136, 94, ST7735_CYAN);
}

FLASHMEM void renderEnv(float att, float dec, float sus, float rel) {
  tft.drawLine(100, 94, 100 + (att * 15), 74, ST7735_CYAN);
  tft.drawLine(100 + (att * 15), 74.0, 100 + ((att + dec) * 15), 94 - (sus * 20), ST7735_CYAN);
  tft.drawFastHLine(100 + ((att + dec) * 15), 94 - (sus * 20), 40 - ((att + dec) * 15), ST7735_CYAN);
  tft.drawLine(139, 94 - (sus * 20), 139 + (rel * 13), 94, ST7735_CYAN);
}

FLASHMEM void renderCurrentParameterPage() {
  switch (state) {
    case PARAMETER:
      tft.fillScreen(ST7735_BLACK);
      tft.setFont(&FreeSans12pt7b);
      tft.setCursor(0, 53);
      tft.setTextColor(ST7735_YELLOW);
      tft.setTextSize(1);
      tft.println(currentParameter);

// Not a necessary feature perhaps
//      if (midiOutCh > 0) {
//        tft.setTextColor(ST77XX_ORANGE);
//        tft.setFont(&Org_01);
//        tft.setTextSize(2);
//        tft.setCursor(140, 35);
//        tft.println(midiOutCh);
//        tft.setFont(&FreeSans12pt7b);
//        tft.setTextSize(1);
//      }
      renderPeak();
      tft.drawFastHLine(10, 63, tft.width() - 20, ST7735_RED);
      tft.setCursor(1, 90);
      tft.setTextColor(ST7735_WHITE);
      tft.println(currentValue);
      if (pickUpActive) {
        tft.fillCircle(150, 70, 5, ST77XX_DARKGREY);
        tft.drawFastHLine(146, 70, 4, ST7735_WHITE);
      }
      switch (paramType) {
        case PULSE:
          renderPulseWidth(currentFloatValue);
          break;
        case VAR_TRI:
          renderVarTriangle(currentFloatValue);
          break;
        case FILTER_ENV:
          renderEnv(filterAttack * 0.0001f, filterDecay * 0.0001f, filterSustain, filterRelease * 0.0001f);
          break;
        case AMP_ENV:
          renderEnv(ampAttack * 0.0001f, ampDecay * 0.0001f, ampSustain, ampRelease * 0.0001f);
          break;
      }
      break;
  }
}

FLASHMEM void renderDeletePatchPage() {
  tft.fillScreen(ST7735_BLACK);
  tft.setFont(&FreeSansBold18pt7b);
  tft.setCursor(5, 53);
  tft.setTextColor(ST7735_YELLOW);
  tft.setTextSize(1);
  tft.println("Delete?");
  tft.drawFastHLine(10, 60, tft.width() - 20, ST7735_RED);
  tft.setFont(&FreeSans9pt7b);
  tft.setCursor(0, 78);
  tft.setTextColor(ST7735_YELLOW);
  tft.println(patches.last().patchNo);
  tft.setCursor(35, 78);
  tft.setTextColor(ST7735_WHITE);
  tft.println(patches.last().patchName);
  tft.fillRect(0, 85, tft.width(), 23, ST77XX_DARKRED);
  tft.setCursor(0, 98);
  tft.setTextColor(ST7735_YELLOW);
  tft.println(patches.first().patchNo);
  tft.setCursor(35, 98);
  tft.setTextColor(ST7735_WHITE);
  tft.println(patches.first().patchName);
}

FLASHMEM void renderDeleteMessagePage() {
  tft.fillScreen(ST7735_BLACK);
  tft.setFont(&FreeSans12pt7b);
  tft.setCursor(2, 53);
  tft.setTextColor(ST7735_YELLOW);
  tft.setTextSize(1);
  tft.println("Renumbering");
  tft.setCursor(10, 90);
  tft.println("SD Card");
}

FLASHMEM void renderSavePage() {
  tft.fillScreen(ST7735_BLACK);
  tft.setFont(&FreeSansBold18pt7b);
  tft.setCursor(5, 53);
  tft.setTextColor(ST7735_YELLOW);
  tft.setTextSize(1);
  tft.println("Save?");
  tft.drawFastHLine(10, 60, tft.width() - 20, ST7735_RED);
  tft.setFont(&FreeSans9pt7b);
  tft.setCursor(0, 78);
  tft.setTextColor(ST7735_YELLOW);
  tft.println(patches[patches.size() - 2].patchNo);
  tft.setCursor(35, 78);
  tft.setTextColor(ST7735_WHITE);
  tft.println(patches[patches.size() - 2].patchName);
  tft.fillRect(0, 85, tft.width(), 23, ST77XX_DARKRED);
  tft.setCursor(0, 98);
  tft.setTextColor(ST7735_YELLOW);
  tft.println(patches.last().patchNo);
  tft.setCursor(35, 98);
  tft.setTextColor(ST7735_WHITE);
  tft.println(patches.last().patchName);
}

FLASHMEM void renderReinitialisePage()
{
  tft.fillScreen(ST7735_BLACK);
  tft.setFont(&FreeSans12pt7b);
  tft.setTextColor(ST7735_YELLOW);
  tft.setTextSize(1);
  tft.setCursor(5, 53);
  tft.println("Initialise to");
  tft.setCursor(5, 90);
  tft.println("panel setting");
}

FLASHMEM void renderPatchNamingPage()
{
  tft.fillScreen(ST7735_BLACK);
  tft.setFont(&FreeSans12pt7b);
  tft.setTextColor(ST7735_YELLOW);
  tft.setTextSize(1);
  tft.setCursor(0, 53);
  tft.println("Rename Patch");
  tft.drawFastHLine(10, 63, tft.width() - 20, ST7735_RED);
  tft.setTextColor(ST7735_WHITE);
  tft.setCursor(5, 90);
  tft.println(newPatchName);
}

FLASHMEM void renderRecallPage()
{
  tft.fillScreen(ST7735_BLACK);
  tft.setFont(&FreeSans9pt7b);
  tft.setCursor(0, 45);
  tft.setTextColor(ST7735_YELLOW);
  tft.println(patches.last().patchNo);
  tft.setCursor(35, 45);
  tft.setTextColor(ST7735_WHITE);
  tft.println(patches.last().patchName);

  tft.fillRect(0, 56, tft.width(), 23, 0xA000);
  tft.setCursor(0, 72);
  tft.setTextColor(ST7735_YELLOW);
  tft.println(patches.first().patchNo);
  tft.setCursor(35, 72);
  tft.setTextColor(ST7735_WHITE);
  tft.println(patches.first().patchName);

  tft.setCursor(0, 98);
  tft.setTextColor(ST7735_YELLOW);
  patches.size() > 1 ? tft.println(patches[1].patchNo) : tft.println(patches.last().patchNo);
  tft.setCursor(35, 98);
  tft.setTextColor(ST7735_WHITE);
  patches.size() > 1 ? tft.println(patches[1].patchName) : tft.println(patches.last().patchName);
}

FLASHMEM void showRenamingPage(String newName) {
  newPatchName = newName;
}

FLASHMEM void renderUpDown(uint16_t  x, uint16_t  y, uint16_t  colour) {
  //Produces up/down indicator glyph at x,y
  tft.setCursor(x, y);
  tft.fillTriangle(x, y, x + 8, y - 8, x + 16, y, colour);
  tft.fillTriangle(x, y + 4, x + 8, y + 12, x + 16, y + 4, colour);
}

FLASHMEM void renderSettingsPage() {
  tft.fillScreen(ST7735_BLACK);
  tft.setFont(&FreeSans12pt7b);
  tft.setTextColor(ST7735_YELLOW);
  tft.setTextSize(1);
  tft.setCursor(0, 53);
  tft.println(currentSettingsOption);
  if (currentSettingsPart == SETTINGS) renderUpDown(140, 42, ST7735_YELLOW);
  tft.drawFastHLine(10, 63, tft.width() - 20, ST7735_RED);
  tft.setTextColor(ST7735_WHITE);
  tft.setCursor(5, 90);
  tft.println(currentSettingsValue);
  if (currentSettingsPart == SETTINGSVALUE) renderUpDown(140, 80, ST7735_WHITE);
}

FLASHMEM void showCurrentParameterPage( const char *param, float val, int pType) {
  currentParameter = param;
  currentValue = String(val);
  currentFloatValue = val;
  paramType = pType;
  startTimer();
}

FLASHMEM void showCurrentParameterPage(const char *param, String val, int pType) {
  if (state == SETTINGS || state == SETTINGSVALUE)state = PARAMETER;//Exit settings page if showing
  currentParameter = param;
  currentValue = val;
  paramType = pType;
  startTimer();
}

FLASHMEM void showCurrentParameterPage(const char *param, String val) {
  showCurrentParameterPage(param, val, PARAMETER);
}

FLASHMEM void showPatchPage(String number, String patchName) {
  currentPgmNum = number;
  currentPatchName = patchName;
}

FLASHMEM void showSettingsPage(char *  option, char * value, int settingsPart) {
  currentSettingsOption = option;
  currentSettingsValue = value;
  currentSettingsPart = settingsPart;
}

FLASHMEM void enableScope(boolean enable) {
  enable ? scope.ScreenSetup(&tft) : scope.ScreenSetup(NULL);
}

void displayThread() {
  threads.delay(2000); //Give bootup page chance to display
  while (1) {
    switch (state) {
      case PARAMETER:
        if ((millis() - timer) > DISPLAYTIMEOUT) {
          pickUpActive = false;
          renderCurrentPatchPage();
        } else {
          pickUpActive = pickUp;
          renderCurrentParameterPage();
        }
        break;
      case RECALL:
        renderRecallPage();
        break;
      case SAVE:
        renderSavePage();
        break;
      case REINITIALISE:
        renderReinitialisePage();
        tft.updateScreen(); //update before delay
        threads.delay(1000);
        state = PARAMETER;
        break;
      case PATCHNAMING:
        renderPatchNamingPage();
        break;
      case PATCH:
        renderCurrentPatchPage();
        break;
      case DELETE:
        renderDeletePatchPage();
        break;
      case DELETEMSG:
        renderDeleteMessagePage();
        break;
      case SETTINGS:
      case SETTINGSVALUE:
        renderSettingsPage();
        break;
    }
    tft.updateScreen();
  }
}

void setupDisplay() {
  tft.useFrameBuffer(true);
  tft.initR(INITR_GREENTAB);
  tft.setRotation(3);
  tft.invertDisplay(false);
  renderBootUpPage();
  tft.updateScreen();
  threads.addThread(displayThread);
}


ST7735_t3.cpp
Code:
/*************************************************** 
  This is a library for the Adafruit 1.8" SPI display.
  This library works with the Adafruit 1.8" TFT Breakout w/SD card
  ----> http://www.adafruit.com/products/358
  as well as Adafruit raw 1.8" TFT display
  ----> http://www.adafruit.com/products/618
 
  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
 ****************************************************/

#include "ST7735_t3.h"
#include <limits.h>
#include "pins_arduino.h"
#include "wiring_private.h"
#include <SPI.h>

#ifdef ENABLE_ST77XX_FRAMEBUFFER
//#define DEBUG_ASYNC_UPDATE
//#define DEBUG_ASYNC_LEDS
#ifdef DEBUG_ASYNC_LEDS
  #define DEBUG_PIN_1 0
  #define DEBUG_PIN_2 1
  #define DEBUG_PIN_3 2
#endif

volatile short _dma_dummy_rx;

ST7735_t3 *ST7735_t3::_dmaActiveDisplay[3] = {0, 0, 0};

#if defined(__MK66FX1M0__) 
 DMASetting   ST7735_t3::_dmasettings[3][4];
#endif

#if defined(__IMXRT1062__)  // Teensy 4.x
// On T4 Setup the buffers to be used one per SPI buss... 
// This way we make sure it is hopefully in uncached memory
ST7735DMA_Data ST7735_t3::_dma_data[3];   // one structure for each SPI buss... 
#endif

#endif

// Constructor when using software SPI.  All output pins are configurable.
ST7735_t3::ST7735_t3(uint8_t cs, uint8_t rs, uint8_t sid, uint8_t sclk, uint8_t rst) :
   Adafruit_GFX(ST7735_TFTWIDTH, ST7735_TFTHEIGHT_160), hwSPI(false),
  _cs(cs), _rs(rs),  _rst(rst), _sid(sid), _sclk(sclk)
{
  #ifdef ENABLE_ST77XX_FRAMEBUFFER
    _pfbtft = NULL; 
    _use_fbtft = 0;           // Are we in frame buffer mode?
  _we_allocated_buffer = NULL;
  _dma_state = 0;
    #endif
  _screenHeight = ST7735_TFTHEIGHT_160;
  _screenWidth = ST7735_TFTWIDTH; 
}


// Constructor when using hardware SPI.  Faster, but must use SPI pins
// specific to each board type (e.g. 11,13 for Uno, 51,52 for Mega, etc.)
ST7735_t3::ST7735_t3(uint8_t cs, uint8_t rs, uint8_t rst) :
  Adafruit_GFX(ST7735_TFTWIDTH, ST7735_TFTHEIGHT_160), hwSPI(true),
  _cs(cs), _rs(rs),  _rst(rst), _sid((uint8_t)-1), _sclk((uint8_t)-1)
{
  #ifdef ENABLE_ST77XX_FRAMEBUFFER
    _pfbtft = NULL; 
    _use_fbtft = 0;           // Are we in frame buffer mode?
  _we_allocated_buffer = NULL;
  _dma_state = 0;
    #endif
  _screenHeight = ST7735_TFTHEIGHT_160;
  _screenWidth = ST7735_TFTWIDTH; 
}

/***************************************************************/
/*     Teensy 3.6                          */
/***************************************************************/
#if defined(__MK66FX1M0__)

inline void ST7735_t3::waitTransmitComplete(void)  {
    uint32_t tmp __attribute__((unused));
    while (!(_pkinetisk_spi->SR & SPI_SR_TCF)) ; // wait until final output done
    tmp = _pkinetisk_spi->POPR;                  // drain the final RX FIFO word
}

inline void ST7735_t3::waitTransmitComplete(uint32_t mcr) {
    uint32_t tmp __attribute__((unused));
    while (1) {
        uint32_t sr = _pkinetisk_spi->SR;
        if (sr & SPI_SR_EOQF) break;  // wait for last transmit
        if (sr &  0xF0) tmp = _pkinetisk_spi->POPR;
    }
    _pkinetisk_spi->SR = SPI_SR_EOQF;
    _pkinetisk_spi->MCR = mcr;
    while (_pkinetisk_spi->SR & 0xF0) {
        tmp = _pkinetisk_spi->POPR;
    }
}

inline void ST7735_t3::spiwrite(uint8_t c)
{
  // pass 1 if we actually are setup to with MOSI and SCLK on hardware SPI use it...
  if (_pspi) {
    _pspi->transfer(c);
    return;
  }

  for (uint8_t bit = 0x80; bit; bit >>= 1) {
    *datapin = ((c & bit) ? 1 : 0);
    *clkpin = 1;
    *clkpin = 0;
  }
}

inline void ST7735_t3::spiwrite16(uint16_t d)
{
  // pass 1 if we actually are setup to with MOSI and SCLK on hardware SPI use it...
  if (_pspi) {
    _pspi->transfer16(d);
    return;
  }
  spiwrite(d >> 8);
  spiwrite(d);
}

void ST7735_t3::writecommand(uint8_t c)
{
  if (hwSPI) {
    _pkinetisk_spi->PUSHR = c | (pcs_command << 16) | SPI_PUSHR_CTAS(0);
    while (((_pkinetisk_spi->SR) & (15 << 12)) > _fifo_full_test) ; // wait if FIFO full
  } else {
    *rspin = 0;
    spiwrite(c);
  }
}

void ST7735_t3::writecommand_last(uint8_t c) {
  if (hwSPI) {
    uint32_t mcr = _pkinetisk_spi->MCR;
    _pkinetisk_spi->PUSHR = c | (pcs_command << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_EOQ;
    waitTransmitComplete(mcr);
  } else {
    *rspin = 0;
    spiwrite(c);
  }
}

void ST7735_t3::writedata(uint8_t c)
{
  if (hwSPI) {
    _pkinetisk_spi->PUSHR = c | (pcs_data << 16) | SPI_PUSHR_CTAS(0);
    while (((_pkinetisk_spi->SR) & (15 << 12)) > _fifo_full_test) ; // wait if FIFO full
  } else {
    *rspin = 1;
    spiwrite(c);
  }
}

void ST7735_t3::writedata_last(uint8_t c)
{
  if (hwSPI) {
    uint32_t mcr = _pkinetisk_spi->MCR;
    _pkinetisk_spi->PUSHR = c | (pcs_data << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_EOQ;
    waitTransmitComplete(mcr);
  } else {
    *rspin = 1;
    spiwrite(c);
  }
}

void ST7735_t3::writedata16(uint16_t d)
{
  if (hwSPI) {
    _pkinetisk_spi->PUSHR = d | (pcs_data << 16) | SPI_PUSHR_CTAS(1);
    while (((_pkinetisk_spi->SR) & (15 << 12)) > _fifo_full_test) ; // wait if FIFO full
  } else {
    *rspin = 1;
    spiwrite16(d);
  }
}


void ST7735_t3::writedata16_last(uint16_t d)
{
  if (hwSPI) {
    uint32_t mcr = _pkinetisk_spi->MCR;
    _pkinetisk_spi->PUSHR = d | (pcs_data << 16) | SPI_PUSHR_CTAS(1) | SPI_PUSHR_EOQ;
    waitTransmitComplete(mcr);
  } else {
    *rspin = 1;
    spiwrite16(d);
  }
}


#define CTAR_24MHz   (SPI_CTAR_PBR(0) | SPI_CTAR_BR(0) | SPI_CTAR_CSSCK(0) | SPI_CTAR_DBR)
#define CTAR_16MHz   (SPI_CTAR_PBR(1) | SPI_CTAR_BR(0) | SPI_CTAR_CSSCK(0) | SPI_CTAR_DBR)
#define CTAR_12MHz   (SPI_CTAR_PBR(0) | SPI_CTAR_BR(0) | SPI_CTAR_CSSCK(0))
#define CTAR_8MHz    (SPI_CTAR_PBR(1) | SPI_CTAR_BR(0) | SPI_CTAR_CSSCK(0))
#define CTAR_6MHz    (SPI_CTAR_PBR(0) | SPI_CTAR_BR(1) | SPI_CTAR_CSSCK(1))
#define CTAR_4MHz    (SPI_CTAR_PBR(1) | SPI_CTAR_BR(1) | SPI_CTAR_CSSCK(1))

void ST7735_t3::setBitrate(uint32_t n)
{
  if (n >= 24000000) {
    ctar = CTAR_24MHz;
  } else if (n >= 16000000) {
    ctar = CTAR_16MHz;
  } else if (n >= 12000000) {
    ctar = CTAR_12MHz;
  } else if (n >= 8000000) {
    ctar = CTAR_8MHz;
  } else if (n >= 6000000) {
    ctar = CTAR_6MHz;
  } else {
    ctar = CTAR_4MHz;
  }
  SIM_SCGC6 |= SIM_SCGC6_SPI0;
  _pkinetisk_spi->MCR = SPI_MCR_MDIS | SPI_MCR_HALT;
  _pkinetisk_spi->CTAR0 = ctar | SPI_CTAR_FMSZ(7);
  _pkinetisk_spi->CTAR1 = ctar | SPI_CTAR_FMSZ(15);
  _pkinetisk_spi->MCR = SPI_MCR_MSTR | SPI_MCR_PCSIS(0x1F) | SPI_MCR_CLR_TXF | SPI_MCR_CLR_RXF;
}


/***************************************************************/
/*     Teensy 4.                                               */
/***************************************************************/
#elif defined(__IMXRT1062__)  // Teensy 4.x
inline void ST7735_t3::spiwrite(uint8_t c)
{
//Serial.println(c, HEX);
  if (_pspi) {
    _pspi->transfer(c);
  } else {
    // Fast SPI bitbang swiped from LPD8806 library
    for(uint8_t bit = 0x80; bit; bit >>= 1) {
      if(c & bit) DIRECT_WRITE_HIGH(_mosiport, _mosipinmask);
      else        DIRECT_WRITE_LOW(_mosiport, _mosipinmask);
      DIRECT_WRITE_HIGH(_sckport, _sckpinmask);
      asm("nop; nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;");
      DIRECT_WRITE_LOW(_sckport, _sckpinmask);
    }
  }
}

void ST7735_t3::writecommand(uint8_t c)
{
  if (hwSPI) {
    maybeUpdateTCR(LPSPI_TCR_PCS(0) | LPSPI_TCR_FRAMESZ(7) /*| LPSPI_TCR_CONT*/);
    _pimxrt_spi->TDR = c;
    _pending_rx_count++;  //
    waitFifoNotFull();
  } else {
    DIRECT_WRITE_LOW(_dcport, _dcpinmask);
    spiwrite(c);
  }
}

void ST7735_t3::writecommand_last(uint8_t c)
{
  if (hwSPI) {
    maybeUpdateTCR(LPSPI_TCR_PCS(0) | LPSPI_TCR_FRAMESZ(7));
    _pimxrt_spi->TDR = c;
    _pending_rx_count++;  //
    waitTransmitComplete();
  } else {
    DIRECT_WRITE_LOW(_dcport, _dcpinmask);
    spiwrite(c);
  }

}

void ST7735_t3::writedata(uint8_t c)
{
  if (hwSPI) {
    maybeUpdateTCR(LPSPI_TCR_PCS(1) | LPSPI_TCR_FRAMESZ(7));
    _pimxrt_spi->TDR = c;
    _pending_rx_count++;  //
    waitTransmitComplete();
  } else {
    DIRECT_WRITE_HIGH(_dcport, _dcpinmask);
    spiwrite(c);
  }
} 

void ST7735_t3::writedata_last(uint8_t c)
{
  if (hwSPI) {
    maybeUpdateTCR(LPSPI_TCR_PCS(1) | LPSPI_TCR_FRAMESZ(7));
    _pimxrt_spi->TDR = c;
    _pending_rx_count++;  //
    waitTransmitComplete();
  } else {
    DIRECT_WRITE_HIGH(_dcport, _dcpinmask);
    spiwrite(c);
  }
} 


void ST7735_t3::writedata16(uint16_t d)
{
  if (hwSPI) {
    maybeUpdateTCR(LPSPI_TCR_PCS(1) | LPSPI_TCR_FRAMESZ(15) | LPSPI_TCR_CONT);
    _pimxrt_spi->TDR = d;
    _pending_rx_count++;  //
    waitFifoNotFull();
  } else {
    DIRECT_WRITE_HIGH(_dcport, _dcpinmask);
    spiwrite(d >> 8);
    spiwrite(d);
  }
} 

void ST7735_t3::writedata16_last(uint16_t d)
{
  if (hwSPI) {
    maybeUpdateTCR(LPSPI_TCR_PCS(1) | LPSPI_TCR_FRAMESZ(15));
    _pimxrt_spi->TDR = d;
//    _pimxrt_spi->SR = LPSPI_SR_WCF | LPSPI_SR_FCF | LPSPI_SR_TCF;
    _pending_rx_count++;  //
    waitTransmitComplete();
  } else {
    DIRECT_WRITE_HIGH(_dcport, _dcpinmask);
    spiwrite(d >> 8);
    spiwrite(d);
  }
} 

void ST7735_t3::setBitrate(uint32_t n)
{
  if (n >= 8000000) {
    SPI.setClockDivider(SPI_CLOCK_DIV2);
  } else if (n >= 4000000) {
    SPI.setClockDivider(SPI_CLOCK_DIV4);
  } else if (n >= 2000000) {
    SPI.setClockDivider(SPI_CLOCK_DIV8);
  } else {
    SPI.setClockDivider(SPI_CLOCK_DIV16);
  }
}
#endif


// Rather than a bazillion writecommand() and writedata() calls, screen
// initialization commands and arguments are organized in these tables
// stored in PROGMEM.  The table may look bulky, but that's mostly the
// formatting -- storage-wise this is hundreds of bytes more compact
// than the equivalent code.  Companion function follows.
#define DELAY 0x80
static const uint8_t PROGMEM
  Bcmd[] = {                  // Initialization commands for 7735B screens
    18,                       // 18 commands in list:
    ST7735_SWRESET,   DELAY,  //  1: Software reset, no args, w/delay
      50,                     //     50 ms delay
    ST7735_SLPOUT ,   DELAY,  //  2: Out of sleep mode, no args, w/delay
      255,                    //     255 = 500 ms delay
    ST7735_COLMOD , 1+DELAY,  //  3: Set color mode, 1 arg + delay:
      0x05,                   //     16-bit color
      10,                     //     10 ms delay
    ST7735_FRMCTR1, 3+DELAY,  //  4: Frame rate control, 3 args + delay:
      0x00,                   //     fastest refresh
      0x06,                   //     6 lines front porch
      0x03,                   //     3 lines back porch
      10,                     //     10 ms delay
    ST7735_MADCTL , 1      ,  //  5: Memory access ctrl (directions), 1 arg:
      0x08,                   //     Row addr/col addr, bottom to top refresh
    ST7735_DISSET5, 2      ,  //  6: Display settings #5, 2 args, no delay:
      0x15,                   //     1 clk cycle nonoverlap, 2 cycle gate
                              //     rise, 3 cycle osc equalize
      0x02,                   //     Fix on VTL
    ST7735_INVCTR , 1      ,  //  7: Display inversion control, 1 arg:
      0x0,                    //     Line inversion
    ST7735_PWCTR1 , 2+DELAY,  //  8: Power control, 2 args + delay:
      0x02,                   //     GVDD = 4.7V
      0x70,                   //     1.0uA
      10,                     //     10 ms delay
    ST7735_PWCTR2 , 1      ,  //  9: Power control, 1 arg, no delay:
      0x05,                   //     VGH = 14.7V, VGL = -7.35V
    ST7735_PWCTR3 , 2      ,  // 10: Power control, 2 args, no delay:
      0x01,                   //     Opamp current small
      0x02,                   //     Boost frequency
    ST7735_VMCTR1 , 2+DELAY,  // 11: Power control, 2 args + delay:
      0x3C,                   //     VCOMH = 4V
      0x38,                   //     VCOML = -1.1V
      10,                     //     10 ms delay
    ST7735_PWCTR6 , 2      ,  // 12: Power control, 2 args, no delay:
      0x11, 0x15,
    ST7735_GMCTRP1,16      ,  // 13: Magical unicorn dust, 16 args, no delay:
      0x09, 0x16, 0x09, 0x20, //     (seriously though, not sure what
      0x21, 0x1B, 0x13, 0x19, //      these config values represent)
      0x17, 0x15, 0x1E, 0x2B,
      0x04, 0x05, 0x02, 0x0E,
    ST7735_GMCTRN1,16+DELAY,  // 14: Sparkles and rainbows, 16 args + delay:
      0x0B, 0x14, 0x08, 0x1E, //     (ditto)
      0x22, 0x1D, 0x18, 0x1E,
      0x1B, 0x1A, 0x24, 0x2B,
      0x06, 0x06, 0x02, 0x0F,
      10,                     //     10 ms delay
    ST7735_CASET  , 4      ,  // 15: Column addr set, 4 args, no delay:
      0x00, 0x02,             //     XSTART = 2
      0x00, 0x81,             //     XEND = 129
    ST7735_RASET  , 4      ,  // 16: Row addr set, 4 args, no delay:
      0x00, 0x02,             //     XSTART = 1
      0x00, 0x81,             //     XEND = 160
    ST7735_NORON  ,   DELAY,  // 17: Normal display on, no args, w/delay
      10,                     //     10 ms delay
    ST7735_DISPON ,   DELAY,  // 18: Main screen turn on, no args, w/delay
      255 },                  //     255 = 500 ms delay

  Rcmd1[] = {                 // Init for 7735R, part 1 (red or green tab)
    15,                       // 15 commands in list:
    ST7735_SWRESET,   DELAY,  //  1: Software reset, 0 args, w/delay
      150,                    //     150 ms delay
    ST7735_SLPOUT ,   DELAY,  //  2: Out of sleep mode, 0 args, w/delay
      255,                    //     500 ms delay
    ST7735_FRMCTR1, 3      ,  //  3: Frame rate ctrl - normal mode, 3 args:
      0x01, 0x2C, 0x2D,       //     Rate = fosc/(1x2+40) * (LINE+2C+2D)
    ST7735_FRMCTR2, 3      ,  //  4: Frame rate control - idle mode, 3 args:
      0x01, 0x2C, 0x2D,       //     Rate = fosc/(1x2+40) * (LINE+2C+2D)
    ST7735_FRMCTR3, 6      ,  //  5: Frame rate ctrl - partial mode, 6 args:
      0x01, 0x2C, 0x2D,       //     Dot inversion mode
      0x01, 0x2C, 0x2D,       //     Line inversion mode
    ST7735_INVCTR , 1      ,  //  6: Display inversion ctrl, 1 arg, no delay:
      0x07,                   //     No inversion
    ST7735_PWCTR1 , 3      ,  //  7: Power control, 3 args, no delay:
      0xA2,
      0x02,                   //     -4.6V
      0x84,                   //     AUTO mode
    ST7735_PWCTR2 , 1      ,  //  8: Power control, 1 arg, no delay:
      0xC5,                   //     VGH25 = 2.4C VGSEL = -10 VGH = 3 * AVDD
    ST7735_PWCTR3 , 2      ,  //  9: Power control, 2 args, no delay:
      0x0A,                   //     Opamp current small
      0x00,                   //     Boost frequency
    ST7735_PWCTR4 , 2      ,  // 10: Power control, 2 args, no delay:
      0x8A,                   //     BCLK/2, Opamp current small & Medium low
      0x2A,  
    ST7735_PWCTR5 , 2      ,  // 11: Power control, 2 args, no delay:
      0x8A, 0xEE,
    ST7735_VMCTR1 , 1      ,  // 12: Power control, 1 arg, no delay:
      0x0E,
    ST7735_INVOFF , 0      ,  // 13: Don't invert display, no args, no delay
    ST7735_MADCTL , 1      ,  // 14: Memory access control (directions), 1 arg:
      0xC8,                   //     row addr/col addr, bottom to top refresh
    ST7735_COLMOD , 1      ,  // 15: set color mode, 1 arg, no delay:
      0x05 },                 //     16-bit color

  Rcmd2green[] = {            // Init for 7735R, part 2 (green tab only)
    2,                        //  2 commands in list:
    ST7735_CASET  , 4      ,  //  1: Column addr set, 4 args, no delay:
      0x00, 0x02,             //     XSTART = 0
      0x00, 0x7F+0x02,        //     XEND = 127
    ST7735_RASET  , 4      ,  //  2: Row addr set, 4 args, no delay:
      0x00, 0x01,             //     XSTART = 0
      0x00, 0x9F+0x01 },      //     XEND = 159
  Rcmd2red[] = {              // Init for 7735R, part 2 (red tab only)
    2,                        //  2 commands in list:
    ST7735_CASET  , 4      ,  //  1: Column addr set, 4 args, no delay:
      0x00, 0x00,             //     XSTART = 0
      0x00, 0x7F,             //     XEND = 127
    ST7735_RASET  , 4      ,  //  2: Row addr set, 4 args, no delay:
      0x00, 0x00,             //     XSTART = 0
      0x00, 0x9F },           //     XEND = 159

  Rcmd2green144[] = {         // Init for 7735R, part 2 (green 1.44 tab)
    2,                        //  2 commands in list:
    ST7735_CASET  , 4      ,  //  1: Column addr set, 4 args, no delay:
      0x00, 0x00,             //     XSTART = 0
      0x00, 0x7F,             //     XEND = 127
    ST7735_RASET  , 4      ,  //  2: Row addr set, 4 args, no delay:
      0x00, 0x00,             //     XSTART = 0
      0x00, 0x7F },           //     XEND = 127

  Rcmd2green160x80[] = {            // 7735R init, part 2 (mini 160x80)
    2,                              //  2 commands in list:
    ST7735_CASET,   4,              //  1: Column addr set, 4 args, no delay:
      0x00, 0x00,                   //     XSTART = 0
      0x00, 0x4F,                   //     XEND = 79
    ST7735_RASET,   4,              //  2: Row addr set, 4 args, no delay:
      0x00, 0x00,                   //     XSTART = 0
      0x00, 0x9F },                 //     XEND = 159

  Rcmd3[] = {                 // Init for 7735R, part 3 (red or green tab)
    4,                        //  4 commands in list:
    ST7735_GMCTRP1, 16      , //  1: Magical unicorn dust, 16 args, no delay:
      0x02, 0x1c, 0x07, 0x12,
      0x37, 0x32, 0x29, 0x2d,
      0x29, 0x25, 0x2B, 0x39,
      0x00, 0x01, 0x03, 0x10,
    ST7735_GMCTRN1, 16      , //  2: Sparkles and rainbows, 16 args, no delay:
      0x03, 0x1d, 0x07, 0x06,
      0x2E, 0x2C, 0x29, 0x2D,
      0x2E, 0x2E, 0x37, 0x3F,
      0x00, 0x00, 0x02, 0x10,
    ST7735_NORON  ,    DELAY, //  3: Normal display on, no args, w/delay
      10,                     //     10 ms delay
    ST7735_DISPON ,    DELAY, //  4: Main screen turn on, no args w/delay
      100 };                  //     100 ms delay


// Companion code to the above tables.  Reads and issues
// a series of LCD commands stored in PROGMEM byte array.
void ST7735_t3::commandList(const uint8_t *addr)
{
  uint8_t  numCommands, numArgs;
  uint16_t ms;

  beginSPITransaction();
  numCommands = pgm_read_byte(addr++);    // Number of commands to follow
  //Serial.printf("CommandList: numCmds:%d\n", numCommands); Serial.flush();
  while(numCommands--) {        // For each command...
    writecommand_last(pgm_read_byte(addr++)); //   Read, issue command
    numArgs  = pgm_read_byte(addr++); //   Number of args to follow
    ms       = numArgs & DELAY;   //   If hibit set, delay follows args
    numArgs &= ~DELAY;      //   Mask out delay bit
    while(numArgs > 1) {      //   For each argument...
      writedata(pgm_read_byte(addr++)); //   Read, issue argument
      numArgs--;
    }

    if (numArgs) writedata_last(pgm_read_byte(addr++)); //   Read, issue argument - wait until this one completes
    if(ms) {
      ms = pgm_read_byte(addr++); // Read post-command delay time (ms)
      if(ms == 255) ms = 500;   // If 255, delay for 500 ms
      //Serial.printf("delay %d\n", ms); Serial.flush();
      endSPITransaction();
      delay(ms);
      beginSPITransaction();
    }
  }
  endSPITransaction();
}


// Initialization code common to both 'B' and 'R' type displays
void ST7735_t3::commonInit(const uint8_t *cmdList, uint8_t mode)
{
  _colstart  = _rowstart = 0; // May be overridden in init func
    _ystart = _xstart = 0;

#if defined(__MK66FX1M0__)
  if (_sid == (uint8_t)-1) _sid = 11;
  if (_sclk == (uint8_t)-1) _sclk = 13;

  // Lets try to handle cases where DC is not hardware, without going all the way down to bit bang!
  //if (SPI.pinIsMOSI(_sid) && SPI.pinIsSCK(_sclk) && SPI.pinIsChipSelect(_rs)) {
  if (SPI.pinIsMOSI(_sid) && SPI.pinIsSCK(_sclk)) {
    _pspi = &SPI;
    _spi_num = 0;          // Which buss is this spi on? 
    _pkinetisk_spi = &KINETISK_SPI0;  // Could hack our way to grab this from SPI object, but...
    _fifo_full_test = (3 << 12);
    //Serial.println("ST7735_t3::commonInit SPI");

    #if  defined(__MK64FX512__) || defined(__MK66FX1M0__)
  } else if (SPI1.pinIsMOSI(_sid) && SPI1.pinIsSCK(_sclk)) {
    _pspi = &SPI1;
    _spi_num = 1;          // Which buss is this spi on? 

    _pkinetisk_spi = &KINETISK_SPI1;
    _fifo_full_test = (0 << 12);
    //Serial.println("ST7735_t3::commonInit SPI1");
  } else if (SPI2.pinIsMOSI(_sid) && SPI2.pinIsSCK(_sclk)) {
    _pspi = &SPI2;
    _spi_num = 2;          // Which buss is this spi on? 
    _pkinetisk_spi = &KINETISK_SPI2;
    _fifo_full_test = (0 << 12);
    //Serial.println("ST7735_t3::commonInit SPI2");
    #endif    
  } else _pspi = nullptr;

  if (_pspi) {
    hwSPI = true;
    _pspi->setMOSI(_sid);
    _pspi->setSCK(_sclk);
    _pspi->begin();
    //Serial.println("After SPI begin");
    _spiSettings = SPISettings(ST7735_SPICLOCK, MSBFIRST, mode);
    // See if both CS and DC are valid CS pins.
    if (_pspi->pinIsChipSelect(_rs, _cs)) {
      pcs_data = _pspi->setCS(_cs);
      pcs_command = pcs_data | _pspi->setCS(_rs);
      cspin = 0; // Let know that we are not setting manual
      //Serial.println("Both CS and DC are SPI pins");
    // See if at least DC is hardware CS...   
    } else if (_pspi->pinIsChipSelect(_rs)) {
      // We already verified that _rs was valid CS pin above.
      pcs_data = 0;
      pcs_command = pcs_data | _pspi->setCS(_rs);
      if (_cs != 0xff) {
        pinMode(_cs, OUTPUT);
        cspin = portOutputRegister(digitalPinToPort(_cs));
        *cspin = 1;
      }
    } else {
      // DC is not, and won't bother to check CS... 
      pcs_data = 0;
      pcs_command = 0;
      if (_cs != 0xff) {
        pinMode(_cs, OUTPUT);
        cspin = portOutputRegister(digitalPinToPort(_cs));
        *cspin = 1;
      }
      pinMode(_rs, OUTPUT);
      rspin = portOutputRegister(digitalPinToPort(_rs));
      *rspin = 0;
      // Pass 1, now lets set hwSPI false
      hwSPI = false;
    }
    // Hack to get hold of the SPI Hardware information... 
    uint32_t *pa = (uint32_t*)((void*)_pspi);
    _spi_hardware = (SPIClass::SPI_Hardware_t*)(void*)pa[1];
  } else {
    //Serial.println("ST7735_t3::commonInit Software SPI :(");
    hwSPI = false;
    _pspi = nullptr;
    cspin = (_cs != 0xff)? portOutputRegister(digitalPinToPort(_cs)) : 0;
    rspin = portOutputRegister(digitalPinToPort(_rs));
    clkpin = portOutputRegister(digitalPinToPort(_sclk));
    datapin = portOutputRegister(digitalPinToPort(_sid));
    *cspin = 1;
    *rspin = 0;
    *clkpin = 0;
    *datapin = 0;
    if (_cs != 0xff) pinMode(_cs, OUTPUT);
    pinMode(_rs, OUTPUT);
    pinMode(_sclk, OUTPUT);
    pinMode(_sid, OUTPUT);
  }
  // Teensy 4
#elif defined(__IMXRT1062__)  // Teensy 4.x 
  if (_sid == (uint8_t)-1) _sid = 11;
  if (_sclk == (uint8_t)-1) _sclk = 13;
  if (SPI.pinIsMOSI(_sid) && SPI.pinIsSCK(_sclk)) {
    _pspi = &SPI;
    _spi_num = 0;          // Which buss is this spi on? 
    _pimxrt_spi = &IMXRT_LPSPI4_S;  // Could hack our way to grab this from SPI object, but...

  } else if (SPI1.pinIsMOSI(_sid) && SPI1.pinIsSCK(_sclk)) {
    _pspi = &SPI1;
    _spi_num = 1;          // Which buss is this spi on? 
    _pimxrt_spi = &IMXRT_LPSPI3_S;
  } else if (SPI2.pinIsMOSI(_sid) && SPI2.pinIsSCK(_sclk)) {
    _pspi = &SPI2;
    _spi_num = 2;          // Which buss is this spi on? 
    _pimxrt_spi = &IMXRT_LPSPI1_S;
  } else _pspi = nullptr;

  if (_pspi) {
    hwSPI = true;
    _pspi->begin();
    _pending_rx_count = 0;
    _spiSettings = SPISettings(ST7735_SPICLOCK, MSBFIRST, mode);
    _pspi->beginTransaction(_spiSettings); // Should have our settings. 
    _pspi->transfer(0); // hack to see if it will actually change then...
    _pspi->endTransaction();
    _spi_tcr_current = _pimxrt_spi->TCR; // get the current TCR value 
//    uint32_t *phack = (uint32_t* )&_spiSettings;
//    Serial.printf("SPI Settings: TCR: %x %x (%x %x)\n", _spi_tcr_current, _pimxrt_spi->TCR, phack[0], phack[1]);
    // Hack to get hold of the SPI Hardware information... 
    uint32_t *pa = (uint32_t*)((void*)_pspi);
    _spi_hardware = (SPIClass::SPI_Hardware_t*)(void*)pa[1];
  
  } else {
    hwSPI = false;
    _sckport = portOutputRegister(_sclk);
    _sckpinmask = digitalPinToBitMask(_sclk);
    pinMode(_sclk, OUTPUT); 
    DIRECT_WRITE_LOW(_sckport, _sckpinmask);

    _mosiport = portOutputRegister(_sid);
    _mosipinmask = digitalPinToBitMask(_sid);
    pinMode(_sid, OUTPUT);  
    DIRECT_WRITE_LOW(_mosiport, _mosipinmask);

  }
  if (_cs != 0xff) {
    _csport = portOutputRegister(_cs);
    _cspinmask = digitalPinToBitMask(_cs);
    pinMode(_cs, OUTPUT); 
    DIRECT_WRITE_HIGH(_csport, _cspinmask);   
  } else _csport = 0;

  if (_pspi && _pspi->pinIsChipSelect(_rs)) {
    _pspi->setCS(_rs);
    _dcport = 0;
    _dcpinmask = 0;
  } else {
    //Serial.println("ST7735_t3: Error not DC is not valid hardware CS pin");
    _dcport = portOutputRegister(_rs);
    _dcpinmask = digitalPinToBitMask(_rs);
    pinMode(_rs, OUTPUT); 
    DIRECT_WRITE_HIGH(_dcport, _dcpinmask);
  }
  maybeUpdateTCR(LPSPI_TCR_PCS(1) | LPSPI_TCR_FRAMESZ(7));

#endif
  // BUGBUG
//  digitalWrite(_cs, LOW);
  if (_rst != 0xff) {
    pinMode(_rst, OUTPUT);
    digitalWrite(_rst, HIGH);
    delay(100);
    digitalWrite(_rst, LOW);
    delay(100);
    digitalWrite(_rst, HIGH);
    delay(200);
  }

  if(cmdList) commandList(cmdList);
}


// Initialization for ST7735B screens
void ST7735_t3::initB(void)
{
  commonInit(Bcmd);
}


// Initialization for ST7735R screens (green or red tabs)
void ST7735_t3::initR(uint8_t options)
{
  commonInit(Rcmd1);
  if (options == INITR_GREENTAB) {
    commandList(Rcmd2green);
    _colstart = 2;
    _rowstart = 1;
  } else if(options == INITR_144GREENTAB) {
    _screenHeight = ST7735_TFTHEIGHT_144;
    commandList(Rcmd2green144);
    _colstart = 2;
    _rowstart = 3;
  } else if(options == INITR_144GREENTAB_OFFSET) {
    _screenHeight = ST7735_TFTHEIGHT_144;
    commandList(Rcmd2green144);
    _colstart = 0;
    _rowstart = 32;
    } else if(options == INITR_MINI160x80) {
      _screenHeight   = ST7735_TFTHEIGHT_160;
      _screenWidth    = ST7735_TFTWIDTH_80;
      commandList(Rcmd2green160x80);
      _colstart = 24;
      _rowstart = 0;
  } else {
    // _colstart, _rowstart left at default '0' values
    commandList(Rcmd2red);
  }
  commandList(Rcmd3);

  // if black or mini, change MADCTL color filter
  if ((options == INITR_BLACKTAB)  || (options == INITR_MINI160x80)){
    writecommand(ST7735_MADCTL);
    writedata_last(0xC0);
  }

  tabcolor = options;
  setRotation(0);
}


void ST7735_t3::setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
  beginSPITransaction();
  setAddr(x0, y0, x1, y1);
  writecommand(ST7735_RAMWR); // write to RAM
  // The setAddrWindow/pushColor will only work if SPI is kept active during this loop...
  endSPITransaction();
}


void ST7735_t3::pushColor(uint16_t color, boolean last_pixel)
{
  //beginSPITransaction();
  if (last_pixel) {
    writedata16_last(color);
    endSPITransaction();
  } else {
    writedata16(color);
  }
}

void ST7735_t3::drawPixel(int16_t x, int16_t y, uint16_t color)
{
  if ((x < 0) ||(x >= _width) || (y < 0) || (y >= _height)) return;
  #ifdef ENABLE_ST77XX_FRAMEBUFFER
  if (_use_fbtft) {
    _pfbtft[y*_width + x] = color;

  } else 
  #endif
  {
    beginSPITransaction();
    setAddr(x,y,x+1,y+1);
    writecommand(ST7735_RAMWR);
    writedata16_last(color);
    endSPITransaction();
  }
}


void ST7735_t3::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color)
{
  // Rudimentary clipping
  if ((x >= _width) || (y >= _height)) return;
  if ((y+h-1) >= _height) h = _height-y;
  #ifdef ENABLE_ST77XX_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(ST7735_RAMWR);
    while (h-- > 1) {
      writedata16(color);
    }
    writedata16_last(color);
    endSPITransaction();
  }
}


void ST7735_t3::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color)
{
  // Rudimentary clipping
  if ((x >= _width) || (y >= _height)) return;
  if ((x+w-1) >= _width)  w = _width-x;

  #ifdef ENABLE_ST77XX_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(ST7735_RAMWR);
    while (w-- > 1) {
      writedata16(color);
    }
    writedata16_last(color);
    endSPITransaction();
  }
}



void ST7735_t3::fillScreen(uint16_t color)
{
  fillRect(0, 0,  _width, _height, color);
}



// fill a rectangle
void ST7735_t3::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color)
{
  // rudimentary clipping (drawChar w/big text requires this)
  if ((x >= _width) || (y >= _height)) return;
  if ((x + w - 1) >= _width)  w = _width  - x;
  if ((y + h - 1) >= _height) h = _height - y;
  #ifdef ENABLE_ST77XX_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
  {
    beginSPITransaction();
    setAddr(x, y, x+w-1, y+h-1);
    writecommand(ST7735_RAMWR);
    for (y=h; y>0; y--) {
      for(x=w; x>1; x--) {
        writedata16(color);
      }
      writedata16_last(color);
    }
    endSPITransaction();
  }
}


#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 ST7735_t3::setRotation(uint8_t m)
{
  beginSPITransaction();
  writecommand(ST7735_MADCTL);
  rotation = m % 4; // can't be higher than 3
  switch (rotation) {
  case 0:
      if ((tabcolor == INITR_BLACKTAB) || (tabcolor == INITR_MINI160x80)) {
      writedata_last(MADCTL_MX | MADCTL_MY | MADCTL_RGB);
    } else {
      writedata_last(MADCTL_MX | MADCTL_MY | MADCTL_BGR);
    }
    _width  = _screenWidth;
    _height = _screenHeight;
      _xstart = _colstart;
      _ystart = _rowstart;
    break;
  case 1:
      if ((tabcolor == INITR_BLACKTAB) || (tabcolor == INITR_MINI160x80)) {
      writedata_last(MADCTL_MY | MADCTL_MV | MADCTL_RGB);
    } else {
      writedata_last(MADCTL_MY | MADCTL_MV | MADCTL_BGR);
    }
    _height = _screenWidth;
    _width = _screenHeight;
      _ystart = _colstart;
      _xstart = _rowstart;
    break;
  case 2:
      if ((tabcolor == INITR_BLACKTAB) || (tabcolor == INITR_MINI160x80)) {
      writedata_last(MADCTL_RGB);
    } else {
      writedata_last(MADCTL_BGR);
    }
    _width  = _screenWidth;
    _height = _screenHeight;
      _xstart = _colstart;
      // hack to make work on a couple different displays
      _ystart = (_rowstart==0 || _rowstart==32)? 0 : 1;//_rowstart;
    break;
  case 3:
      if ((tabcolor == INITR_BLACKTAB) || (tabcolor == INITR_MINI160x80)) {
      writedata_last(MADCTL_MX | MADCTL_MV | MADCTL_RGB);
    } else {
      writedata_last(MADCTL_MX | MADCTL_MV | MADCTL_BGR);
    }
    _width = _screenHeight;
    _height = _screenWidth;
      _ystart = _colstart;
      // hack to make work on a couple different displays
      _xstart = (_rowstart==0 || _rowstart==32)? 0 : 1;//_rowstart;
    break;
  }
  _rot = rotation;  // remember the rotation... 
  //Serial.printf("SetRotation(%d) _xstart=%d _ystart=%d _width=%d, _height=%d\n", _rot, _xstart, _ystart, _width, _height);
  endSPITransaction();
}

void ST7735_t3::setRowColStart(uint16_t x, uint16_t y) {
  _rowstart = x;
  _colstart = y;
  if (_rot != 0xff) setRotation(_rot);
}


void ST7735_t3::invertDisplay(boolean i)
{
  beginSPITransaction();
  writecommand_last(i ? ST7735_INVON : ST7735_INVOFF);
  endSPITransaction();

}

/*!
 @brief   Adafruit_SPITFT Send Command handles complete sending of commands and const data
 @param   commandByte       The Command Byte
 @param   dataBytes         A pointer to the Data bytes to send
 @param   numDataBytes      The number of bytes we should send
 */
void ST7735_t3::sendCommand(uint8_t commandByte, const uint8_t *dataBytes, uint8_t numDataBytes) {
    beginSPITransaction();

    writecommand_last(commandByte); // Send the command byte
  
    while (numDataBytes > 1) {
    writedata(*dataBytes++); // Send the data bytes
    numDataBytes--;
    }
    if (numDataBytes) writedata_last(*dataBytes);
  
    endSPITransaction();
}

void ST7735_t3::writeRect(int16_t x, int16_t y, int16_t w, int16_t h, const uint16_t *pcolors)
{
  beginSPITransaction();
  setAddr(x, y, x + w - 1, y + h - 1);
  writecommand(ST7735_RAMWR);
  for (y = h; y > 0; y--) {
    for (x = w; x > 1; x--) {
      writedata16(*pcolors++);
    }
    writedata16_last(*pcolors++);
  }
  endSPITransaction();
}
#ifdef ENABLE_ST77XX_FRAMEBUFFER
void ST7735_t3::dmaInterrupt(void) {
  if (_dmaActiveDisplay[0])  {
    _dmaActiveDisplay[0]->process_dma_interrupt();
  }
}
void ST7735_t3::dmaInterrupt1(void) {
  if (_dmaActiveDisplay[1])  {
    _dmaActiveDisplay[1]->process_dma_interrupt();
  }
}
void ST7735_t3::dmaInterrupt2(void) {
  if (_dmaActiveDisplay[2])  {
    _dmaActiveDisplay[2]->process_dma_interrupt();
  }
}

//=============================================================================
// Frame buffer support. 
//=============================================================================
#ifdef ENABLE_ST77XX_FRAMEBUFFER
#ifdef DEBUG_ASYNC_UPDATE
extern void dumpDMA_TCD(DMABaseClass *dmabc);
#endif

void ST7735_t3::process_dma_interrupt(void) {
#ifdef DEBUG_ASYNC_LEDS
  digitalWriteFast(DEBUG_PIN_2, HIGH);
#endif
  // Serial.println(" ST7735_t3::process_dma_interrupt");
#if defined(__MK66FX1M0__) 
  // T3.6
  _dma_frame_count++;
  _dmatx.clearInterrupt();

  // See if we are in continuous mode or not..
  if ((_dma_state & ST77XX_DMA_CONT) == 0) {
    // We are in single refresh mode or the user has called cancel so
    // Lets try to release the CS pin
    while (((_pkinetisk_spi->SR) & (15 << 12)) > _fifo_full_test) ; // wait if FIFO full
    writecommand_last(ST7735_NOP);
    endSPITransaction();
    _dma_state &= ~ST77XX_DMA_ACTIVE;
    _dmaActiveDisplay[_spi_num] = 0;  // We don't have a display active any more... 

  }
#elif defined(__IMXRT1062__)  // Teensy 4.x
  // T4
  bool still_more_dma = true;
  _dma_sub_frame_count++;
  #if defined(DEBUG_ASYNC_UPDATE)
  Serial.print(".");
  #endif
  if (_dma_sub_frame_count == _dma_cnt_sub_frames_per_frame) {
  #ifdef DEBUG_ASYNC_LEDS
    digitalWriteFast(DEBUG_PIN_3, HIGH);
  #endif
    #if defined(DEBUG_ASYNC_UPDATE)
    Serial.println("*");
    #endif
    // We completed a frame. 
    _dma_frame_count++;
    // See if we are logically done
    if (_dma_state & ST77XX_DMA_FINISH) {
      //Serial.println("$");
      still_more_dma = false;

      // We are in single refresh mode or the user has called cancel so
      // Lets try to release the CS pin
      // Lets wait until FIFO is not empty
      //Serial.printf("Before FSR wait: %x %x\n", _pimxrt_spi->FSR, _pimxrt_spi->SR);
      while (_pimxrt_spi->FSR & 0x1f)  ;  // wait until this one is complete

      //Serial.printf("Before SR busy wait: %x\n", _pimxrt_spi->SR);
      while (_pimxrt_spi->SR & LPSPI_SR_MBF)  ; // wait until this one is complete

      _dma_data[_spi_num]._dmatx.clearComplete();
      //Serial.println("Restore FCR");
      _pimxrt_spi->FCR = LPSPI_FCR_TXWATER(15); // _spi_fcr_save; // restore the FSR status... 
      _pimxrt_spi->DER = 0;   // DMA no longer doing TX (or RX)

      _pimxrt_spi->CR = LPSPI_CR_MEN | LPSPI_CR_RRF | LPSPI_CR_RTF;   // actually clear both...
      _pimxrt_spi->SR = 0x3f00; // clear out all of the other status...


//      maybeUpdateTCR(LPSPI_TCR_PCS(0) | LPSPI_TCR_FRAMESZ(7));  // output Command with 8 bits
      // Serial.printf("Output NOP (SR %x CR %x FSR %x FCR %x %x TCR:%x)\n", _pimxrt_spi->SR, _pimxrt_spi->CR, _pimxrt_spi->FSR, 
      //  _pimxrt_spi->FCR, _spi_fcr_save, _pimxrt_spi->TCR);
      _pending_rx_count = 0;  // Make sure count is zero
//      writecommand_last(ST7735_NOP);

      // Serial.println("Do End transaction");
      endSPITransaction();
      _dma_state &= ~(ST77XX_DMA_ACTIVE | ST77XX_DMA_FINISH);
      _dmaActiveDisplay[_spi_num] = 0;  // We don't have a display active any more... 

      // Serial.println("After End transaction");
      #if defined(DEBUG_ASYNC_UPDATE)
      Serial.println("$");
      #endif
    }
    _dma_sub_frame_count = 0;
#ifdef DEBUG_ASYNC_LEDS
    digitalWriteFast(DEBUG_PIN_3, LOW);
#endif
  }

  if (still_more_dma) {
    // we are still in a sub-frame so we need to copy memory down...
    if (_dma_sub_frame_count == (_dma_cnt_sub_frames_per_frame-2)) {
      if ((_dma_state & ST77XX_DMA_CONT) == 0) {
        if (_dma_sub_frame_count & 1) _dma_data[_spi_num]._dmasettings[0].disableOnCompletion();
        else _dma_data[_spi_num]._dmasettings[1].disableOnCompletion();
        #if defined(DEBUG_ASYNC_UPDATE)
        Serial.println("!");
        #endif
        _dma_state |= ST77XX_DMA_FINISH;  // let system know we set the finished state

      }
    }
    if (_dma_sub_frame_count & 1) {
      memcpy(_dma_data[_spi_num]._dma_buffer1, &_pfbtft[_dma_pixel_index], _dma_buffer_size*2);
    } else {      
      memcpy(_dma_data[_spi_num]._dma_buffer2, &_pfbtft[_dma_pixel_index], _dma_buffer_size*2);
    }
    _dma_pixel_index += _dma_buffer_size;
    if (_dma_pixel_index >= (_count_pixels))
      _dma_pixel_index = 0;   // we will wrap around 
  }
  _dma_data[_spi_num]._dmatx.clearInterrupt();
  _dma_data[_spi_num]._dmatx.clearComplete();
  asm("dsb");
#else
  //--------------------------------------------------------------------
  // T3.5...
  _dmarx.clearInterrupt();
  _dmatx.clearComplete();
  _dmarx.clearComplete();

  if (!_dma_count_remaining && !(_dma_state & ST77XX_DMA_CONT)) {
    // The DMA transfers are done.
    _dma_frame_count++;
#ifdef DEBUG_ASYNC_LEDS
    digitalWriteFast(DEBUG_PIN_3, HIGH);
#endif

    _pkinetisk_spi->RSER = 0;
    //_pkinetisk_spi->MCR = SPI_MCR_MSTR | SPI_MCR_CLR_RXF | SPI_MCR_PCSIS(0x1F);  // clear out the queue
    _pkinetisk_spi->SR = 0xFF0F0000;
    _pkinetisk_spi->CTAR0  &= ~(SPI_CTAR_FMSZ(8));  // Hack restore back to 8 bits

    writecommand_last(ST7735_NOP);
    endSPITransaction();
    _dma_state &= ~ST77XX_DMA_ACTIVE;
    _dmaActiveDisplay[_spi_num] = 0;  // We don't have a display active any more... 
#ifdef DEBUG_ASYNC_LEDS
    digitalWriteFast(DEBUG_PIN_3, LOW);
#endif

  } else {
    uint16_t w;
    if (_dma_count_remaining) { // Still part of one frome. 
      _dma_count_remaining -= _dma_write_size_words;
      w = *((uint16_t*)_dmatx.TCD->SADDR);
      _dmatx.TCD->SADDR = (volatile uint8_t*)(_dmatx.TCD->SADDR) + 2;
    } else {  // start a new frame
      _dma_frame_count++;
      _dmatx.sourceBuffer(&_pfbtft[1], (_dma_write_size_words-1)*2);
      _dmatx.TCD->SLAST = 0;  // Finish with it pointing to next location
      w = _pfbtft[0];
      _dma_count_remaining = _count_pixels - _dma_write_size_words; // how much more to transfer? 
    }
#ifdef DEBUG_ASYNC_UPDATE
//    dumpDMA_TCD(&_dmatx);
//    dumpDMA_TCD(&_dmarx);
#endif
    _pkinetisk_spi->PUSHR = (w | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT);
    _dmarx.enable();
    _dmatx.enable();
  }

#endif  
#ifdef DEBUG_ASYNC_LEDS
  digitalWriteFast(DEBUG_PIN_2, LOW);
#endif
}
//=======================================================================
// Add optinal support for using frame buffer to speed up complex outputs
//=======================================================================
void ST7735_t3::setFrameBuffer(uint16_t *frame_buffer) 
{
  _pfbtft = frame_buffer;
  // we may not know the size of it, if called before init.
/*  if (_pfbtft != NULL) {
    memset(_pfbtft, 0, _width*_height*2);
  } */
}

uint8_t ST7735_t3::useFrameBuffer(boolean b)    // use the frame buffer?  First call will allocate
{
  if (b) {
    // First see if we need to allocate buffer
    if (_pfbtft == NULL) {
      // Hack to start frame buffer on 32 byte boundary
      // Note: If called before init maybe larger than we need
      _count_pixels =  _width * _height;
      _we_allocated_buffer = (uint16_t *)malloc(_count_pixels*2+32);
      if (_we_allocated_buffer == NULL)
        return 0; // failed 
      _pfbtft = (uint16_t*) (((uintptr_t)_we_allocated_buffer + 32) & ~ ((uintptr_t) (31)));
      memset(_pfbtft, 0, _count_pixels*2);  
    }
    _use_fbtft = 1;
  } else 
    _use_fbtft = 0;

  return _use_fbtft;  
}

void ST7735_t3::freeFrameBuffer(void)           // explicit call to release the buffer
{
  if (_we_allocated_buffer) {
    free(_we_allocated_buffer);
    _pfbtft = NULL;
    _use_fbtft = 0; // make sure the use is turned off
    _we_allocated_buffer = NULL;
  }
}
void ST7735_t3::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?
  if (_use_fbtft) {
    beginSPITransaction();
    // Doing full window. 
    setAddr(0, 0, _width-1, _height-1);
    writecommand(ST7735_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[(_count_pixels)-1]; // setup 
    uint16_t *pftbft = _pfbtft;

    // Quick write out the data;
    while (pftbft < pfbtft_end) {
      writedata16(*pftbft++);
    }
    writedata16_last(*pftbft);

    endSPITransaction();
  }
}      

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

  Serial.printf("SA:%x SO:%d AT:%x NB:%x SL:%d DA:%x DO: %d CI:%x DL:%x CS:%x BI:%x\n", (uint32_t)dmabc->TCD->SADDR,
    dmabc->TCD->SOFF, dmabc->TCD->ATTR, dmabc->TCD->NBYTES, dmabc->TCD->SLAST, (uint32_t)dmabc->TCD->DADDR, 
    dmabc->TCD->DOFF, dmabc->TCD->CITER, dmabc->TCD->DLASTSGA, dmabc->TCD->CSR, dmabc->TCD->BITER);
}
#endif

//==============================================
#ifdef ENABLE_ST77XX_FRAMEBUFFER
void  ST7735_t3::initDMASettings(void) 
{
  // Serial.printf("initDMASettings called %d\n", _dma_state);
  if (_dma_state) {  // should test for init, but...
    return; // we already init this. 
  }
#ifdef DEBUG_ASYNC_LEDS 
  pinMode(DEBUG_PIN_1, OUTPUT); digitalWrite(DEBUG_PIN_1, LOW);
  pinMode(DEBUG_PIN_2, OUTPUT); digitalWrite(DEBUG_PIN_2, LOW);
  pinMode(DEBUG_PIN_3, OUTPUT); digitalWrite(DEBUG_PIN_3, LOW);
#endif


  //Serial.println("InitDMASettings");
  uint8_t dmaTXevent = _spi_hardware->tx_dma_channel;
  _count_pixels = _width*_height; // cache away the size of the display. 

//  Serial.printf("cbDisplay: %u COUNT_WORDS_WRITE:%d(%x) spi_num:%d\n", _count_pixels, COUNT_WORDS_WRITE, COUNT_WORDS_WRITE, _spi_num);
#if defined(__MK66FX1M0__) 
    uint8_t  cnt_dma_settings = 2;   // how many do we need for this display?
  uint32_t COUNT_WORDS_WRITE = (_count_pixels) / 2;

  // The 240x320 display requires us to expand to another DMA setting. 
  if (COUNT_WORDS_WRITE >= 32768) {
    COUNT_WORDS_WRITE = (_count_pixels) / 3;
    cnt_dma_settings = 3;
  }
  // T3.6
  //Serial.printf("CWW: %d %d %d\n", CBALLOC, SCREEN_DMA_NUM_SETTINGS, count_words_write);
  // Now lets setup DMA access to this memory... 
  _cnt_dma_settings = cnt_dma_settings; // save away code that needs to update
  _dmasettings[_spi_num][0].sourceBuffer(&_pfbtft[1], (COUNT_WORDS_WRITE-1)*2);
  _dmasettings[_spi_num][0].destination(_pkinetisk_spi->PUSHR);

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

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

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

  // Setup DMA main object
  //Serial.println("Setup _dmatx");
  _dmatx.begin(true);
  _dmatx.triggerAtHardwareEvent(dmaTXevent);
  _dmatx = _dmasettings[_spi_num][0];
  // probably could use const table of functio_ns...
  if (_spi_num == 0) _dmatx.attachInterrupt(dmaInterrupt);
  else if (_spi_num == 1) _dmatx.attachInterrupt(dmaInterrupt1);
  else _dmatx.attachInterrupt(dmaInterrupt2);

#elif defined(__IMXRT1062__)  // Teensy 4.x
  // See if moving the frame buffer to other memory that is not cached helps out
  // to remove tearing and the like...I know with 256 it will be either 256 or 248...
  _dma_buffer_size = ST77XX_DMA_BUFFER_SIZE;
  _dma_cnt_sub_frames_per_frame = (_count_pixels) / _dma_buffer_size;
  while ((_dma_cnt_sub_frames_per_frame * _dma_buffer_size) != (_count_pixels)) {
    _dma_buffer_size--;
    _dma_cnt_sub_frames_per_frame = (_count_pixels) / _dma_buffer_size;   
  }

#if defined(DEBUG_ASYNC_UPDATE)
  Serial.printf("DMA Init buf size: %d sub frames:%d spi num: %d\n", _dma_buffer_size, _dma_cnt_sub_frames_per_frame, _spi_num);
#endif

  _dma_data[_spi_num]._dmasettings[0].sourceBuffer(_dma_data[_spi_num]._dma_buffer1, _dma_buffer_size*2);
  _dma_data[_spi_num]._dmasettings[0].destination(_pimxrt_spi->TDR);
  _dma_data[_spi_num]._dmasettings[0].TCD->ATTR_DST = 1;
  _dma_data[_spi_num]._dmasettings[0].replaceSettingsOnCompletion(_dma_data[_spi_num]._dmasettings[1]);
  _dma_data[_spi_num]._dmasettings[0].interruptAtCompletion();

  _dma_data[_spi_num]._dmasettings[1].sourceBuffer(_dma_data[_spi_num]._dma_buffer2, _dma_buffer_size*2);
  _dma_data[_spi_num]._dmasettings[1].destination(_pimxrt_spi->TDR);
  _dma_data[_spi_num]._dmasettings[1].TCD->ATTR_DST = 1;
  _dma_data[_spi_num]._dmasettings[1].replaceSettingsOnCompletion(_dma_data[_spi_num]._dmasettings[0]);
  _dma_data[_spi_num]._dmasettings[1].interruptAtCompletion();

  // Setup DMA main object
  //Serial.println("Setup _dmatx");
  // Serial.println("DMA initDMASettings - before dmatx");
  _dma_data[_spi_num]._dmatx.begin(true);
  _dma_data[_spi_num]._dmatx.triggerAtHardwareEvent(dmaTXevent);
  _dma_data[_spi_num]._dmatx = _dma_data[_spi_num]._dmasettings[0];
  // probably could use const table of functions...
  if (_spi_num == 0) _dma_data[_spi_num]._dmatx.attachInterrupt(dmaInterrupt);
  else if (_spi_num == 1) _dma_data[_spi_num]._dmatx.attachInterrupt(dmaInterrupt1);
  else _dma_data[_spi_num]._dmatx.attachInterrupt(dmaInterrupt2);
#else
  // T3.5
  // Lets setup the write size.  For SPI we can use up to 32767 so same size as we use on T3.6...
  // But SPI1 and SPI2 max of 511.  We will use 480 in that case as even divider...

  uint32_t COUNT_WORDS_WRITE = (_count_pixels) / 2;

  // The 240x320 display requires us to expand to another DMA setting. 
  if (COUNT_WORDS_WRITE >= 32768) {
    COUNT_WORDS_WRITE = (_count_pixels) / 3;
  }
  _dmarx.disable();
  _dmarx.source(_pkinetisk_spi->POPR);
  _dmarx.TCD->ATTR_SRC = 1;
  _dmarx.destination(_dma_dummy_rx);
  _dmarx.disableOnCompletion();
  _dmarx.triggerAtHardwareEvent(_spi_hardware->rx_dma_channel);
  // probably could use const table of functions...
  if (_spi_num == 0) _dmarx.attachInterrupt(dmaInterrupt);
  else if (_spi_num == 1) _dmarx.attachInterrupt(dmaInterrupt1);
  else _dmarx.attachInterrupt(dmaInterrupt2);

  _dmarx.interruptAtCompletion();

  // We may be using settings chain here so lets set it up. 
  // Now lets setup TX chain.  Note if trigger TX is not set
  // we need to have the RX do it for us.
  _dmatx.disable();
  _dmatx.destination(_pkinetisk_spi->PUSHR);
  _dmatx.TCD->ATTR_DST = 1;
  _dmatx.disableOnCompletion();
  // Current SPIN, has both RX/TX same for SPI1/2 so just know f
  if (_pspi == &SPI) {
    _dmatx.triggerAtHardwareEvent(dmaTXevent);
    _dma_write_size_words = COUNT_WORDS_WRITE;
  } else {
    _dma_write_size_words = 480;
      _dmatx.triggerAtTransfersOf(_dmarx);
  }
  //Serial.printf("Init DMA Settings: TX:%d size:%d\n", dmaTXevent, _dma_write_size_words);

#endif
  _dma_state = ST77XX_DMA_INIT;  // Should be first thing set!
  // Serial.println("DMA initDMASettings - end");

}
#endif

void ST7735_t3::dumpDMASettings() {
#ifdef DEBUG_ASYNC_UPDATE
#if defined(__MK66FX1M0__) 
  // T3.6
  Serial.printf("DMA dump TCDs %d\n", _dmatx.channel);
  dumpDMA_TCD(&_dmatx);
  dumpDMA_TCD(&_dmasettings[_spi_num][0]);
  dumpDMA_TCD(&_dmasettings[_spi_num][1]);
  dumpDMA_TCD(&_dmasettings[_spi_num][2]);
  dumpDMA_TCD(&_dmasettings[_spi_num][3]);
#elif defined(__IMXRT1062__)  // Teensy 4.x
  // Serial.printf("DMA dump TCDs %d\n", _dmatx.channel);
  dumpDMA_TCD(&_dma_data[_spi_num]._dmatx);
  dumpDMA_TCD(&_dma_data[_spi_num]._dmasettings[0]);
  dumpDMA_TCD(&_dma_data[_spi_num]._dmasettings[1]);
#else
  Serial.printf("DMA dump TX:%d RX:%d\n", _dmatx.channel, _dmarx.channel);
  dumpDMA_TCD(&_dmatx);
  dumpDMA_TCD(&_dmarx);
#endif  
#endif

}

bool ST7735_t3::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...
  // Also bail if we are working with a hardware SPI port. 
  #ifdef ENABLE_ST77XX_FRAMEBUFFER
  if (!_use_fbtft || !_pspi) return false;


  #if defined(__MK64FX512__) || defined(__MK20DX256__)  // If T3.5 only allow on SPI...
  // The T3.5 DMA to SPI has issues with preserving stuff like we want 16 bit mode
  // and we want CS to stay on... So hack it.  We will turn off using CS for the CS
  //  pin.
  if (!cspin && (_cs != 0xff)) {
    //Serial.println("***T3.5 CS Pin hack");
    pcs_data = 0;
    pcs_command = pcs_data | _pspi->setCS(_rs);
    pinMode(_cs, OUTPUT);
    cspin    = portOutputRegister(digitalPinToPort(_cs));
    *cspin = 1;
  }
  #endif

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

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

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


#ifdef DEBUG_ASYNC_UPDATE
  dumpDMASettings();
#endif
  beginSPITransaction();

  // Doing full window. 
  setAddr(0, 0, _width-1, _height-1);
  writecommand(ST7735_RAMWR);

  // Write the first Word out before enter DMA as to setup the proper CS/DC/Continue flaugs
  writedata16(*_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[_spi_num] = this;
  _dma_state |= ST77XX_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();
  //==========================================
  // T4
  //==========================================
#elif defined(__IMXRT1062__)  // Teensy 4.x
    // Start off remove disable on completion from both...
  // it will be the ISR that disables it... 
  _dma_data[_spi_num]._dmasettings[0].TCD->CSR &= ~( DMA_TCD_CSR_DREQ);
  _dma_data[_spi_num]._dmasettings[1].TCD->CSR &= ~( DMA_TCD_CSR_DREQ);

#ifdef DEBUG_ASYNC_UPDATE
  dumpDMASettings();
#endif
  // Lets copy first parts of frame buffer into our two sub-frames
  memcpy(_dma_data[_spi_num]._dma_buffer1, _pfbtft, _dma_buffer_size*2);
  memcpy(_dma_data[_spi_num]._dma_buffer2, &_pfbtft[_dma_buffer_size], _dma_buffer_size*2);
  _dma_pixel_index = _dma_buffer_size*2;
  _dma_sub_frame_count = 0; // 

  beginSPITransaction();
  // Doing full window. 
  setAddr(0, 0, _width-1, _height-1);
  writecommand_last(ST7735_RAMWR);

  // Update TCR to 16 bit mode. and output the first entry.
  _spi_fcr_save = _pimxrt_spi->FCR; // remember the FCR
  _pimxrt_spi->FCR = 0; // clear water marks...   
  maybeUpdateTCR(LPSPI_TCR_PCS(1) | LPSPI_TCR_FRAMESZ(15) | LPSPI_TCR_RXMSK /*| LPSPI_TCR_CONT*/);
//  _pimxrt_spi->CFGR1 |= LPSPI_CFGR1_NOSTALL;
//  maybeUpdateTCR(LPSPI_TCR_PCS(1) | LPSPI_TCR_FRAMESZ(15) | LPSPI_TCR_CONT);
  _pimxrt_spi->DER = LPSPI_DER_TDDE;
  _pimxrt_spi->SR = 0x3f00; // clear out all of the other status...

    _dma_data[_spi_num]._dmatx.triggerAtHardwareEvent( _spi_hardware->tx_dma_channel );


  _dma_data[_spi_num]._dmatx = _dma_data[_spi_num]._dmasettings[0];

    _dma_data[_spi_num]._dmatx.begin(false);
    _dma_data[_spi_num]._dmatx.enable();

  _dma_frame_count = 0;  // Set frame count back to zero. 
  _dmaActiveDisplay[_spi_num] = this;
  if (update_cont) {
    _dma_state |= ST77XX_DMA_CONT;
  } else {
    _dma_state &= ~ST77XX_DMA_CONT;

  }

  _dma_state |= ST77XX_DMA_ACTIVE;
#else
  //==========================================
  // T3.5
  //==========================================

  // lets setup the initial pointers. 
  _dmatx.sourceBuffer(&_pfbtft[1], (_dma_write_size_words-1)*2);
  _dmatx.TCD->SLAST = 0;  // Finish with it pointing to next location
  _dmarx.transferCount(_dma_write_size_words);
  _dma_count_remaining = _count_pixels - _dma_write_size_words; // how much more to transfer? 
  //Serial.printf("SPI1/2 - TC:%d TR:%d\n", _dma_write_size_words, _dma_count_remaining);

#ifdef DEBUG_ASYNC_UPDATE
  dumpDMASettings();
#endif

  beginSPITransaction();
  // Doing full window. 
  setAddr(0, 0, _width-1, _height-1);
  writecommand(ST7735_RAMWR);

  // Write the first Word out before enter DMA as to setup the proper CS/DC/Continue flaugs
  // On T3.5 DMA only appears to work with CTAR 0 so hack it up...
  _pkinetisk_spi->CTAR0 |= SPI_CTAR_FMSZ(8);  // Hack convert from 8 bit to 16 bit...

  _pkinetisk_spi->MCR = SPI_MCR_MSTR | SPI_MCR_CLR_RXF | SPI_MCR_PCSIS(0x1F);

  _pkinetisk_spi->SR = 0xFF0F0000;

  // Lets try to output the first byte to make sure that we are in 16 bit mode...
  _pkinetisk_spi->PUSHR = *_pfbtft | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT;  

  if (_pspi == &SPI) {
    // SPI - has both TX and RX so use it
    _pkinetisk_spi->RSER =  SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS | SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS;

      _dmarx.enable();
      _dmatx.enable();
  } else {
    _pkinetisk_spi->RSER =  SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS ;
      _dmatx.triggerAtTransfersOf(_dmarx);
      _dmatx.enable();
      _dmarx.enable();
  }

  _dma_frame_count = 0;  // Set frame count back to zero. 
  _dmaActiveDisplay[_spi_num] = this;
  if (update_cont) {
    _dma_state |= ST77XX_DMA_CONT;
  } else {
    _dma_state &= ~ST77XX_DMA_CONT;

  }

  _dma_state |= ST77XX_DMA_ACTIVE;
#endif  
#ifdef DEBUG_ASYNC_LEDS
  digitalWriteFast(DEBUG_PIN_1, LOW);
#endif
  return true;
    #else
    return false;     // no frame buffer so will never start... 
  #endif

}      

void ST7735_t3::endUpdateAsync() {
  // make sure it is on
  #ifdef ENABLE_ST77XX_FRAMEBUFFER
  if (_dma_state & ST77XX_DMA_CONT) {
    _dma_state &= ~ST77XX_DMA_CONT; // Turn of the continueous mode
#if defined(__MK66FX1M0__) 
    _dmasettings[_spi_num][_cnt_dma_settings].disableOnCompletion();
#endif
  }
  #endif
}
  
void ST7735_t3::waitUpdateAsyncComplete(void) 
{
  #ifdef ENABLE_ST77XX_FRAMEBUFFER
#ifdef DEBUG_ASYNC_LEDS
  digitalWriteFast(DEBUG_PIN_3, HIGH);
#endif

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

#endif


Thanks for help. Rolf
 
Last edited:
This isn’t the right thread for the Teensy modified library, but the issue appears to be that you don’t call init before turning on the frame buffer here.
Code:
 void setupDisplay() {
  tft.useFrameBuffer(true);
  tft.initR(INITR_GREENTAB);
  tft.setRotation(3);
  tft.invertDisplay(false);
  renderBootUpPage();
  tft.updateScreen();
  threads.addThread(displayThread);
}
Once init is called it sets the width and height then useFrameBuffer uses that to allocate the correct amount, so swap those and you should be good to go.
 
Hallo vjmuzik

Thanks for your tip. I found the mistake. It was an incorrect initial value for colstart and rowstart in initialization function (Rcmd1). The values must be zero.

Code excerpt from ST7735_t3.cpp
Code:
...

// Initialization for ST7735R screens (green or red tabs)
void ST7735_t3::initR(uint8_t options)
{
  commonInit(Rcmd1);
  if (options == INITR_GREENTAB) {
    commandList(Rcmd2green);
    //_colstart = 2;
    //_rowstart = 1;
	_colstart = 0;
	_rowstart = 0;
  } else if(options == INITR_144GREENTAB) {
    _screenHeight = ST7735_TFTHEIGHT_144;
    commandList(Rcmd2green144);
    _colstart = 2;
    _rowstart = 3;
  } else if(options == INITR_144GREENTAB_OFFSET) {
    _screenHeight = ST7735_TFTHEIGHT_144;
    commandList(Rcmd2green144);
    _colstart = 0;
    _rowstart = 32;
    } else if(options == INITR_MINI160x80) {
      _screenHeight   = ST7735_TFTHEIGHT_160;
      _screenWidth    = ST7735_TFTWIDTH_80;
     commandList(Rcmd2green160x80);
      _colstart = 24;
      _rowstart = 0;
  } else {
    // _colstart, _rowstart left at default '0' values
    commandList(Rcmd2red);
  }
  commandList(Rcmd3);

  // if black or mini, change MADCTL color filter
  if ((options == INITR_BLACKTAB)  || (options == INITR_MINI160x80)){
    writecommand(ST7735_MADCTL);
    writedata_last(0xC0);
  }

  tabcolor = options;
  setRotation(0);
}

Greetings Rolf
 
Looks like you should be using INITR_REDTAB for your specific board as well, which already has the correct values for colstart and rowstart for your display.
 
OK. I still have a little problem. The colors are not right. Where could that be?

red = blue

geen = neon

blue = red
 
Last edited:
Probably an issue on which TAB color you choose: INITR_GREENTAB
Try some of the others. Also depending on your manufacturer of the display, they may have wired it differently than Adafruit which might imply needing change for things like color.
 
Hi

I solved the problem with the incorrect colors.

In the initialization for the display I changed INITR_GREENTAB for INITR_BLACKTAB.

Framebuffer and colors now work correctly :)

Framebuffer-Color2.jpg


Code:
void setupDisplay() {
  tft.useFrameBuffer(true);
  //tft.initR(INITR_GREENTAB);
  tft.initR(INITR_BLACKTAB);
  tft.setRotation(3);
  tft.invertDisplay(false);
  renderBootUpPage();
  tft.updateScreen();
  threads.addThread(displayThread);
}

Greetings Rolf
 
In my ongoing effort to see how many displays I can reasonably tack onto a Teensy 4.x I've finally delved into the world of DMA, after a slew of testing done earlier today I've finished it up just a little bit ago. The results are very promising so far with the only displays I have on hand, but this should work with any display that uses Adafruit_SPITFT regardless. I've also posted an example that works with up to 12 displays, you also don't need 12 physically hooked up to see what the performance would be like if you had 12 hooked up. This more complex sketch has the displays frame locked to get the most performance with this many displays, that being said here are some basic numbers pulled from that sketch showing the difference of with and without DMA.
Code:
[SIZE=4]60Mhz SPI ST7735S 80x160 16 Bit Color[/SIZE]
[LIST]
[*][SIZE=3]12 screens 10 fps no DMA[/SIZE]
[INDENT]3,087,581 loops left[/INDENT]
[*][SIZE=3]12 screens 23 fps no DMA (max)[/SIZE]
[INDENT]10,987 loops left[/INDENT]

[*][SIZE=3]12 screens 10 fps DMA[/SIZE]
[INDENT]5,361,142 loops left[/INDENT]
[*][SIZE=3]12 screens 22 fps DMA (max)[/SIZE]
[INDENT]5,237,242 loops left[/INDENT]
[/LIST]
80Mhz SPI unstable on breadboard

The current code only supports a max of 12 displays because most people will not need that much outside of special case scenarios. I've also only tested this on the main SPI bus, but the other 2 buses should already be setup to work should someone try it. This only uses one DMA channel so it will not be asynchronous if you try to use more than one SPI bus, to coincide with that if you try to write to another display while one is already doing DMA it'll drop the frame.

I will try to get some other displays in the future so I can better test this library, but so far it's turning out well for the project that I will eventually need it for.
Update is on GitHub: https://github.com/vjmuzik/Adafruit_GFX_Buffer
 
Hi friends

I still have a problem with the framebuffer. When I use texts and drawings are not displayed permanently :confused:

example

Code:
tft.fillScreen(ST7735_BLACK);
tft.setCursor(0,0);
tft.fillRect(0,0,159,13,ST7735_WHITE);
tft.setTextColor(ST7735_BLACK);
tft.setFont(NULL);
tft.setTextSize(0);
tft.setCursor(5,3);
tft.setTextColor(ST7735_BLACK);
tft.println("OSC2 PARAMETER");
 
Hello friends.

I found a solution.

In the ST7735_t3 Lib from PaulStoffregen there is a function asynchronous updates (frame buffer). With this I can deactivate the constant redrawing of the screen. This gives you control over when the screen is updated.

If you draw something on the screen, tft.updateScreen () must then be called.

Link: https://github.com/PaulStoffregen/ST7735_t3/blob/master/README.md


Asynchronous Update support (Frame buffer)
Code:
        bool	updateScreenAsync(bool update_cont = false); - Starts an update either one shot or continuous
	void	waitUpdateAsyncComplete(void);  - Wait for any active update to complete
	void	endUpdateAsync();			 - Turn of the continuous mode.
	boolean	asyncUpdateActive(void)      - Lets you know if an async operation is still active


Example
Code:
//*************************************************************************
// render current Parameter on Menu Page
//*************************************************************************
FLASHMEM void renderCurrentParameter(uint8_t Page,uint8_t ParameterNr, uint8_t value)
{
	// Parameter to Page 1 ----------------------------------------------
	if (Page == 1){
		if (ParameterNr == 0){
			tft.fillRect(80,20,22,10,ST7735_BLUE);
			tft.setCursor(82,21);
			tft.setTextColor(ST7735_WHITE);
			tft.print(value);
			tft.updateScreen();
			renderPulseWidth(currentFloatValue);
		}
		if (ParameterNr == 1){
			tft.fillRect(80,40,22,10,ST7735_BLUE);
			tft.setCursor(82,41);
			tft.setTextColor(ST7735_WHITE);
			tft.print(value);
			tft.updateScreen();
		}
	}
}

Gruß Rolf
 
Back
Top