diff --git a/Adafruit_SPITFT.cpp b/Adafruit_SPITFT.cpp index 05d71ab5..e9458153 100644 --- a/Adafruit_SPITFT.cpp +++ b/Adafruit_SPITFT.cpp @@ -909,7 +909,7 @@ void Adafruit_SPITFT::setSPISpeed(uint32_t freq) { */ void Adafruit_SPITFT::startWrite(void) { SPI_BEGIN_TRANSACTION(); - if(_cs >= 0) SPI_CS_LOW(); + SPI_CS_LOW(); } /*! @@ -919,7 +919,7 @@ void Adafruit_SPITFT::startWrite(void) { for all display types; not an SPI-specific function. */ void Adafruit_SPITFT::endWrite(void) { - if(_cs >= 0) SPI_CS_HIGH(); + SPI_CS_HIGH(); SPI_END_TRANSACTION(); } @@ -942,7 +942,16 @@ void Adafruit_SPITFT::endWrite(void) { void Adafruit_SPITFT::writePixel(int16_t x, int16_t y, uint16_t color) { if((x >= 0) && (x < _width) && (y >= 0) && (y < _height)) { setAddrWindow(x, y, 1, 1); +#if defined(__AVR__) + #if defined(USE_HYPER_SPI) + _hyper_SpiWrite16((uint8_t)(color >> 8), (uint8_t)color); + #else + AVR_WRITESPI((uint8_t)(color >> 8)); + AVR_WRITESPI((uint8_t)(color)); + #endif // USE_HYPER_SPI +#else SPI_WRITE16(color); +#endif } } @@ -1097,6 +1106,137 @@ void Adafruit_SPITFT::dmaWait(void) { #endif } +#if !defined(USE_FAST_FILLS) || !defined(__AVR__) + +inline void Adafruit_SPITFT::writeColorFast(uint16_t color, uint32_t len) { writeColor(color, len); } +inline void Adafruit_SPITFT::writeColorFastSmall(uint16_t color, uint16_t len) { writeColor(color, len); } + +#else + +#define WRITE_COLOR_LOOP_UNROLL 4 + +/*! + @brief Issue a series of pixels, all the same color. Not self- + contained; should follow startWrite() and setAddrWindow() calls. + This is a "fast" version that uses loop unrolling and smaller loop + variables to run faster. + This breaks apart the length given into a maximum of 256 runs + of 256 * 4 writes each, which is 256k pixels (262144) PLUS + any remainder less than 1024 which is run in unrolled loops, + PLUS any fractional remainder less than 256 (so, max len is 262144 + 1024 + 255). + @param color 16-bit pixel color in '565' RGB format. + @param len Number of pixels to draw. +*/ +void Adafruit_SPITFT::writeColorFast(uint16_t color, uint32_t len) { + uint8_t hi = (color >> 8), lo = color; + // Calculate the high loop count. + // uint16_t hiloop_cnt = len / (256 * WRITE_COLOR_LOOP_UNROLL); + uint16_t hiloop_cnt = len >> 10; // Shift by 10 is div 1024, faster than above line + uint8_t tail_loops, cLoops; + + // Subtract the high loop length, leaving some remainder + // len -= ((uint32_t)hiloop_cnt * 256 * WRITE_COLOR_LOOP_UNROLL); + len -= ((uint32_t)hiloop_cnt << 10); // (shift 10 is mult 1024) Subtract the high loop length, leaving some remainder + tail_loops = (uint8_t)(len / WRITE_COLOR_LOOP_UNROLL); // Calculate the small unroll loops (up to (255*4) pixels) + len -= (uint32_t)tail_loops * WRITE_COLOR_LOOP_UNROLL; // Subtract, leaving a possible remainder (up to 255) + + uint16_t hloop = hiloop_cnt; + + if (hiloop_cnt > 10) { + a = micros(); + } + + // Run all the "high" loops which are 256 unrolls each. + while (hloop--) { + cLoops = 255; + + // It's important that this is a do...while because counting that way get us a full 256 loops out of a byte loop var + do { +#ifdef USE_HYPER_SPI + _hyper_SpiWrite16(hi, lo); + _hyper_SpiWrite16(hi, lo); + _hyper_SpiWrite16(hi, lo); + _hyper_SpiWrite16(hi, lo); +#else + AVR_WRITESPI(hi); AVR_WRITESPI(lo); + AVR_WRITESPI(hi); AVR_WRITESPI(lo); + AVR_WRITESPI(hi); AVR_WRITESPI(lo); + AVR_WRITESPI(hi); AVR_WRITESPI(lo); +#endif // USE_HYPER_SPI + } while (cLoops--); + } + +#ifdef USE_HYPER_SPI + while(!(SPSR & _BV(SPIF))) {} +#endif // USE_HYPER_SPI + + // Run up to 255 unrolled loops + while (tail_loops--) { +#ifdef USE_HYPER_SPI + _hyper_SpiWrite16(hi, lo); + _hyper_SpiWrite16(hi, lo); + _hyper_SpiWrite16(hi, lo); + _hyper_SpiWrite16(hi, lo); +#else + AVR_WRITESPI(hi); AVR_WRITESPI(lo); + AVR_WRITESPI(hi); AVR_WRITESPI(lo); + AVR_WRITESPI(hi); AVR_WRITESPI(lo); + AVR_WRITESPI(hi); AVR_WRITESPI(lo); +#endif // USE_HYPER_SPI + } + +#ifdef USE_HYPER_SPI + while(!(SPSR & _BV(SPIF))) {} +#endif // USE_HYPER_SPI + + // Run any straggler pixels + cLoops = (uint8_t)len; + while (cLoops--) { +#ifdef USE_HYPER_SPI + _hyper_SpiWrite16(hi, lo); +#else + AVR_WRITESPI(hi); AVR_WRITESPI(lo); +#endif // USE_HYPER_SPI + } +} + +void Adafruit_SPITFT::writeColorFastSmall(uint16_t color, uint16_t len) { + uint8_t hi = color >> 8, lo = color; + + uint8_t tail_loops = (uint8_t)(len / WRITE_COLOR_LOOP_UNROLL); + len -= (uint16_t)tail_loops * WRITE_COLOR_LOOP_UNROLL; + + while (tail_loops--) { +#ifdef USE_HYPER_SPI + _hyper_SpiWrite16(hi, lo); + _hyper_SpiWrite16(hi, lo); + _hyper_SpiWrite16(hi, lo); + _hyper_SpiWrite16(hi, lo); +#else + AVR_WRITESPI(hi); AVR_WRITESPI(lo); + AVR_WRITESPI(hi); AVR_WRITESPI(lo); + AVR_WRITESPI(hi); AVR_WRITESPI(lo); + AVR_WRITESPI(hi); AVR_WRITESPI(lo); +#endif // USE_HYPER_SPI + } + +#ifdef USE_HYPER_SPI + while(!(SPSR & _BV(SPIF))) {} +#endif // USE_HYPER_SPI + + uint8_t a = (uint8_t)len; + + while (a--) { +#ifdef USE_HYPER_SPI + _hyper_SpiWrite16(hi, lo); +#else + AVR_WRITESPI(hi); AVR_WRITESPI(lo); +#endif // USE_HYPER_SPI + } +} + +#endif // USE_FAST_FILLS + /*! @brief Issue a series of pixels, all the same color. Not self- contained; should follow startWrite() and setAddrWindow() calls. @@ -1242,8 +1382,12 @@ void Adafruit_SPITFT::writeColor(uint16_t color, uint32_t len) { #else // !ESP8266 while(len--) { #if defined(__AVR__) + #ifdef USE_HYPER_SPI + _hyper_SpiWrite16(hi, lo); + #else AVR_WRITESPI(hi); AVR_WRITESPI(lo); + #endif #elif defined(ESP32) hwspi._spi->write(hi); hwspi._spi->write(lo); @@ -1386,6 +1530,44 @@ void Adafruit_SPITFT::writeFillRect(int16_t x, int16_t y, } } +/*! + @brief Draw a filled rectangle to the display. Not self-contained; + should follow startWrite(). Typically used by higher-level + graphics primitives; user code shouldn't need to call this and + is likely to use the self-contained fillRect() instead. + writeFillRect() performs its own edge clipping and rejection; + see writeFillRectPreclipped() for a more 'raw' implementation. + @param x Horizontal position of first corner. + @param y Vertical position of first corner. + @param w Rectangle width in pixels. + @param h Rectangle height in pixels. + @param color 16-bit fill color in '565' RGB format. + @note Written in this deep-nested way because C by definition will + optimize for the 'if' case, not the 'else' -- avoids branches + and rejects clipped rectangles at the least-work possibility. +*/ +void Adafruit_SPITFT::writeFillRectSmall(int16_t x, int16_t y, + uint8_t w, uint8_t h, uint16_t color) { + if(w && h) { // Nonzero width and height? + if(x < _width) { // Not off right + if(y < _height) { // Not off bottom + int16_t x2 = x + w - 1; + if(x2 >= 0) { // Not off left + int16_t y2 = y + h - 1; + if(y2 >= 0) { // Not off top + // Rectangle partly or fully overlaps screen + if(x < 0) { x = 0; w = x2 + 1; } // Clip left + if(y < 0) { y = 0; h = y2 + 1; } // Clip top + if(x2 >= _width) { w = _width - x; } // Clip right + if(y2 >= _height) { h = _height - y; } // Clip bottom + writeFillRectSmallPreclipped(x, y, w, h, color); + } + } + } + } + } +} + /*! @brief Draw a horizontal line on the display. Performs edge clipping and rejection. Not self-contained; should follow startWrite(). @@ -1411,7 +1593,7 @@ void inline Adafruit_SPITFT::writeFastHLine(int16_t x, int16_t y, int16_t w, // Line partly or fully overlaps screen if(x < 0) { x = 0; w = x2 + 1; } // Clip left if(x2 >= _width) { w = _width - x; } // Clip right - writeFillRectPreclipped(x, y, w, 1, color); + writeFillRectSmallPreclipped(x, y, w, 1, color); } } } @@ -1442,7 +1624,7 @@ void inline Adafruit_SPITFT::writeFastVLine(int16_t x, int16_t y, int16_t h, // Line partly or fully overlaps screen if(y < 0) { y = 0; h = y2 + 1; } // Clip top if(y2 >= _height) { h = _height - y; } // Clip bottom - writeFillRectPreclipped(x, y, 1, h, color); + writeFillRectSmallPreclipped(x, y, 1, h, color); } } } @@ -1471,7 +1653,34 @@ void inline Adafruit_SPITFT::writeFastVLine(int16_t x, int16_t y, int16_t h, inline void Adafruit_SPITFT::writeFillRectPreclipped(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) { setAddrWindow(x, y, w, h); - writeColor(color, (uint32_t)w * h); + writeColorFast(color, (uint32_t)w * h); +} + + +/*! + @brief A lower-level version of writeFillRectSmall(). This version requires + all inputs are in-bounds, that width and height are positive, + and no part extends offscreen. NO EDGE CLIPPING OR REJECTION IS + PERFORMED. If higher-level graphics primitives are written to + handle their own clipping earlier in the drawing process, this + can avoid unnecessary function calls and repeated clipping + operations in the lower-level functions. + @param x Horizontal position of first corner. MUST BE WITHIN + SCREEN BOUNDS. + @param y Vertical position of first corner. MUST BE WITHIN SCREEN + BOUNDS. + @param w Rectangle width in pixels. MUST BE POSITIVE AND NOT + EXTEND OFF SCREEN. + @param h Rectangle height in pixels. MUST BE POSITIVE AND NOT + EXTEND OFF SCREEN. + @param color 16-bit fill color in '565' RGB format. + @note This is a new function, no graphics primitives besides rects + and horizontal/vertical lines are written to best use this yet. +*/ +inline void Adafruit_SPITFT::writeFillRectSmallPreclipped(int16_t x, int16_t y, + uint8_t w, uint8_t h, uint16_t color) { + setAddrWindow(x, y, w, h); + writeColorFastSmall(color, (uint16_t)w * h); } @@ -1584,7 +1793,7 @@ void Adafruit_SPITFT::drawFastHLine(int16_t x, int16_t y, int16_t w, if(x < 0) { x = 0; w = x2 + 1; } // Clip left if(x2 >= _width) { w = _width - x; } // Clip right startWrite(); - writeFillRectPreclipped(x, y, w, 1, color); + writeFillRectSmallPreclipped(x, y, w, 1, color); endWrite(); } } @@ -1620,7 +1829,7 @@ void Adafruit_SPITFT::drawFastVLine(int16_t x, int16_t y, int16_t h, if(y < 0) { y = 0; h = y2 + 1; } // Clip top if(y2 >= _height) { h = _height - y; } // Clip bottom startWrite(); - writeFillRectPreclipped(x, y, 1, h, color); + writeFillRectSmallPreclipped(x, y, 1, h, color); endWrite(); } } @@ -1727,7 +1936,7 @@ uint16_t Adafruit_SPITFT::color565(uint8_t red, uint8_t green, uint8_t blue) { */ void Adafruit_SPITFT::sendCommand(uint8_t commandByte, uint8_t *dataBytes, uint8_t numDataBytes) { SPI_BEGIN_TRANSACTION(); - if(_cs >= 0) SPI_CS_LOW(); + SPI_CS_LOW(); SPI_DC_LOW(); // Command mode spiWrite(commandByte); // Send the command byte @@ -1738,7 +1947,7 @@ void Adafruit_SPITFT::sendCommand(uint8_t commandByte, uint8_t *dataBytes, uint8 dataBytes++; } - if(_cs >= 0) SPI_CS_HIGH(); + SPI_CS_HIGH(); SPI_END_TRANSACTION(); } @@ -1750,7 +1959,7 @@ void Adafruit_SPITFT::sendCommand(uint8_t commandByte, uint8_t *dataBytes, uint8 */ void Adafruit_SPITFT::sendCommand(uint8_t commandByte, const uint8_t *dataBytes, uint8_t numDataBytes) { SPI_BEGIN_TRANSACTION(); - if(_cs >= 0) SPI_CS_LOW(); + SPI_CS_LOW(); SPI_DC_LOW(); // Command mode spiWrite(commandByte); // Send the command byte @@ -1760,7 +1969,7 @@ void Adafruit_SPITFT::sendCommand(uint8_t commandByte, const uint8_t *dataBytes, spiWrite(pgm_read_byte(dataBytes++)); // Send the data bytes } - if(_cs >= 0) SPI_CS_HIGH(); + SPI_CS_HIGH(); SPI_END_TRANSACTION(); } diff --git a/Adafruit_SPITFT.h b/Adafruit_SPITFT.h index da2507b4..3bfa15f2 100644 --- a/Adafruit_SPITFT.h +++ b/Adafruit_SPITFT.h @@ -30,6 +30,8 @@ #if defined(__AVR__) typedef uint8_t ADAGFX_PORT_t; ///< PORT values are 8-bit #define USE_FAST_PINIO ///< Use direct PORT register access + #define USE_FAST_FILLS + #define USE_HYPER_SPI #elif defined(ARDUINO_STM32_FEATHER) // WICED typedef class HardwareSPI SPIClass; ///< SPI is a bit odd on WICED typedef uint32_t ADAGFX_PORT_t; ///< PORT values are 32-bit @@ -226,6 +228,15 @@ class Adafruit_SPITFT : public Adafruit_GFX { // writePixels() variant. void dmaWait(void); + // These functions trade code space for speed by unrolling loops and using + // smaller counter vars. Speedups range from 8-24% faster. + void writeColorFast(uint16_t color, uint32_t len); + void writeColorFastSmall(uint16_t color, uint16_t len); + void writeFillRectSmall(int16_t x, int16_t y, uint8_t w, uint8_t h, + uint16_t color); + inline void writeFillRectSmallPreclipped(int16_t x, int16_t y, + uint8_t w, uint8_t h, uint16_t color); + // These functions are similar to the 'write' functions above, but with // a chip-select and/or SPI transaction built-in. They're typically used @@ -269,6 +280,42 @@ class Adafruit_SPITFT : public Adafruit_GFX { // SPI_WRITE16 and SPI_WRITE32 anyway so those names were kept rather // than the less-obnoxious camelcase variants, oh well. +#ifdef USE_HYPER_SPI + // 16-bit SPI write with minimal hand-tuned delay (assuming max DIV2 SPI rate) + static inline void _hyper_SpiWrite16(uint8_t _hi, uint8_t _lo) __attribute__((always_inline)) + { + uint8_t temp; + __asm__ __volatile__ + ( + " out %[spi],%[high]\n" // write SPI data (18 cycles until next write) + " adiw r24,0\n" // +2 (2-cycle NOP) + " adiw r24,0\n" // +2 (2-cycle NOP) + " adiw r24,0\n" // +2 (2-cycle NOP) + " adiw r24,0\n" // +2 (2-cycle NOP) + " adiw r24,0\n" // +2 (2-cycle NOP) + " adiw r24,0\n" // +2 (2-cycle NOP) + " adiw r24,0\n" // +2 (2-cycle NOP) + " adiw r24,0\n" // +2 (2-cycle NOP) + " nop\n" + + " out %[spi],%[low]\n" // write SPI data (18 cycles until next write) + " adiw r24,0\n" // +2 (2-cycle NOP) + " adiw r24,0\n" // +2 (2-cycle NOP) + " adiw r24,0\n" // +2 (2-cycle NOP) + " adiw r24,0\n" // +2 (2-cycle NOP) + " adiw r24,0\n" // +2 (2-cycle NOP) + " adiw r24,0\n" // +2 (2-cycle NOP) + " adiw r24,0\n" // +2 (2-cycle NOP) + " adiw r24,0\n" // +2 (2-cycle NOP) + " nop\n" + + : [temp] "=d" (temp) + : [spi] "I" (_SFR_IO_ADDR(SPDR)), [low] "r" ((uint8_t)_lo), [high] "r" ((uint8_t)_hi) + : + ); + } +#endif // USE_HYPER_SPI + // Placing these functions entirely in the class definition inlines // them implicitly them while allowing their use in other code: @@ -278,7 +325,7 @@ class Adafruit_SPITFT : public Adafruit_GFX { Despite function name, this is used even if the display connection is parallel. */ - void SPI_CS_HIGH(void) { + inline void SPI_CS_HIGH(void) { #if defined(USE_FAST_PINIO) #if defined(HAS_PORT_SET_CLR) #if defined(KINETISK) @@ -300,7 +347,7 @@ class Adafruit_SPITFT : public Adafruit_GFX { Despite function name, this is used even if the display connection is parallel. */ - void SPI_CS_LOW(void) { + inline void SPI_CS_LOW(void) { #if defined(USE_FAST_PINIO) #if defined(HAS_PORT_SET_CLR) #if defined(KINETISK) @@ -319,7 +366,7 @@ class Adafruit_SPITFT : public Adafruit_GFX { /*! @brief Set the data/command line HIGH (data mode). */ - void SPI_DC_HIGH(void) { + inline void SPI_DC_HIGH(void) { #if defined(USE_FAST_PINIO) #if defined(HAS_PORT_SET_CLR) #if defined(KINETISK) @@ -338,7 +385,7 @@ class Adafruit_SPITFT : public Adafruit_GFX { /*! @brief Set the data/command line LOW (command mode). */ - void SPI_DC_LOW(void) { + inline void SPI_DC_LOW(void) { #if defined(USE_FAST_PINIO) #if defined(HAS_PORT_SET_CLR) #if defined(KINETISK)