Skip to content

Commit

Permalink
Implement faster code...
Browse files Browse the repository at this point in the history
- Fix loop unrolled fast pixel write loops and enable
- Borrow hand-tuned SPI write routine from ILI9341-PDQ

Changes reduce screen fill time from 236ms to 179ms
which is about 97% of the max speed at 8MHZ SPI2X.
  • Loading branch information
paulrnash committed Jun 4, 2023
1 parent 63823f9 commit a9851b3
Show file tree
Hide file tree
Showing 2 changed files with 271 additions and 15 deletions.
231 changes: 220 additions & 11 deletions Adafruit_SPITFT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

/*!
Expand All @@ -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();
}

Expand All @@ -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
}
}

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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().
Expand All @@ -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);
}
}
}
Expand Down Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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);
}


Expand Down Expand Up @@ -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();
}
}
Expand Down Expand Up @@ -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();
}
}
Expand Down Expand Up @@ -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
Expand All @@ -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();
}

Expand All @@ -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
Expand All @@ -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();
}

Expand Down
Loading

0 comments on commit a9851b3

Please sign in to comment.