diff options
| author | mrfaptastic <12006953+mrfaptastic@users.noreply.github.com> | 2020-08-02 22:03:16 +0100 |
|---|---|---|
| committer | mrfaptastic <12006953+mrfaptastic@users.noreply.github.com> | 2020-08-02 22:03:16 +0100 |
| commit | 3159202ccfb58c971ff61aa0ec1bf771cc72983a (patch) | |
| tree | 8bfd958b2c5f7a301985ff8fb04a4719b49fef2a | |
| parent | 80d5ddff4c01564ea020b8f6ab00a1a3224f0fe3 (diff) | |
Improvement to ensure DMA payload limit not hit.
| -rw-r--r-- | ESP32-RGB64x32MatrixPanel-I2S-DMA.cpp | 92 | ||||
| -rw-r--r-- | ESP32-RGB64x32MatrixPanel-I2S-DMA.h | 2 | ||||
| -rw-r--r-- | ESP32-VirtualMatrixPanel-I2S-DMA.h | 134 | ||||
| -rw-r--r-- | esp32_i2s_parallel.c | 5 | ||||
| -rw-r--r-- | esp32_i2s_parallel.h | 3 |
5 files changed, 216 insertions, 20 deletions
diff --git a/ESP32-RGB64x32MatrixPanel-I2S-DMA.cpp b/ESP32-RGB64x32MatrixPanel-I2S-DMA.cpp index c62f6e8..91f3e81 100644 --- a/ESP32-RGB64x32MatrixPanel-I2S-DMA.cpp +++ b/ESP32-RGB64x32MatrixPanel-I2S-DMA.cpp @@ -156,7 +156,7 @@ bool RGB64x32MatrixPanel_I2S_DMA::allocateDMAmemory() int ramrequired = numDMAdescriptorsPerRow * ROWS_PER_FRAME * _num_frame_buffers * sizeof(lldesc_t); int largestblockfree = heap_caps_get_largest_free_block(MALLOC_CAP_DMA); #if SERIAL_DEBUG - Serial.printf("numdesciptors per row %d, lsbMsbTransitionBit of %d requires %d RAM, %d available, leaving %d free: \r\n", numDMAdescriptorsPerRow, lsbMsbTransitionBit, ramrequired, largestblockfree, largestblockfree - ramrequired); + Serial.printf("lsbMsbTransitionBit of %d with %d DMA descriptors per frame row, requires %d bytes RAM, %d available, leaving %d free: \r\n", lsbMsbTransitionBit, numDMAdescriptorsPerRow, ramrequired, largestblockfree, largestblockfree - ramrequired); #endif if(ramrequired < largestblockfree) @@ -187,8 +187,8 @@ bool RGB64x32MatrixPanel_I2S_DMA::allocateDMAmemory() calculated_refresh_rate = actualRefreshRate; #if SERIAL_DEBUG - Serial.printf("lsbMsbTransitionBit of %d gives %d Hz refresh: \r\n", lsbMsbTransitionBit, actualRefreshRate); - #endif + Serial.printf("lsbMsbTransitionBit of %d gives %d Hz refresh: \r\n", lsbMsbTransitionBit, actualRefreshRate); + #endif if (actualRefreshRate > min_refresh_rate) // HACK Hard Coded: 100 break; @@ -201,19 +201,41 @@ bool RGB64x32MatrixPanel_I2S_DMA::allocateDMAmemory() Serial.printf("Raised lsbMsbTransitionBit to %d/%d to meet minimum refresh rate\r\n", lsbMsbTransitionBit, PIXEL_COLOR_DEPTH_BITS - 1); - // lsbMsbTransition Bit is now finalized - recalcuate descriptor count in case it changed to hit min refresh rate + /*** + * Step 2a: lsbMsbTransition bit is now finalised - recalculate the DMA descriptor count required, which is used for + * memory allocation of the DMA linked list memory structure. + */ numDMAdescriptorsPerRow = 1; for(int i=lsbMsbTransitionBit + 1; i<PIXEL_COLOR_DEPTH_BITS; i++) { numDMAdescriptorsPerRow += 1<<(i - lsbMsbTransitionBit - 1); } + //Serial.printf("Size of (sizeof(rowBitStruct) * PIXEL_COLOR_DEPTH_BITS): %d.\r\n", (sizeof(rowBitStruct) * PIXEL_COLOR_DEPTH_BITS)); + //Serial.printf("Size of sizeof(rowColorDepthStruct): %d.\r\n", sizeof(rowColorDepthStruct)); + + + // Going to need a little more DMA LL memory structure RAM. + // Refer to 'DMA_LL_PAYLOAD_SPLIT' code in configureDMA() below to understand why this exists. + // numDMAdescriptorsPerRow is also used to calcaulte descount which is super important in i2s_parallel_config_t SoC DMA setup. + if ( sizeof(rowColorDepthStruct) > DMA_MAX ) { + + #if SERIAL_DEBUG + Serial.println("Split DMA payload enabled. Increasing DMA descriptor count per frame row by one."); + #endif + + //numDMAdescriptorsPerRow += 1; + numDMAdescriptorsPerRow += PIXEL_COLOR_DEPTH_BITS-1; + // Not if numDMAdescriptorsPerRow is even just one descriptor too large, DMA linked list will not correctly loop. + } + + /*** * Step 3: Allocate memory for DMA linked list, linking up each framebuffer row in sequence for GPIO output. */ _dma_linked_list_memory_required = numDMAdescriptorsPerRow * ROWS_PER_FRAME * _num_frame_buffers * sizeof(lldesc_t); #if SERIAL_DEBUG - Serial.printf("Descriptors for lsbMsbTransitionBit %d/%d with %d rows require %d bytes of DMA RAM\r\n", lsbMsbTransitionBit, PIXEL_COLOR_DEPTH_BITS - 1, ROWS_PER_FRAME, _dma_linked_list_memory_required); + Serial.printf("Descriptors for lsbMsbTransitionBit of %d/%d with %d frame rows require %d bytes of DMA RAM with %d numDMAdescriptorsPerRow.\r\n", lsbMsbTransitionBit, PIXEL_COLOR_DEPTH_BITS - 1, ROWS_PER_FRAME, _dma_linked_list_memory_required, numDMAdescriptorsPerRow); #endif _total_dma_capable_memory_reserved += _dma_linked_list_memory_required; @@ -279,11 +301,18 @@ void RGB64x32MatrixPanel_I2S_DMA::configureDMA(int r1_pin, int g1_pin, int b1_ lldesc_t *previous_dmadesc_b = 0; int current_dmadescriptor_offset = 0; + // HACK: If we need to split the payload in 1/2 so that it doesn't breach DMA_MAX, lets do it by the color_depth. + // We also declare a ridiculously long variable as well... row_bit_struct_color_depth_dma_payload_break_point + int num_dma_payload_color_depths = PIXEL_COLOR_DEPTH_BITS; + if ( sizeof(rowColorDepthStruct) > DMA_MAX ) { + num_dma_payload_color_depths = 1; + } + /* Fill DMA linked lists for both frames (as in, halves of the HUB75 panel) * .. and if double buffering is enabled, link it up for both buffers. */ - for(int j = 0; j < ROWS_PER_FRAME; j++) - { + for(int j = 0; j < ROWS_PER_FRAME; j++) { + // Split framebuffer malloc hack 'improvement' frameStruct *fb_malloc_ptr = matrix_framebuffer_malloc_1; int fb_malloc_j = j; @@ -298,25 +327,55 @@ void RGB64x32MatrixPanel_I2S_DMA::configureDMA(int r1_pin, int g1_pin, int b1_ } #endif - // first set of data is LSB through MSB, single pass - all color bits are displayed once, which takes care of everything below and inlcluding LSBMSB_TRANSITION_BIT - // TODO: size must be less than DMA_MAX - worst case for SmartMatrix Library: 16-bpp with 256 pixels per row would exceed this, need to break into two - link_dma_desc(&dmadesc_a[current_dmadescriptor_offset], previous_dmadesc_a, &(fb_malloc_ptr[0].rowdata[fb_malloc_j].rowbits[0].data), sizeof(rowBitStruct) * PIXEL_COLOR_DEPTH_BITS); - previous_dmadesc_a = &dmadesc_a[current_dmadescriptor_offset]; + #if SERIAL_DEBUG + Serial.printf("DMA payload of %d bytes. DMA_MAX is %d.\r\n", sizeof(rowBitStruct) * PIXEL_COLOR_DEPTH_BITS, DMA_MAX); + #endif + + + // first set of data is LSB through MSB, single pass (IF TOTAL SIZE < DMA_MAX) - all color bits are displayed once, which takes care of everything below and inlcluding LSBMSB_TRANSITION_BIT + // NOTE: size must be less than DMA_MAX - worst case for library: 16-bpp with 256 pixels per row would exceed this, need to break into two + link_dma_desc(&dmadesc_a[current_dmadescriptor_offset], previous_dmadesc_a, &(fb_malloc_ptr[0].rowdata[fb_malloc_j].rowbits[0].data), sizeof(rowBitStruct) * num_dma_payload_color_depths); + previous_dmadesc_a = &dmadesc_a[current_dmadescriptor_offset]; if (double_buffering_enabled) { - link_dma_desc(&dmadesc_b[current_dmadescriptor_offset], previous_dmadesc_b, &(fb_malloc_ptr[1].rowdata[fb_malloc_j].rowbits[0].data), sizeof(rowBitStruct) * PIXEL_COLOR_DEPTH_BITS); - previous_dmadesc_b = &dmadesc_b[current_dmadescriptor_offset]; } - + link_dma_desc(&dmadesc_b[current_dmadescriptor_offset], previous_dmadesc_b, &(fb_malloc_ptr[1].rowdata[fb_malloc_j].rowbits[0].data), sizeof(rowBitStruct) * num_dma_payload_color_depths); + previous_dmadesc_b = &dmadesc_b[current_dmadescriptor_offset]; } + current_dmadescriptor_offset++; + // If the number of pixels per row is to great for the size of a single payload, so we need to split what we were going to send above. + if ( sizeof(rowColorDepthStruct) > DMA_MAX ) { + + #if SERIAL_DEBUG + Serial.printf("Spliting DMA payload for %d color depths into %d byte payloads.\r\n", PIXEL_COLOR_DEPTH_BITS-1, sizeof(rowBitStruct) ); + #endif + + for (int cd = 1; cd < PIXEL_COLOR_DEPTH_BITS; cd++) { + + // first set of data is LSB through MSB, single pass - all color bits are displayed once, which takes care of everything below and inlcluding LSBMSB_TRANSITION_BIT + // TODO: size must be less than DMA_MAX - worst case for library: 16-bpp with 256 pixels per row would exceed this, need to break into two + link_dma_desc(&dmadesc_a[current_dmadescriptor_offset], previous_dmadesc_a, &(fb_malloc_ptr[0].rowdata[fb_malloc_j].rowbits[cd].data), sizeof(rowBitStruct) ); + previous_dmadesc_a = &dmadesc_a[current_dmadescriptor_offset]; + + if (double_buffering_enabled) { + link_dma_desc(&dmadesc_b[current_dmadescriptor_offset], previous_dmadesc_b, &(fb_malloc_ptr[1].rowdata[fb_malloc_j].rowbits[cd].data), sizeof(rowBitStruct) ); + previous_dmadesc_b = &dmadesc_b[current_dmadescriptor_offset]; } + + current_dmadescriptor_offset++; + } // additional linked list items + + } // row depth struct + + for(int i=lsbMsbTransitionBit + 1; i<PIXEL_COLOR_DEPTH_BITS; i++) { + // binary time division setup: we need 2 of bit (LSBMSB_TRANSITION_BIT + 1) four of (LSBMSB_TRANSITION_BIT + 2), etc // because we sweep through to MSB each time, it divides the number of times we have to sweep in half (saving linked list RAM) // we need 2^(i - LSBMSB_TRANSITION_BIT - 1) == 1 << (i - LSBMSB_TRANSITION_BIT - 1) passes from i to MSB //Serial.printf("buffer %d: repeat %d times, size: %d, from %d - %d\r\n", current_dmadescriptor_offset, 1<<(i - lsbMsbTransitionBit - 1), (PIXEL_COLOR_DEPTH_BITS - i), i, PIXEL_COLOR_DEPTH_BITS-1); for(int k=0; k < 1<<(i - lsbMsbTransitionBit - 1); k++) { - + link_dma_desc(&dmadesc_a[current_dmadescriptor_offset], previous_dmadesc_a, &(fb_malloc_ptr[0].rowdata[fb_malloc_j].rowbits[i].data), sizeof(rowBitStruct) * (PIXEL_COLOR_DEPTH_BITS - i)); previous_dmadesc_a = &dmadesc_a[current_dmadescriptor_offset]; @@ -328,10 +387,11 @@ void RGB64x32MatrixPanel_I2S_DMA::configureDMA(int r1_pin, int g1_pin, int b1_ } // end color depth ^ 2 linked list } // end color depth loop + } // end frame rows #if SERIAL_DEBUG - Serial.println("configureDMA(): Configured LL structure.\r\n"); + Serial.printf("configureDMA(): Configured LL structure. %d DMA Linked List descriptors populated.\r\n", current_dmadescriptor_offset); #endif dmadesc_a[desccount-1].eof = 1; diff --git a/ESP32-RGB64x32MatrixPanel-I2S-DMA.h b/ESP32-RGB64x32MatrixPanel-I2S-DMA.h index 1850839..1cc5931 100644 --- a/ESP32-RGB64x32MatrixPanel-I2S-DMA.h +++ b/ESP32-RGB64x32MatrixPanel-I2S-DMA.h @@ -15,7 +15,7 @@ #define SPLIT_MEMORY_MODE 1 /* Use GFX_Root (https://github.com/mrfaptastic/GFX_Root) instead of - * Adafruit_GFX library. No real benefit unless you don't want Bus_IO library. + * Adafruit_GFX library. No real benefit unless you don't want Bus_IO & Wire.h library dependencies. */ //#define USE_GFX_ROOT 1 diff --git a/ESP32-VirtualMatrixPanel-I2S-DMA.h b/ESP32-VirtualMatrixPanel-I2S-DMA.h new file mode 100644 index 0000000..c42b962 --- /dev/null +++ b/ESP32-VirtualMatrixPanel-I2S-DMA.h @@ -0,0 +1,134 @@ +#ifndef _ESP32_VIRTUAL_MATRIX_PANEL_I2S_DMA +#define _ESP32_VIRTUAL_MATRIX_PANEL_I2S_DMA + +/******************************************************************* + Contributed by Brian Lough + YouTube: https://www.youtube.com/brianlough + Tindie: https://www.tindie.com/stores/brianlough/ + Twitter: https://twitter.com/witnessmenow + *******************************************************************/ + +#include "ESP32-RGB64x32MatrixPanel-I2S-DMA.h" + +struct VirtualCoords { + int16_t x; + int16_t y; +}; + + +#ifdef USE_GFX_ROOT +class VirtualMatrixPanel : public GFX +#else +class VirtualMatrixPanel : public Adafruit_GFX +#endif +{ + + public: + int16_t virtualResX; + int16_t virtualResY; + + int16_t module_rows; + int16_t module_cols; + + int16_t screenResX; + int16_t screenResY; + + RGB64x32MatrixPanel_I2S_DMA *display; + +#ifdef USE_GFX_ROOT + VirtualMatrixPanel(RGB64x32MatrixPanel_I2S_DMA &disp, int vmodule_rows, int vmodule_cols, int screenX, int screenY) + : GFX(vmodule_cols*screenX, vmodule_rows*screenY) +#else + VirtualMatrixPanel(RGB64x32MatrixPanel_I2S_DMA &disp, int vmodule_rows, int vmodule_cols, int screenX, int screenY) + : Adafruit_GFX(vmodule_cols*screenX, vmodule_rows*screenY) +#endif + { + this->display = &disp; + module_rows = vmodule_rows; + module_cols = vmodule_cols; + screenResX = screenX; + screenResY = screenY; + + virtualResX = vmodule_rows*screenY; + virtualResY = vmodule_cols*screenX; + } + + VirtualCoords getCoords(int16_t x, int16_t y); + + // equivalent methods of the matrix library so it can be just swapped out. + virtual void drawPixel(int16_t x, int16_t y, uint16_t color); + virtual void fillScreen(uint16_t color); // overwrite adafruit implementation + void clearScreen() { + fillScreen(0); + } + void drawPixelRGB565(int16_t x, int16_t y, uint16_t color); + void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b); + void drawPixelRGB24(int16_t x, int16_t y, rgb_24 color); + void drawIcon (int *ico, int16_t x, int16_t y, int16_t module_cols, int16_t module_rows); + + uint16_t color444(uint8_t r, uint8_t g, uint8_t b) { + return display->color444(r, g, b); + } + uint16_t color565(uint8_t r, uint8_t g, uint8_t b) { + return display->color565(r, g, b); + } + uint16_t Color333(uint8_t r, uint8_t g, uint8_t b) { + return display->Color333(r, g, b); + } + + private: + VirtualCoords coords; + +}; // end Class header + +inline VirtualCoords VirtualMatrixPanel::getCoords(int16_t x, int16_t y) { + int16_t xOffset = (y / screenResY) * (module_cols * screenResX); + coords.x = x + xOffset; + coords.y = y % screenResY; + return coords; +} + +inline void VirtualMatrixPanel::drawPixel(int16_t x, int16_t y, uint16_t color) +{ + VirtualCoords coords = getCoords(x, y); + this->display->drawPixel(coords.x, coords.y, color); +} + +inline void VirtualMatrixPanel::fillScreen(uint16_t color) // adafruit virtual void override +{ + // No need to map this. + this->display->fillScreen(color); +} + +inline void VirtualMatrixPanel::drawPixelRGB565(int16_t x, int16_t y, uint16_t color) +{ + VirtualCoords coords = getCoords(x, y); + this->display->drawPixelRGB565( coords.x, coords.y, color); +} + + +inline void VirtualMatrixPanel::drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b) +{ + VirtualCoords coords = getCoords(x, y); + this->display->drawPixelRGB888( coords.x, coords.y, r, g, b); +} + +inline void VirtualMatrixPanel::drawPixelRGB24(int16_t x, int16_t y, rgb_24 color) +{ + VirtualCoords coords = getCoords(x, y); + this->display->drawPixelRGB24(coords.x, coords.y, color); +} + +// need to recreate this one, as it wouldnt work to just map where it starts. +inline void VirtualMatrixPanel::drawIcon (int *ico, int16_t x, int16_t y, int16_t module_cols, int16_t module_rows) { + int i, j; + for (i = 0; i < module_rows; i++) { + for (j = 0; j < module_cols; j++) { + // This is a call to this libraries version of drawPixel + // which will map each pixel, which is what we want. + drawPixelRGB565 (x + j, y + i, ico[i * module_cols + j]); + } + } +} + +#endif
\ No newline at end of file diff --git a/esp32_i2s_parallel.c b/esp32_i2s_parallel.c index 52a3adc..f45fb96 100644 --- a/esp32_i2s_parallel.c +++ b/esp32_i2s_parallel.c @@ -32,8 +32,6 @@ //#include "esp_heap_caps.h" #include "esp32_i2s_parallel.h" -#define DMA_MAX (4096-4) - typedef struct { volatile lldesc_t *dmadesc_a, *dmadesc_b; int desccount_a, desccount_b; @@ -106,7 +104,8 @@ static void fill_dma_desc(volatile lldesc_t *dmadesc, i2s_parallel_buffer_desc_t // size must be less than DMA_MAX - need to handle breaking long transfer into two descriptors before call // DMA_MAX by the way is the maximum data packet size you can hold in one chunk -void link_dma_desc(volatile lldesc_t *dmadesc, volatile lldesc_t *prevdmadesc, void *memory, size_t size) { +void link_dma_desc(volatile lldesc_t *dmadesc, volatile lldesc_t *prevdmadesc, void *memory, size_t size) +{ if(size > DMA_MAX) size = DMA_MAX; dmadesc->size = size; diff --git a/esp32_i2s_parallel.h b/esp32_i2s_parallel.h index beb59d8..d012422 100644 --- a/esp32_i2s_parallel.h +++ b/esp32_i2s_parallel.h @@ -12,6 +12,9 @@ extern "C" { #include "soc/i2s_struct.h" #include "rom/lldesc.h" +#define DMA_MAX (4096-4) +//#define DMA_MAX (512) + typedef enum { I2S_PARALLEL_BITS_8=8, I2S_PARALLEL_BITS_16=16, |
