/*************************************************** Arduino TFT graphics library targetted at ESP8266 based boards. (ESP32 support is planned!) This library has been derived from the Adafruit_GFX library and the associated driver library. See text at the end of this file. This is a standalone library that contains the hardware driver, the graphics funtions and the proportional fonts. The larger fonts are Run Length Encoded to reduce their size. Created by Bodmer 2/12/16 Bodmer: Added RPi 16 bit display support ****************************************************/ #include "TFT_eSPI.h" #include #include // SUPPORT_TRANSACTIONS is manadatory for ESP32 so the hal mutex is toggled #if defined (ESP32) && !defined (SUPPORT_TRANSACTIONS) #define SUPPORT_TRANSACTIONS #endif // If it is a 16bit serial display we must transfer 16 bits every time #ifdef RPI_ILI9486_DRIVER #define SEND_16_BITS #define CMD_BITS 16-1 #else #define CMD_BITS 8-1 #endif // Fast SPI block write prototype void spiWriteBlock(uint16_t color, uint32_t repeat); // If the SPI library has transaction support, these functions // establish settings and protect from interference from other // libraries. Otherwise, they simply do nothing. inline void TFT_eSPI::spi_begin(void){ #if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) if (locked) {locked = false; SPI.beginTransaction(SPISettings(SPI_FREQUENCY, MSBFIRST, SPI_MODE0));} #endif } inline void TFT_eSPI::spi_end(void){ #if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) if(!inTransaction) {if (!locked) {locked = true; SPI.endTransaction();}} #endif } inline void TFT_eSPI::spi_begin_touch(void){ #if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) if (locked) {locked = false; SPI.beginTransaction(SPISettings(SPI_TOUCH_FREQUENCY, MSBFIRST, SPI_MODE0));} #else SPI.setFrequency(SPI_TOUCH_FREQUENCY); #endif } inline void TFT_eSPI::spi_end_touch(void){ #if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) if(!inTransaction) {if (!locked) {locked = true; SPI.endTransaction();}} #else SPI.setFrequency(SPI_FREQUENCY); #endif } /*************************************************************************************** ** Function name: TFT_eSPI ** Description: Constructor , we must use hardware SPI pins ***************************************************************************************/ TFT_eSPI::TFT_eSPI(int16_t w, int16_t h) { // The control pins are deliberately set to the inactive state (CS high) as setup() // might call and initialise other SPI peripherals which would could cause conflicts // if CS is floating or undefined. #ifdef TFT_CS digitalWrite(TFT_CS, HIGH); // Chip select high (inactive) pinMode(TFT_CS, OUTPUT); #endif // Configure chip select for touchscreen controller if present #ifdef TOUCH_CS digitalWrite(TOUCH_CS, HIGH); // Chip select high (inactive) pinMode(TOUCH_CS, OUTPUT); #endif #ifdef TFT_WR digitalWrite(TFT_WR, HIGH); // Set write strobe high (inactive) pinMode(TFT_WR, OUTPUT); #endif #ifdef TFT_DC digitalWrite(TFT_DC, HIGH); // Data/Command high = data mode pinMode(TFT_DC, OUTPUT); #endif #ifdef TFT_RST if (TFT_RST >= 0) { digitalWrite(TFT_RST, HIGH); // Set high, do not share pin with another SPI device pinMode(TFT_RST, OUTPUT); } #endif _width_orig = _width = w; // Set by specific xxxxx_Defines.h file or by users sketch _height_orig = _height = h; // Set by specific xxxxx_Defines.h file or by users sketch rotation = 0; cursor_y = cursor_x = 0; textfont = 1; textsize = 1; textcolor = 0xFFFF; // White textbgcolor = 0x0000; // Black padX = 0; // No padding textwrap = true; // Wrap text when using print stream textdatum = TL_DATUM; // Top Left text alignment is default fontsloaded = 0; locked = true; // ESP32 transaction mutex lock flags inTransaction = false; addr_row = 0xFFFF; addr_col = 0xFFFF; #ifdef LOAD_GLCD fontsloaded = 0x0002; // Bit 1 set #endif #ifdef LOAD_FONT2 fontsloaded |= 0x0004; // Bit 2 set #endif #ifdef LOAD_FONT4 fontsloaded |= 0x0010; // Bit 4 set #endif #ifdef LOAD_FONT6 fontsloaded |= 0x0040; // Bit 6 set #endif #ifdef LOAD_FONT7 fontsloaded |= 0x0080; // Bit 7 set #endif #ifdef LOAD_FONT8 fontsloaded |= 0x0100; // Bit 8 set #endif } /*************************************************************************************** ** Function name: begin ** Description: Included for backwards compatibility ***************************************************************************************/ void TFT_eSPI::begin(void) { init(); } /*************************************************************************************** ** Function name: init ** Description: Reset, then initialise the TFT display registers ***************************************************************************************/ void TFT_eSPI::init(void) { #if !defined (ESP32) #ifdef TFT_CS cspinmask = (uint32_t) digitalPinToBitMask(TFT_CS); #endif #ifdef TFT_DC dcpinmask = (uint32_t) digitalPinToBitMask(TFT_DC); #endif #ifdef TFT_WR wrpinmask = (uint32_t) digitalPinToBitMask(TFT_WR); #endif #ifdef TFT_SPI_OVERLAP // Overlap mode SD0=MISO, SD1=MOSI, CLK=SCLK must use D3 as CS // pins(int8_t sck, int8_t miso, int8_t mosi, int8_t ss); //SPI.pins( 6, 7, 8, 0); SPI.pins(6, 7, 8, 0); #endif SPI.begin(); // This will set HMISO to input #else #if defined (TFT_MOSI) && !defined (TFT_SPI_OVERLAP) SPI.begin(TFT_SCLK, TFT_MISO, TFT_MOSI, -1); #else SPI.begin(); #endif #endif inTransaction = false; locked = true; // SUPPORT_TRANSACTIONS is manadatory for ESP32 so the hal mutex is toggled // so the code here is for ESP8266 only #if !defined (SUPPORT_TRANSACTIONS) && defined (ESP8266) SPI.setBitOrder(MSBFIRST); SPI.setDataMode(SPI_MODE0); SPI.setFrequency(SPI_FREQUENCY); #endif // Set to output once again in case D6 (MISO) is used for CS #ifdef TFT_CS digitalWrite(TFT_CS, HIGH); // Chip select high (inactive) pinMode(TFT_CS, OUTPUT); #else SPI.setHwCs(1); // Use hardware SS toggling #endif // Set to output once again in case D6 (MISO) is used for DC #ifdef TFT_DC digitalWrite(TFT_DC, HIGH); // Data/Command high = data mode pinMode(TFT_DC, OUTPUT); #endif // Toggle RST low to reset #ifdef TFT_RST if (TFT_RST >= 0) { digitalWrite(TFT_RST, HIGH); delay(5); digitalWrite(TFT_RST, LOW); delay(20); digitalWrite(TFT_RST, HIGH); delay(150); } #endif spi_begin(); writecommand(TFT_SWRST); // Software reset spi_end(); delay(5); // Wait for software reset to complete spi_begin(); // This loads the driver specific initialisation code <<<<<<<<<<<<<<<<<<<<< ADD NEW DRIVERS TO THE LIST HERE <<<<<<<<<<<<<<<<<<<<<<< #if defined (ILI9341_DRIVER) #include "TFT_Drivers/ILI9341_Init.h" #elif defined (ST7735_DRIVER) #include "TFT_Drivers/ST7735_Init.h" #elif defined (ILI9163_DRIVER) #include "TFT_Drivers/ILI9163_Init.h" #elif defined (S6D02A1_DRIVER) #include "TFT_Drivers/S6D02A1_Init.h" #elif defined (RPI_ILI9486_DRIVER) #include "TFT_Drivers/RPI_ILI9486_Init.h" #endif spi_end(); } /*************************************************************************************** ** Function name: setRotation ** Description: rotate the screen orientation m = 0-3 or 4-7 for BMP drawing ***************************************************************************************/ void TFT_eSPI::setRotation(uint8_t m) { spi_begin(); // This loads the driver specific rotation code <<<<<<<<<<<<<<<<<<<<< ADD NEW DRIVERS TO THE LIST HERE <<<<<<<<<<<<<<<<<<<<<<< #if defined (ILI9341_DRIVER) #include "TFT_Drivers/ILI9341_Rotation.h" #elif defined (ST7735_DRIVER) #include "TFT_Drivers/ST7735_Rotation.h" #elif defined (ILI9163_DRIVER) #include "TFT_Drivers/ILI9163_Rotation.h" #elif defined (S6D02A1_DRIVER) #include "TFT_Drivers/S6D02A1_Rotation.h" #elif defined (RPI_ILI9486_DRIVER) #include "TFT_Drivers/RPI_ILI9486_Rotation.h" #endif delayMicroseconds(10); spi_end(); addr_row = 0xFFFF; addr_col = 0xFFFF; } /*************************************************************************************** ** Function name: commandList, used for FLASH based lists only (e.g. ST7735) ** Description: Get initialisation commands from FLASH and send to TFT ***************************************************************************************/ void TFT_eSPI::commandList (const uint8_t *addr) { uint8_t numCommands; uint8_t numArgs; uint8_t ms; spi_begin(); numCommands = pgm_read_byte(addr++); // Number of commands to follow while (numCommands--) // For each command... { writecommand(pgm_read_byte(addr++)); // Read, issue command numArgs = pgm_read_byte(addr++); // Number of args to follow ms = numArgs & TFT_INIT_DELAY; // If hibit set, delay follows args numArgs &= ~TFT_INIT_DELAY; // Mask out delay bit while (numArgs--) // For each argument... { writedata(pgm_read_byte(addr++)); // Read, issue argument } if (ms) { ms = pgm_read_byte(addr++); // Read post-command delay time (ms) delay( (ms==255 ? 500 : ms) ); } } spi_end(); } /*************************************************************************************** ** Function name: spiwrite ** Description: Write 8 bits to SPI port (legacy support only) ***************************************************************************************/ void TFT_eSPI::spiwrite(uint8_t c) { SPI.transfer(c); } /*************************************************************************************** ** Function name: writecommand ** Description: Send an 8 bit command to the TFT ***************************************************************************************/ void TFT_eSPI::writecommand(uint8_t c) { DC_C; CS_L; #ifdef SEND_16_BITS SPI.transfer(0); #endif SPI.transfer(c); CS_H; DC_D; } /*************************************************************************************** ** Function name: writedata ** Description: Send a 8 bit data value to the TFT ***************************************************************************************/ void TFT_eSPI::writedata(uint8_t c) { CS_L; #ifdef SEND_16_BITS SPI.transfer(0); #endif SPI.transfer(c); CS_H; } /*************************************************************************************** ** Function name: readcommand8 (for ILI9341 Interface II i.e. IM [3:0] = "1101") ** Description: Read a 8 bit data value from an indexed command register ***************************************************************************************/ uint8_t TFT_eSPI::readcommand8(uint8_t cmd_function, uint8_t index) { spi_begin(); index = 0x10 + (index & 0x0F); DC_C; CS_L; SPI.transfer(0xD9); DC_D; SPI.transfer(index); CS_H; DC_C; CS_L; SPI.transfer(cmd_function); DC_D; uint8_t reg = SPI.transfer(0); CS_H; spi_end(); return reg; } /*************************************************************************************** ** Function name: readcommand16 (for ILI9341 Interface II i.e. IM [3:0] = "1101") ** Description: Read a 16 bit data value from an indexed command register ***************************************************************************************/ uint16_t TFT_eSPI::readcommand16(uint8_t cmd_function, uint8_t index) { uint32_t reg = 0; reg |= (readcommand8(cmd_function, index + 0) << 8); reg |= (readcommand8(cmd_function, index + 1) << 0); return reg; } /*************************************************************************************** ** Function name: readcommand32 (for ILI9341 Interface II i.e. IM [3:0] = "1101") ** Description: Read a 32 bit data value from an indexed command register ***************************************************************************************/ uint32_t TFT_eSPI::readcommand32(uint8_t cmd_function, uint8_t index) { uint32_t reg; reg = (readcommand8(cmd_function, index + 0) << 24); reg |= (readcommand8(cmd_function, index + 1) << 16); reg |= (readcommand8(cmd_function, index + 2) << 8); reg |= (readcommand8(cmd_function, index + 3) << 0); return reg; } /*************************************************************************************** ** Function name: read pixel (for SPI Interface II i.e. IM [3:0] = "1101") ** Description: Read 565 pixel colours from a pixel ***************************************************************************************/ uint16_t TFT_eSPI::readPixel(int32_t x0, int32_t y0) { spi_begin(); readAddrWindow(x0, y0, x0, y0); // Sets CS low // Dummy read to throw away don't care value SPI.transfer(0); // Read window pixel 24 bit RGB values uint8_t r = SPI.transfer(0); uint8_t g = SPI.transfer(0); uint8_t b = SPI.transfer(0); CS_H; spi_end(); return color565(r, g, b); } /*************************************************************************************** ** Function name: read rectangle (for SPI Interface II i.e. IM [3:0] = "1101") ** Description: Read 565 pixel colours from a defined area ***************************************************************************************/ void TFT_eSPI::readRect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint16_t *data) { if ((x > _width) || (y > _height) || (w == 0) || (h == 0)) return; spi_begin(); readAddrWindow(x, y, x + w - 1, y + h - 1); // Sets CS low // Dummy read to throw away don't care value SPI.transfer(0); // Read window pixel 24 bit RGB values uint32_t len = w * h; while (len--) { // Read the 3 RGB bytes, colour is actually only in the top 6 bits of each byte // as the TFT stores colours as 18 bits uint8_t r = SPI.transfer(0); uint8_t g = SPI.transfer(0); uint8_t b = SPI.transfer(0); // Swapped colour byte order for compatibility with pushRect() *data++ = (r & 0xF8) | (g & 0xE0) >> 5 | (b & 0xF8) << 5 | (g & 0x1C) << 11; } // Write NOP command to stop read mode //DC_C; //SPI.transfer(TFT_NOP); //DC_D; CS_H; spi_end(); } /*************************************************************************************** ** Function name: push rectangle (for SPI Interface II i.e. IM [3:0] = "1101") ** Description: push 565 pixel colours into a defined area ***************************************************************************************/ void TFT_eSPI::pushRect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint16_t *data) { if ((x >= _width) || (y >= _height) || (w == 0) || (h == 0)) return; spi_begin(); setAddrWindow(x, y, x + w - 1, y + h - 1); // Sets CS low and sent RAMWR uint32_t len = w * h * 2; // Check alignment of pointer to 32 bits uint8_t offset = (uint32_t)data & 0x3; if (offset > len) offset = len; // Make pointer 32 bit align using immune call then use the faster writeBytes() if (offset) { SPI.writePattern((uint8_t*)data, offset, 1); len -= offset; data += offset; } if (len) SPI.writeBytes((uint8_t*)data, len); CS_H; spi_end(); } /*************************************************************************************** ** Function name: push sprite ** Description: plot 16 bit sprite in a defined area with clipping ***************************************************************************************/ void TFT_eSPI::pushSprite(int32_t x, int32_t y, uint32_t w, uint32_t h, uint16_t *data) { if ((x >= (int32_t)_width) || (y >= (int32_t)_height)) return; int32_t dx = 0; int32_t dy = 0; int32_t dw = w; int32_t dh = h; if (x < 0) { dw += x; dx = -x; x = 0; } if (y < 0) { dh += y; dy = -y; y = 0; } if ((x + w) > _width ) dw = _width - x; if ((y + h) > _height) dh = _height - y; if (dw < 1 || dh < 1) return; spi_begin(); setAddrWindow(x, y, x + dw - 1, y + dh - 1); // Sets CS low and sent RAMWR data += dx + dy * w; dw <<= 1; while (dh--) { // Check alignment of pointer to 32 bits uint8_t offset = (uint32_t)data & 0x3; if (offset > dw) offset = dw; int32_t len = dw; uint8_t* ptr = (uint8_t*)data; // Make pointer 32 bit align using immune call then use the faster writeBytes() if (offset) { SPI.writePattern(ptr, offset, 1); len -= offset; ptr += offset; } if (len) SPI.writeBytes(ptr, len); data += w; } CS_H; spi_end(); } /*************************************************************************************** ** Function name: push sprite ** Description: plot 16 bit sprite with 1 colour being transparent ***************************************************************************************/ void TFT_eSPI::pushSprite(int32_t x, int32_t y, uint32_t w, uint32_t h, uint16_t *data, uint16_t transp) { if ((x >= (int32_t)_width) || (y >= (int32_t)_height)) return; int32_t dx = 0; int32_t dy = 0; int32_t dw = w; int32_t dh = h; if (x < 0) { dw += x; dx = -x; x = 0; } if (y < 0) { dh += y; dy = -y; y = 0; } if ((x + w) > _width ) dw = _width - x; if ((y + h) > _height) dh = _height - y; if (dw < 1 || dh < 1) return; spi_begin(); data += dx + dy * w; int32_t xe = x + dw - 1, ye = y + dh - 1; while (dh--) { int32_t len = dw; uint16_t* ptr = data; int32_t px = x; boolean move = true; while (len--) { if (transp != *ptr) { if (move) { move = false; setAddrWindow(px, y, xe, ye); } SPI.write16(*ptr>>8 | *ptr <<8); } else move = true; px++; ptr++; } y++; data += w; } CS_H; spi_end(); } /*************************************************************************************** ** Function name: push sprite ** Description: plot 8 bit sprite with clipping using a line buffer ***************************************************************************************/ void TFT_eSPI::pushSprite(int32_t x, int32_t y, uint32_t w, uint32_t h, uint8_t *data) { if ((x >= (int32_t)_width) || (y >= (int32_t)_height)) return; int32_t dx = 0; int32_t dy = 0; int32_t dw = w; int32_t dh = h; if (x < 0) { dw += x; dx = -x; x = 0; } if (y < 0) { dh += y; dy = -y; y = 0; } if ((x + w) > _width ) dw = _width - x; if ((y + h) > _height) dh = _height - y; if (dw < 1 || dh < 1) return; spi_begin(); setAddrWindow(x, y, x + dw - 1, y + dh - 1); // Sets CS low and sent RAMWR data += dx + dy * w; // Line buffer makes plotting faster, pointer is aligned to 32 bits uint32_t lineBuf[1+(dw>>1)]; uint8_t blue[] = {0, 11, 21, 31}; // blue 2 to 5 bit colour lookup table _lastColor = -1; // Set to illegal value // Used to store last shifted colour uint8_t msbColor = 0; uint8_t lsbColor = 0; while (dh--) { uint32_t len = dw; uint8_t* ptr = data; uint8_t* linePtr = (uint8_t*)lineBuf; while(len--) { uint32_t color = *ptr++; // Shifts are slow so check if colour has changed first if (color != _lastColor) { // =====Green===== ===============Red============== msbColor = (color & 0x1C)>>2 | (color & 0xC0)>>3 | (color & 0xE0); // =====Green===== =======Blue====== lsbColor = (color & 0x1C)<<3 | blue[color & 0x03]; _lastColor = color; } *linePtr++ = msbColor; *linePtr++ = lsbColor; } // lineBuf is always 32 bit aligned so can use writeBytes! if (dw) SPI.writeBytes((uint8_t*)lineBuf, dw<<1); data += w; } CS_H; spi_end(); } /*************************************************************************************** ** Function name: push sprite ** Description: plot 8 bit sprite with 1 colour being transparent ***************************************************************************************/ void TFT_eSPI::pushSprite(int32_t x, int32_t y, uint32_t w, uint32_t h, uint8_t *data, uint8_t transp) { if ((x >= (int32_t)_width) || (y >= (int32_t)_height)) return; int32_t dx = 0; int32_t dy = 0; int32_t dw = w; int32_t dh = h; if (x < 0) { dw += x; dx = -x; x = 0; } if (y < 0) { dh += y; dy = -y; y = 0; } if ((x + w) > _width ) dw = _width - x; if ((y + h) > _height) dh = _height - y; if (dw < 1 || dh < 1) return; spi_begin(); data += dx + dy * w; int32_t xe = x + dw - 1, ye = y + dh - 1; uint8_t blue[] = {0, 11, 21, 31}; // blue 2 to 5 bit colour lookup table _lastColor = -1; // Set to illegal value // Used to store last shifted colour uint16_t color565 = 0; while (dh--) { int32_t len = dw; uint8_t* ptr = data; //<<<<<<<<< changed to 8 bit int32_t px = x; boolean move = true; while (len--) { if (transp != *ptr) { if (move) { move = false; setAddrWindow(px, y, xe, ye); } uint32_t color = *ptr; // Shifts are slow so check if colour has changed first if (color != _lastColor) { // =====Green===== ===============Red============== color565 = (color & 0x1C)<<6 | (color & 0xC0)<<5 | (color & 0xE0)<<8 // =====Green===== =======Blue====== | (color & 0x1C)<<3 | blue[color & 0x03]; _lastColor = color; } SPI.write16(color565); } else move = true; px++; ptr++; } y++; data += w; } CS_H; spi_end(); } /*************************************************************************************** ** Function name: read rectangle (for SPI Interface II i.e. IM [3:0] = "1101") ** Description: Read RGB pixel colours from a defined area ***************************************************************************************/ // If w and h are 1, then 1 pixel is read, *data array size must be 3 bytes per pixel void TFT_eSPI::readRectRGB(int32_t x0, int32_t y0, int32_t w, int32_t h, uint8_t *data) { spi_begin(); readAddrWindow(x0, y0, x0 + w - 1, y0 + h - 1); // Sets CS low // Dummy read to throw away don't care value SPI.transfer(0); // Read window pixel 24 bit RGB values, buffer must be set in sketch to 3 * w * h uint32_t len = w * h; while (len--) { // Read the 3 RGB bytes, colour is actually only in the top 6 bits of each byte // as the TFT stores colours as 18 bits *data++ = SPI.transfer(0); *data++ = SPI.transfer(0); *data++ = SPI.transfer(0); } CS_H; spi_end(); } /*************************************************************************************** ** Function name: drawCircle ** Description: Draw a circle outline ***************************************************************************************/ /* // Midpoint circle algorithm, we can optimise this since y = 0 on first pass // and we can eliminate the multiply as well void TFT_eSPI::drawCircle(int32_t x0, int32_t y0, int32_t radius, uint32_t color) { int32_t x = radius; int32_t y = 0; int32_t err = 0; while (x >= y) { drawPixel(x0 + x, y0 + y, color); drawPixel(x0 + x, y0 - y, color); drawPixel(x0 - x, y0 - y, color); drawPixel(x0 - x, y0 + y, color); drawPixel(x0 + y, y0 + x, color); drawPixel(x0 + y, y0 - x, color); drawPixel(x0 - y, y0 - x, color); drawPixel(x0 - y, y0 + x, color); if (err <= 0) { y += 1; err += 2*y + 1; } if (err > 0) { x -= 1; err -= 2*x + 1; } } } */ // Optimised midpoint circle algorithm void TFT_eSPI::drawCircle(int32_t x0, int32_t y0, int32_t r, uint32_t color) { int32_t x = 0; int32_t dx = 1; int32_t dy = r+r; int32_t p = -(r>>1); spi_begin(); inTransaction = true; // These are ordered to minimise coordinate changes in x or y // drawPixel can then send fewer bounding box commands drawPixel(x0 + r, y0, color); drawPixel(x0 - r, y0, color); drawPixel(x0, y0 - r, color); drawPixel(x0, y0 + r, color); while(x=0) { dy-=2; p-=dy; r--; } dx+=2; p+=dx; x++; // These are ordered to minimise coordinate changes in x or y // drawPixel can then send fewer bounding box commands drawPixel(x0 + x, y0 + r, color); drawPixel(x0 - x, y0 + r, color); drawPixel(x0 - x, y0 - r, color); drawPixel(x0 + x, y0 - r, color); drawPixel(x0 + r, y0 + x, color); drawPixel(x0 - r, y0 + x, color); drawPixel(x0 - r, y0 - x, color); drawPixel(x0 + r, y0 - x, color); } inTransaction = false; spi_end(); } /*************************************************************************************** ** Function name: drawCircleHelper ** Description: Support function for circle drawing ***************************************************************************************/ void TFT_eSPI::drawCircleHelper( int32_t x0, int32_t y0, int32_t r, uint8_t cornername, uint32_t color) { int32_t f = 1 - r; int32_t ddF_x = 1; int32_t ddF_y = -2 * r; int32_t x = 0; while (x < r) { if (f >= 0) { r--; ddF_y += 2; f += ddF_y; } x++; ddF_x += 2; f += ddF_x; if (cornername & 0x4) { drawPixel(x0 + x, y0 + r, color); drawPixel(x0 + r, y0 + x, color); } if (cornername & 0x2) { drawPixel(x0 + x, y0 - r, color); drawPixel(x0 + r, y0 - x, color); } if (cornername & 0x8) { drawPixel(x0 - r, y0 + x, color); drawPixel(x0 - x, y0 + r, color); } if (cornername & 0x1) { drawPixel(x0 - r, y0 - x, color); drawPixel(x0 - x, y0 - r, color); } } } /*************************************************************************************** ** Function name: fillCircle ** Description: draw a filled circle ***************************************************************************************/ /* void TFT_eSPI::fillCircle(int32_t x0, int32_t y0, int32_t r, uint32_t color) { drawFastVLine(x0, y0 - r, r + r + 1, color); fillCircleHelper(x0, y0, r, 3, 0, color); } */ // Optimised midpoint circle algorithm void TFT_eSPI::fillCircle(int32_t x0, int32_t y0, int32_t r, uint32_t color) { int32_t x = 0; int32_t dx = 1; int32_t dy = r+r; int32_t p = -(r>>1); spi_begin(); inTransaction = true; drawFastVLine(x0, y0 - r, dy+1, color); while(x=0) { dy-=2; p-=dy; r--; } dx+=2; p+=dx; x++; drawFastVLine(x0 + x, y0 - r, 2 * r+1, color); drawFastVLine(x0 - x, y0 - r, 2 * r+1, color); drawFastVLine(x0 + r, y0 - x, 2 * x+1, color); drawFastVLine(x0 - r, y0 - x, 2 * x+1, color); } inTransaction = false; spi_end(); } /* // Another algorithm, this one tends to produce less pretty circles with odd edge pixels void TFT_eSPI::fillCircle(int32_t x, int32_t y, int32_t r, uint32_t color) { for (int y1 = -r; y1 <= 0; y1++) for (int x1 = -r; x1 <= 0; x1++) if (x1 * x1 + y1 * y1 <= r * r) { drawFastHLine(x + x1, y + y1, 2 * (-x1), color); drawFastHLine(x + x1, y - y1, 2 * (-x1), color); break; } } */ /*************************************************************************************** ** Function name: fillCircleHelper ** Description: Support function for filled circle drawing ***************************************************************************************/ // Used to support drawing roundrects void TFT_eSPI::fillCircleHelper(int32_t x0, int32_t y0, int32_t r, uint8_t cornername, int32_t delta, uint32_t color) { int32_t f = 1 - r; int32_t ddF_x = 1; int32_t ddF_y = -r - r; int32_t x = 0; delta++; while (x < r) { if (f >= 0) { r--; ddF_y += 2; f += ddF_y; } x++; ddF_x += 2; f += ddF_x; if (cornername & 0x1) { drawFastVLine(x0 + x, y0 - r, r + r + delta, color); drawFastVLine(x0 + r, y0 - x, x + x + delta, color); } if (cornername & 0x2) { drawFastVLine(x0 - x, y0 - r, r + r + delta, color); drawFastVLine(x0 - r, y0 - x, x + x + delta, color); } } } /*************************************************************************************** ** Function name: drawEllipse ** Description: Draw a ellipse outline ***************************************************************************************/ void TFT_eSPI::drawEllipse(int16_t x0, int16_t y0, int16_t rx, int16_t ry, uint16_t color) { if (rx<2) return; if (ry<2) return; int32_t x, y; int32_t rx2 = rx * rx; int32_t ry2 = ry * ry; int32_t fx2 = 4 * rx2; int32_t fy2 = 4 * ry2; int32_t s; spi_begin(); inTransaction = true; for (x = 0, y = ry, s = 2*ry2+rx2*(1-2*ry); ry2*x <= rx2*y; x++) { // These are ordered to minimise coordinate changes in x or y // drawPixel can then send fewer bounding box commands drawPixel(x0 + x, y0 + y, color); drawPixel(x0 - x, y0 + y, color); drawPixel(x0 - x, y0 - y, color); drawPixel(x0 + x, y0 - y, color); if (s >= 0) { s += fx2 * (1 - y); y--; } s += ry2 * ((4 * x) + 6); } for (x = rx, y = 0, s = 2*rx2+ry2*(1-2*rx); rx2*y <= ry2*x; y++) { // These are ordered to minimise coordinate changes in x or y // drawPixel can then send fewer bounding box commands drawPixel(x0 + x, y0 + y, color); drawPixel(x0 - x, y0 + y, color); drawPixel(x0 - x, y0 - y, color); drawPixel(x0 + x, y0 - y, color); if (s >= 0) { s += fy2 * (1 - x); x--; } s += rx2 * ((4 * y) + 6); } inTransaction = false; spi_end(); } /*************************************************************************************** ** Function name: fillEllipse ** Description: draw a filled ellipse ***************************************************************************************/ void TFT_eSPI::fillEllipse(int16_t x0, int16_t y0, int16_t rx, int16_t ry, uint16_t color) { if (rx<2) return; if (ry<2) return; int32_t x, y; int32_t rx2 = rx * rx; int32_t ry2 = ry * ry; int32_t fx2 = 4 * rx2; int32_t fy2 = 4 * ry2; int32_t s; spi_begin(); inTransaction = true; for (x = 0, y = ry, s = 2*ry2+rx2*(1-2*ry); ry2*x <= rx2*y; x++) { drawFastHLine(x0 - x, y0 - y, x + x + 1, color); drawFastHLine(x0 - x, y0 + y, x + x + 1, color); if (s >= 0) { s += fx2 * (1 - y); y--; } s += ry2 * ((4 * x) + 6); } for (x = rx, y = 0, s = 2*rx2+ry2*(1-2*rx); rx2*y <= ry2*x; y++) { drawFastHLine(x0 - x, y0 - y, x + x + 1, color); drawFastHLine(x0 - x, y0 + y, x + x + 1, color); if (s >= 0) { s += fy2 * (1 - x); x--; } s += rx2 * ((4 * y) + 6); } inTransaction = false; spi_end(); } /*************************************************************************************** ** Function name: fillScreen ** Description: Clear the screen to defined colour ***************************************************************************************/ void TFT_eSPI::fillScreen(uint32_t color) { fillRect(0, 0, _width, _height, color); } /*************************************************************************************** ** Function name: drawRect ** Description: Draw a rectangle outline ***************************************************************************************/ // Draw a rectangle void TFT_eSPI::drawRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) { spi_begin(); inTransaction = true; drawFastHLine(x, y, w, color); drawFastHLine(x, y + h - 1, w, color); drawFastVLine(x, y, h, color); drawFastVLine(x + w - 1, y, h, color); inTransaction = false; spi_end(); } /*************************************************************************************** ** Function name: drawRoundRect ** Description: Draw a rounded corner rectangle outline ***************************************************************************************/ // Draw a rounded rectangle void TFT_eSPI::drawRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t r, uint32_t color) { spi_begin(); inTransaction = true; // smarter version drawFastHLine(x + r , y , w - r - r, color); // Top drawFastHLine(x + r , y + h - 1, w - r - r, color); // Bottom drawFastVLine(x , y + r , h - r - r, color); // Left drawFastVLine(x + w - 1, y + r , h - r - r, color); // Right // draw four corners drawCircleHelper(x + r , y + r , r, 1, color); drawCircleHelper(x + w - r - 1, y + r , r, 2, color); drawCircleHelper(x + w - r - 1, y + h - r - 1, r, 4, color); drawCircleHelper(x + r , y + h - r - 1, r, 8, color); inTransaction = false; spi_end(); } /*************************************************************************************** ** Function name: fillRoundRect ** Description: Draw a rounded corner filled rectangle ***************************************************************************************/ // Fill a rounded rectangle void TFT_eSPI::fillRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t r, uint32_t color) { spi_begin(); inTransaction = true; // smarter version fillRect(x + r, y, w - r - r, h, color); // draw four corners fillCircleHelper(x + w - r - 1, y + r, r, 1, h - r - r - 1, color); fillCircleHelper(x + r , y + r, r, 2, h - r - r - 1, color); inTransaction = false; spi_end(); } /*************************************************************************************** ** Function name: drawTriangle ** Description: Draw a triangle outline using 3 arbitrary points ***************************************************************************************/ // Draw a triangle void TFT_eSPI::drawTriangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t color) { spi_begin(); inTransaction = true; drawLine(x0, y0, x1, y1, color); drawLine(x1, y1, x2, y2, color); drawLine(x2, y2, x0, y0, color); inTransaction = false; spi_end(); } /*************************************************************************************** ** Function name: fillTriangle ** Description: Draw a filled triangle using 3 arbitrary points ***************************************************************************************/ // Fill a triangle - original Adafruit function works well and code footprint is small void TFT_eSPI::fillTriangle ( int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t color) { int32_t a, b, y, last; spi_begin(); inTransaction = true; // Sort coordinates by Y order (y2 >= y1 >= y0) if (y0 > y1) { swap_coord(y0, y1); swap_coord(x0, x1); } if (y1 > y2) { swap_coord(y2, y1); swap_coord(x2, x1); } if (y0 > y1) { swap_coord(y0, y1); swap_coord(x0, x1); } if (y0 == y2) { // Handle awkward all-on-same-line case as its own thing a = b = x0; if (x1 < a) a = x1; else if (x1 > b) b = x1; if (x2 < a) a = x2; else if (x2 > b) b = x2; drawFastHLine(a, y0, b - a + 1, color); return; } int32_t dx01 = x1 - x0, dy01 = y1 - y0, dx02 = x2 - x0, dy02 = y2 - y0, dx12 = x2 - x1, dy12 = y2 - y1, sa = 0, sb = 0; // For upper part of triangle, find scanline crossings for segments // 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1 // is included here (and second loop will be skipped, avoiding a /0 // error there), otherwise scanline y1 is skipped here and handled // in the second loop...which also avoids a /0 error here if y0=y1 // (flat-topped triangle). if (y1 == y2) last = y1; // Include y1 scanline else last = y1 - 1; // Skip it for (y = y0; y <= last; y++) { a = x0 + sa / dy01; b = x0 + sb / dy02; sa += dx01; sb += dx02; if (a > b) swap_coord(a, b); drawFastHLine(a, y, b - a + 1, color); } // For lower part of triangle, find scanline crossings for segments // 0-2 and 1-2. This loop is skipped if y1=y2. sa = dx12 * (y - y1); sb = dx02 * (y - y0); for (; y <= y2; y++) { a = x1 + sa / dy12; b = x0 + sb / dy02; sa += dx12; sb += dx02; if (a > b) swap_coord(a, b); drawFastHLine(a, y, b - a + 1, color); } inTransaction = false; spi_end(); } /*************************************************************************************** ** Function name: drawBitmap ** Description: Draw an image stored in an array on the TFT ***************************************************************************************/ void TFT_eSPI::drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color) { spi_begin(); inTransaction = true; int32_t i, j, byteWidth = (w + 7) / 8; for (j = 0; j < h; j++) { for (i = 0; i < w; i++ ) { if (pgm_read_byte(bitmap + j * byteWidth + i / 8) & (128 >> (i & 7))) { drawPixel(x + i, y + j, color); } } } inTransaction = false; spi_end(); } /*************************************************************************************** ** Function name: setCursor ** Description: Set the text cursor x,y position ***************************************************************************************/ void TFT_eSPI::setCursor(int16_t x, int16_t y) { cursor_x = x; cursor_y = y; } /*************************************************************************************** ** Function name: setCursor ** Description: Set the text cursor x,y position and font ***************************************************************************************/ void TFT_eSPI::setCursor(int16_t x, int16_t y, uint8_t font) { textfont = font; cursor_x = x; cursor_y = y; } /*************************************************************************************** ** Function name: setTextSize ** Description: Set the text size multiplier ***************************************************************************************/ void TFT_eSPI::setTextSize(uint8_t s) { if (s>7) s = 7; // Limit the maximum size multiplier so byte variables can be used for rendering textsize = (s > 0) ? s : 1; // Don't allow font size 0 } /*************************************************************************************** ** Function name: setTextColor ** Description: Set the font foreground colour (background is transparent) ***************************************************************************************/ void TFT_eSPI::setTextColor(uint16_t c) { // For 'transparent' background, we'll set the bg // to the same as fg instead of using a flag textcolor = textbgcolor = c; } /*************************************************************************************** ** Function name: setTextColor ** Description: Set the font foreground and background colour ***************************************************************************************/ void TFT_eSPI::setTextColor(uint16_t c, uint16_t b) { textcolor = c; textbgcolor = b; } /*************************************************************************************** ** Function name: setTextWrap ** Description: Define if text should wrap at end of line ***************************************************************************************/ void TFT_eSPI::setTextWrap(boolean w) { textwrap = w; } /*************************************************************************************** ** Function name: setTextDatum ** Description: Set the text position reference datum ***************************************************************************************/ void TFT_eSPI::setTextDatum(uint8_t d) { textdatum = d; } /*************************************************************************************** ** Function name: setTextPadding ** Description: Define padding width (aids erasing old text and numbers) ***************************************************************************************/ void TFT_eSPI::setTextPadding(uint16_t x_width) { padX = x_width; } /*************************************************************************************** ** Function name: getRotation ** Description: Return the rotation value (as used by setRotation()) ***************************************************************************************/ uint8_t TFT_eSPI::getRotation(void) { return rotation; } /*************************************************************************************** ** Function name: getTextDatum ** Description: Return the text datum value (as used by setTextDatum()) ***************************************************************************************/ uint8_t TFT_eSPI::getTextDatum(void) { return textdatum; } /*************************************************************************************** ** Function name: width ** Description: Return the pixel width of display (per current rotation) ***************************************************************************************/ // Return the size of the display (per current rotation) int16_t TFT_eSPI::width(void) { return _width; } /*************************************************************************************** ** Function name: height ** Description: Return the pixel height of display (per current rotation) ***************************************************************************************/ int16_t TFT_eSPI::height(void) { return _height; } /*************************************************************************************** ** Function name: textWidth ** Description: Return the width in pixels of a string in a given font ***************************************************************************************/ int16_t TFT_eSPI::textWidth(const String& string) { int16_t len = string.length() + 2; char buffer[len]; string.toCharArray(buffer, len); return textWidth(buffer, textfont); } int16_t TFT_eSPI::textWidth(const String& string, int font) { int16_t len = string.length() + 2; char buffer[len]; string.toCharArray(buffer, len); return textWidth(buffer, font); } int16_t TFT_eSPI::textWidth(const char *string) { return textWidth(string, textfont); } int16_t TFT_eSPI::textWidth(const char *string, int font) { unsigned int str_width = 0; unsigned char uniCode; char *widthtable; if (font>1 && font<9) { widthtable = (char *)pgm_read_dword( &(fontdata[font].widthtbl ) ) - 32; //subtract the 32 outside the loop while (*string) { uniCode = *(string++); if (uniCode > 31 && uniCode < 128) str_width += pgm_read_byte( widthtable + uniCode); // Normally we need to subract 32 from uniCode else str_width += pgm_read_byte( widthtable + 32); // Set illegal character = space width } } else { #ifdef LOAD_GFXFF if(gfxFont) // New font { while (*string) { uniCode = *(string++); if ((uniCode >= (uint8_t)pgm_read_byte(&gfxFont->first)) && (uniCode <= (uint8_t)pgm_read_byte(&gfxFont->last ))) { uniCode -= pgm_read_byte(&gfxFont->first); GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[uniCode]); // If this is not the last character then use xAdvance if (*string) str_width += pgm_read_byte(&glyph->xAdvance); // Else use the offset plus width since this can be bigger than xAdvance else str_width += ((int8_t)pgm_read_byte(&glyph->xOffset) + pgm_read_byte(&glyph->width)); } } } else #endif { #ifdef LOAD_GLCD while (*string++) str_width += 6; #endif } } return str_width * textsize; } /*************************************************************************************** ** Function name: fontsLoaded ** Description: return an encoded 16 bit value showing the fonts loaded ***************************************************************************************/ // Returns a value showing which fonts are loaded (bit N set = Font N loaded) uint16_t TFT_eSPI::fontsLoaded(void) { return fontsloaded; } /*************************************************************************************** ** Function name: fontHeight ** Description: return the height of a font (yAdvance for free fonts) ***************************************************************************************/ int16_t TFT_eSPI::fontHeight(int16_t font) { #ifdef LOAD_GFXFF if (font==1) { if(gfxFont) // New font { return pgm_read_byte(&gfxFont->yAdvance) * textsize; } } #endif return pgm_read_byte( &fontdata[font].height ) * textsize; } /*************************************************************************************** ** Function name: drawChar ** Description: draw a single character in the Adafruit GLCD font ***************************************************************************************/ void TFT_eSPI::drawChar(int32_t x, int32_t y, unsigned char c, uint32_t color, uint32_t bg, uint8_t size) { if ((x >= (int16_t)_width) || // Clip right (y >= (int16_t)_height) || // Clip bottom ((x + 6 * size - 1) < 0) || // Clip left ((y + 8 * size - 1) < 0)) // Clip top return; if (c < 32) return; #ifdef LOAD_GLCD //>>>>>>>>>>>>>>>>>> #ifdef LOAD_GFXFF if(!gfxFont) { // 'Classic' built-in font #endif //>>>>>>>>>>>>>>>>>> boolean fillbg = (bg != color); if ((size==1) && fillbg) { byte column[6]; byte mask = 0x1; spi_begin(); //inTransaction = true; setAddrWindow(x, y, x+5, y+8); for (int8_t i = 0; i < 5; i++ ) column[i] = pgm_read_byte(font + (c * 5) + i); column[5] = 0; #if defined (ESP8266) color = (color >> 8) | (color << 8); bg = (bg >> 8) | (bg << 8); uint32_t spimask = ~((SPIMMOSI << SPILMOSI) | (SPIMMISO << SPILMISO)); SPI1U1 = (SPI1U1 & spimask) | (15 << SPILMOSI) | (15 << SPILMISO); for (int8_t j = 0; j < 8; j++) { for (int8_t k = 0; k < 5; k++ ) { if (column[k] & mask) { SPI1W0 = color; } else { SPI1W0 = bg; } SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} } mask <<= 1; SPI1W0 = bg; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} } #else // for ESP32 for (int8_t j = 0; j < 8; j++) { for (int8_t k = 0; k < 5; k++ ) { if (column[k] & mask) { SPI.write16(color); } else { SPI.write16(bg); } } mask <<= 1; SPI.write16(bg); } #endif CS_H; //inTransaction = false; spi_end(); } else { spi_begin(); inTransaction = true; for (int8_t i = 0; i < 6; i++ ) { uint8_t line; if (i == 5) line = 0x0; else line = pgm_read_byte(font + (c * 5) + i); if (size == 1) // default size { for (int8_t j = 0; j < 8; j++) { if (line & 0x1) drawPixel(x + i, y + j, color); line >>= 1; } } else { // big size for (int8_t j = 0; j < 8; j++) { if (line & 0x1) fillRect(x + (i * size), y + (j * size), size, size, color); else if (fillbg) fillRect(x + i * size, y + j * size, size, size, bg); line >>= 1; } } } inTransaction = false; spi_end(); } //>>>>>>>>>>>>>>>>>>>>>>>>>>> #ifdef LOAD_GFXFF } else { // Custom font #endif //>>>>>>>>>>>>>>>>>>>>>>>>>>> #endif // LOAD_GLCD #ifdef LOAD_GFXFF // Filter out bad characters not present in font if ((c >= (uint8_t)pgm_read_byte(&gfxFont->first)) && (c <= (uint8_t)pgm_read_byte(&gfxFont->last ))) { spi_begin(); inTransaction = true; //>>>>>>>>>>>>>>>>>>>>>>>>>>> c -= pgm_read_byte(&gfxFont->first); GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c]); uint8_t *bitmap = (uint8_t *)pgm_read_dword(&gfxFont->bitmap); uint16_t bo = pgm_read_word(&glyph->bitmapOffset); uint8_t w = pgm_read_byte(&glyph->width), h = pgm_read_byte(&glyph->height), xa = pgm_read_byte(&glyph->xAdvance); int8_t xo = pgm_read_byte(&glyph->xOffset), yo = pgm_read_byte(&glyph->yOffset); uint8_t xx, yy, bits, bit=0; int16_t xo16 = 0, yo16 = 0; if(size > 1) { xo16 = xo; yo16 = yo; } // Here we have 3 versions of the same function just for evaluation purposes // Comment out the next two #defines to revert to the slower Adafruit implementation // If FAST_LINE is defined then the free fonts are rendered using horizontal lines // this makes rendering fonts 2-5 times faster. Particularly good for large fonts. // This is an elegant solution since it still uses generic functions present in the // stock library. // If FAST_SHIFT is defined then a slightly faster (at least for AVR processors) // shifting bit mask is used // Free fonts don't look good when the size multiplier is >1 so we could remove // code if this is not wanted and speed things up #define FAST_HLINE #define FAST_SHIFT //FIXED_SIZE is an option in User_Setup.h that only works with FAST_LINE enabled #ifdef FIXED_SIZE x+=xo; // Save 88 bytes of FLASH y+=yo; #endif #ifdef FAST_HLINE #ifdef FAST_SHIFT uint16_t hpc = 0; // Horizontal foreground pixel count for(yy=0; yy>= 1; } // Draw pixels for this line as we are about to increment yy if (hpc) { #ifndef FIXED_SIZE if(size == 1) drawFastHLine(x+xo+xx-hpc, y+yo+yy, hpc, color); else fillRect(x+(xo16+xx-hpc)*size, y+(yo16+yy)*size, size*hpc, size, color); #else drawFastHLine(x+xx-hpc, y+yy, hpc, color); #endif hpc=0; } } #else uint16_t hpc = 0; // Horizontal foreground pixel count for(yy=0; yy> 8) | (uint16_t)(xs << 8) | ((uint8_t)(xe >> 8)<<16 | (xe << 24)); SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} // Row addr set DC_C; SPI1U1 = mask | (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO); SPI1W0 = TFT_PASET; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} DC_D; SPI1U1 = mask | (31 << SPILMOSI) | (31 << SPILMISO); // Load the two coords as a 32 bit value and shift in one go SPI1W0 = (ys >> 8) | (uint16_t)(ys << 8) | ((uint8_t)(ye >> 8)<<16 | (ye << 24)); SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} // write to RAM DC_C; SPI1U1 = mask | (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO); SPI1W0 = TFT_RAMWR; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} DC_D; //spi_end(); } #elif defined (ESP8266) && !defined (RPI_WRITE_STROBE) && defined (RPI_ILI9486_DRIVER) // This is for the RPi display that needs 16 bits void TFT_eSPI::setAddrWindow(int32_t xs, int32_t ys, int32_t xe, int32_t ye) { //spi_begin(); addr_col = 0xFFFF; addr_row = 0xFFFF; // Column addr set DC_C; CS_L; uint32_t mask = ~((SPIMMOSI << SPILMOSI) | (SPIMMISO << SPILMISO)); mask = SPI1U1 & mask; SPI1U1 = mask | (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO); SPI1W0 = TFT_CASET<<8; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} DC_D; uint8_t xBin[] = { 0, (uint8_t) (xs>>8), 0, (uint8_t) (xs>>0), 0, (uint8_t) (xe>>8), 0, (uint8_t) (xe>>0), }; SPI.writePattern(&xBin[0], 8, 1); // Row addr set DC_C; SPI1U1 = mask | (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO); SPI1W0 = TFT_PASET<<8; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} DC_D; uint8_t yBin[] = { 0, (uint8_t) (ys>>8), 0, (uint8_t) (ys>>0), 0, (uint8_t) (ye>>8), 0, (uint8_t) (ye>>0), }; SPI.writePattern(&yBin[0], 8, 1); // write to RAM DC_C; SPI1U1 = mask | (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO); SPI1W0 = TFT_RAMWR<<8; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} DC_D; //spi_end(); } #else #if defined (ESP8266) && defined (RPI_ILI9486_DRIVER) // This is for the RPi display that needs 16 bits inline void TFT_eSPI::setAddrWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) { //spi_begin(); CS_L; uint32_t mask = ~((SPIMMOSI << SPILMOSI) | (SPIMMISO << SPILMISO)); mask = SPI1U1 & mask; SPI1U1 = mask | (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO); // Column addr set DC_C; SPI1W0 = TFT_CASET<<(CMD_BITS + 1 - 8); SPI1CMD |= SPIBUSY; addr_col = 0xFFFF; // Use the waiting time to do something useful addr_row = 0xFFFF; while(SPI1CMD & SPIBUSY) {} DC_D; SPI1W0 = x0 >> 0; SPI1CMD |= SPIBUSY; x0 = x0 << 8; // Use the waiting time to do something useful while(SPI1CMD & SPIBUSY) {} SPI1W0 = x0; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} SPI1W0 = x1 >> 0; SPI1CMD |= SPIBUSY; x1 = x1 << 8; // Use the waiting time to do something useful while(SPI1CMD & SPIBUSY) {} SPI1W0 = x1; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} // Row addr set DC_C; SPI1W0 = TFT_PASET<<(CMD_BITS + 1 - 8); SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} DC_D; SPI1W0 = y0 >> 0; SPI1CMD |= SPIBUSY; y0 = y0 << 8; // Use the waiting time to do something useful while(SPI1CMD & SPIBUSY) {} SPI1W0 = y0; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} SPI1W0 = y1 >> 0; SPI1CMD |= SPIBUSY; y1 = y1 << 8; // Use the waiting time to do something useful while(SPI1CMD & SPIBUSY) {} SPI1W0 = y1; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} // write to RAM DC_C; SPI1W0 = TFT_RAMWR<<(CMD_BITS + 1 - 8); SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} DC_D; //spi_end(); } #else // This is for the ESP32 inline void TFT_eSPI::setAddrWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) { //spi_begin(); addr_col = 0xFFFF; addr_row = 0xFFFF; #ifdef CGRAM_OFFSET x0+=colstart; x1+=colstart; y0+=rowstart; y1+=rowstart; #endif #if !defined (RPI_ILI9486_DRIVER) uint32_t xaw = ((uint32_t)x0 << 16) | x1; uint32_t yaw = ((uint32_t)y0 << 16) | y1; #endif // Column addr set DC_C; CS_L; #if defined (RPI_ILI9486_DRIVER) SPI.write16(TFT_CASET); #else SPI.write(TFT_CASET); #endif DC_D; #if defined (RPI_ILI9486_DRIVER) uint8_t xBin[] = { 0, (uint8_t) (x0>>8), 0, (uint8_t) (x0>>0), 0, (uint8_t) (x1>>8), 0, (uint8_t) (x1>>0), }; SPI.writePattern(&xBin[0], 8, 1); #else SPI.write32(xaw); #endif // Row addr set DC_C; #if defined (RPI_ILI9486_DRIVER) SPI.write16(TFT_PASET); #else SPI.write(TFT_PASET); #endif DC_D; #if defined (RPI_ILI9486_DRIVER) uint8_t yBin[] = { 0, (uint8_t) (y0>>8), 0, (uint8_t) (y0>>0), 0, (uint8_t) (y1>>8), 0, (uint8_t) (y1>>0), }; SPI.writePattern(&yBin[0], 8, 1); #else SPI.write32(yaw); #endif // write to RAM DC_C; #if defined (RPI_ILI9486_DRIVER) SPI.write16(TFT_RAMWR); #else SPI.write(TFT_RAMWR); #endif DC_D; //spi_end(); } #endif // end RPI_ILI9486_DRIVER check #endif // end ESP32 check /*************************************************************************************** ** Function name: readAddrWindow ** Description: define an area to read a stream of pixels ***************************************************************************************/ // Chip select stays low #if defined (ESP8266) && !defined (RPI_WRITE_STROBE) void TFT_eSPI::readAddrWindow(int32_t xs, int32_t ys, int32_t xe, int32_t ye) { //spi_begin(); addr_col = 0xFFFF; addr_row = 0xFFFF; #ifdef CGRAM_OFFSET xs+=colstart; xe+=colstart; ys+=rowstart; ye+=rowstart; #endif // Column addr set DC_C; CS_L; uint32_t mask = ~((SPIMMOSI << SPILMOSI) | (SPIMMISO << SPILMISO)); mask = SPI1U1 & mask; SPI1U1 = mask | (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO); SPI1W0 = TFT_CASET; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} DC_D; SPI1U1 = mask | (31 << SPILMOSI) | (31 << SPILMISO); // Load the two coords as a 32 bit value and shift in one go SPI1W0 = (xs >> 8) | (uint16_t)(xs << 8) | ((uint8_t)(xe >> 8)<<16 | (xe << 24)); SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} // Row addr set DC_C; SPI1U1 = mask | (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO); SPI1W0 = TFT_PASET; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} DC_D; SPI1U1 = mask | (31 << SPILMOSI) | (31 << SPILMISO); // Load the two coords as a 32 bit value and shift in one go SPI1W0 = (ys >> 8) | (uint16_t)(ys << 8) | ((uint8_t)(ye >> 8)<<16 | (ye << 24)); SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} // read from RAM DC_C; SPI1U1 = mask | (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO); SPI1W0 = TFT_RAMRD; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} DC_D; //spi_end(); } #else //ESP32 void TFT_eSPI::readAddrWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) { //spi_begin(); addr_col = 0xFFFF; addr_row = 0xFFFF; #ifdef CGRAM_OFFSET x0+=colstart; x1+=colstart; y0+=rowstart; y1+=rowstart; #endif uint32_t xaw = ((uint32_t)x0 << 16) | x1; uint32_t yaw = ((uint32_t)y0 << 16) | y1; // Column addr set DC_C; CS_L; SPI.write(TFT_CASET); DC_D; SPI.write32(xaw); // Row addr set DC_C; SPI.write(TFT_PASET); DC_D; SPI.write32(yaw); DC_C; SPI.transfer(TFT_RAMRD); // Read CGRAM command DC_D; //spi_end(); } #endif /*************************************************************************************** ** Function name: drawPixel ** Description: push a single pixel at an arbitrary position ***************************************************************************************/ #if defined (ESP8266) && !defined (RPI_WRITE_STROBE) void TFT_eSPI::drawPixel(uint32_t x, uint32_t y, uint32_t color) { // Faster range checking, possible because x and y are unsigned if ((x >= _width) || (y >= _height)) return; #ifdef CGRAM_OFFSET x+=colstart; y+=rowstart; #endif spi_begin(); CS_L; uint32_t mask = ~((SPIMMOSI << SPILMOSI) | (SPIMMISO << SPILMISO)); mask = SPI1U1 & mask; // No need to send x if it has not changed (speeds things up) if (addr_col != x) { DC_C; SPI1U1 = mask | (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO); SPI1W0 = TFT_CASET<<(CMD_BITS + 1 - 8); SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} DC_D; #if defined (RPI_ILI9486_DRIVER) // This is for the RPi display that needs 16 bits per byte uint8_t cBin[] = { 0, (uint8_t) (x>>8), 0, (uint8_t) (x>>0)}; SPI.writePattern(&cBin[0], 4, 2); #else SPI1U1 = mask | (31 << SPILMOSI) | (31 << SPILMISO); // Load the two coords as a 32 bit value and shift in one go uint32_t xswap = (x >> 8) | (uint16_t)(x << 8); SPI1W0 = xswap | (xswap << 16); SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} #endif addr_col = x; } // No need to send y if it has not changed (speeds things up) if (addr_row != y) { DC_C; SPI1U1 = mask | (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO); SPI1W0 = TFT_PASET<<(CMD_BITS + 1 - 8); SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} DC_D; #if defined (RPI_ILI9486_DRIVER) // This is for the RPi display that needs 16 bits per byte uint8_t cBin[] = { 0, (uint8_t) (y>>8), 0, (uint8_t) (y>>0)}; SPI.writePattern(&cBin[0], 4, 2); #else SPI1U1 = mask | (31 << SPILMOSI) | (31 << SPILMISO); // Load the two coords as a 32 bit value and shift in one go uint32_t yswap = (y >> 8) | (uint16_t)(y << 8); SPI1W0 = yswap | (yswap << 16); SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} #endif addr_row = y; } DC_C; SPI1U1 = mask | (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO); SPI1W0 = TFT_RAMWR<<(CMD_BITS + 1 - 8); SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} DC_D; SPI1U1 = mask | (15 << SPILMOSI) | (15 << SPILMISO); SPI1W0 = (color >> 8) | (color << 8); SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} CS_H; spi_end(); } #else #if defined (ESP8266) && defined (RPI_ILI9486_DRIVER) // This is for the RPi display that needs 16 bits void TFT_eSPI::drawPixel(uint32_t x, uint32_t y, uint32_t color) { // Faster range checking, possible because x and y are unsigned if ((x >= _width) || (y >= _height)) return; spi_begin(); CS_L; uint32_t mask = ~((SPIMMOSI << SPILMOSI) | (SPIMMISO << SPILMISO)); mask = SPI1U1 & mask; SPI1U1 = mask | (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO); // No need to send x if it has not changed (speeds things up) if (addr_col != x) { DC_C; SPI1W0 = TFT_CASET<<(CMD_BITS + 1 - 8); SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} DC_D; SPI1W0 = x >> 0; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} SPI1W0 = x << 8; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} SPI1W0 = x >> 0; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} SPI1W0 = x << 8; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} addr_col = x; } // No need to send y if it has not changed (speeds things up) if (addr_row != y) { DC_C; SPI1W0 = TFT_PASET<<(CMD_BITS + 1 - 8); SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} DC_D; SPI1W0 = y >> 0; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} SPI1W0 = y << 8; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} SPI1W0 = y >> 0; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} SPI1W0 = y << 8; SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} addr_row = y; } DC_C; SPI1W0 = TFT_RAMWR<<(CMD_BITS + 1 - 8); SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} DC_D; SPI1W0 = (color >> 8) | (color << 8); SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} CS_H; spi_end(); } #else // ESP32 void TFT_eSPI::drawPixel(uint32_t x, uint32_t y, uint32_t color) { // Faster range checking, possible because x and y are unsigned if ((x >= _width) || (y >= _height)) return; spi_begin(); #ifdef CGRAM_OFFSET x+=colstart; y+=rowstart; #endif #if !defined (RPI_ILI9486_DRIVER) uint32_t xaw = ((uint32_t)x << 16) | x; uint32_t yaw = ((uint32_t)y << 16) | y; #endif CS_L; // No need to send x if it has not changed (speeds things up) if (addr_col != x) { DC_C; #if defined (RPI_ILI9486_DRIVER) SPI.write16(TFT_CASET); #else SPI.write(TFT_CASET); #endif DC_D; #if defined (RPI_ILI9486_DRIVER) uint8_t xBin[] = { 0, (uint8_t) (x>>8), 0, (uint8_t) (x>>0), 0, (uint8_t) (x>>8), 0, (uint8_t) (x>>0), }; SPI.writePattern(&xBin[0], 8, 1); #else SPI.write32(xaw); #endif addr_col = x; } // No need to send y if it has not changed (speeds things up) if (addr_row != y) { DC_C; #if defined (RPI_ILI9486_DRIVER) SPI.write16(TFT_PASET); #else SPI.write(TFT_PASET); #endif DC_D; #if defined (RPI_ILI9486_DRIVER) uint8_t yBin[] = { 0, (uint8_t) (y>>8), 0, (uint8_t) (y>>0), 0, (uint8_t) (y>>8), 0, (uint8_t) (y>>0), }; SPI.writePattern(&yBin[0], 8, 1); #else SPI.write32(yaw); #endif addr_row = y; } DC_C; #if defined (RPI_ILI9486_DRIVER) SPI.write16(TFT_RAMWR); #else SPI.write(TFT_RAMWR); #endif DC_D; SPI.write16(color); CS_H; spi_end(); } #endif #endif /*************************************************************************************** ** Function name: pushColor ** Description: push a single pixel ***************************************************************************************/ void TFT_eSPI::pushColor(uint16_t color) { spi_begin(); CS_L; SPI.write16(color); CS_H; spi_end(); } /*************************************************************************************** ** Function name: pushColor ** Description: push a single colour to "len" pixels ***************************************************************************************/ void TFT_eSPI::pushColor(uint16_t color, uint16_t len) { spi_begin(); CS_L; #ifdef RPI_WRITE_STROBE uint8_t colorBin[] = { (uint8_t) (color >> 8), (uint8_t) color }; if(len) SPI.writePattern(&colorBin[0], 2, 1); len--; while(len--) {WR_L; WR_H;} #else spiWriteBlock(color, len); #endif CS_H; spi_end(); } /*************************************************************************************** ** Function name: pushColors ** Description: push an aray of pixels for BMP image drawing ***************************************************************************************/ // Sends an array of 16-bit color values to the TFT; used // externally by BMP examples. Assumes that setWindow() has // previously been called to define the bounds. Max 255 pixels at // a time (BMP examples read in small chunks due to limited RAM). void TFT_eSPI::pushColors(uint16_t *data, uint8_t len) { spi_begin(); CS_L; #if defined (ESP32) while (len--) SPI.write16(*(data++)); #else uint32_t mask = ~((SPIMMOSI << SPILMOSI) | (SPIMMISO << SPILMISO)); SPI1U1 = (SPI1U1 & mask) | (63 << SPILMOSI) | (63 << SPILMISO); while(len>3) { uint32_t color0 = (*(data) >> 8) | (uint16_t)(*(data) << 8); data++; color0 |= ((*(data) >> 8) | (*(data) << 8)) << 16; data++; uint32_t color1 = (*(data) >> 8) | (uint16_t)(*(data) << 8); data++; color1 |= ((*(data) >> 8) | (*(data) << 8)) << 16; data++; len -= 4; while(SPI1CMD & SPIBUSY) {} SPI1W0 = color0; SPI1W1 = color1; SPI1CMD |= SPIBUSY; } if (len) { while(SPI1CMD & SPIBUSY) {} SPI1U1 = (SPI1U1 & mask) | (15 << SPILMOSI) | (15 << SPILMISO); while(len--) { uint16_t color = (*(data) >> 8) | (*(data) << 8); data++; while(SPI1CMD & SPIBUSY) {} SPI1W0 = color; SPI1CMD |= SPIBUSY; } } while(SPI1CMD & SPIBUSY) {} #endif CS_H; spi_end(); } /*************************************************************************************** ** Function name: pushColors ** Description: push an aray of pixels for 16 bit raw image drawing ***************************************************************************************/ // Assumed that setWindow() has previously been called void TFT_eSPI::pushColors(uint8_t *data, uint32_t len) { spi_begin(); CS_L; #if defined (RPI_WRITE_STROBE) //while ( len ) {SPI.writePattern(data, 2, 1); data += 2; len -= 2; } while ( len >=64 ) {SPI.writePattern(data, 64, 1); data += 64; len -= 64; } if (len) SPI.writePattern(data, len, 1); #else #if (SPI_FREQUENCY == 80000000) while ( len >=64 ) {SPI.writePattern(data, 64, 1); data += 64; len -= 64; } if (len) SPI.writePattern(data, len, 1); #else SPI.writeBytes(data, len); #endif #endif CS_H; spi_end(); } /*************************************************************************************** ** Function name: drawLine ** Description: draw a line between 2 arbitrary points ***************************************************************************************/ // Bresenham's algorithm - thx wikipedia - speed enhanced by Bodmer to use // an eficient FastH/V Line draw routine for line segments of 2 pixels or more #if defined (RPI_ILI9486_DRIVER) || defined (ESP32) || defined (RPI_WRITE_STROBE) void TFT_eSPI::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color) { spi_begin(); inTransaction = true; boolean steep = abs(y1 - y0) > abs(x1 - x0); if (steep) { swap_coord(x0, y0); swap_coord(x1, y1); } if (x0 > x1) { swap_coord(x0, x1); swap_coord(y0, y1); } int32_t dx = x1 - x0, dy = abs(y1 - y0);; int32_t err = dx >> 1, ystep = -1, xs = x0, dlen = 0; if (y0 < y1) ystep = 1; // Split into steep and not steep for FastH/V separation if (steep) { for (; x0 <= x1; x0++) { dlen++; err -= dy; if (err < 0) { err += dx; if (dlen == 1) drawPixel(y0, xs, color); else drawFastVLine(y0, xs, dlen, color); dlen = 0; y0 += ystep; xs = x0 + 1; } } if (dlen) drawFastVLine(y0, xs, dlen, color); } else { for (; x0 <= x1; x0++) { dlen++; err -= dy; if (err < 0) { err += dx; if (dlen == 1) drawPixel(xs, y0, color); else drawFastHLine(xs, y0, dlen, color); dlen = 0; y0 += ystep; xs = x0 + 1; } } if (dlen) drawFastHLine(xs, y0, dlen, color); } inTransaction = false; spi_end(); } #else // This is a weeny bit faster void TFT_eSPI::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color) { spi_begin(); boolean steep = abs(y1 - y0) > abs(x1 - x0); if (steep) { swap_coord(x0, y0); swap_coord(x1, y1); } if (x0 > x1) { swap_coord(x0, x1); swap_coord(y0, y1); } if (x1 < 0) return; int16_t dx, dy; dx = x1 - x0; dy = abs(y1 - y0); int16_t err = dx / 2; int8_t ystep = (y0 < y1) ? 1 : (-1); uint32_t mask = ~((SPIMMOSI << SPILMOSI) | (SPIMMISO << SPILMISO)); mask = (SPI1U1 & mask) | (15 << SPILMOSI) | (15 << SPILMISO); SPI1U = SPIUMOSI | SPIUSSE; int16_t swapped_color = (color >> 8) | (color << 8); if (steep) // y increments every iteration (y0 is x-axis, and x0 is y-axis) { if (x1 >= _height) x1 = _height - 1; for (; x0 <= x1; x0++) { if ((x0 >= 0) && (y0 >= 0) && (y0 < _width)) break; err -= dy; if (err < 0) { err += dx; y0 += ystep; } } if (x0 > x1) return; setAddrWindow(y0, x0, y0, _height); SPI1U1 = mask; SPI1W0 = swapped_color; for (; x0 <= x1; x0++) { while(SPI1CMD & SPIBUSY) {} SPI1CMD |= SPIBUSY; err -= dy; if (err < 0) { y0 += ystep; if ((y0 < 0) || (y0 >= _width)) break; err += dx; while(SPI1CMD & SPIBUSY) {} setAddrWindow(y0, x0+1, y0, _height); SPI1U1 = mask; SPI1W0 = swapped_color; } } } else // x increments every iteration (x0 is x-axis, and y0 is y-axis) { if (x1 >= _width) x1 = _width - 1; for (; x0 <= x1; x0++) { if ((x0 >= 0) && (y0 >= 0) && (y0 < _height)) break; err -= dy; if (err < 0) { err += dx; y0 += ystep; } } if (x0 > x1) return; setAddrWindow(x0, y0, _width, y0); SPI1U1 = mask; SPI1W0 = swapped_color; for (; x0 <= x1; x0++) { while(SPI1CMD & SPIBUSY) {} SPI1CMD |= SPIBUSY; err -= dy; if (err < 0) { y0 += ystep; if ((y0 < 0) || (y0 >= _height)) break; err += dx; while(SPI1CMD & SPIBUSY) {} setAddrWindow(x0+1, y0, _width, y0); SPI1U1 = mask; SPI1W0 = swapped_color; } } } while(SPI1CMD & SPIBUSY) {} SPI1U = SPIUMOSI | SPIUDUPLEX | SPIUSSE; CS_H; spi_end(); } #endif /*************************************************************************************** ** Function name: drawFastVLine ** Description: draw a vertical line ***************************************************************************************/ #if defined (ESP8266) && !defined (RPI_WRITE_STROBE) void TFT_eSPI::drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color) { // Rudimentary clipping if ((x >= _width) || (y >= _height) || (h < 1)) return; if ((y + h - 1) >= _height) h = _height - y; spi_begin(); setAddrWindow(x, y, x, y + h - 1); spiWriteBlock(color, h); CS_H; spi_end(); } #else void TFT_eSPI::drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color) { // Rudimentary clipping if ((x >= _width) || (y >= _height) || (h < 1)) return; if ((y + h - 1) >= _height) h = _height - y; spi_begin(); setAddrWindow(x, y, x, y + h - 1); #ifdef RPI_WRITE_STROBE #if defined (ESP8266) SPI1W0 = (color >> 8) | (color << 8); SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} #else SPI.write16(color); #endif h--; while(h--) {WR_L; WR_H;} #else //while(h--) SPI.write16(color); spiWriteBlock(color, h); #endif CS_H; spi_end(); } #endif /*************************************************************************************** ** Function name: drawFastHLine ** Description: draw a horizontal line ***************************************************************************************/ #if defined (ESP8266) && !defined (RPI_WRITE_STROBE) void TFT_eSPI::drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color) { // Rudimentary clipping if ((x >= _width) || (y >= _height) || (w < 1)) return; if ((x + w - 1) >= _width) w = _width - x; spi_begin(); setAddrWindow(x, y, x + w - 1, y); spiWriteBlock(color, w); CS_H; spi_end(); } #else void TFT_eSPI::drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color) { // Rudimentary clipping if ((x >= _width) || (y >= _height) || (w < 1)) return; if ((x + w - 1) >= _width) w = _width - x; spi_begin(); setAddrWindow(x, y, x + w - 1, y); #ifdef RPI_WRITE_STROBE #if defined (ESP8266) SPI1W0 = (color >> 8) | (color << 8); SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} #else SPI.write16(color); #endif w--; while(w--) {WR_L; WR_H;} #else //while(w--) SPI.write16(color); spiWriteBlock(color, w); #endif CS_H; spi_end(); } #endif /*************************************************************************************** ** Function name: fillRect ** Description: draw a filled rectangle ***************************************************************************************/ #if defined (ESP8266) && !defined (RPI_WRITE_STROBE) void TFT_eSPI::fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) { // rudimentary clipping (drawChar w/big text requires this) if ((x > _width) || (y > _height) || (w < 1) || (h < 1)) return; if ((x + w - 1) > _width) w = _width - x; if ((y + h - 1) > _height) h = _height - y; spi_begin(); setAddrWindow(x, y, x + w - 1, y + h - 1); spiWriteBlock(color, w * h); CS_H; spi_end(); } #else void TFT_eSPI::fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) { // rudimentary clipping (drawChar w/big text requires this) if ((x > _width) || (y > _height) || (w < 1) || (h < 1)) return; if ((x + w - 1) > _width) w = _width - x; if ((y + h - 1) > _height) h = _height - y; spi_begin(); setAddrWindow(x, y, x + w - 1, y + h - 1); uint32_t n = (uint32_t)w * (uint32_t)h; #ifdef RPI_WRITE_STROBE if(n) {SPI.write16(color); n--;} while(n--) {WR_L; WR_H;} #else //while(n--) SPI.write16(color); spiWriteBlock(color, n); #endif CS_H; spi_end(); } #endif /*************************************************************************************** ** Function name: color565 ** Description: convert three 8 bit RGB levels to a 16 bit colour value ***************************************************************************************/ uint16_t TFT_eSPI::color565(uint8_t r, uint8_t g, uint8_t b) { return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); } /*************************************************************************************** ** Function name: invertDisplay ** Description: invert the display colours i = 1 invert, i = 0 normal ***************************************************************************************/ void TFT_eSPI::invertDisplay(boolean i) { spi_begin(); // Send the command twice as otherwise it does not always work! writecommand(i ? TFT_INVON : TFT_INVOFF); writecommand(i ? TFT_INVON : TFT_INVOFF); spi_end(); } /*************************************************************************************** ** Function name: write ** Description: draw characters piped through serial stream ***************************************************************************************/ size_t TFT_eSPI::write(uint8_t utf8) { if (utf8 == '\r') return 1; uint8_t uniCode = utf8; // Work with a copy if (utf8 == '\n') uniCode+=22; // Make it a valid space character to stop errors else if (utf8 < 32) return 0; uint16_t width = 0; uint16_t height = 0; //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv //Serial.print((uint8_t) uniCode); // Debug line sends all printed TFT text to serial port //Serial.println(uniCode, HEX); // Debug line sends all printed TFT text to serial port //delay(5); // Debug optional wait for serial port to flush through //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< #ifdef LOAD_GFXFF if(!gfxFont) { #endif //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< #ifdef LOAD_FONT2 if (textfont == 2) { if (utf8 > 127) return 0; // This is 20us faster than using the fontdata structure (0.443ms per character instead of 0.465ms) width = pgm_read_byte(widtbl_f16 + uniCode-32); height = chr_hgt_f16; // Font 2 is rendered in whole byte widths so we must allow for this width = (width + 6) / 8; // Width in whole bytes for font 2, should be + 7 but must allow for font width change width = width * 8; // Width converted back to pixles } #ifdef LOAD_RLE else #endif #endif #ifdef LOAD_RLE { if ((textfont>2) && (textfont<9)) { if (utf8 > 127) return 0; // Uses the fontinfo struct array to avoid lots of 'if' or 'switch' statements // A tad slower than above but this is not significant and is more convenient for the RLE fonts width = pgm_read_byte( (uint8_t *)pgm_read_dword( &(fontdata[textfont].widthtbl ) ) + uniCode-32 ); height= pgm_read_byte( &fontdata[textfont].height ); } } #endif #ifdef LOAD_GLCD if (textfont==1) { width = 6; height = 8; } #else if (textfont==1) return 0; #endif height = height * textsize; if (utf8 == '\n') { cursor_y += height; cursor_x = 0; } else { if (textwrap && (cursor_x + width * textsize > _width)) { cursor_y += height; cursor_x = 0; } if (cursor_y >= _height) cursor_y = 0; cursor_x += drawChar(uniCode, cursor_x, cursor_y, textfont); } //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< #ifdef LOAD_GFXFF } // Custom GFX font else { if(utf8 == '\n') { cursor_x = 0; cursor_y += (int16_t)textsize * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); } else { if (uniCode > (uint8_t)pgm_read_byte(&gfxFont->last )) return 0; if (uniCode < (uint8_t)pgm_read_byte(&gfxFont->first)) return 0; uint8_t c2 = uniCode - pgm_read_byte(&gfxFont->first); GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]); uint8_t w = pgm_read_byte(&glyph->width), h = pgm_read_byte(&glyph->height); if((w > 0) && (h > 0)) { // Is there an associated bitmap? int16_t xo = (int8_t)pgm_read_byte(&glyph->xOffset); if(textwrap && ((cursor_x + textsize * (xo + w)) > _width)) { // Drawing character would go off right edge; wrap to new line cursor_x = 0; cursor_y += (int16_t)textsize * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); } if (cursor_y >= _height) cursor_y = 0; drawChar(cursor_x, cursor_y, uniCode, textcolor, textbgcolor, textsize); } cursor_x += pgm_read_byte(&glyph->xAdvance) * (int16_t)textsize; } } #endif // LOAD_GFXFF //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< return 1; } /*************************************************************************************** ** Function name: drawChar ** Description: draw a unicode onto the screen ***************************************************************************************/ int16_t TFT_eSPI::drawChar(unsigned int uniCode, int x, int y) { return drawChar(uniCode, x, y, textfont); } int16_t TFT_eSPI::drawChar(unsigned int uniCode, int x, int y, int font) { if (font==1) { #ifdef LOAD_GLCD #ifndef LOAD_GFXFF drawChar(x, y, uniCode, textcolor, textbgcolor, textsize); return 6 * textsize; #endif #else #ifndef LOAD_GFXFF return 0; #endif #endif #ifdef LOAD_GFXFF drawChar(x, y, uniCode, textcolor, textbgcolor, textsize); if(!gfxFont) { // 'Classic' built-in font #ifdef LOAD_GLCD return 6 * textsize; #else return 0; #endif } else { if((uniCode >= pgm_read_byte(&gfxFont->first)) && (uniCode <= pgm_read_byte(&gfxFont->last) )) { uint8_t c2 = uniCode - pgm_read_byte(&gfxFont->first); GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]); return pgm_read_byte(&glyph->xAdvance) * textsize; } else { return 0; } } #endif } if ((font>1) && (font<9) && ((uniCode < 32) || (uniCode > 127))) return 0; int width = 0; int height = 0; uint32_t flash_address = 0; uniCode -= 32; #ifdef LOAD_FONT2 if (font == 2) { // This is faster than using the fontdata structure flash_address = pgm_read_dword(&chrtbl_f16[uniCode]); width = pgm_read_byte(widtbl_f16 + uniCode); height = chr_hgt_f16; } #ifdef LOAD_RLE else #endif #endif #ifdef LOAD_RLE { if ((font>2) && (font<9)) { // This is slower than above but is more convenient for the RLE fonts flash_address = pgm_read_dword( pgm_read_dword( &(fontdata[font].chartbl ) ) + uniCode*sizeof(void *) ); width = pgm_read_byte( (uint8_t *)pgm_read_dword( &(fontdata[font].widthtbl ) ) + uniCode ); height= pgm_read_byte( &fontdata[font].height ); } } #endif int w = width; int pX = 0; int pY = y; byte line = 0; #ifdef LOAD_FONT2 // chop out code if we do not need it if (font == 2) { w = w + 6; // Should be + 7 but we need to compensate for width increment w = w / 8; if (x + width * textsize >= (int16_t)_width) return width * textsize ; if (textcolor == textbgcolor || textsize != 1) { for (int i = 0; i < height; i++) { if (textcolor != textbgcolor) fillRect(x, pY, width * textsize, textsize, textbgcolor); for (int k = 0; k < w; k++) { line = pgm_read_byte((uint8_t *)flash_address + w * i + k); if (line) { if (textsize == 1) { pX = x + k * 8; if (line & 0x80) drawPixel(pX, pY, textcolor); if (line & 0x40) drawPixel(pX + 1, pY, textcolor); if (line & 0x20) drawPixel(pX + 2, pY, textcolor); if (line & 0x10) drawPixel(pX + 3, pY, textcolor); if (line & 0x08) drawPixel(pX + 4, pY, textcolor); if (line & 0x04) drawPixel(pX + 5, pY, textcolor); if (line & 0x02) drawPixel(pX + 6, pY, textcolor); if (line & 0x01) drawPixel(pX + 7, pY, textcolor); } else { pX = x + k * 8 * textsize; if (line & 0x80) fillRect(pX, pY, textsize, textsize, textcolor); if (line & 0x40) fillRect(pX + textsize, pY, textsize, textsize, textcolor); if (line & 0x20) fillRect(pX + 2 * textsize, pY, textsize, textsize, textcolor); if (line & 0x10) fillRect(pX + 3 * textsize, pY, textsize, textsize, textcolor); if (line & 0x08) fillRect(pX + 4 * textsize, pY, textsize, textsize, textcolor); if (line & 0x04) fillRect(pX + 5 * textsize, pY, textsize, textsize, textcolor); if (line & 0x02) fillRect(pX + 6 * textsize, pY, textsize, textsize, textcolor); if (line & 0x01) fillRect(pX + 7 * textsize, pY, textsize, textsize, textcolor); } } } pY += textsize; } } else // Faster drawing of characters and background using block write { spi_begin(); setAddrWindow(x, y, (x + w * 8) - 1, y + height - 1); byte mask; for (int i = 0; i < height; i++) { for (int k = 0; k < w; k++) { line = pgm_read_byte((uint8_t *)flash_address + w * i + k); pX = x + k * 8; mask = 0x80; while (mask) { if (line & mask) { SPI.write16(textcolor); } else { SPI.write16(textbgcolor); } mask = mask >> 1; } } pY += textsize; } CS_H; spi_end(); } } #ifdef LOAD_RLE else #endif #endif //FONT2 #ifdef LOAD_RLE //674 bytes of code // Font is not 2 and hence is RLE encoded { spi_begin(); w *= height; // Now w is total number of pixels in the character if ((textsize != 1) || (textcolor == textbgcolor)) { if (textcolor != textbgcolor) fillRect(x, pY, width * textsize, textsize * height, textbgcolor); int px = 0, py = pY; // To hold character block start and end column and row values int pc = 0; // Pixel count byte np = textsize * textsize; // Number of pixels in a drawn pixel byte tnp = 0; // Temporary copy of np for while loop byte ts = textsize - 1; // Temporary copy of textsize // 16 bit pixel count so maximum font size is equivalent to 180x180 pixels in area // w is total number of pixels to plot to fill character block while (pc < w) { line = pgm_read_byte((uint8_t *)flash_address); flash_address++; // 20 bytes smaller by incrementing here if (line & 0x80) { line &= 0x7F; line++; if (ts) { px = x + textsize * (pc % width); // Keep these px and py calculations outside the loop as they are slow py = y + textsize * (pc / width); } else { px = x + pc % width; // Keep these px and py calculations outside the loop as they are slow py = y + pc / width; } while (line--) { // In this case the while(line--) is faster pc++; // This is faster than putting pc+=line before while()? setAddrWindow(px, py, px + ts, py + ts); if (ts) { tnp = np; while (tnp--) { SPI.write16(textcolor); } } else { SPI.write16(textcolor); } px += textsize; if (px >= (x + width * textsize)) { px = x; py += textsize; } } } else { line++; pc += line; } } CS_H; spi_end(); } else // Text colour != background && textsize = 1 // so use faster drawing of characters and background using block write { //spi_begin(); setAddrWindow(x, y, x + width - 1, y + height - 1); uint8_t textcolorBin[] = { (uint8_t) (textcolor >> 8), (uint8_t) textcolor }; uint8_t textbgcolorBin[] = { (uint8_t) (textbgcolor >> 8), (uint8_t) textbgcolor }; // Maximum font size is equivalent to 180x180 pixels in area while (w > 0) { line = pgm_read_byte((uint8_t *)flash_address++); // 8 bytes smaller when incrementing here if (line & 0x80) { line &= 0x7F; line++; w -= line; #ifdef RPI_WRITE_STROBE SPI.writePattern(&textcolorBin[0], 2, 1); line--; while(line--) {WR_L; WR_H;} #else spiWriteBlock(textcolor,line); #endif } else { line++; w -= line; #ifdef RPI_WRITE_STROBE SPI.writePattern(&textbgcolorBin[0], 2, 1); line--; while(line--) {WR_L; WR_H;} #else spiWriteBlock(textbgcolor,line); #endif } } CS_H; spi_end(); } } // End of RLE font rendering #endif return width * textsize; // x + } /*************************************************************************************** ** Function name: drawString (with or without user defined font) ** Description : draw string with padding if it is defined ***************************************************************************************/ // Without font number, uses font set by setTextFont() int16_t TFT_eSPI::drawString(const String& string, int poX, int poY) { int16_t len = string.length() + 2; char buffer[len]; string.toCharArray(buffer, len); return drawString(buffer, poX, poY, textfont); } // With font number int16_t TFT_eSPI::drawString(const String& string, int poX, int poY, int font) { int16_t len = string.length() + 2; char buffer[len]; string.toCharArray(buffer, len); return drawString(buffer, poX, poY, font); } // Without font number, uses font set by setTextFont() int16_t TFT_eSPI::drawString(const char *string, int poX, int poY) { return drawString(string, poX, poY, textfont); } // With font number int16_t TFT_eSPI::drawString(const char *string, int poX, int poY, int font) { int16_t sumX = 0; uint8_t padding = 1, baseline = 0; uint16_t cwidth = textWidth(string, font); // Find the pixel width of the string in the font uint16_t cheight = 8 * textsize; #ifdef LOAD_GFXFF if (font == 1) { if(gfxFont) { cheight = glyph_ab * textsize; poY += cheight; // Adjust for baseline datum of free fonts baseline = cheight; padding =101; // Different padding method used for Free Fonts // We need to make an adjustment for the botom of the string (eg 'y' character) if ((textdatum == BL_DATUM) || (textdatum == BC_DATUM) || (textdatum == BR_DATUM)) { cheight += glyph_bb * textsize; } } } #endif if (textdatum || padX) { // If it is not font 1 (GLCD or free font) get the basline and pixel height of the font if (font!=1) { baseline = pgm_read_byte( &fontdata[font].baseline ) * textsize; cheight = fontHeight(font); } switch(textdatum) { case TC_DATUM: poX -= cwidth/2; padding += 1; break; case TR_DATUM: poX -= cwidth; padding += 2; break; case ML_DATUM: poY -= cheight/2; //padding += 0; break; case MC_DATUM: poX -= cwidth/2; poY -= cheight/2; padding += 1; break; case MR_DATUM: poX -= cwidth; poY -= cheight/2; padding += 2; break; case BL_DATUM: poY -= cheight; //padding += 0; break; case BC_DATUM: poX -= cwidth/2; poY -= cheight; padding += 1; break; case BR_DATUM: poX -= cwidth; poY -= cheight; padding += 2; break; case L_BASELINE: poY -= baseline; //padding += 0; break; case C_BASELINE: poX -= cwidth/2; poY -= baseline; padding += 1; break; case R_BASELINE: poX -= cwidth; poY -= baseline; padding += 2; break; } // Check coordinates are OK, adjust if not if (poX < 0) poX = 0; if (poX+cwidth>_width) poX = _width - cwidth; if (poY < 0) poY = 0; if (poY+cheight-baseline>_height) poY = _height - cheight; } int8_t xo = 0; #ifdef LOAD_GFXFF if ((font == 1) && (gfxFont) && (textcolor!=textbgcolor)) { cheight = (glyph_ab + glyph_bb) * textsize; // Get the offset for the first character only to allow for negative offsets uint8_t c2 = *string; if((c2 >= pgm_read_byte(&gfxFont->first)) && (c2 <= pgm_read_byte(&gfxFont->last) )) { c2 -= pgm_read_byte(&gfxFont->first); GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]); xo = pgm_read_byte(&glyph->xOffset) * textsize; // Adjust for negative xOffset if (xo > 0) xo = 0; else cwidth -= xo; // Add 1 pixel of padding all round //cheight +=2; //fillRect(poX+xo-1, poY - 1 - glyph_ab * textsize, cwidth+2, cheight, textbgcolor); fillRect(poX+xo, poY - glyph_ab * textsize, cwidth, cheight, textbgcolor); } padding -=100; } #endif while (*string) sumX += drawChar(*(string++), poX+sumX, poY, font); //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv // Switch on debugging for the padding areas //#define PADDING_DEBUG #ifndef PADDING_DEBUG //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ if((padX>cwidth) && (textcolor!=textbgcolor)) { int16_t padXc = poX+cwidth+xo; #ifdef LOAD_GFXFF if ((font == 1) && (gfxFont)) { poX +=xo; // Adjust for negative offset start character poY -= glyph_ab * textsize; } #endif switch(padding) { case 1: fillRect(padXc,poY,padX-cwidth,cheight, textbgcolor); break; case 2: fillRect(padXc,poY,(padX-cwidth)>>1,cheight, textbgcolor); padXc = (padX-cwidth)>>1; if (padXc>poX) padXc = poX; fillRect(poX - padXc,poY,(padX-cwidth)>>1,cheight, textbgcolor); break; case 3: if (padXc>padX) padXc = padX; fillRect(poX + cwidth - padXc,poY,padXc-cwidth,cheight, textbgcolor); break; } } #else //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv // This is debug code to show text (green box) and blanked (white box) areas // It shows that the padding areas are being correctly sized and positioned if((padX>sumX) && (textcolor!=textbgcolor)) { int16_t padXc = poX+sumX; // Maximum left side padding #ifdef LOAD_GFXFF if ((font == 1) && (gfxFont)) poY -= glyph_ab; #endif drawRect(poX,poY,sumX,cheight, TFT_GREEN); switch(padding) { case 1: drawRect(padXc,poY,padX-sumX,cheight, TFT_WHITE); break; case 2: drawRect(padXc,poY,(padX-sumX)>>1, cheight, TFT_WHITE); padXc = (padX-sumX)>>1; if (padXc>poX) padXc = poX; drawRect(poX - padXc,poY,(padX-sumX)>>1,cheight, TFT_WHITE); break; case 3: if (padXc>padX) padXc = padX; drawRect(poX + sumX - padXc,poY,padXc-sumX,cheight, TFT_WHITE); break; } } #endif //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ return sumX; } /*************************************************************************************** ** Function name: drawCentreString (deprecated, use setTextDatum()) ** Descriptions: draw string centred on dX ***************************************************************************************/ int16_t TFT_eSPI::drawCentreString(const String& string, int dX, int poY, int font) { int16_t len = string.length() + 2; char buffer[len]; string.toCharArray(buffer, len); return drawCentreString(buffer, dX, poY, font); } int16_t TFT_eSPI::drawCentreString(const char *string, int dX, int poY, int font) { static byte tempdatum = textdatum; int sumX = 0; textdatum = TC_DATUM; sumX = drawString(string, dX, poY, font); textdatum = tempdatum; return sumX; } /*************************************************************************************** ** Function name: drawRightString (deprecated, use setTextDatum()) ** Descriptions: draw string right justified to dX ***************************************************************************************/ int16_t TFT_eSPI::drawRightString(const String& string, int dX, int poY, int font) { int16_t len = string.length() + 2; char buffer[len]; string.toCharArray(buffer, len); return drawRightString(buffer, dX, poY, font); } int16_t TFT_eSPI::drawRightString(const char *string, int dX, int poY, int font) { static byte tempdatum = textdatum; int16_t sumX = 0; textdatum = TR_DATUM; sumX = drawString(string, dX, poY, font); textdatum = tempdatum; return sumX; } /*************************************************************************************** ** Function name: drawNumber ** Description: draw a long integer ***************************************************************************************/ int16_t TFT_eSPI::drawNumber(long long_num, int poX, int poY) { char str[12]; ltoa(long_num, str, 10); return drawString(str, poX, poY, textfont); } int16_t TFT_eSPI::drawNumber(long long_num, int poX, int poY, int font) { char str[12]; ltoa(long_num, str, 10); return drawString(str, poX, poY, font); } /*************************************************************************************** ** Function name: drawFloat ** Descriptions: drawFloat, prints 7 non zero digits maximum ***************************************************************************************/ // Assemble and print a string, this permits alignment relative to a datum // looks complicated but much more compact and actually faster than using print class int16_t TFT_eSPI::drawFloat(float floatNumber, int dp, int poX, int poY) { return drawFloat(floatNumber, dp, poX, poY, textfont); } int16_t TFT_eSPI::drawFloat(float floatNumber, int dp, int poX, int poY, int font) { char str[14]; // Array to contain decimal string uint8_t ptr = 0; // Initialise pointer for array int8_t digits = 1; // Count the digits to avoid array overflow float rounding = 0.5; // Round up down delta if (dp > 7) dp = 7; // Limit the size of decimal portion // Adjust the rounding value for (uint8_t i = 0; i < dp; ++i) rounding /= 10.0; if (floatNumber < -rounding) // add sign, avoid adding - sign to 0.0! { str[ptr++] = '-'; // Negative number str[ptr] = 0; // Put a null in the array as a precaution digits = 0; // Set digits to 0 to compensate so pointer value can be used later floatNumber = -floatNumber; // Make positive } floatNumber += rounding; // Round up or down // For error put ... in string and return (all TFT_eSPI library fonts contain . character) if (floatNumber >= 2147483647) { strcpy(str, "..."); return drawString(str, poX, poY, font); } // No chance of overflow from here on // Get integer part unsigned long temp = (unsigned long)floatNumber; // Put integer part into array ltoa(temp, str + ptr, 10); // Find out where the null is to get the digit count loaded while ((uint8_t)str[ptr] != 0) ptr++; // Move the pointer along digits += ptr; // Count the digits str[ptr++] = '.'; // Add decimal point str[ptr] = '0'; // Add a dummy zero str[ptr + 1] = 0; // Add a null but don't increment pointer so it can be overwritten // Get the decimal portion floatNumber = floatNumber - temp; // Get decimal digits one by one and put in array // Limit digit count so we don't get a false sense of resolution uint8_t i = 0; while ((i < dp) && (digits < 9)) // while (i < dp) for no limit but array size must be increased { i++; floatNumber *= 10; // for the next decimal temp = floatNumber; // get the decimal ltoa(temp, str + ptr, 10); ptr++; digits++; // Increment pointer and digits count floatNumber -= temp; // Remove that digit } // Finally we can plot the string and return pixel length return drawString(str, poX, poY, font); } /*************************************************************************************** ** Function name: setFreeFont ** Descriptions: Sets the GFX free font to use ***************************************************************************************/ #ifdef LOAD_GFXFF void TFT_eSPI::setFreeFont(const GFXfont *f) { textfont = 1; gfxFont = (GFXfont *)f; glyph_ab = 0; glyph_bb = 0; uint8_t numChars = pgm_read_byte(&gfxFont->last) - pgm_read_byte(&gfxFont->first); // Find the biggest above and below baseline offsets for (uint8_t c = 0; c < numChars; c++) { GFXglyph *glyph1 = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c]); int8_t ab = -pgm_read_byte(&glyph1->yOffset); if (ab > glyph_ab) glyph_ab = ab; int8_t bb = pgm_read_byte(&glyph1->height) - ab; if (bb > glyph_bb) glyph_bb = bb; } } /*************************************************************************************** ** Function name: setTextFont ** Description: Set the font for the print stream ***************************************************************************************/ void TFT_eSPI::setTextFont(uint8_t f) { textfont = (f > 0) ? f : 1; // Don't allow font 0 gfxFont = NULL; } #else /*************************************************************************************** ** Function name: setFreeFont ** Descriptions: Sets the GFX free font to use ***************************************************************************************/ // Alternative to setTextFont() so we don't need two different named functions void TFT_eSPI::setFreeFont(uint8_t font) { setTextFont(font); } /*************************************************************************************** ** Function name: setTextFont ** Description: Set the font for the print stream ***************************************************************************************/ void TFT_eSPI::setTextFont(uint8_t f) { textfont = (f > 0) ? f : 1; // Don't allow font 0 } #endif /*************************************************************************************** ** Function name: spiBlockWrite ** Description: Write a block of pixels of the same colour ***************************************************************************************/ #if defined (ESP8266) && (SPI_FREQUENCY != 80000000) void spiWriteBlock(uint16_t color, uint32_t repeat) { uint16_t color16 = (color >> 8) | (color << 8); uint32_t color32 = color16 | color16 << 16; uint32_t mask = ~(SPIMMOSI << SPILMOSI); mask = SPI1U1 & mask; SPI1U = SPIUMOSI | SPIUSSE; SPI1W0 = color32; SPI1W1 = color32; SPI1W2 = color32; SPI1W3 = color32; if (repeat > 8) { SPI1W4 = color32; SPI1W5 = color32; SPI1W6 = color32; SPI1W7 = color32; } if (repeat > 16) { SPI1W8 = color32; SPI1W9 = color32; SPI1W10 = color32; SPI1W11 = color32; } if (repeat > 24) { SPI1W12 = color32; SPI1W13 = color32; SPI1W14 = color32; SPI1W15 = color32; } if (repeat > 31) { SPI1U1 = mask | (511 << SPILMOSI); while(repeat>31) { while(SPI1CMD & SPIBUSY) {} SPI1CMD |= SPIBUSY; repeat -= 32; } while(SPI1CMD & SPIBUSY) {} } if (repeat) { repeat = (repeat << 4) - 1; SPI1U1 = mask | (repeat << SPILMOSI); SPI1CMD |= SPIBUSY; while(SPI1CMD & SPIBUSY) {} } SPI1U = SPIUMOSI | SPIUDUPLEX | SPIUSSE; } #elif ESP8266 // ESP32 or a ESP8266 running at 80MHz SPI so slow things down #define BUFFER_SIZE 64 void spiWriteBlock(uint16_t color, uint32_t repeat) { uint8_t colorBin[] = { (uint8_t) (color >> 8), (uint8_t) color}; SPI.writePattern(&colorBin[0], 2, repeat); } #else // Low level register based ESP32 code #include "soc/spi_reg.h" #define SPI_NUM 0x3 void spiWriteBlock(uint16_t color, uint32_t repeat) { uint16_t color16 = (color >> 8) | (color << 8); uint32_t color32 = color16 | color16 << 16; if (repeat > 15) { SET_PERI_REG_BITS(SPI_MOSI_DLEN_REG(SPI_NUM), SPI_USR_MOSI_DBITLEN, 255, SPI_USR_MOSI_DBITLEN_S); while(repeat>15) { while (READ_PERI_REG(SPI_CMD_REG(SPI_NUM))&SPI_USR); for (uint32_t i=0; i<16; i++) WRITE_PERI_REG((SPI_W0_REG(SPI_NUM) + (i << 2)), color32); SET_PERI_REG_MASK(SPI_CMD_REG(SPI_NUM), SPI_USR); repeat -= 16; } while (READ_PERI_REG(SPI_CMD_REG(SPI_NUM))&SPI_USR); } if (repeat) { repeat = (repeat << 4) - 1; SET_PERI_REG_BITS(SPI_MOSI_DLEN_REG(SPI_NUM), SPI_USR_MOSI_DBITLEN, repeat, SPI_USR_MOSI_DBITLEN_S); for (uint32_t i=0; i<16; i++) WRITE_PERI_REG((SPI_W0_REG(SPI_NUM) + (i << 2)), color32); SET_PERI_REG_MASK(SPI_CMD_REG(SPI_NUM), SPI_USR); while (READ_PERI_REG(SPI_CMD_REG(SPI_NUM))&SPI_USR); } } #endif // The following touch screen support code by maxpautsch was merged 1/10/17 // https://github.com/maxpautsch // Define TOUCH_CS is the user setup file to enable this code // A demo is provided in examples Generic folder // Additions by Bodmer to double sample and use Z value to improve detection reliability #ifdef TOUCH_CS /*************************************************************************************** ** Function name: getTouchRaw ** Description: read raw touch position. Return false if not pressed. ***************************************************************************************/ uint8_t TFT_eSPI::getTouchRaw(uint16_t *x, uint16_t *y){ uint16_t tmp; CS_H; spi_begin_touch(); T_CS_L; // Start bit + YP sample request for x position tmp = SPI.transfer(0xd0); tmp = SPI.transfer(0); tmp = tmp <<5; tmp |= 0x1f & (SPI.transfer(0)>>3); *x = tmp; // Start bit + XP sample request for y position SPI.transfer(0x90); tmp = SPI.transfer(0); tmp = tmp <<5; tmp |= 0x1f & (SPI.transfer(0)>>3); *y = tmp; T_CS_H; spi_end_touch(); return true; } /*************************************************************************************** ** Function name: getTouchRawZ ** Description: read raw pressure on touchpad and return Z value. ***************************************************************************************/ uint16_t TFT_eSPI::getTouchRawZ(void){ CS_H; spi_begin_touch(); T_CS_L; // Z sample request uint16_t tz = 0xFFF; SPI.transfer(0xb1); tz += SPI.transfer16(0xc1) >> 3; tz -= SPI.transfer16(0x91) >> 3; T_CS_H; spi_end_touch(); return tz; } /*************************************************************************************** ** Function name: validTouch ** Description: read validated position. Return false if not pressed. ***************************************************************************************/ #define _RAWERR 10 // Deadband in position samples uint8_t TFT_eSPI::validTouch(uint16_t *x, uint16_t *y, uint16_t threshold){ uint16_t x_tmp, y_tmp, x_tmp2, y_tmp2; // Wait until pressure stops increasing uint16_t z1 = 1; uint16_t z2 = 0; while (z1 > z2) { z2 = z1; z1 = getTouchRawZ(); delay(1); } // Serial.print("Z = ");Serial.println(z1); if (z1 <= threshold) return false; getTouchRaw(&x_tmp,&y_tmp); // Serial.print("Sample 1 x,y = "); Serial.print(x_tmp);Serial.print(",");Serial.print(y_tmp); // Serial.print(", Z = ");Serial.println(z1); delay(1); // Small delay to the next sample if (getTouchRawZ() <= threshold) return false; delay(2); // Small delay to the next sample getTouchRaw(&x_tmp2,&y_tmp2); // Serial.print("Sample 2 x,y = "); Serial.print(x_tmp2);Serial.print(",");Serial.println(y_tmp2); // Serial.print("Sample difference = ");Serial.print(abs(x_tmp - x_tmp2));Serial.print(",");Serial.println(abs(y_tmp - y_tmp2)); if (abs(x_tmp - x_tmp2) > _RAWERR) return false; if (abs(y_tmp - y_tmp2) > _RAWERR) return false; *x = x_tmp; *y = y_tmp; return true; } /*************************************************************************************** ** Function name: getTouch ** Description: read callibrated position. Return false if not pressed. ***************************************************************************************/ #define Z_THRESHOLD 350 // Touch pressure threshold for validating touches uint8_t TFT_eSPI::getTouch(uint16_t *x, uint16_t *y){ uint16_t x_tmp, y_tmp, xx, yy; if(!validTouch(&x_tmp, &y_tmp, Z_THRESHOLD)) return false; if(!touchCalibration_rotate){ xx=(x_tmp-touchCalibration_x0)*_width/touchCalibration_x1; yy=(y_tmp-touchCalibration_y0)*_height/touchCalibration_y1; if(touchCalibration_invert_x) xx = _width - xx; if(touchCalibration_invert_y) yy = _height - yy; } else { yy=(x_tmp-touchCalibration_x0)*_height/touchCalibration_x1; xx=(y_tmp-touchCalibration_y0)*_width/touchCalibration_y1; if(touchCalibration_invert_x) xx = _width - xx; if(touchCalibration_invert_y) yy = _height - yy; } if (xx >= _width || yy >= _height) return false; *x = xx; *y = yy; return true; } /*************************************************************************************** ** Function name: calibrateTouch ** Description: generates calibration parameters for touchscreen. ***************************************************************************************/ void TFT_eSPI::calibrateTouch(uint16_t *parameters, uint32_t color_fg, uint32_t color_bg, uint8_t size){ int16_t values[] = {0,0,0,0,0,0,0,0}; uint16_t x_tmp, y_tmp; for(uint8_t i = 0; i<4; i++){ fillRect(0, 0, size+1, size+1, color_bg); fillRect(0, _height-size-1, size+1, size+1, color_bg); fillRect(_width-size-1, 0, size+1, size+1, color_bg); fillRect(_width-size-1, _height-size-1, size+1, size+1, color_bg); if (i == 5) break; // used to clear the arrows switch (i) { case 0: // up left drawLine(0, 0, 0, size, color_fg); drawLine(0, 0, size, 0, color_fg); drawLine(0, 0, size , size, color_fg); break; case 1: // bot left drawLine(0, _height-size-1, 0, _height-1, color_fg); drawLine(0, _height-1, size, _height-1, color_fg); drawLine(size, _height-size-1, 0, _height-1 , color_fg); break; case 2: // up right drawLine(_width-size-1, 0, _width-1, 0, color_fg); drawLine(_width-size-1, size, _width-1, 0, color_fg); drawLine(_width-1, size, _width-1, 0, color_fg); break; case 3: // bot right drawLine(_width-size-1, _height-size-1, _width-1, _height-1, color_fg); drawLine(_width-1, _height-1-size, _width-1, _height-1, color_fg); drawLine(_width-1-size, _height-1, _width-1, _height-1, color_fg); break; } // user has to get the chance to release if(i>0) delay(1000); for(uint8_t j= 0; j<8; j++){ // Use a lower detect threshold as corners tend to be less sensitive while(!validTouch(&x_tmp, &y_tmp, Z_THRESHOLD/2)) delay(10); values[i*2 ] += x_tmp; values[i*2+1] += y_tmp; } values[i*2 ] /= 8; values[i*2+1] /= 8; } // check orientation // from case 0 to case 1, the y value changed. // If the meassured delta of the touch x axis is bigger than the delta of the y axis, the touch and TFT axes are switched. touchCalibration_rotate = false; if(abs(values[0]-values[2]) > abs(values[1]-values[3])){ touchCalibration_rotate = true; touchCalibration_x1 = (values[0] + values[4])/2; // calc min x touchCalibration_x0 = (values[2] + values[6])/2; // calc max x touchCalibration_y1 = (values[1] + values[3])/2; // calc min y touchCalibration_y0 = (values[5] + values[7])/2; // calc max y } else { touchCalibration_x0 = (values[0] + values[2])/2; // calc min x touchCalibration_x1 = (values[4] + values[6])/2; // calc max x touchCalibration_y0 = (values[1] + values[5])/2; // calc min y touchCalibration_y1 = (values[3] + values[7])/2; // calc max y } // in addition, the touch screen axis could be in the opposit direction of the TFT axis touchCalibration_invert_x = false; if(touchCalibration_x0 > touchCalibration_x1){ values[0]=touchCalibration_x0; touchCalibration_x0 = touchCalibration_x1; touchCalibration_x1 = values[0]; touchCalibration_invert_x = true; } touchCalibration_invert_y = false; if(touchCalibration_y0 > touchCalibration_y1){ values[0]=touchCalibration_y0; touchCalibration_y0 = touchCalibration_y1; touchCalibration_y1 = values[0]; touchCalibration_invert_y = true; } // pre calculate touchCalibration_x1 -= touchCalibration_x0; touchCalibration_y1 -= touchCalibration_y0; // export parameters, if pointer valid if(parameters != NULL){ parameters[0] = touchCalibration_x0; parameters[1] = touchCalibration_x1; parameters[2] = touchCalibration_y0; parameters[3] = touchCalibration_y1; parameters[4] = touchCalibration_rotate | (touchCalibration_invert_x <<1) | (touchCalibration_invert_y <<2); } } /*************************************************************************************** ** Function name: setTouch ** Description: imports calibration parameters for touchscreen. ***************************************************************************************/ void TFT_eSPI::setTouch(uint16_t *parameters){ touchCalibration_x0 = parameters[0]; touchCalibration_x1 = parameters[1]; touchCalibration_y0 = parameters[2]; touchCalibration_y1 = parameters[3]; touchCalibration_rotate = parameters[4] & 0x01; touchCalibration_invert_x = parameters[4] & 0x02; touchCalibration_invert_y = parameters[4] & 0x04; } #else // TOUCH CS is not defined so generate dummy functions that do nothing /*************************************************************************************** ** Function name: getTouchRaw ** Description: read raw touch position. Return false if not pressed. ***************************************************************************************/ uint8_t TFT_eSPI::getTouchRaw(uint16_t *x, uint16_t *y){ return true; } /*************************************************************************************** ** Function name: getTouchRawZ ** Description: read raw pressure on touchpad and return Z value. ***************************************************************************************/ uint16_t TFT_eSPI::getTouchRawZ(void){ return true; } /*************************************************************************************** ** Function name: validTouch ** Description: read validated position. Return false if not pressed. ***************************************************************************************/ uint8_t TFT_eSPI::validTouch(uint16_t *x, uint16_t *y, uint16_t threshold){ return true; } /*************************************************************************************** ** Function name: getTouch ** Description: read callibrated position. Return false if not pressed. ***************************************************************************************/ uint8_t TFT_eSPI::getTouch(uint16_t *x, uint16_t *y){ return true; } /*************************************************************************************** ** Function name: calibrateTouch ** Description: generates calibration parameters for touchscreen. ***************************************************************************************/ void TFT_eSPI::calibrateTouch(uint16_t *parameters, uint32_t color_bg, uint32_t color_fg, uint8_t size){ } /*************************************************************************************** ** Function name: setTouch ** Description: imports calibration parameters for touchscreen. ***************************************************************************************/ void TFT_eSPI::setTouch(uint16_t *parameters){ } #endif // TOUCH_CS /*************************************************************************************** ** Code for the GFX button UI element ** Grabbed from Adafruit_GFX library and enhanced to handle any label font ***************************************************************************************/ TFT_eSPI_Button::TFT_eSPI_Button(void) { _gfx = 0; } // Classic initButton() function: pass center & size void TFT_eSPI_Button::initButton( TFT_eSPI *gfx, int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t outline, uint16_t fill, uint16_t textcolor, char *label, uint8_t textsize) { // Tweak arguments and pass to the newer initButtonUL() function... initButtonUL(gfx, x - (w / 2), y - (h / 2), w, h, outline, fill, textcolor, label, textsize); } // Newer function instead accepts upper-left corner & size void TFT_eSPI_Button::initButtonUL( TFT_eSPI *gfx, int16_t x1, int16_t y1, uint16_t w, uint16_t h, uint16_t outline, uint16_t fill, uint16_t textcolor, char *label, uint8_t textsize) { _x1 = x1; _y1 = y1; _w = w; _h = h; _outlinecolor = outline; _fillcolor = fill; _textcolor = textcolor; _textsize = textsize; _gfx = gfx; strncpy(_label, label, 9); } void TFT_eSPI_Button::drawButton(boolean inverted) { uint16_t fill, outline, text; if(!inverted) { fill = _fillcolor; outline = _outlinecolor; text = _textcolor; } else { fill = _textcolor; outline = _outlinecolor; text = _fillcolor; } uint8_t r = min(_w, _h) / 4; // Corner radius _gfx->fillRoundRect(_x1, _y1, _w, _h, r, fill); _gfx->drawRoundRect(_x1, _y1, _w, _h, r, outline); _gfx->setTextColor(text); _gfx->setTextSize(_textsize); static byte tempdatum = _gfx->getTextDatum(); _gfx->setTextDatum(MC_DATUM); _gfx->drawString(_label, _x1 + (_w/2), _y1 + (_h/2)); _gfx->setTextDatum(tempdatum); } boolean TFT_eSPI_Button::contains(int16_t x, int16_t y) { return ((x >= _x1) && (x < (_x1 + _w)) && (y >= _y1) && (y < (_y1 + _h))); } void TFT_eSPI_Button::press(boolean p) { laststate = currstate; currstate = p; } boolean TFT_eSPI_Button::isPressed() { return currstate; } boolean TFT_eSPI_Button::justPressed() { return (currstate && !laststate); } boolean TFT_eSPI_Button::justReleased() { return (!currstate && laststate); } /*************************************************************************************** // The following class creates Sprites in RAM, graphics can then be drawn in the Sprite // and rendered quickly onto the TFT screen. The class inherits the graphics functions // from the TFT_eSPI class. Some functions are overridden by this class so that the // graphics are written to the Sprite rather than the TFT. // Coded by Bodmer ***************************************************************************************/ /*************************************************************************************** ** Function name: TFT_eSprite ** Description: Class constructor *************************************************************************************x*/ TFT_eSprite::TFT_eSprite(TFT_eSPI *tft) { _tft = tft; // Pointer to tft class so we can call member functions _iwidth = 0; // Initialise width and height to 0 (it does not exist yet) _iheight = 0; _bpp16 = true; _created = false; _xs = 0; // window bounds for pushColor _ys = 0; _xe = 0; _ye = 0; _xptr = 0; // pushColor coordinate _yptr = 0; _icursor_y = _icursor_x = 0; // Text cursor position } /*************************************************************************************** ** Function name: createSprite ** Description: Create a sprite (bitmap) of defined width and height *************************************************************************************x*/ void TFT_eSprite::createSprite(int16_t w, int16_t h) { if ( w < 1 || h < 1 || _created) return; _iwidth = w; _iheight = h; if(_bpp16) { _img = (uint16_t*) malloc(w * h * 2); if (_img) _created = true; } else { _img8 = ( uint8_t*) malloc(w * h); if (_img8) _created = true; } } /*************************************************************************************** ** Function name: setDepth ** Description: Set bits per pixel for colour (8 or 16) *************************************************************************************x*/ void TFT_eSprite::setColorDepth(int8_t b) { // Can't change an existing sprite's colour depth so delete it if (_created) { if (_bpp16) free(_img); else free(_img8); } // Now define the new colour depth if ( b > 8 ) _bpp16 = true; // Bytes per pixel else _bpp16 = false; // If it existed, re-create the sprite with the new colour depth if (_created) { _created = false; createSprite(_iwidth, _iheight); } } /*************************************************************************************** ** Function name: deleteSprite ** Description: Delete the sprite to free up memory (RAM) *************************************************************************************x*/ void TFT_eSprite::deleteSprite(void) { if (!_created ) return; if (_bpp16) free(_img); else free(_img8); _created = false; } /*************************************************************************************** ** Function name: pushSprite ** Description: Push the sprite to the TFT at x, y *************************************************************************************x*/ void TFT_eSprite::pushSprite(int32_t x, int32_t y) { if (!_created ) return; if (_bpp16) _tft->pushSprite(x, y, _iwidth, _iheight, _img ); else _tft->pushSprite(x, y, _iwidth, _iheight, _img8); } /*************************************************************************************** ** Function name: pushSprite ** Description: Push the sprite to the TFT at x, y with transparent colour *************************************************************************************x*/ void TFT_eSprite::pushSprite(int32_t x, int32_t y, uint16_t transp) { if (!_created ) return; if (_bpp16) _tft->pushSprite(x, y, _iwidth, _iheight, _img, transp ); else { transp = (uint8_t)((transp & 0xE000)>>8 | (transp & 0x0700)>>6 | (transp & 0x0018)>>3); _tft->pushSprite(x, y, _iwidth, _iheight, _img8, (uint8_t) transp); } } /*************************************************************************************** ** Function name: readPixel ** Description: Read 565 colour of a pixel at defined coordinates *************************************************************************************x*/ uint16_t TFT_eSprite::readPixel(int32_t x, int32_t y) { if (!_created ) return 0; if (_bpp16) { uint16_t color = _img[x + y * _iwidth]; return (color >> 8) | (color << 8); } uint16_t color = _img8[x + y * _iwidth]; if (color != 0) { uint8_t blue[] = {0, 11, 21, 31}; color = (color & 0xE0)<<8 | (color & 0xC0)<<5 | (color & 0x1C)<<6 | (color & 0x1C)<<3 | blue[color & 0x03]; } return color; } /*************************************************************************************** ** Function name: pushRect (same as pushBitmap) ** Description: push 565 colour bitmap into a defined area *************************************************************************************x*/ void TFT_eSprite::pushRect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint16_t *data) { if ((x > _iwidth) || (y > _iheight) || (w == 0) || (h == 0) || !_created) return; if (_bpp16) { for (uint32_t yp = y; yp < y + h; yp++) { for (uint32_t xp = x; xp < x + w; xp++) { _img[xp + yp * _iwidth] = *data++; } } } else { for (uint32_t yp = y; yp < y + h; yp++) { for (uint32_t xp = x; xp < x + w; xp++) { uint16_t color = *data++; _img8[xp + yp * _iwidth] = (uint8_t)((color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3); } } } } /*************************************************************************************** ** Function name: pushBitmap (same as pushRect) ** Description: push 565 colour bitmap into a defined area ***************************************************************************************/ void TFT_eSprite::pushBitmap(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint16_t *data) { pushRect(x, y, w, h, data); } /*************************************************************************************** ** Function name: setWindow ** Description: Set the bounds of a window for pushColor and writeColor *************************************************************************************x*/ void TFT_eSprite::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) { if (x0 > x1) swap_coord(x0, x1); if (y0 > y1) swap_coord(y0, y1); if (x0 < 0) x0 = 0; if (x0 >= _iwidth) x0 = _iwidth; if (x1 < 0) x1 = 0; if (x1 >= _iwidth) x1 = _iwidth; if (y0 < 0) y0 = 0; if (y0 >= _iheight) y0 = _iheight; if (y1 < 0) y1 = 0; if (y1 >= _iheight) y1 = _iheight; _xs = x0; _ys = y0; _xe = x1; _ye = y1; _xptr = _xs; _yptr = _ys; } /*************************************************************************************** ** Function name: pushColor ** Description: Send a new pixel to the set window *************************************************************************************x*/ void TFT_eSprite::pushColor(uint32_t color) { if (!_created ) return; // Write the colour to RAM in set window if (_bpp16) _img [_xptr + _yptr * _iwidth] = (uint16_t) (color >> 8) | (color << 8); else _img8[_xptr + _yptr * _iwidth] = (uint8_t )((color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3); // Increment x _xptr++; // Wrap on x and y to start, increment y if needed if (_xptr > _xe) { _xptr = _xs; _yptr++; if (_yptr > _ye) _yptr = _ys; } } /*************************************************************************************** ** Function name: pushColor ** Description: Send a "len" new pixels to the set window *************************************************************************************x*/ void TFT_eSprite::pushColor(uint32_t color, uint16_t len) { if (!_created ) return; uint16_t pixelColor; if (_bpp16) pixelColor = (uint16_t) (color >> 8) | (color << 8); else pixelColor = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3; while(len--) writeColor(pixelColor); } /*************************************************************************************** ** Function name: writeColor ** Description: Write a pixel with pre-formatted colour to the set window *************************************************************************************x*/ void TFT_eSprite::writeColor(uint16_t color) { if (!_created ) return; // Write 16 bit RGB 565 encoded colour to RAM if (_bpp16) _img [_xptr + _yptr * _iwidth] = color; // Write 8 bit RGB 332 encoded colour to RAM else _img8[_xptr + _yptr * _iwidth] = (uint8_t) color; // Increment x _xptr++; // Wrap on x and y to start, increment y if needed if (_xptr > _xe) { _xptr = _xs; _yptr++; if (_yptr > _ye) _yptr = _ys; } } /*************************************************************************************** ** Function name: fillSprite ** Description: Fill the whole sprite with defined colour *************************************************************************************x*/ void TFT_eSprite::fillSprite(uint32_t color) { if (!_created ) return; // Use memset if possible as it is super fast if(( (uint8_t)color == (uint8_t)(color>>8) ) && _bpp16) memset(_img, (uint8_t)color, _iwidth * _iheight * 2); else if (!_bpp16) { color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3; memset(_img8, (uint8_t)color, _iwidth * _iheight); } else fillRect(0, 0, _iwidth, _iheight, color); } /*************************************************************************************** ** Function name: setCursor ** Description: Set the sprite text cursor x,y position *************************************************************************************x*/ void TFT_eSprite::setCursor(int16_t x, int16_t y) { _icursor_x = x; _icursor_y = y; } /*************************************************************************************** ** Function name: width ** Description: Return the width of sprite *************************************************************************************x*/ // Return the size of the display int16_t TFT_eSprite::width(void) { if (!_created ) return 0; return _iwidth; } /*************************************************************************************** ** Function name: height ** Description: Return the height of sprite *************************************************************************************x*/ int16_t TFT_eSprite::height(void) { if (!_created ) return 0; return _iheight; } /*************************************************************************************** ** Function name: drawChar ** Description: draw a single character in the Adafruit GLCD or freefont *************************************************************************************x*/ void TFT_eSprite::drawChar(int32_t x, int32_t y, unsigned char c, uint32_t color, uint32_t bg, uint8_t size) { if (!_created ) return; if ((x >= _iwidth) || // Clip right (y >= _iheight) || // Clip bottom ((x + 6 * size - 1) < 0) || // Clip left ((y + 8 * size - 1) < 0)) // Clip top return; #ifdef LOAD_GLCD //>>>>>>>>>>>>>>>>>> #ifdef LOAD_GFXFF if(!gfxFont) { // 'Classic' built-in font #endif //>>>>>>>>>>>>>>>>>> boolean fillbg = (bg != color); if ((size==1) && fillbg) { byte column[6]; byte mask = 0x1; for (int8_t i = 0; i < 5; i++ ) column[i] = pgm_read_byte(font + (c * 5) + i); column[5] = 0; int8_t j, k; for (j = 0; j < 8; j++) { for (k = 0; k < 5; k++ ) { if (column[k] & mask) { drawPixel(x + k, y + j, color); } else { drawPixel(x + k, y + j, bg); } } mask <<= 1; drawPixel(x + k, y + j, bg); } } else { for (int8_t i = 0; i < 6; i++ ) { uint8_t line; if (i == 5) line = 0x0; else line = pgm_read_byte(font + (c * 5) + i); if (size == 1) // default size { for (int8_t j = 0; j < 8; j++) { if (line & 0x1) drawPixel(x + i, y + j, color); line >>= 1; } } else { // big size for (int8_t j = 0; j < 8; j++) { if (line & 0x1) fillRect(x + (i * size), y + (j * size), size, size, color); else if (fillbg) fillRect(x + i * size, y + j * size, size, size, bg); line >>= 1; } } } } //>>>>>>>>>>>>>>>>>>>>>>>>>>> #ifdef LOAD_GFXFF } else { // Custom font #endif //>>>>>>>>>>>>>>>>>>>>>>>>>>> #endif // LOAD_GLCD #ifdef LOAD_GFXFF // Filter out bad characters not present in font if ((c >= (uint8_t)pgm_read_byte(&gfxFont->first)) && (c <= (uint8_t)pgm_read_byte(&gfxFont->last ))) { //>>>>>>>>>>>>>>>>>>>>>>>>>>> c -= pgm_read_byte(&gfxFont->first); GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c]); uint8_t *bitmap = (uint8_t *)pgm_read_dword(&gfxFont->bitmap); uint16_t bo = pgm_read_word(&glyph->bitmapOffset); uint8_t w = pgm_read_byte(&glyph->width), h = pgm_read_byte(&glyph->height), xa = pgm_read_byte(&glyph->xAdvance); int8_t xo = pgm_read_byte(&glyph->xOffset), yo = pgm_read_byte(&glyph->yOffset); uint8_t xx, yy, bits, bit=0; int16_t xo16 = 0, yo16 = 0; if(size > 1) { xo16 = xo; yo16 = yo; } uint16_t hpc = 0; // Horizontal foreground pixel count for(yy=0; yy>= 1; } // Draw pixels for this line as we are about to increment yy if (hpc) { if(size == 1) drawFastHLine(x+xo+xx-hpc, y+yo+yy, hpc, color); else fillRect(x+(xo16+xx-hpc)*size, y+(yo16+yy)*size, size*hpc, size, color); hpc=0; } } } #endif #ifdef LOAD_GLCD #ifdef LOAD_GFXFF } // End classic vs custom font #endif #endif } /*************************************************************************************** ** Function name: drawPixel ** Description: push a single pixel at an arbitrary position *************************************************************************************x*/ void TFT_eSprite::drawPixel(uint32_t x, uint32_t y, uint32_t color) { // x and y are unsigned so that -ve coordinates turn into large positive ones // this make bounds checking a bit faster if ((x >= _iwidth) || (y >= _iheight) || !_created) return; if (_bpp16) { color = (color >> 8) | (color << 8); _img[x+y*_iwidth] = (uint16_t) color; } else { _img8[x+y*_iwidth] = (uint8_t)((color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3); } } /*************************************************************************************** ** Function name: drawLine ** Description: draw a line between 2 arbitrary points *************************************************************************************x*/ void TFT_eSprite::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color) { if (!_created ) return; boolean steep = abs(y1 - y0) > abs(x1 - x0); if (steep) { swap_coord(x0, y0); swap_coord(x1, y1); } if (x0 > x1) { swap_coord(x0, x1); swap_coord(y0, y1); } int32_t dx = x1 - x0, dy = abs(y1 - y0);; int32_t err = dx >> 1, ystep = -1, xs = x0, dlen = 0; if (y0 < y1) ystep = 1; // Split into steep and not steep for FastH/V separation if (steep) { for (; x0 <= x1; x0++) { dlen++; err -= dy; if (err < 0) { err += dx; if (dlen == 1) drawPixel(y0, xs, color); else drawFastVLine(y0, xs, dlen, color); dlen = 0; y0 += ystep; xs = x0 + 1; } } if (dlen) drawFastVLine(y0, xs, dlen, color); } else { for (; x0 <= x1; x0++) { dlen++; err -= dy; if (err < 0) { err += dx; if (dlen == 1) drawPixel(xs, y0, color); else drawFastHLine(xs, y0, dlen, color); dlen = 0; y0 += ystep; xs = x0 + 1; } } if (dlen) drawFastHLine(xs, y0, dlen, color); } } /*************************************************************************************** ** Function name: drawFastVLine ** Description: draw a vertical line *************************************************************************************x*/ void TFT_eSprite::drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color) { if ((x < 0) || (x >= _iwidth) || (y >= _iheight) || !_created) return; if (y < 0) { h += y; y = 0; } if ((y + h) > _iheight) h = _iheight - y; if (h < 1) return; if (_bpp16) { color = (color >> 8) | (color << 8); int32_t yp = x + _iwidth * y; while (h--) {_img[yp] = (uint16_t) color; yp += _iwidth;} } else { color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3; while (h--) _img8[x + _iwidth * y++] = (uint8_t) color; } } /*************************************************************************************** ** Function name: drawFastHLine ** Description: draw a horizontal line *************************************************************************************x*/ void TFT_eSprite::drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color) { if ((y < 0) || (x >= _iwidth) || (y >= _iheight) || !_created) return; if (x < 0) { w += x; x = 0; } if ((x + w) > _iwidth) w = _iwidth - x; if (w < 1) return; if (_bpp16) { color = (color >> 8) | (color << 8); while (w--) _img[_iwidth * y + x++] = (uint16_t) color; } else { color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3; memset(_img8+_iwidth * y + x, (uint8_t)color, w); } } /*************************************************************************************** ** Function name: fillRect ** Description: draw a filled rectangle *************************************************************************************x*/ void TFT_eSprite::fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) { if (!_created ) return; if (x < 0) { w += x; x = 0; } if ((x < 0) || (y < 0) || (x >= _iwidth) || (y >= _iheight)) return; if ((x + w) > _iwidth) w = _iwidth - x; if ((y + h) > _iheight) h = _iheight - y; if ((w < 1) || (h < 1)) return; int32_t yp = _iwidth * y; if (_bpp16) { color = (color >> 8) | (color << 8); while (h--) { uint32_t ix = x, iw = w; while (iw--) _img[yp + ix++] = (uint16_t) color; yp += _iwidth; } } else { color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3; while (h--) { memset(_img8 + yp + x, (uint8_t)color, w); yp += _iwidth; } } } /*************************************************************************************** ** Function name: write ** Description: draw characters piped through serial stream *************************************************************************************x*/ size_t TFT_eSprite::write(uint8_t utf8) { if (!_created ) return 0; if (utf8 == '\r') return 1; uint8_t uniCode = utf8; // Work with a copy if (utf8 == '\n') uniCode+=22; // Make it a valid space character to stop errors else if (utf8 < 32) return 0; uint16_t width = 0; uint16_t height = 0; //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv //Serial.print((uint8_t) uniCode); // Debug line sends all printed TFT text to serial port //Serial.println(uniCode, HEX); // Debug line sends all printed TFT text to serial port //delay(5); // Debug optional wait for serial port to flush through //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< #ifdef LOAD_GFXFF if(!gfxFont) { #endif //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< #ifdef LOAD_FONT2 if (textfont == 2) { if (utf8 > 127) return 0; // This is 20us faster than using the fontdata structure (0.443ms per character instead of 0.465ms) width = pgm_read_byte(widtbl_f16 + uniCode-32); height = chr_hgt_f16; // Font 2 is rendered in whole byte widths so we must allow for this width = (width + 6) / 8; // Width in whole bytes for font 2, should be + 7 but must allow for font width change width = width * 8; // Width converted back to pixles } #ifdef LOAD_RLE else #endif #endif #ifdef LOAD_RLE { if ((textfont>2) && (textfont<9)) { if (utf8 > 127) return 0; // Uses the fontinfo struct array to avoid lots of 'if' or 'switch' statements // A tad slower than above but this is not significant and is more convenient for the RLE fonts width = pgm_read_byte( (uint8_t *)pgm_read_dword( &(fontdata[textfont].widthtbl ) ) + uniCode-32 ); height= pgm_read_byte( &fontdata[textfont].height ); } } #endif #ifdef LOAD_GLCD if (textfont==1) { width = 6; height = 8; } #else if (textfont==1) return 0; #endif height = height * textsize; if (utf8 == '\n') { _icursor_y += height; _icursor_x = 0; } else { if (textwrap && (_icursor_x + width * textsize > _iwidth)) { _icursor_y += height; _icursor_x = 0; } if (_icursor_y >= _iheight) _icursor_y = 0; _icursor_x += drawChar(uniCode, _icursor_x, _icursor_y, textfont); } //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< #ifdef LOAD_GFXFF } // Custom GFX font else { if(utf8 == '\n') { _icursor_x = 0; _icursor_y += (int16_t)textsize * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); } else { if (uniCode > (uint8_t)pgm_read_byte(&gfxFont->last )) return 0; if (uniCode < (uint8_t)pgm_read_byte(&gfxFont->first)) return 0; uint8_t c2 = uniCode - pgm_read_byte(&gfxFont->first); GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]); uint8_t w = pgm_read_byte(&glyph->width), h = pgm_read_byte(&glyph->height); if((w > 0) && (h > 0)) { // Is there an associated bitmap? int16_t xo = (int8_t)pgm_read_byte(&glyph->xOffset); if(textwrap && ((_icursor_x + textsize * (xo + w)) > _iwidth)) { // Drawing character would go off right edge; wrap to new line _icursor_x = 0; _icursor_y += (int16_t)textsize * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); } if (_icursor_y >= _iheight) _icursor_y = 0; drawChar(_icursor_x, _icursor_y, uniCode, textcolor, textbgcolor, textsize); } _icursor_x += pgm_read_byte(&glyph->xAdvance) * (int16_t)textsize; } } #endif // LOAD_GFXFF //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< return 1; } /*************************************************************************************** ** Function name: drawChar ** Description: draw a unicode onto the screen *************************************************************************************x*/ int16_t TFT_eSprite::drawChar(unsigned int uniCode, int x, int y) { return drawChar(uniCode, x, y, textfont); } int16_t TFT_eSprite::drawChar(unsigned int uniCode, int x, int y, int font) { if (!_created ) return 0; if (font==1) { #ifdef LOAD_GLCD #ifndef LOAD_GFXFF drawChar(x, y, uniCode, textcolor, textbgcolor, textsize); return 6 * textsize; #endif #else #ifndef LOAD_GFXFF return 0; #endif #endif #ifdef LOAD_GFXFF drawChar(x, y, uniCode, textcolor, textbgcolor, textsize); if(!gfxFont) { // 'Classic' built-in font #ifdef LOAD_GLCD return 6 * textsize; #else return 0; #endif } else { if((uniCode >= pgm_read_byte(&gfxFont->first)) && (uniCode <= pgm_read_byte(&gfxFont->last) )) { uint8_t c2 = uniCode - pgm_read_byte(&gfxFont->first); GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]); return pgm_read_byte(&glyph->xAdvance) * textsize; } else { return 0; } } #endif } if ((font>1) && (font<9) && ((uniCode < 32) || (uniCode > 127))) return 0; int width = 0; int height = 0; uint32_t flash_address = 0; uniCode -= 32; #ifdef LOAD_FONT2 if (font == 2) { // This is faster than using the fontdata structure flash_address = pgm_read_dword(&chrtbl_f16[uniCode]); width = pgm_read_byte(widtbl_f16 + uniCode); height = chr_hgt_f16; } #ifdef LOAD_RLE else #endif #endif #ifdef LOAD_RLE { if ((font>2) && (font<9)) { // This is slower than above but is more convenient for the RLE fonts flash_address = pgm_read_dword( pgm_read_dword( &(fontdata[font].chartbl ) ) + uniCode*sizeof(void *) ); width = pgm_read_byte( (uint8_t *)pgm_read_dword( &(fontdata[font].widthtbl ) ) + uniCode ); height= pgm_read_byte( &fontdata[font].height ); } } #endif int w = width; int pX = 0; int pY = y; byte line = 0; #ifdef LOAD_FONT2 // chop out code if we do not need it if (font == 2) { w = w + 6; // Should be + 7 but we need to compensate for width increment w = w / 8; if (x + width * textsize >= _iwidth) return width * textsize ; for (int i = 0; i < height; i++) { if (textcolor != textbgcolor) fillRect(x, pY, width * textsize, textsize, textbgcolor); for (int k = 0; k < w; k++) { line = pgm_read_byte((uint8_t *)flash_address + w * i + k); if (line) { if (textsize == 1) { pX = x + k * 8; if (line & 0x80) drawPixel(pX, pY, textcolor); if (line & 0x40) drawPixel(pX + 1, pY, textcolor); if (line & 0x20) drawPixel(pX + 2, pY, textcolor); if (line & 0x10) drawPixel(pX + 3, pY, textcolor); if (line & 0x08) drawPixel(pX + 4, pY, textcolor); if (line & 0x04) drawPixel(pX + 5, pY, textcolor); if (line & 0x02) drawPixel(pX + 6, pY, textcolor); if (line & 0x01) drawPixel(pX + 7, pY, textcolor); } else { pX = x + k * 8 * textsize; if (line & 0x80) fillRect(pX, pY, textsize, textsize, textcolor); if (line & 0x40) fillRect(pX + textsize, pY, textsize, textsize, textcolor); if (line & 0x20) fillRect(pX + 2 * textsize, pY, textsize, textsize, textcolor); if (line & 0x10) fillRect(pX + 3 * textsize, pY, textsize, textsize, textcolor); if (line & 0x08) fillRect(pX + 4 * textsize, pY, textsize, textsize, textcolor); if (line & 0x04) fillRect(pX + 5 * textsize, pY, textsize, textsize, textcolor); if (line & 0x02) fillRect(pX + 6 * textsize, pY, textsize, textsize, textcolor); if (line & 0x01) fillRect(pX + 7 * textsize, pY, textsize, textsize, textcolor); } } } pY += textsize; } } #ifdef LOAD_RLE else #endif #endif //FONT2 #ifdef LOAD_RLE //674 bytes of code // Font is not 2 and hence is RLE encoded { w *= height; // Now w is total number of pixels in the character if (textcolor != textbgcolor) fillRect(x, pY, width * textsize, textsize * height, textbgcolor); int16_t color; if (_bpp16) color = (textcolor >> 8) | (textcolor << 8); else color = ((textcolor & 0xE000)>>8 | (textcolor & 0x0700)>>6 | (textcolor & 0x0018)>>3); int px = 0, py = pY; // To hold character block start and end column and row values int pc = 0; // Pixel count byte np = textsize * textsize; // Number of pixels in a drawn pixel byte ts = textsize - 1; // Temporary copy of textsize // 16 bit pixel count so maximum font size is equivalent to 180x180 pixels in area // w is total number of pixels to plot to fill character block while (pc < w) { line = pgm_read_byte((uint8_t *)flash_address); flash_address++; // 20 bytes smaller by incrementing here if (line & 0x80) { line &= 0x7F; line++; if (ts) { px = x + textsize * (pc % width); // Keep these px and py calculations outside the loop as they are slow py = y + textsize * (pc / width); } else { px = x + pc % width; // Keep these px and py calculations outside the loop as they are slow py = y + pc / width; } while (line--) { pc++; setWindow(px, py, px + ts, py + ts); if (ts) while (np--) writeColor(color); else writeColor(color); px += textsize; if (px >= (x + width * textsize)) { px = x; py += textsize; } } } else { line++; pc += line; } } } // End of RLE font rendering #endif return width * textsize; // x + }