Improvement to ensure DMA payload limit not hit.

This commit is contained in:
mrfaptastic 2020-08-02 22:03:16 +01:00
parent 80d5ddff4c
commit 3159202ccf
5 changed files with 216 additions and 20 deletions

View file

@ -156,7 +156,7 @@ bool RGB64x32MatrixPanel_I2S_DMA::allocateDMAmemory()
int ramrequired = numDMAdescriptorsPerRow * ROWS_PER_FRAME * _num_frame_buffers * sizeof(lldesc_t); int ramrequired = numDMAdescriptorsPerRow * ROWS_PER_FRAME * _num_frame_buffers * sizeof(lldesc_t);
int largestblockfree = heap_caps_get_largest_free_block(MALLOC_CAP_DMA); int largestblockfree = heap_caps_get_largest_free_block(MALLOC_CAP_DMA);
#if SERIAL_DEBUG #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 #endif
if(ramrequired < largestblockfree) if(ramrequired < largestblockfree)
@ -187,8 +187,8 @@ bool RGB64x32MatrixPanel_I2S_DMA::allocateDMAmemory()
calculated_refresh_rate = actualRefreshRate; calculated_refresh_rate = actualRefreshRate;
#if SERIAL_DEBUG #if SERIAL_DEBUG
Serial.printf("lsbMsbTransitionBit of %d gives %d Hz refresh: \r\n", lsbMsbTransitionBit, actualRefreshRate); Serial.printf("lsbMsbTransitionBit of %d gives %d Hz refresh: \r\n", lsbMsbTransitionBit, actualRefreshRate);
#endif #endif
if (actualRefreshRate > min_refresh_rate) // HACK Hard Coded: 100 if (actualRefreshRate > min_refresh_rate) // HACK Hard Coded: 100
break; 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); 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; numDMAdescriptorsPerRow = 1;
for(int i=lsbMsbTransitionBit + 1; i<PIXEL_COLOR_DEPTH_BITS; i++) { for(int i=lsbMsbTransitionBit + 1; i<PIXEL_COLOR_DEPTH_BITS; i++) {
numDMAdescriptorsPerRow += 1<<(i - lsbMsbTransitionBit - 1); 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. * 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); _dma_linked_list_memory_required = numDMAdescriptorsPerRow * ROWS_PER_FRAME * _num_frame_buffers * sizeof(lldesc_t);
#if SERIAL_DEBUG #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 #endif
_total_dma_capable_memory_reserved += _dma_linked_list_memory_required; _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; lldesc_t *previous_dmadesc_b = 0;
int current_dmadescriptor_offset = 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) /* 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. * .. 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' // Split framebuffer malloc hack 'improvement'
frameStruct *fb_malloc_ptr = matrix_framebuffer_malloc_1; frameStruct *fb_malloc_ptr = matrix_framebuffer_malloc_1;
int fb_malloc_j = j; int fb_malloc_j = j;
@ -298,25 +327,55 @@ void RGB64x32MatrixPanel_I2S_DMA::configureDMA(int r1_pin, int g1_pin, int b1_
} }
#endif #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 #if SERIAL_DEBUG
// 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 Serial.printf("DMA payload of %d bytes. DMA_MAX is %d.\r\n", sizeof(rowBitStruct) * PIXEL_COLOR_DEPTH_BITS, DMA_MAX);
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); #endif
previous_dmadesc_a = &dmadesc_a[current_dmadescriptor_offset];
// 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) { 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); 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]; } previous_dmadesc_b = &dmadesc_b[current_dmadescriptor_offset]; }
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++) { 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 // 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) // 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 // 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); //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++) { 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)); 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]; 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 ^ 2 linked list
} // end color depth loop } // end color depth loop
} // end frame rows } // end frame rows
#if SERIAL_DEBUG #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 #endif
dmadesc_a[desccount-1].eof = 1; dmadesc_a[desccount-1].eof = 1;

View file

@ -15,7 +15,7 @@
#define SPLIT_MEMORY_MODE 1 #define SPLIT_MEMORY_MODE 1
/* Use GFX_Root (https://github.com/mrfaptastic/GFX_Root) instead of /* 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 //#define USE_GFX_ROOT 1

View file

@ -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

View file

@ -32,8 +32,6 @@
//#include "esp_heap_caps.h" //#include "esp_heap_caps.h"
#include "esp32_i2s_parallel.h" #include "esp32_i2s_parallel.h"
#define DMA_MAX (4096-4)
typedef struct { typedef struct {
volatile lldesc_t *dmadesc_a, *dmadesc_b; volatile lldesc_t *dmadesc_a, *dmadesc_b;
int desccount_a, desccount_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 // 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 // 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; if(size > DMA_MAX) size = DMA_MAX;
dmadesc->size = size; dmadesc->size = size;

View file

@ -12,6 +12,9 @@ extern "C" {
#include "soc/i2s_struct.h" #include "soc/i2s_struct.h"
#include "rom/lldesc.h" #include "rom/lldesc.h"
#define DMA_MAX (4096-4)
//#define DMA_MAX (512)
typedef enum { typedef enum {
I2S_PARALLEL_BITS_8=8, I2S_PARALLEL_BITS_8=8,
I2S_PARALLEL_BITS_16=16, I2S_PARALLEL_BITS_16=16,