From f6748bf9064ed3f95ff695a60511e65d7c7275e9 Mon Sep 17 00:00:00 2001 From: Bodmer Date: Tue, 5 May 2020 21:21:28 +0100 Subject: [PATCH] Add ESP32 SPI DMA capability DMA test examples now work on ESP32 --- Extensions/Sprite.cpp | 45 ++-- Processors/TFT_eSPI_ESP32.c | 249 +++++++++++++++++- Processors/TFT_eSPI_ESP32.h | 14 +- TFT_eSPI.h | 8 +- .../DMA test/Flash_Jpg_DMA/Flash_Jpg_DMA.ino | 2 +- .../SpriteRotatingCube/SpriteRotatingCube.ino | 7 +- examples/DMA test/boing_ball/boing_ball.ino | 14 +- keywords.txt | 1 + library.json | 2 +- library.properties | 2 +- 10 files changed, 309 insertions(+), 35 deletions(-) diff --git a/Extensions/Sprite.cpp b/Extensions/Sprite.cpp index 36a638d..c606677 100644 --- a/Extensions/Sprite.cpp +++ b/Extensions/Sprite.cpp @@ -80,9 +80,23 @@ void* TFT_eSprite::createSprite(int16_t w, int16_t h, uint8_t frames) _img = (uint16_t*) _img8; _img4 = _img8; + if ( (_bpp == 16) && (frames > 1) ) { + _img8_2 = _img8 + (w * h * 2 + 1); + } + + // ESP32 only 16bpp check + //if (esp_ptr_dma_capable(_img8_1)) Serial.println("DMA capable Sprite pointer _img8_1"); + //else Serial.println("Not a DMA capable Sprite pointer _img8_1"); + //if (esp_ptr_dma_capable(_img8_2)) Serial.println("DMA capable Sprite pointer _img8_2"); + //else Serial.println("Not a DMA capable Sprite pointer _img8_2"); + + if ( (_bpp == 8) && (frames > 1) ) { + _img8_2 = _img8 + (w * h + 1); + } + // This is to make it clear what pointer size is expected to be used // but casting in the user sketch is needed due to the use of void* - if (_bpp == 1) + if (_(bpp == 1) && (frames > 1) ) { w = (w+7) & 0xFFF8; _img8_2 = _img8 + ( (w>>3) * h + 1 ); @@ -124,22 +138,25 @@ void* TFT_eSprite::callocSprite(int16_t w, int16_t h, uint8_t frames) // hence will run faster in normal circumstances. uint8_t* ptr8 = NULL; + if (frames > 2) frames = 2; // Currently restricted to 2 frame buffers + if (frames < 1) frames = 1; + if (_bpp == 16) { #if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT) - if ( psramFound() && this->_psram_enable ) ptr8 = ( uint8_t*) ps_calloc(w * h + 1, sizeof(uint16_t)); + if ( psramFound() && this->_psram_enable && !_tft->DMA_Enabled) ptr8 = ( uint8_t*) ps_calloc(frames * w * h + frames, sizeof(uint16_t)); else #endif - ptr8 = ( uint8_t*) calloc(w * h + 1, sizeof(uint16_t)); + ptr8 = ( uint8_t*) calloc(frames * w * h + frames, sizeof(uint16_t)); } else if (_bpp == 8) { #if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT) - if ( psramFound() && this->_psram_enable ) ptr8 = ( uint8_t*) ps_calloc(w * h + 1, sizeof(uint8_t)); + if ( psramFound() && this->_psram_enable ) ptr8 = ( uint8_t*) ps_calloc(frames * w * h + frames, sizeof(uint8_t)); else #endif - ptr8 = ( uint8_t*) calloc(w * h + 1, sizeof(uint8_t)); + ptr8 = ( uint8_t*) calloc(frames * w * h + frames, sizeof(uint8_t)); } else if (_bpp == 4) @@ -147,10 +164,10 @@ void* TFT_eSprite::callocSprite(int16_t w, int16_t h, uint8_t frames) w = (w+1) & 0xFFFE; // width needs to be multiple of 2, with an extra "off screen" pixel _iwidth = w; #if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT) - if ( psramFound() && this->_psram_enable ) ptr8 = ( uint8_t*) ps_calloc(((w * h) >> 1) + 1, sizeof(uint8_t)); + if ( psramFound() && this->_psram_enable ) ptr8 = ( uint8_t*) ps_calloc(((frames * w * h) >> 1) + frames, sizeof(uint8_t)); else #endif - ptr8 = ( uint8_t*) calloc(((w * h) >> 1) + 1, sizeof(uint8_t)); + ptr8 = ( uint8_t*) calloc(((frames * w * h) >> 1) + frames, sizeof(uint8_t)); } else // Must be 1 bpp @@ -163,8 +180,6 @@ void* TFT_eSprite::callocSprite(int16_t w, int16_t h, uint8_t frames) _iwidth = w; // _iwidth is rounded up to be multiple of 8, so might not be = _dwidth _bitwidth = w; - if (frames > 2) frames = 2; // Currently restricted to 2 frame buffers - if (frames < 1) frames = 1; #if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT) if ( psramFound() && this->_psram_enable ) ptr8 = ( uint8_t*) ps_calloc(frames * (w>>3) * h + frames, sizeof(uint8_t)); else @@ -238,15 +253,15 @@ void* TFT_eSprite::frameBuffer(int8_t f) { if (!_created) return NULL; - if (_bpp == 16) return _img; - - if (_bpp == 8) return _img8; - - if (_bpp == 4) return _img4; - if ( f == 2 ) _img8 = _img8_2; else _img8 = _img8_1; + if (_bpp == 16) _img = (uint16_t*)_img8; + + //if (_bpp == 8) _img8 = _img8; + + if (_bpp == 4) _img4 = _img8; + return _img8; } diff --git a/Processors/TFT_eSPI_ESP32.c b/Processors/TFT_eSPI_ESP32.c index 8e0fdc7..d61dde7 100644 --- a/Processors/TFT_eSPI_ESP32.c +++ b/Processors/TFT_eSPI_ESP32.c @@ -11,7 +11,18 @@ #ifdef USE_HSPI_PORT SPIClass spi = SPIClass(HSPI); #else // use default VSPI port - SPIClass& spi = SPI; + //SPIClass& spi = SPI; + SPIClass spi = SPIClass(VSPI); + #endif +#endif + +#ifdef ESP32_DMA + // DMA SPA handle + spi_device_handle_t dmaHAL; + #ifdef USE_HSPI_PORT + spi_host_device_t spi_host = HSPI_HOST; + #else + spi_host_device_t spi_host = VSPI_HOST; #endif #endif @@ -475,3 +486,239 @@ void TFT_eSPI::pushPixels(const void* data_in, uint32_t len){ //////////////////////////////////////////////////////////////////////////////////////// #endif // End of display interface specific functions //////////////////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////////////////// +#if defined ESP32_DMA && !defined (TFT_PARALLEL_8_BIT) // DMA FUNCTIONS +//////////////////////////////////////////////////////////////////////////////////////// + +/*************************************************************************************** +** Function name: dmaBusy +** Description: Check if DMA is busy (currently blocking!) +***************************************************************************************/ +bool TFT_eSPI::dmaBusy(void) +{ + if (!DMA_Enabled || !spiBusyCheck) return false; + //spi_transaction_t rtrans; + //bool trans_result=spi_device_polling_transmit(dmaHAL, &rtrans); + //return trans_result; + // This works but blocks + dmaWait(); + return false; +} + + +/*************************************************************************************** +** Function name: dmaWait +** Description: Check if DMA is busy (blocking!) +***************************************************************************************/ +void TFT_eSPI::dmaWait(void) +{ + if (!DMA_Enabled || !spiBusyCheck) return; + spi_transaction_t *rtrans; + esp_err_t ret; + for (int i = 0; i < spiBusyCheck; ++i) + { + ret = spi_device_get_trans_result(dmaHAL, &rtrans, portMAX_DELAY); + assert(ret == ESP_OK); + } + spiBusyCheck = 0; +} + + +/*************************************************************************************** +** Function name: pushImageDMA +** Description: Push pixels to TFT (len must be less than 32767) +***************************************************************************************/ +// This will byte swap the original image if setSwapBytes(true) was called by sketch. +void TFT_eSPI::pushPixelsDMA(uint16_t* image, uint32_t len) +{ + if ((len == 0) || (!DMA_Enabled)) return; + dmaWait(); + esp_err_t ret; + static spi_transaction_t trans; + + memset(&trans, 0, sizeof(spi_transaction_t)); + + trans.user = (void *)1; + trans.tx_buffer = image; //finally send the line data + trans.length = len * 16; //Data length, in bits + trans.flags = 0; //SPI_TRANS_USE_TXDATA flag + + ret = spi_device_queue_trans(dmaHAL, &trans, portMAX_DELAY); + assert(ret == ESP_OK); + + spiBusyCheck = 1; +} + + +/*************************************************************************************** +** Function name: pushImageDMA +** Description: Push image to a window (w*h must be less than 65536) +***************************************************************************************/ +// This will clip and also swap bytes if setSwapBytes(true) was called by sketch +void TFT_eSPI::pushImageDMA(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t* image, uint16_t* buffer) +{ + if ((x >= _width) || (y >= _height) || (!DMA_Enabled)) 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 + dw) > _width ) dw = _width - x; + if ((y + dh) > _height) dh = _height - y; + + if (dw < 1 || dh < 1) return; + + if (buffer == nullptr) buffer = image; + + uint32_t len = dw*dh; + + dmaWait(); + + // If image is clipped, copy pixels into a contiguous block + if ( (dw != w) || (dh != h) ) { + if(_swapBytes) { + for (int32_t yb = 0; yb < dh; yb++) { + for (int32_t xb = 0; xb < dw; xb++) { + uint32_t src = xb + dx + w * (yb + dy); + (buffer[xb + yb * dw] = image[src] << 8 | image[src] >> 8); + } + } + } + else { + for (int32_t yb = 0; yb < dh; yb++) { + memcpy((uint8_t*) (buffer + yb * dw), (uint8_t*) (image + dx + w * (yb + dy)), dw << 1); + } + } + } + // else, if a buffer pointer has been provided copy whole image to the buffer + else if (buffer != image || _swapBytes) { + if(_swapBytes) { + for (uint32_t i = 0; i < len; i++) (buffer[i] = image[i] << 8 | image[i] >> 8); + } + else { + memcpy(buffer, image, len*2); + } + } + + esp_err_t ret; + static spi_transaction_t trans[6]; + for (int i = 0; i < 6; i++) + { + memset(&trans[i], 0, sizeof(spi_transaction_t)); + if ((i & 1) == 0) + { + trans[i].length = 8; + trans[i].user = (void *)0; + } + else + { + trans[i].length = 8 * 4; + trans[i].user = (void *)1; + } + trans[i].flags = SPI_TRANS_USE_TXDATA; + } + trans[0].tx_data[0] = 0x2A; //Column Address Set + trans[1].tx_data[0] = x >> 8; //Start Col High + trans[1].tx_data[1] = x & 0xFF; //Start Col Low + trans[1].tx_data[2] = (x + dw - 1) >> 8; //End Col High + trans[1].tx_data[3] = (x + dw - 1) & 0xFF; //End Col Low + trans[2].tx_data[0] = 0x2B; //Page address set + trans[3].tx_data[0] = y >> 8; //Start page high + trans[3].tx_data[1] = y & 0xFF; //start page low + trans[3].tx_data[2] = (y + dh - 1) >> 8; //end page high + trans[3].tx_data[3] = (y + dh - 1) & 0xFF; //end page low + trans[4].tx_data[0] = 0x2C; //memory write + trans[5].tx_buffer = buffer; //finally send the line data + trans[5].length = dw * 2 * 8 * dh; //Data length, in bits + trans[5].flags = 0; //undo SPI_TRANS_USE_TXDATA flag + for (int i = 0; i < 6; i++) + { + ret = spi_device_queue_trans(dmaHAL, &trans[i], portMAX_DELAY); + assert(ret == ESP_OK); + } + spiBusyCheck = 6; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Processor specific DMA initialisation +//////////////////////////////////////////////////////////////////////////////////////// + +// The DMA functions here work with SPI only (not parallel) +/*************************************************************************************** +** Function name: dc_callback +** Description: Toggles DC line during transaction +***************************************************************************************/ +extern "C" void dc_callback(); + +void IRAM_ATTR dc_callback(spi_transaction_t *spi_tx) +{ + if ((bool)spi_tx->user) DC_D; + else DC_C; +} + +/*************************************************************************************** +** Function name: initDMA +** Description: Initialise the DMA engine - returns true if init OK +***************************************************************************************/ +bool TFT_eSPI::initDMA(void) +{ + if (DMA_Enabled) return false; + + esp_err_t ret; + spi_bus_config_t buscfg = { + .mosi_io_num = TFT_MOSI, + .miso_io_num = TFT_MISO, + .sclk_io_num = TFT_SCLK, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .max_transfer_sz = TFT_WIDTH * TFT_HEIGHT * 2 + 8, // TFT screen size + .flags = 0, + .intr_flags = 0 + }; + spi_device_interface_config_t devcfg = { + .command_bits = 0, + .address_bits = 0, + .dummy_bits = 0, + .mode = TFT_SPI_MODE, + .duty_cycle_pos = 0, + .cs_ena_pretrans = 0, + .cs_ena_posttrans = 0, + .clock_speed_hz = SPI_FREQUENCY, + .input_delay_ns = 0, + .spics_io_num = TFT_CS, + .flags = 0, + .queue_size = 7, + .pre_cb = dc_callback, //Callback to handle D/C line + .post_cb = 0 + }; + ret = spi_bus_initialize(spi_host, &buscfg, 1); + ESP_ERROR_CHECK(ret); + ret = spi_bus_add_device(spi_host, &devcfg, &dmaHAL); + ESP_ERROR_CHECK(ret); + + DMA_Enabled = true; + spiBusyCheck = 0; + return true; +} + +/*************************************************************************************** +** Function name: deInitDMA +** Description: Disconnect the DMA engine from SPI +***************************************************************************************/ +void TFT_eSPI::deInitDMA(void) +{ + if (!DMA_Enabled) return; + + spi_bus_free(spi_host); + DMA_Enabled = false; +} + +//////////////////////////////////////////////////////////////////////////////////////// +#endif // End of DMA FUNCTIONS +//////////////////////////////////////////////////////////////////////////////////////// diff --git a/Processors/TFT_eSPI_ESP32.h b/Processors/TFT_eSPI_ESP32.h index 82ed210..0da6a18 100644 --- a/Processors/TFT_eSPI_ESP32.h +++ b/Processors/TFT_eSPI_ESP32.h @@ -10,14 +10,12 @@ // Include processor specific header #include "soc/spi_reg.h" +#include "driver/spi_master.h" // Processor specific code used by SPI bus transaction startWrite and endWrite functions #define SET_BUS_WRITE_MODE // Not used #define SET_BUS_READ_MODE // Not used -// Code to check if DMA is busy, used by SPI bus transaction transaction and endWrite functions -#define DMA_BUSY_CHECK // DMA not implemented for this processor (yet) - // SUPPORT_TRANSACTIONS is mandatory for ESP32 so the hal mutex is toggled #if !defined (SUPPORT_TRANSACTIONS) #define SUPPORT_TRANSACTIONS @@ -304,6 +302,16 @@ //////////////////////////////////////////////////////////////////////////////////////// #else + #define ESP32_DMA // DMA is available for SPI + + // Code to check if DMA is busy, used by SPI bus transaction transaction and endWrite functions + #ifdef ESP32_DMA + // Code to check if DMA is busy, used by SPI DMA + transaction + endWrite functions + #define DMA_BUSY_CHECK { if (DMA_Enabled) dmaWait(); } + #else + #define DMA_BUSY_CHECK + #endif + // ESP32 low level SPI writes for 8, 16 and 32 bit values // to avoid the function call overhead #define TFT_WRITE_BITS(D, B) \ diff --git a/TFT_eSPI.h b/TFT_eSPI.h index 2ce93f9..77b05bf 100644 --- a/TFT_eSPI.h +++ b/TFT_eSPI.h @@ -16,7 +16,7 @@ #ifndef _TFT_eSPIH_ #define _TFT_eSPIH_ -#define TFT_ESPI_VERSION "2.2.3" +#define TFT_ESPI_VERSION "2.2.4" /*************************************************************************************** ** Section 1: Load required header files @@ -615,10 +615,12 @@ class TFT_eSPI : public Print { void pushPixelsDMA(uint16_t* image, uint32_t len); // Check if the DMA is complete - use while(tft.dmaBusy); for a blocking wait + // Note: for ESP32 the dmaBusy() function is blocking at the moment - to be updated bool dmaBusy(void); + void dmaWait(void); - bool DMA_Enabled = false; // Flag for DMA enabled state - + bool DMA_Enabled = false; // Flag for DMA enabled state + uint8_t spiBusyCheck = 0; // Number of ESP32 transfer buffers to check // Bare metal functions void startWrite(void); // Begin SPI transaction diff --git a/examples/DMA test/Flash_Jpg_DMA/Flash_Jpg_DMA.ino b/examples/DMA test/Flash_Jpg_DMA/Flash_Jpg_DMA.ino index b87332e..d45e593 100644 --- a/examples/DMA test/Flash_Jpg_DMA/Flash_Jpg_DMA.ino +++ b/examples/DMA test/Flash_Jpg_DMA/Flash_Jpg_DMA.ino @@ -42,7 +42,7 @@ bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap) // bitmap can then be updated by the jpeg decoder while DMA is in progress if (dmaBufferSel) dmaBufferPtr = dmaBuffer2; else dmaBufferPtr = dmaBuffer1; - dmaBufferSel != dmaBufferSel; // Toggle buffer selection + dmaBufferSel = !dmaBufferSel; // Toggle buffer selection // pushImageDMA() will clip the image block at screen boundaries before initiating DMA tft.pushImageDMA(x, y, w, h, bitmap, dmaBufferPtr); // Initiate DMA - blocking only if last DMA is not complete // The DMA transfer of image block to the TFT is now in progress... diff --git a/examples/DMA test/SpriteRotatingCube/SpriteRotatingCube.ino b/examples/DMA test/SpriteRotatingCube/SpriteRotatingCube.ino index 0ad21ef..07f4831 100644 --- a/examples/DMA test/SpriteRotatingCube/SpriteRotatingCube.ino +++ b/examples/DMA test/SpriteRotatingCube/SpriteRotatingCube.ino @@ -1,7 +1,7 @@ // TFT_eSPI library demo, principally for STM32F processors with DMA: // https://en.wikipedia.org/wiki/Direct_memory_access -// Tested with Nucleo 64 STM32F446RE and Nucleo 144 STM32F767ZI +// Tested with ESP32, Nucleo 64 STM32F446RE and Nucleo 144 STM32F767ZI // TFT's with SPI can use DMA, the sketch also works with 8 bit // parallel TFT's (tested with ILI9341 and ILI9481) @@ -22,6 +22,9 @@ // (Tested with Nucleo 64 STM32F446RE and Nucleo 144 STM32F767ZI) // STM32F767 27MHz SPI 50% processor load: Non DMA 52 fps, with DMA 101 fps // STM32F767 27MHz SPI 0% processor load: Non DMA 97 fps, with DMA 102 fps + +// ESP32 27MHz SPI 0% processor load: Non DMA 90 fps, with DMA 101 fps +// ESP32 40MHz SPI 0% processor load: Non DMA 127 fps, with DMA 145 fps // NOTE: FOR SPI DISPLAYS ONLY #define USE_DMA_TO_TFT @@ -158,7 +161,7 @@ void setup() { spr[1].setTextDatum(MC_DATUM); #ifdef USE_DMA_TO_TFT - // DMA - should work with STM32F2xx/F4xx/F7xx processors + // DMA - should work with ESP32, STM32F2xx/F4xx/F7xx processors // NOTE: >>>>>> DMA IS FOR SPI DISPLAYS ONLY <<<<<< tft.initDMA(); // Initialise the DMA engine (tested with STM32F446 and STM32F767) #endif diff --git a/examples/DMA test/boing_ball/boing_ball.ino b/examples/DMA test/boing_ball/boing_ball.ino index 2a5aff7..0d9a9c8 100644 --- a/examples/DMA test/boing_ball/boing_ball.ino +++ b/examples/DMA test/boing_ball/boing_ball.ino @@ -1,6 +1,5 @@ // 'Boing' ball demo -// ESP32 110 fps (no DMA) // STM32F767 55MHz SPI 170 fps without DMA // STM32F767 55MHz SPI 227 fps with DMA // STM32F446 55MHz SPI 110 fps without DMA @@ -20,6 +19,10 @@ // Blue Pill overclocked to 128MHz *no* DMA - 32MHz SPI 64 fps // Blue Pill overclocked to 128MHz with DMA - 32MHz SPI 116 fps +// ESP32 - 8 bit parallel 110 fps (no DMA) +// ESP32 - 40MHz SPI *no* DMA 93 fps +// ESP32 - 40MHz SPI with DMA 112 fps + #define SCREENWIDTH 320 #define SCREENHEIGHT 240 @@ -36,8 +39,8 @@ TFT_eSPI tft = TFT_eSPI(); // Invoke custom library #define RED 0xF800 #define WHITE 0xFFFF -#define YBOTTOM 123 // Ball Y coord at bottom - #define YBOUNCE -3.5 // Upward velocity on ball bounce +#define YBOTTOM 123 // Ball Y coord at bottom +#define YBOUNCE -3.5 // Upward velocity on ball bounce // Ball coordinates are stored floating-point because screen refresh // is so quick, whole-pixel movements are just too fast! @@ -58,9 +61,6 @@ void setup() { Serial.begin(115200); // while(!Serial); - // Turn on backlight (required on PyPortal) - - tft.begin(); tft.setRotation(3); // Landscape orientation, USB at bottom right tft.setSwapBytes(false); @@ -70,8 +70,6 @@ void setup() { tft.initDMA(); - delay(2000); - tft.drawBitmap(0, 0, (const uint8_t *)background, SCREENWIDTH, SCREENHEIGHT, GRIDCOLOR); startTime = millis(); diff --git a/keywords.txt b/keywords.txt index 6f8fb9d..2debf03 100644 --- a/keywords.txt +++ b/keywords.txt @@ -86,6 +86,7 @@ pushImageDMA KEYWORD2 pushBlockDMA KEYWORD2 pushPixelsDMA KEYWORD2 dmaBusy KEYWORD2 +dmaWait KEYWORD2 getTouchRaw KEYWORD2 convertRawXY KEYWORD2 diff --git a/library.json b/library.json index e811913..3bed7b0 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "TFT_eSPI", - "version": "2.2.3", + "version": "2.2.4", "keywords": "Arduino, tft, ePaper, display, STM32, ESP8266, NodeMCU, ESP32, M5Stack, ILI9341, ST7735, ILI9163, S6D02A1, ILI9486, ST7789, RM68140", "description": "A TFT and ePaper SPI graphics library with optimisation for ESP8266, ESP32 and STM32", "repository": diff --git a/library.properties b/library.properties index 4e3e895..972e24a 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=TFT_eSPI -version=2.2.3 +version=2.2.4 author=Bodmer maintainer=Bodmer sentence=TFT graphics library for Arduino processors with performance optimisation for STM32, ESP8266 and ESP32