commit
80e2a4385c
8 changed files with 736 additions and 449 deletions
|
@ -3,155 +3,371 @@
|
|||
// Credits: Louis Beaudoin <https://github.com/pixelmatix/SmartMatrix/tree/teensylc>
|
||||
// and Sprite_TM: https://www.esp32.com/viewtopic.php?f=17&t=3188 and https://www.esp32.com/viewtopic.php?f=13&t=3256
|
||||
|
||||
void RGB64x32MatrixPanel_I2S_DMA::configureDMA(int r1_pin, int g1_pin, int b1_pin, int r2_pin, int g2_pin, int b2_pin, int a_pin, int b_pin, int c_pin, int d_pin, int e_pin, int lat_pin, int oe_pin, int clk_pin)
|
||||
/*
|
||||
|
||||
This is example code to driver a p3(2121)64*32 -style RGB LED display. These types of displays do not have memory and need to be refreshed
|
||||
continuously. The display has 2 RGB inputs, 4 inputs to select the active line, a pixel clock input, a latch enable input and an output-enable
|
||||
input. The display can be seen as 2 64x16 displays consisting of the upper half and the lower half of the display. Each half has a separate
|
||||
RGB pixel input, the rest of the inputs are shared.
|
||||
|
||||
Each display half can only show one line of RGB pixels at a time: to do this, the RGB data for the line is input by setting the RGB input pins
|
||||
to the desired value for the first pixel, giving the display a clock pulse, setting the RGB input pins to the desired value for the second pixel,
|
||||
giving a clock pulse, etc. Do this 64 times to clock in an entire row. The pixels will not be displayed yet: until the latch input is made high,
|
||||
the display will still send out the previously clocked in line. Pulsing the latch input high will replace the displayed data with the data just
|
||||
clocked in.
|
||||
|
||||
The 4 line select inputs select where the currently active line is displayed: when provided with a binary number (0-15), the latched pixel data
|
||||
will immediately appear on this line. Note: While clocking in data for a line, the *previous* line is still displayed, and these lines should
|
||||
be set to the value to reflect the position the *previous* line is supposed to be on.
|
||||
|
||||
Finally, the screen has an OE input, which is used to disable the LEDs when latching new data and changing the state of the line select inputs:
|
||||
doing so hides any artifacts that appear at this time. The OE line is also used to dim the display by only turning it on for a limited time every
|
||||
line.
|
||||
|
||||
All in all, an image can be displayed by 'scanning' the display, say, 100 times per second. The slowness of the human eye hides the fact that
|
||||
only one line is showed at a time, and the display looks like every pixel is driven at the same time.
|
||||
|
||||
Now, the RGB inputs for these types of displays are digital, meaning each red, green and blue subpixel can only be on or off. This leads to a
|
||||
color palette of 8 pixels, not enough to display nice pictures. To get around this, we use binary code modulation.
|
||||
|
||||
Binary code modulation is somewhat like PWM, but easier to implement in our case. First, we define the time we would refresh the display without
|
||||
binary code modulation as the 'frame time'. For, say, a four-bit binary code modulation, the frame time is divided into 15 ticks of equal length.
|
||||
|
||||
We also define 4 subframes (0 to 3), defining which LEDs are on and which LEDs are off during that subframe. (Subframes are the same as a
|
||||
normal frame in non-binary-coded-modulation mode, but are showed faster.) From our (non-monochrome) input image, we take the (8-bit: bit 7
|
||||
to bit 0) RGB pixel values. If the pixel values have bit 7 set, we turn the corresponding LED on in subframe 3. If they have bit 6 set,
|
||||
we turn on the corresponding LED in subframe 2, if bit 5 is set subframe 1, if bit 4 is set in subframe 0.
|
||||
|
||||
Now, in order to (on average within a frame) turn a LED on for the time specified in the pixel value in the input data, we need to weigh the
|
||||
subframes. We have 15 pixels: if we show subframe 3 for 8 of them, subframe 2 for 4 of them, subframe 1 for 2 of them and subframe 1 for 1 of
|
||||
them, this 'automatically' happens. (We also distribute the subframes evenly over the ticks, which reduces flicker.)
|
||||
|
||||
In this code, we use the I2S peripheral in parallel mode to achieve this. Essentially, first we allocate memory for all subframes. This memory
|
||||
contains a sequence of all the signals (2xRGB, line select, latch enable, output enable) that need to be sent to the display for that subframe.
|
||||
Then we ask the I2S-parallel driver to set up a DMA chain so the subframes are sent out in a sequence that satisfies the requirement that
|
||||
subframe x has to be sent out for (2^x) ticks. Finally, we fill the subframes with image data.
|
||||
|
||||
We use a frontbuffer/backbuffer technique here to make sure the display is refreshed in one go and drawing artifacts do not reach the display.
|
||||
In practice, for small displays this is not really necessarily.
|
||||
|
||||
*/
|
||||
|
||||
#define ESP32_NUM_FRAME_BUFFERS 1
|
||||
|
||||
|
||||
bool RGB64x32MatrixPanel_I2S_DMA::allocateDMAmemory()
|
||||
{
|
||||
// calculate the lowest LSBMSB_TRANSITION_BIT value that will fit in memory
|
||||
int numDescriptorsPerRow;
|
||||
lsbMsbTransitionBit = 0;
|
||||
while(1) {
|
||||
numDescriptorsPerRow = 1;
|
||||
for(int i=lsbMsbTransitionBit + 1; i<COLOR_DEPTH_BITS; i++) {
|
||||
numDescriptorsPerRow += 1<<(i - lsbMsbTransitionBit - 1);
|
||||
|
||||
/***
|
||||
* Step 1: Look at the overall DMA capable memory for the DMA FRAMEBUFFER data only (not the DMA linked list descriptors yet)
|
||||
* and do some pre-checks.
|
||||
*/
|
||||
|
||||
int _num_frame_buffers = (double_buffering_enabled) ? 2:1;
|
||||
size_t _frame_buffer_memory_required = sizeof(frameStruct) * _num_frame_buffers;
|
||||
size_t _dma_linked_list_memory_required = 0;
|
||||
size_t _total_dma_capable_memory_reserved = 0;
|
||||
|
||||
// 1. Calculate and malloc the LARGEST available DMA memory block to matrix_framebuffer_malloc_1
|
||||
#if SERIAL_DEBUG
|
||||
Serial.printf("Panel Height: %d pixels.\r\n", MATRIX_HEIGHT);
|
||||
Serial.printf("Panel Width: %d pixels.\r\n", MATRIX_WIDTH);
|
||||
|
||||
if (double_buffering_enabled) {
|
||||
Serial.println("DOUBLE FRAME BUFFERS / DOUBLE BUFFERING IS ENABLED. DOUBLE THE RAM REQUIRED!");
|
||||
}
|
||||
|
||||
int ramrequired = numDescriptorsPerRow * ROWS_PER_FRAME * ESP32_NUM_FRAME_BUFFERS * sizeof(lldesc_t);
|
||||
ramrequired += 64000; // HACK Hard Coded: Keep at least 64k free!
|
||||
Serial.println("DMA memory blocks available before any malloc's: ");
|
||||
heap_caps_print_heap_info(MALLOC_CAP_DMA);
|
||||
|
||||
|
||||
Serial.printf("FYI: Size of an ESP32 DMA linked list descriptor (lldesc_t) is %d bytes\r\n", sizeof(lldesc_t));
|
||||
Serial.printf("We're going to need %d bytes of SRAM just for the frame buffer(s).\r\n", _frame_buffer_memory_required);
|
||||
Serial.printf("Largest DMA capable SRAM memory block is %d bytes.\r\n", heap_caps_get_largest_free_block(MALLOC_CAP_DMA));
|
||||
#endif
|
||||
|
||||
// Can we fit the framebuffer into the single DMA capable memory block available?
|
||||
if ( heap_caps_get_largest_free_block(MALLOC_CAP_DMA) < _frame_buffer_memory_required ) {
|
||||
|
||||
#if SERIAL_DEBUG
|
||||
Serial.printf("######### Insufficent memory for requested resolution. Reduce MATRIX_COLOR_DEPTH and try again.\r\n\tAdditional %d bytes of memory required.\r\n\r\n", (_frame_buffer_memory_required-heap_caps_get_largest_free_block(MALLOC_CAP_DMA)) );
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Allocate the framebuffer 1 memory, fail if we can even do this
|
||||
matrix_framebuffer_malloc_1 = (frameStruct *)heap_caps_malloc(_frame_buffer_memory_required, MALLOC_CAP_DMA);
|
||||
if ( matrix_framebuffer_malloc_1 == NULL ) {
|
||||
#if SERIAL_DEBUG
|
||||
Serial.println("ERROR: Couldn't malloc matrix_framebuffer_malloc_1! Critical fail.\r\n");
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_total_dma_capable_memory_reserved += _frame_buffer_memory_required;
|
||||
|
||||
// SPLIT MEMORY MODE
|
||||
#ifdef SPLIT_MEMORY_MODE
|
||||
|
||||
Serial.println("SPLIT MEMORY MODE ENABLED!");
|
||||
|
||||
#if SERIAL_DEBUG
|
||||
Serial.print("Rows per frame (overall): "); Serial.println(ROWS_PER_FRAME, DEC);
|
||||
Serial.print("Rows per split framebuffer malloc: "); Serial.println(SPLIT_MEMORY_ROWS_PER_FRAME, DEC);
|
||||
#endif
|
||||
|
||||
|
||||
// Can we fit the framebuffer into the single DMA capable memory block available?
|
||||
// Can we fit the framebuffer into the single DMA capable memory block available?
|
||||
if ( heap_caps_get_largest_free_block(MALLOC_CAP_DMA) < _frame_buffer_memory_required ) {
|
||||
|
||||
#if SERIAL_DEBUG
|
||||
Serial.printf("######### Insufficent memory for second framebuffer for requested resolution. Reduce MATRIX_COLOR_DEPTH and try again.\r\n\tAdditional %d bytes of memory required.\r\n\r\n", (_frame_buffer_memory_required-heap_caps_get_largest_free_block(MALLOC_CAP_DMA)) );
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate the framebuffer 2 memory, fail if we can even do this
|
||||
matrix_framebuffer_malloc_2 = (frameStruct *)heap_caps_malloc(_frame_buffer_memory_required, MALLOC_CAP_DMA);
|
||||
if ( matrix_framebuffer_malloc_2 == NULL ) {
|
||||
#if SERIAL_DEBUG
|
||||
Serial.println("ERROR: Couldn't malloc matrix_framebuffer_malloc_2! Critical fail.\r\n");
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_total_dma_capable_memory_reserved += _frame_buffer_memory_required;
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/***
|
||||
* Step 2: Calculate the amount of memory required for the DMA engine's linked list descriptors.
|
||||
* Credit to SmartMatrix for this stuff.
|
||||
*/
|
||||
|
||||
// Calculate what color depth is actually possible based on memory avaialble vs. required dma linked-list descriptors.
|
||||
// aka. Calculate the lowest LSBMSB_TRANSITION_BIT value that will fit in memory
|
||||
int numDMAdescriptorsPerRow = 0;
|
||||
lsbMsbTransitionBit = 0;
|
||||
while(1) {
|
||||
numDMAdescriptorsPerRow = 1;
|
||||
for(int i=lsbMsbTransitionBit + 1; i<PIXEL_COLOR_DEPTH_BITS; i++) {
|
||||
numDMAdescriptorsPerRow += 1<<(i - lsbMsbTransitionBit - 1);
|
||||
}
|
||||
|
||||
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);
|
||||
#endif
|
||||
|
||||
Serial.printf("lsbMsbTransitionBit of %d requires %d RAM, %d available, leaving %d free: \r\n", lsbMsbTransitionBit, ramrequired, largestblockfree, largestblockfree - ramrequired);
|
||||
|
||||
if(ramrequired < (largestblockfree))
|
||||
if(ramrequired < largestblockfree)
|
||||
break;
|
||||
|
||||
if(lsbMsbTransitionBit < COLOR_DEPTH_BITS - 1)
|
||||
if(lsbMsbTransitionBit < PIXEL_COLOR_DEPTH_BITS - 1)
|
||||
lsbMsbTransitionBit++;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if(numDescriptorsPerRow * ROWS_PER_FRAME * ESP32_NUM_FRAME_BUFFERS * sizeof(lldesc_t) > heap_caps_get_largest_free_block(MALLOC_CAP_DMA)){
|
||||
assert("Not enough RAM for SmartMatrix descriptors");
|
||||
Serial.printf("Not enough RAM for SmartMatrix descriptors\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.printf("Raised lsbMsbTransitionBit to %d/%d to fit in RAM\r\n", lsbMsbTransitionBit, COLOR_DEPTH_BITS - 1);
|
||||
Serial.printf("Raised lsbMsbTransitionBit to %d/%d to fit in remaining RAM\r\n", lsbMsbTransitionBit, PIXEL_COLOR_DEPTH_BITS - 1);
|
||||
|
||||
// calculate the lowest LSBMSB_TRANSITION_BIT value that will fit in memory that will meet or exceed the configured refresh rate
|
||||
while(1) {
|
||||
int psPerClock = 1000000000000UL/ESP32_I2S_CLOCK_SPEED;
|
||||
int nsPerLatch = ((PIXELS_PER_LATCH + CLKS_DURING_LATCH) * psPerClock) / 1000;
|
||||
Serial.printf("ns per latch: %d: \r\n", nsPerLatch);
|
||||
int nsPerLatch = ((PIXELS_PER_ROW + CLKS_DURING_LATCH) * psPerClock) / 1000;
|
||||
|
||||
// add time to shift out LSBs + LSB-MSB transition bit - this ignores fractions...
|
||||
int nsPerRow = COLOR_DEPTH_BITS * nsPerLatch;
|
||||
int nsPerRow = PIXEL_COLOR_DEPTH_BITS * nsPerLatch;
|
||||
|
||||
// add time to shift out MSBs
|
||||
for(int i=lsbMsbTransitionBit + 1; i<COLOR_DEPTH_BITS; i++)
|
||||
nsPerRow += (1<<(i - lsbMsbTransitionBit - 1)) * (COLOR_DEPTH_BITS - i) * nsPerLatch;
|
||||
|
||||
//Serial.printf("nsPerRow: %d: \r\n", nsPerRow);
|
||||
for(int i=lsbMsbTransitionBit + 1; i<PIXEL_COLOR_DEPTH_BITS; i++)
|
||||
nsPerRow += (1<<(i - lsbMsbTransitionBit - 1)) * (PIXEL_COLOR_DEPTH_BITS - i) * nsPerLatch;
|
||||
|
||||
int nsPerFrame = nsPerRow * ROWS_PER_FRAME;
|
||||
Serial.printf("nsPerFrame: %d: \r\n", nsPerFrame);
|
||||
|
||||
int actualRefreshRate = 1000000000UL/(nsPerFrame);
|
||||
|
||||
refreshRate = actualRefreshRate;
|
||||
calculated_refresh_rate = actualRefreshRate;
|
||||
|
||||
Serial.printf("lsbMsbTransitionBit of %d gives %d Hz refresh: \r\n", lsbMsbTransitionBit, actualRefreshRate);
|
||||
|
||||
if (actualRefreshRate > min_refresh_rate) // HACK Hard Coded: 100
|
||||
break;
|
||||
|
||||
|
||||
if(lsbMsbTransitionBit < COLOR_DEPTH_BITS - 1)
|
||||
if(lsbMsbTransitionBit < PIXEL_COLOR_DEPTH_BITS - 1)
|
||||
lsbMsbTransitionBit++;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
Serial.printf("Raised lsbMsbTransitionBit to %d/%d to meet minimum refresh rate\r\n", lsbMsbTransitionBit, COLOR_DEPTH_BITS - 1);
|
||||
Serial.printf("Raised lsbMsbTransitionBit to %d/%d to meet minimum refresh rate\r\n", lsbMsbTransitionBit, PIXEL_COLOR_DEPTH_BITS - 1);
|
||||
|
||||
// TODO: completely fill buffer with data before enabling DMA - can't do this now, lsbMsbTransition bit isn't set in the calc class - also this call will probably have no effect as matrixCalcDivider will skip the first call
|
||||
//matrixCalcCallback();
|
||||
|
||||
// lsbMsbTransition Bit is now finalized - redo descriptor count in case it changed to hit min refresh rate
|
||||
numDescriptorsPerRow = 1;
|
||||
for(int i=lsbMsbTransitionBit + 1; i<COLOR_DEPTH_BITS; i++) {
|
||||
numDescriptorsPerRow += 1<<(i - lsbMsbTransitionBit - 1);
|
||||
// lsbMsbTransition Bit is now finalized - recalcuate descriptor count in case it changed to hit min refresh rate
|
||||
numDMAdescriptorsPerRow = 1;
|
||||
for(int i=lsbMsbTransitionBit + 1; i<PIXEL_COLOR_DEPTH_BITS; i++) {
|
||||
numDMAdescriptorsPerRow += 1<<(i - lsbMsbTransitionBit - 1);
|
||||
}
|
||||
|
||||
Serial.printf("Descriptors for lsbMsbTransitionBit %d/%d with %d rows require %d bytes of DMA RAM\r\n", lsbMsbTransitionBit, COLOR_DEPTH_BITS - 1, ROWS_PER_FRAME, 2 * numDescriptorsPerRow * ROWS_PER_FRAME * sizeof(lldesc_t));
|
||||
/***
|
||||
* 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);
|
||||
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);
|
||||
|
||||
_total_dma_capable_memory_reserved += _dma_linked_list_memory_required;
|
||||
|
||||
// Do a final check to see if we have enough space for the additional DMA linked list descriptors that will be required to link it all up!
|
||||
if(_dma_linked_list_memory_required > heap_caps_get_largest_free_block(MALLOC_CAP_DMA)) {
|
||||
#if SERIAL_DEBUG
|
||||
Serial.printf("ERROR: Not enough SRAM left over for DMA linked-list descriptor memory reservation! Oh so close!\r\n");
|
||||
#endif
|
||||
|
||||
return false;
|
||||
} // linked list descriptors memory check
|
||||
|
||||
// malloc the DMA linked list descriptors that i2s_parallel will need
|
||||
int desccount = numDescriptorsPerRow * ROWS_PER_FRAME;
|
||||
lldesc_t * dmadesc_a = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA);
|
||||
assert("Can't allocate descriptor buffer a");
|
||||
desccount = numDMAdescriptorsPerRow * ROWS_PER_FRAME;
|
||||
|
||||
//lldesc_t * dmadesc_a = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA);
|
||||
dmadesc_a = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA);
|
||||
assert("Can't allocate descriptor framebuffer a");
|
||||
if(!dmadesc_a) {
|
||||
Serial.printf("Could not malloc descriptor buffer a.");
|
||||
return;
|
||||
Serial.printf("Error: Could not malloc descriptor framebuffer a.");
|
||||
return false;
|
||||
}
|
||||
|
||||
lldesc_t * dmadesc_b = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA);
|
||||
assert("Could not malloc descriptor buffer b.");
|
||||
if (double_buffering_enabled) // reserve space for second framebuffer linked list
|
||||
{
|
||||
//lldesc_t * dmadesc_b = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA);
|
||||
dmadesc_b = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA);
|
||||
assert("Could not malloc descriptor framebuffer b.");
|
||||
if(!dmadesc_b) {
|
||||
Serial.printf("can't malloc");
|
||||
return;
|
||||
Serial.printf("Error: Could not malloc descriptor framebuffer b.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Serial.printf("SmartMatrix Mallocs Complete\r\n");
|
||||
Serial.printf("Heap Memory Available: %d bytes total, %d bytes largest free block: \r\n", heap_caps_get_free_size(0), heap_caps_get_largest_free_block(0));
|
||||
Serial.printf("8-bit Accessible Memory Available: %d bytes total, %d bytes largest free block: \r\n", heap_caps_get_free_size(MALLOC_CAP_8BIT), heap_caps_get_largest_free_block(MALLOC_CAP_8BIT));
|
||||
Serial.printf("32-bit Memory Available: %d bytes total, %d bytes largest free block: \r\n", heap_caps_get_free_size(MALLOC_CAP_32BIT), heap_caps_get_largest_free_block(MALLOC_CAP_32BIT));
|
||||
Serial.printf("DMA Memory Available: %d bytes total, %d bytes largest free block: \r\n", heap_caps_get_free_size(MALLOC_CAP_DMA), heap_caps_get_largest_free_block(MALLOC_CAP_DMA));
|
||||
Serial.printf("*** Memory Allocations Complete *** \r\n");
|
||||
Serial.printf("Total memory that was reserved: %d kB.\r\n", _total_dma_capable_memory_reserved/1024);
|
||||
Serial.printf("Heap Memory Available: %d bytes total. Largest free block: %d bytes.\r\n", heap_caps_get_free_size(0), heap_caps_get_largest_free_block(0));
|
||||
Serial.printf("DMA Memory Available: %d bytes total. Largest free block: %d bytes.\r\n", heap_caps_get_free_size(MALLOC_CAP_DMA), heap_caps_get_largest_free_block(MALLOC_CAP_DMA));
|
||||
Serial.printf("General RAM Available: %d bytes total. Largest free block: %d bytes.\r\n", heap_caps_get_free_size(MALLOC_CAP_DEFAULT), heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT));
|
||||
|
||||
lldesc_t *prevdmadesca = 0;
|
||||
lldesc_t *prevdmadescb = 0;
|
||||
int currentDescOffset = 0;
|
||||
|
||||
// fill DMA linked lists for both frames
|
||||
for(int j=0; j<ROWS_PER_FRAME; j++) {
|
||||
#if SERIAL_DEBUG
|
||||
Serial.println("DMA memory blocks available after malloc's: ");
|
||||
heap_caps_print_heap_info(MALLOC_CAP_DMA);
|
||||
|
||||
delay(1000);
|
||||
#endif
|
||||
|
||||
|
||||
return true;
|
||||
|
||||
} // end initMatrixDMABuffer()
|
||||
|
||||
|
||||
|
||||
void RGB64x32MatrixPanel_I2S_DMA::configureDMA(int r1_pin, int g1_pin, int b1_pin, int r2_pin, int g2_pin, int b2_pin, int a_pin, int b_pin, int c_pin, int d_pin, int e_pin, int lat_pin, int oe_pin, int clk_pin)
|
||||
{
|
||||
#if SERIAL_DEBUG
|
||||
Serial.println("configureDMA(): Starting configuration of DMA engine.\r\n");
|
||||
#endif
|
||||
|
||||
|
||||
lldesc_t *previous_dmadesc_a = 0;
|
||||
lldesc_t *previous_dmadesc_b = 0;
|
||||
int current_dmadescriptor_offset = 0;
|
||||
|
||||
/* 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++)
|
||||
{
|
||||
// Split framebuffer malloc hack 'improvement'
|
||||
frameStruct *fb_malloc_ptr = matrix_framebuffer_malloc_1;
|
||||
int fb_malloc_j = j;
|
||||
|
||||
#ifdef SPLIT_MEMORY_MODE
|
||||
if ( j >= SPLIT_MEMORY_ROWS_PER_FRAME ) {
|
||||
fb_malloc_ptr = matrix_framebuffer_malloc_2;
|
||||
fb_malloc_j -= SPLIT_MEMORY_ROWS_PER_FRAME; // IMPORTANT
|
||||
#if SERIAL_DEBUG
|
||||
Serial.printf("Split memory mode. Panel row: %d, mapped to framebuffer malloc 2 offset: %d.\r\n", j, fb_malloc_j);
|
||||
#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
|
||||
// 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[currentDescOffset], prevdmadesca, &(matrixUpdateFrames[0].rowdata[j].rowbits[0].data), sizeof(rowBitStruct) * COLOR_DEPTH_BITS);
|
||||
prevdmadesca = &dmadesc_a[currentDescOffset];
|
||||
link_dma_desc(&dmadesc_b[currentDescOffset], prevdmadescb, &(matrixUpdateFrames[1].rowdata[j].rowbits[0].data), sizeof(rowBitStruct) * COLOR_DEPTH_BITS);
|
||||
prevdmadescb = &dmadesc_b[currentDescOffset];
|
||||
currentDescOffset++;
|
||||
//Serial.printf("row %d: \r\n", j);
|
||||
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];
|
||||
|
||||
for(int i=lsbMsbTransitionBit + 1; i<COLOR_DEPTH_BITS; i++) {
|
||||
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]; }
|
||||
|
||||
current_dmadescriptor_offset++;
|
||||
|
||||
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", nextBufdescIndex, 1<<(i - LSBMSB_TRANSITION_BIT - 1), (COLOR_DEPTH_BITS - i), i, 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++) {
|
||||
link_dma_desc(&dmadesc_a[currentDescOffset], prevdmadesca, &(matrixUpdateFrames[0].rowdata[j].rowbits[i].data), sizeof(rowBitStruct) * (COLOR_DEPTH_BITS - i));
|
||||
prevdmadesca = &dmadesc_a[currentDescOffset];
|
||||
link_dma_desc(&dmadesc_b[currentDescOffset], prevdmadescb, &(matrixUpdateFrames[1].rowdata[j].rowbits[i].data), sizeof(rowBitStruct) * (COLOR_DEPTH_BITS - i));
|
||||
prevdmadescb = &dmadesc_b[currentDescOffset];
|
||||
|
||||
currentDescOffset++;
|
||||
//Serial.printf("i %d, j %d, k %d\r\n", i, j, 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];
|
||||
|
||||
if (double_buffering_enabled) {
|
||||
link_dma_desc(&dmadesc_b[current_dmadescriptor_offset], previous_dmadesc_b, &(fb_malloc_ptr[1].rowdata[fb_malloc_j].rowbits[i].data), sizeof(rowBitStruct) * (PIXEL_COLOR_DEPTH_BITS - i));
|
||||
previous_dmadesc_b = &dmadesc_b[current_dmadescriptor_offset]; }
|
||||
|
||||
current_dmadescriptor_offset++;
|
||||
|
||||
} // end color depth ^ 2 linked list
|
||||
} // end color depth loop
|
||||
} // end frame rows
|
||||
|
||||
#if SERIAL_DEBUG
|
||||
Serial.println("configureDMA(): Configured LL structure.\r\n");
|
||||
#endif
|
||||
|
||||
dmadesc_a[desccount-1].eof = 1;
|
||||
dmadesc_a[desccount-1].qe.stqe_next=(lldesc_t*)&dmadesc_a[0];
|
||||
|
||||
//End markers for DMA LL
|
||||
if (double_buffering_enabled) {
|
||||
dmadesc_b[desccount-1].eof = 1;
|
||||
dmadesc_b[desccount-1].qe.stqe_next=(lldesc_t*)&dmadesc_b[0];
|
||||
} else {
|
||||
dmadesc_b = dmadesc_a; // link to same 'a' buffer
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
//End markers
|
||||
dmadesc_a[desccount-1].eof = 1;
|
||||
dmadesc_b[desccount-1].eof = 1;
|
||||
dmadesc_a[desccount-1].qe.stqe_next=(lldesc_t*)&dmadesc_a[0];
|
||||
dmadesc_b[desccount-1].qe.stqe_next=(lldesc_t*)&dmadesc_b[0];
|
||||
|
||||
Serial.printf("Performing I2S setup.\n");
|
||||
|
||||
*/
|
||||
//Serial.printf("Performing I2S setup.\n");
|
||||
|
||||
i2s_parallel_config_t cfg={
|
||||
.gpio_bus={r1_pin, g1_pin, b1_pin, r2_pin, g2_pin, b2_pin, lat_pin, oe_pin, a_pin, b_pin, c_pin, d_pin, e_pin, -1, -1, -1},
|
||||
.gpio_clk=clk_pin,
|
||||
.clkspeed_hz=ESP32_I2S_CLOCK_SPEED, //ESP32_I2S_CLOCK_SPEED, // formula used is 80000000L/(cfg->clkspeed_hz + 1), must result in >=2. Acceptable values 26.67MHz, 20MHz, 16MHz, 13.34MHz...
|
||||
.bits=MATRIX_I2S_MODE, //MATRIX_I2S_MODE,
|
||||
.bits=ESP32_I2S_DMA_MODE, //ESP32_I2S_DMA_MODE,
|
||||
.bufa=0,
|
||||
.bufb=0,
|
||||
desccount,
|
||||
|
@ -163,42 +379,80 @@ void RGB64x32MatrixPanel_I2S_DMA::configureDMA(int r1_pin, int g1_pin, int b1_
|
|||
//Setup I2S
|
||||
i2s_parallel_setup_without_malloc(&I2S1, &cfg);
|
||||
|
||||
Serial.printf("I2S setup done.\n");
|
||||
#if SERIAL_DEBUG
|
||||
Serial.println("configureDMA(): DMA configuration completed on I2S1.\r\n");
|
||||
#endif
|
||||
|
||||
#if SERIAL_DEBUG
|
||||
Serial.println("DMA Memory Map after DMA LL allocations: ");
|
||||
heap_caps_print_heap_info(MALLOC_CAP_DMA);
|
||||
|
||||
delay(1000);
|
||||
#endif
|
||||
|
||||
// Just os we know
|
||||
dma_configuration_success = true;
|
||||
everything_OK = true;
|
||||
|
||||
} // end initMatrixDMABuff
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* Update a specific co-ordinate in the DMA buffer */
|
||||
void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t y_coord, uint8_t red, uint8_t green, uint8_t blue)
|
||||
{
|
||||
if ( !everything_OK ) {
|
||||
|
||||
if ( !dma_configuration_success)
|
||||
assert("DMA configuration in begin() not performed or completed successfully.");
|
||||
#if SERIAL_DEBUG
|
||||
Serial.println("Cannot updateMatrixDMABuffer as setup failed!");
|
||||
#endif
|
||||
|
||||
// Need to check that the co-ordinates are within range, or it'll break everything big time.
|
||||
if ( x_coord < 0 || y_coord < 0 || x_coord >= MATRIX_WIDTH || y_coord >= MATRIX_HEIGHT)
|
||||
{
|
||||
// Serial.printf("Invalid: x %d, y %d - r %d, g %d, b %d\n", (int)x_coord, (int)y_coord, (int)red, (int)green, (int)blue );
|
||||
// x_coord = y_coord = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
// What half of the HUB75 panel are we painting to?
|
||||
#ifdef SPLIT_MEMORY_MODE
|
||||
#ifdef SERIAL_DEBUG
|
||||
int tmp_y_coord = y_coord;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* 1) Check that the co-ordinates are within range, or it'll break everything big time.
|
||||
* Valid co-ordinates are from 0 to (MATRIX_XXXX-1)
|
||||
*/
|
||||
if ( x_coord < 0 || y_coord < 0 || x_coord >= MATRIX_WIDTH || y_coord >= MATRIX_HEIGHT) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* 2) Convert the vertical axis / y-axis pixel co-ordinate to a matrix panel parallel co-ordinate..
|
||||
* eg. If the y co-ordinate is 23, that's actually in the second half of the panel, row 7.
|
||||
* 23 (y coord) - 16 (for 32px high panel) = 7
|
||||
*/
|
||||
bool paint_top_half = true;
|
||||
if ( y_coord > ROWS_PER_FRAME-1) // co-ords start at zero, y_coord = 15 = 16 (rows per frame)
|
||||
if ( y_coord >= ROWS_PER_FRAME) // co-ords start at zero, y_coord = 15 = 16 (rows per frame)
|
||||
{
|
||||
y_coord -= ROWS_PER_FRAME; // if it's 16, subtract 16. Array position 0 again.
|
||||
y_coord -= ROWS_PER_FRAME; // Subtract the ROWS_PER_FRAME from the pixel co-ord to get the panel co-ord.
|
||||
paint_top_half = false;
|
||||
}
|
||||
|
||||
for(int color_depth_idx=0; color_depth_idx<COLOR_DEPTH_BITS; color_depth_idx++) // color depth - 8 iterations
|
||||
for(int color_depth_idx=0; color_depth_idx<PIXEL_COLOR_DEPTH_BITS; color_depth_idx++) // color depth - 8 iterations
|
||||
{
|
||||
uint16_t mask = (1 << color_depth_idx); // 24 bit color
|
||||
|
||||
// The destination for the pixel bitstream
|
||||
rowBitStruct *p = &matrixUpdateFrames[backbuf_id].rowdata[y_coord].rowbits[color_depth_idx]; //matrixUpdateFrames location to write to uint16_t's
|
||||
//rowBitStruct *p = &matrix_framebuffer_malloc_1[back_buffer_id].rowdata[y_coord].rowbits[color_depth_idx]; //matrixUpdateFrames location to write to uint16_t's
|
||||
rowBitStruct *p = &matrix_framebuffer_malloc_1[back_buffer_id].rowdata[y_coord].rowbits[color_depth_idx];
|
||||
|
||||
#ifdef SPLIT_MEMORY_MODE
|
||||
if (y_coord >= SPLIT_MEMORY_ROWS_PER_FRAME ) {
|
||||
p = &matrix_framebuffer_malloc_2[back_buffer_id].rowdata[(y_coord-SPLIT_MEMORY_ROWS_PER_FRAME)].rowbits[color_depth_idx]; //matrixUpdateFrames location to write to uint16_t's
|
||||
|
||||
#ifdef SERIAL_DEBUG
|
||||
// Serial.printf("fb_malloc_2. y-coord: %d, calc. offset: %d \r\n", tmp_y_coord, (y_coord-SPLIT_MEMORY_ROWS_PER_FRAME) );
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
int v=0; // the output bitstream
|
||||
|
||||
|
@ -216,7 +470,6 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t
|
|||
if (gpioRowAddress & 0x10) v|=BIT_E; // 16
|
||||
|
||||
/* ORIG
|
||||
|
||||
// need to disable OE after latch to hide row transition
|
||||
if((x_coord) == 0) v|=BIT_OE;
|
||||
|
||||
|
@ -225,17 +478,16 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t
|
|||
|
||||
// need to turn off OE one clock before latch, otherwise can get ghosting
|
||||
if((x_coord)==PIXELS_PER_LATCH-1) v|=BIT_OE;
|
||||
|
||||
*/
|
||||
|
||||
// need to disable OE after latch to hide row transition
|
||||
if((x_coord) == 0 ) v|=BIT_OE;
|
||||
|
||||
// drive latch while shifting out last bit of RGB data
|
||||
if((x_coord) == PIXELS_PER_LATCH-1) v|=BIT_LAT;
|
||||
if((x_coord) == PIXELS_PER_ROW-1) v|=BIT_LAT;
|
||||
|
||||
// need to turn off OE one clock before latch, otherwise can get ghosting
|
||||
if((x_coord)==PIXELS_PER_LATCH-2) v|=BIT_OE;
|
||||
if((x_coord)==PIXELS_PER_ROW-2) v|=BIT_OE;
|
||||
|
||||
|
||||
|
||||
|
@ -251,19 +503,18 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t
|
|||
if((x_coord) >= lsbBrightness) v|=BIT_OE; // For Brightness
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* When using the Adafruit drawPixel, we only have one pixel co-ordinate and colour to draw (duh)
|
||||
* so we can't paint a top and bottom half (or whatever row split the panel is) at the same time.
|
||||
* Need to be smart and check the DMA buffer to see what the other half thinks (pun intended)
|
||||
* and persist this when we refresh.
|
||||
/* When using the drawPixel, we are obviously only changing the value of one x,y position,
|
||||
* however, the HUB75 is wired up such that it is always painting TWO lines at the same time
|
||||
* and this reflects the parallel in-DMA-memory data structure of uint16_t's that are getting
|
||||
* pumped out at high speed.
|
||||
*
|
||||
* So we need to ensure we persist the bits (8 of them) of the uint16_t for the row we aren't changing.
|
||||
*
|
||||
* The DMA buffer order has also been reversed (refer to the last code in this function)
|
||||
* so we have to check for this and check the correct position of the MATRIX_DATA_STORAGE_TYPE
|
||||
* data.
|
||||
*/
|
||||
int16_t tmp_x_coord = x_coord;
|
||||
int tmp_x_coord = x_coord;
|
||||
if(x_coord%2)
|
||||
{
|
||||
tmp_x_coord -= 1;
|
||||
|
@ -275,12 +526,9 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t
|
|||
{ // Need to copy what the RGB status is for the bottom pixels
|
||||
|
||||
// Set the color of the pixel of interest
|
||||
if (green & mask)
|
||||
v|=BIT_G1;
|
||||
if (blue & mask)
|
||||
v|=BIT_B1;
|
||||
if (red & mask)
|
||||
v|=BIT_R1;
|
||||
if (green & mask) { v|=BIT_G1; }
|
||||
if (blue & mask) { v|=BIT_B1; }
|
||||
if (red & mask) { v|=BIT_R1; }
|
||||
|
||||
// Persist what was painted to the other half of the frame equiv. pixel
|
||||
if (p->data[tmp_x_coord] & BIT_R2)
|
||||
|
@ -296,12 +544,9 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t
|
|||
{ // Do it the other way around
|
||||
|
||||
// Color to set
|
||||
if (red & mask)
|
||||
v|=BIT_R2;
|
||||
if (green & mask)
|
||||
v|=BIT_G2;
|
||||
if (blue & mask)
|
||||
v|=BIT_B2;
|
||||
if (red & mask) { v|=BIT_R2; }
|
||||
if (green & mask) { v|=BIT_G2; }
|
||||
if (blue & mask) { v|=BIT_B2; }
|
||||
|
||||
// Copy
|
||||
if (p->data[tmp_x_coord] & BIT_R1)
|
||||
|
@ -315,10 +560,6 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t
|
|||
|
||||
} // paint
|
||||
|
||||
//Serial.printf("x: %d, y: %d ", x_coord, y_coord );
|
||||
//Serial.println(v, BIN);
|
||||
|
||||
|
||||
// 16 bit parallel mode
|
||||
//Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering
|
||||
if(x_coord%2){
|
||||
|
@ -329,28 +570,40 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t
|
|||
|
||||
} // color depth loop (8)
|
||||
|
||||
//Show our work!
|
||||
//i2s_parallel_flip_to_buffer(&I2S1, backbuf_id);
|
||||
|
||||
// If we've linked the DMA output to the same backbuf_id that this function is
|
||||
// currently writing too, then the output will be immediate. Else: flipDMABuffer(), then showDMABuffer()
|
||||
|
||||
|
||||
} // updateDMABuffer
|
||||
} // updateMatrixDMABuffer (specific co-ords change)
|
||||
|
||||
|
||||
/* Update the entire buffer with a single specific colour - quicker */
|
||||
void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint8_t red, uint8_t green, uint8_t blue)
|
||||
{
|
||||
if ( !everything_OK ) return;
|
||||
|
||||
for (unsigned int y_coord = 0; y_coord < ROWS_PER_FRAME; y_coord++) // half height - 16 iterations
|
||||
for (unsigned int matrix_frame_parallel_row = 0; matrix_frame_parallel_row < ROWS_PER_FRAME; matrix_frame_parallel_row++) // half height - 16 iterations
|
||||
{
|
||||
for(int color_depth_idx=0; color_depth_idx<COLOR_DEPTH_BITS; color_depth_idx++) // color depth - 8 iterations
|
||||
for(int color_depth_idx=0; color_depth_idx<PIXEL_COLOR_DEPTH_BITS; color_depth_idx++) // color depth - 8 iterations
|
||||
{
|
||||
uint16_t mask = (1 << color_depth_idx); // 24 bit color
|
||||
|
||||
// The destination for the pixel bitstream
|
||||
rowBitStruct *p = &matrixUpdateFrames[backbuf_id].rowdata[y_coord].rowbits[color_depth_idx]; //matrixUpdateFrames location to write to uint16_t's
|
||||
rowBitStruct *p = &matrix_framebuffer_malloc_1[back_buffer_id].rowdata[matrix_frame_parallel_row].rowbits[color_depth_idx]; //matrixUpdateFrames location to write to uint16_t's
|
||||
|
||||
#ifdef SPLIT_MEMORY_MODE
|
||||
if (matrix_frame_parallel_row >= SPLIT_MEMORY_ROWS_PER_FRAME ) {
|
||||
p = &matrix_framebuffer_malloc_2[back_buffer_id].rowdata[(matrix_frame_parallel_row-SPLIT_MEMORY_ROWS_PER_FRAME)].rowbits[color_depth_idx]; //matrixUpdateFrames location to write to uint16_t's
|
||||
|
||||
#ifdef SERIAL_DEBUG
|
||||
//Serial.printf("Using framebuffer_malloc_2. Row %d = Offset %d\r\n", matrix_frame_parallel_row, (matrix_frame_parallel_row-SPLIT_MEMORY_ROWS_PER_FRAME) );
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef SERIAL_DEBUG
|
||||
// Serial.printf("Using framebuffer_malloc_1. Row %d\r\n", matrix_frame_parallel_row );
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
for(int x_coord=0; x_coord < MATRIX_WIDTH; x_coord++) // row pixel width 64 iterations
|
||||
{
|
||||
|
@ -358,11 +611,11 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint8_t red, uint8_t gre
|
|||
int v=0; // the output bitstream
|
||||
|
||||
// if there is no latch to hold address, output ADDX lines directly to GPIO and latch data at end of cycle
|
||||
int gpioRowAddress = y_coord;
|
||||
int gpioRowAddress = matrix_frame_parallel_row;
|
||||
|
||||
// normally output current rows ADDX, special case for LSB, output previous row's ADDX (as previous row is being displayed for one latch cycle)
|
||||
if(color_depth_idx == 0)
|
||||
gpioRowAddress = y_coord-1;
|
||||
gpioRowAddress = matrix_frame_parallel_row-1;
|
||||
|
||||
if (gpioRowAddress & 0x01) v|=BIT_A; // 1
|
||||
if (gpioRowAddress & 0x02) v|=BIT_B; // 2
|
||||
|
@ -372,7 +625,6 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint8_t red, uint8_t gre
|
|||
|
||||
|
||||
/* ORIG
|
||||
|
||||
// need to disable OE after latch to hide row transition
|
||||
if((x_coord) == 0) v|=BIT_OE;
|
||||
|
||||
|
@ -381,17 +633,16 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint8_t red, uint8_t gre
|
|||
|
||||
// need to turn off OE one clock before latch, otherwise can get ghosting
|
||||
if((x_coord)==PIXELS_PER_LATCH-1) v|=BIT_OE;
|
||||
|
||||
*/
|
||||
|
||||
// need to disable OE after latch to hide row transition
|
||||
if((x_coord) == 0 ) v|=BIT_OE;
|
||||
|
||||
// drive latch while shifting out last bit of RGB data
|
||||
if((x_coord) == PIXELS_PER_LATCH-1) v|=BIT_LAT;
|
||||
if((x_coord) == PIXELS_PER_ROW-1) v|=BIT_LAT;
|
||||
|
||||
// need to turn off OE one clock before latch, otherwise can get ghosting
|
||||
if((x_coord)==PIXELS_PER_LATCH-2) v|=BIT_OE;
|
||||
if((x_coord)==PIXELS_PER_ROW-2) v|=BIT_OE;
|
||||
|
||||
|
||||
// turn off OE after brightness value is reached when displaying MSBs
|
||||
|
@ -406,28 +657,14 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint8_t red, uint8_t gre
|
|||
if((x_coord) >= lsbBrightness) v|=BIT_OE; // For Brightness
|
||||
}
|
||||
|
||||
|
||||
// Top half colours
|
||||
if (green & mask)
|
||||
v|=BIT_G1;
|
||||
if (blue & mask)
|
||||
v|=BIT_B1;
|
||||
if (red & mask)
|
||||
v|=BIT_R1;
|
||||
|
||||
// Bottom half colours
|
||||
if (red & mask)
|
||||
v|=BIT_R2;
|
||||
if (green & mask)
|
||||
v|=BIT_G2;
|
||||
if (blue & mask)
|
||||
v|=BIT_B2;
|
||||
|
||||
// Top and bottom matrix MATRIX_ROWS_IN_PARALLEL half colours
|
||||
if (green & mask) { v|=BIT_G1; v|=BIT_R2; }
|
||||
if (blue & mask) { v|=BIT_B1; v|=BIT_G2; }
|
||||
if (red & mask) { v|=BIT_R1; v|=BIT_B2; }
|
||||
|
||||
// 16 bit parallel mode
|
||||
//Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering
|
||||
if(x_coord%2)
|
||||
{
|
||||
if(x_coord%2) {
|
||||
p->data[(x_coord)-1] = v;
|
||||
} else {
|
||||
p->data[(x_coord)+1] = v;
|
||||
|
@ -437,12 +674,4 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint8_t red, uint8_t gre
|
|||
} // colour depth loop (8)
|
||||
} // end row iteration
|
||||
|
||||
|
||||
|
||||
//Show our work!
|
||||
//i2s_parallel_flip_to_buffer(&I2S1, backbuf_id);
|
||||
|
||||
// If we've linked the DMA output to the same backbuf_id that this function is
|
||||
// currently writing too, then the output will be immediate. Else: flipDMABuffer(), then showDMABuffer()
|
||||
|
||||
} // updateDMABuffer
|
||||
} // updateMatrixDMABuffer (full frame paint)
|
|
@ -9,61 +9,18 @@
|
|||
#include "esp_heap_caps.h"
|
||||
#include "esp32_i2s_parallel.h"
|
||||
|
||||
#include "Adafruit_GFX.h"
|
||||
|
||||
/*
|
||||
|
||||
This is example code to driver a p3(2121)64*32 -style RGB LED display. These types of displays do not have memory and need to be refreshed
|
||||
continuously. The display has 2 RGB inputs, 4 inputs to select the active line, a pixel clock input, a latch enable input and an output-enable
|
||||
input. The display can be seen as 2 64x16 displays consisting of the upper half and the lower half of the display. Each half has a separate
|
||||
RGB pixel input, the rest of the inputs are shared.
|
||||
|
||||
Each display half can only show one line of RGB pixels at a time: to do this, the RGB data for the line is input by setting the RGB input pins
|
||||
to the desired value for the first pixel, giving the display a clock pulse, setting the RGB input pins to the desired value for the second pixel,
|
||||
giving a clock pulse, etc. Do this 64 times to clock in an entire row. The pixels will not be displayed yet: until the latch input is made high,
|
||||
the display will still send out the previously clocked in line. Pulsing the latch input high will replace the displayed data with the data just
|
||||
clocked in.
|
||||
|
||||
The 4 line select inputs select where the currently active line is displayed: when provided with a binary number (0-15), the latched pixel data
|
||||
will immediately appear on this line. Note: While clocking in data for a line, the *previous* line is still displayed, and these lines should
|
||||
be set to the value to reflect the position the *previous* line is supposed to be on.
|
||||
|
||||
Finally, the screen has an OE input, which is used to disable the LEDs when latching new data and changing the state of the line select inputs:
|
||||
doing so hides any artifacts that appear at this time. The OE line is also used to dim the display by only turning it on for a limited time every
|
||||
line.
|
||||
|
||||
All in all, an image can be displayed by 'scanning' the display, say, 100 times per second. The slowness of the human eye hides the fact that
|
||||
only one line is showed at a time, and the display looks like every pixel is driven at the same time.
|
||||
|
||||
Now, the RGB inputs for these types of displays are digital, meaning each red, green and blue subpixel can only be on or off. This leads to a
|
||||
color palette of 8 pixels, not enough to display nice pictures. To get around this, we use binary code modulation.
|
||||
|
||||
Binary code modulation is somewhat like PWM, but easier to implement in our case. First, we define the time we would refresh the display without
|
||||
binary code modulation as the 'frame time'. For, say, a four-bit binary code modulation, the frame time is divided into 15 ticks of equal length.
|
||||
|
||||
We also define 4 subframes (0 to 3), defining which LEDs are on and which LEDs are off during that subframe. (Subframes are the same as a
|
||||
normal frame in non-binary-coded-modulation mode, but are showed faster.) From our (non-monochrome) input image, we take the (8-bit: bit 7
|
||||
to bit 0) RGB pixel values. If the pixel values have bit 7 set, we turn the corresponding LED on in subframe 3. If they have bit 6 set,
|
||||
we turn on the corresponding LED in subframe 2, if bit 5 is set subframe 1, if bit 4 is set in subframe 0.
|
||||
|
||||
Now, in order to (on average within a frame) turn a LED on for the time specified in the pixel value in the input data, we need to weigh the
|
||||
subframes. We have 15 pixels: if we show subframe 3 for 8 of them, subframe 2 for 4 of them, subframe 1 for 2 of them and subframe 1 for 1 of
|
||||
them, this 'automatically' happens. (We also distribute the subframes evenly over the ticks, which reduces flicker.)
|
||||
|
||||
In this code, we use the I2S peripheral in parallel mode to achieve this. Essentially, first we allocate memory for all subframes. This memory
|
||||
contains a sequence of all the signals (2xRGB, line select, latch enable, output enable) that need to be sent to the display for that subframe.
|
||||
Then we ask the I2S-parallel driver to set up a DMA chain so the subframes are sent out in a sequence that satisfies the requirement that
|
||||
subframe x has to be sent out for (2^x) ticks. Finally, we fill the subframes with image data.
|
||||
|
||||
We use a frontbuffer/backbuffer technique here to make sure the display is refreshed in one go and drawing artifacts do not reach the display.
|
||||
In practice, for small displays this is not really necessarily.
|
||||
|
||||
*/
|
||||
#include "GFX.h" // Adafruit GFX core class -> https://github.com/mrfaptastic/GFX_Root
|
||||
|
||||
/***************************************************************************************/
|
||||
/* Serial Debugging Output on or off */
|
||||
/* Library compile-time options */
|
||||
|
||||
#define SERIAL_DEBUG_OUTPUT 1
|
||||
// Enable serial debugging of the library, to see how memory is allocated etc.
|
||||
#define SERIAL_DEBUG 1
|
||||
|
||||
// Experimental: Split the framebuffer into two smaller memory allocations.
|
||||
// Can allow a bigger resolution due to the fragmented memory
|
||||
// map of the typical arduino sketch even before setup().
|
||||
//#define SPLIT_MEMORY_MODE 1
|
||||
|
||||
/***************************************************************************************/
|
||||
/* HUB75 RGB pixel WIDTH and HEIGHT.
|
||||
|
@ -78,7 +35,7 @@
|
|||
*
|
||||
*/
|
||||
#ifndef MATRIX_HEIGHT
|
||||
#define MATRIX_HEIGHT 32 //64
|
||||
#define MATRIX_HEIGHT 32
|
||||
#endif
|
||||
|
||||
#ifndef MATRIX_WIDTH
|
||||
|
@ -89,6 +46,8 @@
|
|||
#define MATRIX_ROWS_IN_PARALLEL 2
|
||||
#endif
|
||||
|
||||
#define PIXEL_COLOR_DEPTH_BITS 8 // 8bit per RGB color = 24 bit/per pixel, reduce to save RAM
|
||||
|
||||
/***************************************************************************************/
|
||||
/* ESP32 Pin Definition. You can change this, but best if you keep it as is... */
|
||||
|
||||
|
@ -103,15 +62,16 @@
|
|||
#define B_PIN_DEFAULT 19
|
||||
#define C_PIN_DEFAULT 5
|
||||
#define D_PIN_DEFAULT 17
|
||||
#define E_PIN_DEFAULT -1 // Change to a valid pin if using a 64 pixel row panel.
|
||||
#define E_PIN_DEFAULT -1 // IMPORTANT: Change to a valid pin if using a 64x64px panel.
|
||||
|
||||
#define LAT_PIN_DEFAULT 4
|
||||
#define OE_PIN_DEFAULT 15
|
||||
|
||||
#define CLK_PIN_DEFAULT 16
|
||||
|
||||
// Interesting Fact: We end up using a uint16_t to send data in parallel to the HUB75... but
|
||||
// given we only map to 14 physical output wires/bits, we waste 2 bits.
|
||||
/***************************************************************************************/
|
||||
/* Don't change this stuff unless you know what you are doing */
|
||||
/* Keep this as is. Do not change. */
|
||||
|
||||
// Panel Upper half RGB (numbering according to order in DMA gpio_bus configuration)
|
||||
#define BIT_R1 (1<<0)
|
||||
|
@ -136,37 +96,51 @@
|
|||
|
||||
// RGB Panel Constants / Calculated Values
|
||||
#define COLOR_CHANNELS_PER_PIXEL 3
|
||||
#define PIXELS_PER_LATCH ((MATRIX_WIDTH * MATRIX_HEIGHT) / MATRIX_HEIGHT) // = 64
|
||||
#define COLOR_DEPTH_BITS (COLOR_DEPTH/COLOR_CHANNELS_PER_PIXEL) // = 8
|
||||
#define PIXELS_PER_ROW ((MATRIX_WIDTH * MATRIX_HEIGHT) / MATRIX_HEIGHT) // = 64
|
||||
//#define PIXEL_COLOR_DEPTH_BITS (MATRIX_COLOR_DEPTH/COLOR_CHANNELS_PER_PIXEL) // = 8
|
||||
#define ROWS_PER_FRAME (MATRIX_HEIGHT/MATRIX_ROWS_IN_PARALLEL) // = 16
|
||||
|
||||
/***************************************************************************************/
|
||||
/* Keep this as is. Do not change. */
|
||||
#define CLKS_DURING_LATCH 0
|
||||
#define MATRIX_I2S_MODE I2S_PARALLEL_BITS_16
|
||||
#define MATRIX_DATA_STORAGE_TYPE uint16_t
|
||||
|
||||
#define ESP32_NUM_FRAME_BUFFERS 2
|
||||
#define ESP32_I2S_CLOCK_SPEED (20000000UL)
|
||||
#define COLOR_DEPTH 24
|
||||
|
||||
#define ESP32_I2S_DMA_MODE I2S_PARALLEL_BITS_16 // Pump 16 bits out in parallel
|
||||
#define ESP32_I2S_DMA_STORAGE_TYPE uint16_t // one uint16_t at a time.
|
||||
//#define ESP32_I2S_CLOCK_SPEED (20000000UL) // @ 20Mhz
|
||||
#define ESP32_I2S_CLOCK_SPEED (10000000UL) // @ 10Mhz
|
||||
#define CLKS_DURING_LATCH 0 // Not used.
|
||||
/***************************************************************************************/
|
||||
|
||||
|
||||
// note: sizeof(data) must be multiple of 32 bits, as ESP32 DMA linked list buffer address pointer must be word-aligned.
|
||||
/* rowBitStruct
|
||||
* Note: sizeof(data) must be multiple of 32 bits, as ESP32 DMA linked list buffer address pointer
|
||||
* must be word-aligned.
|
||||
*/
|
||||
struct rowBitStruct {
|
||||
MATRIX_DATA_STORAGE_TYPE data[((MATRIX_WIDTH * MATRIX_HEIGHT) / MATRIX_HEIGHT) + CLKS_DURING_LATCH];
|
||||
// this evaluates to just MATRIX_DATA_STORAGE_TYPE data[64] really;
|
||||
// and array of 64 uint16_t's
|
||||
ESP32_I2S_DMA_STORAGE_TYPE data[PIXELS_PER_ROW + CLKS_DURING_LATCH];
|
||||
// This evaluates to just data[64] really.. an array of 64 uint16_t's
|
||||
};
|
||||
|
||||
/* rowColorDepthStruct
|
||||
* Duplicates of row bit structure, but for each color 'depth'ness.
|
||||
*/
|
||||
struct rowColorDepthStruct {
|
||||
rowBitStruct rowbits[COLOR_DEPTH_BITS];
|
||||
rowBitStruct rowbits[PIXEL_COLOR_DEPTH_BITS];
|
||||
};
|
||||
|
||||
/* frameStruct
|
||||
* Note: This 'frameStruct' will contain ALL the data for a full-frame as BOTH 2x16-row frames are
|
||||
* are contained in parallel within the one uint16_t that is sent in parallel to the HUB75.
|
||||
*/
|
||||
#ifdef SPLIT_MEMORY_MODE
|
||||
//#pragma message("Split DMA Memory Allocation Mode Enabled")
|
||||
#define SPLIT_MEMORY_ROWS_PER_FRAME (ROWS_PER_FRAME/2)
|
||||
struct frameStruct {
|
||||
rowColorDepthStruct rowdata[SPLIT_MEMORY_ROWS_PER_FRAME];
|
||||
};
|
||||
#else
|
||||
struct frameStruct {
|
||||
rowColorDepthStruct rowdata[ROWS_PER_FRAME];
|
||||
};
|
||||
#endif
|
||||
|
||||
typedef struct rgb_24 {
|
||||
rgb_24() : rgb_24(0,0,0) {}
|
||||
|
@ -182,29 +156,27 @@ typedef struct rgb_24 {
|
|||
|
||||
|
||||
/***************************************************************************************/
|
||||
class RGB64x32MatrixPanel_I2S_DMA : public Adafruit_GFX {
|
||||
class RGB64x32MatrixPanel_I2S_DMA : public GFX {
|
||||
// ------- PUBLIC -------
|
||||
public:
|
||||
RGB64x32MatrixPanel_I2S_DMA(bool _doubleBuffer = false) // Double buffer is disabled by default. Any change will display next active DMA buffer output (very quickly). NOTE: Not Implemented
|
||||
: Adafruit_GFX(MATRIX_WIDTH, MATRIX_HEIGHT), doubleBuffer(_doubleBuffer) {
|
||||
|
||||
backbuf_id = 0;
|
||||
brightness = 16; // If you get ghosting... reduce brightness level. 60 seems to be the limit before ghosting on a 64 pixel wide physical panel for some panels
|
||||
min_refresh_rate = 250; // Probably best to leave as is unless you want to experiment. Framerate has an impact on brightness and also power draw - voltage ripple.
|
||||
/**
|
||||
* RGB64x32MatrixPanel_I2S_DMA
|
||||
*
|
||||
* @param {bool} _double_buffer : Double buffer is disabled by default. Enable only if you know what you're doing. Manual switching required with flipDMABuffer() and showDMABuffer()
|
||||
*
|
||||
*/
|
||||
RGB64x32MatrixPanel_I2S_DMA(bool _double_buffer = false)
|
||||
: GFX(MATRIX_WIDTH, MATRIX_HEIGHT), double_buffering_enabled(_double_buffer) {
|
||||
|
||||
}
|
||||
|
||||
// Painfully propagate the DMA pin configuration, or use compiler defaults
|
||||
void begin(int dma_r1_pin = R1_PIN_DEFAULT , int dma_g1_pin = G1_PIN_DEFAULT, int dma_b1_pin = B1_PIN_DEFAULT , int dma_r2_pin = R2_PIN_DEFAULT , int dma_g2_pin = G2_PIN_DEFAULT , int dma_b2_pin = B2_PIN_DEFAULT , int dma_a_pin = A_PIN_DEFAULT , int dma_b_pin = B_PIN_DEFAULT , int dma_c_pin = C_PIN_DEFAULT , int dma_d_pin = D_PIN_DEFAULT , int dma_e_pin = E_PIN_DEFAULT , int dma_lat_pin = LAT_PIN_DEFAULT, int dma_oe_pin = OE_PIN_DEFAULT , int dma_clk_pin = CLK_PIN_DEFAULT)
|
||||
/* Propagate the DMA pin configuration, or use compiler defaults */
|
||||
bool begin(int dma_r1_pin = R1_PIN_DEFAULT , int dma_g1_pin = G1_PIN_DEFAULT, int dma_b1_pin = B1_PIN_DEFAULT , int dma_r2_pin = R2_PIN_DEFAULT , int dma_g2_pin = G2_PIN_DEFAULT , int dma_b2_pin = B2_PIN_DEFAULT , int dma_a_pin = A_PIN_DEFAULT , int dma_b_pin = B_PIN_DEFAULT , int dma_c_pin = C_PIN_DEFAULT , int dma_d_pin = D_PIN_DEFAULT , int dma_e_pin = E_PIN_DEFAULT , int dma_lat_pin = LAT_PIN_DEFAULT, int dma_oe_pin = OE_PIN_DEFAULT , int dma_clk_pin = CLK_PIN_DEFAULT)
|
||||
{
|
||||
|
||||
/* As DMA buffers are dynamically allocated, we must allocated in begin()
|
||||
* Ref: https://github.com/espressif/arduino-esp32/issues/831
|
||||
*/
|
||||
allocateDMAbuffers();
|
||||
|
||||
// Change 'if' to '1' to enable, 0 to not include this Serial output in compiled program
|
||||
#if SERIAL_DEBUG_OUTPUT
|
||||
#if SERIAL_DEBUG
|
||||
Serial.printf("Using pin %d for the R1_PIN\n", dma_r1_pin);
|
||||
Serial.printf("Using pin %d for the G1_PIN\n", dma_g1_pin);
|
||||
Serial.printf("Using pin %d for the B1_PIN\n", dma_b1_pin);
|
||||
|
@ -216,12 +188,16 @@ class RGB64x32MatrixPanel_I2S_DMA : public Adafruit_GFX {
|
|||
Serial.printf("Using pin %d for the C_PIN\n", dma_c_pin);
|
||||
Serial.printf("Using pin %d for the D_PIN\n", dma_d_pin);
|
||||
Serial.printf("Using pin %d for the E_PIN\n", dma_e_pin);
|
||||
|
||||
Serial.printf("Using pin %d for the LAT_PIN\n", dma_lat_pin);
|
||||
Serial.printf("Using pin %d for the OE_PIN\n", dma_oe_pin);
|
||||
Serial.printf("Using pin %d for the CLK_PIN\n", dma_clk_pin);
|
||||
#endif
|
||||
|
||||
/* As DMA buffers are dynamically allocated, we must allocated in begin()
|
||||
* Ref: https://github.com/espressif/arduino-esp32/issues/831
|
||||
*/
|
||||
if ( !allocateDMAmemory() ) { return false; } // couldn't even get the basic ram required.
|
||||
|
||||
|
||||
// Flush the DMA buffers prior to configuring DMA - Avoid visual artefacts on boot.
|
||||
clearScreen(); // Must fill the DMA buffer with the initial output bit sequence or the panel will display garbage
|
||||
|
@ -232,7 +208,14 @@ class RGB64x32MatrixPanel_I2S_DMA : public Adafruit_GFX {
|
|||
// Setup the ESP32 DMA Engine. Sprite_TM built this stuff.
|
||||
configureDMA(dma_r1_pin, dma_g1_pin, dma_b1_pin, dma_r2_pin, dma_g2_pin, dma_b2_pin, dma_a_pin, dma_b_pin, dma_c_pin, dma_d_pin, dma_e_pin, dma_lat_pin, dma_oe_pin, dma_clk_pin ); //DMA and I2S configuration and setup
|
||||
|
||||
showDMABuffer(); // show 0
|
||||
showDMABuffer(); // show backbuf_id of 0
|
||||
|
||||
#if SERIAL_DEBUG
|
||||
if (!everything_OK)
|
||||
Serial.println("RGB64x32MatrixPanel_I2S_DMA::begin() failed.");
|
||||
#endif
|
||||
|
||||
return everything_OK;
|
||||
|
||||
}
|
||||
|
||||
|
@ -258,17 +241,26 @@ class RGB64x32MatrixPanel_I2S_DMA : public Adafruit_GFX {
|
|||
|
||||
inline void flipDMABuffer()
|
||||
{
|
||||
// Flip to other buffer as the backbuffer. i.e. Graphic changes happen to this buffer (but aren't displayed until showDMABuffer())
|
||||
backbuf_id ^=1;
|
||||
if ( !double_buffering_enabled) return;
|
||||
|
||||
#if SERIAL_DEBUG_OUTPUT
|
||||
Serial.printf("Set back buffer to: %d\n", backbuf_id);
|
||||
// Flip to other buffer as the backbuffer. i.e. Graphic changes happen to this buffer (but aren't displayed until showDMABuffer())
|
||||
back_buffer_id ^= 1;
|
||||
|
||||
#if SERIAL_DEBUG
|
||||
Serial.printf("Set back buffer to: %d\n", back_buffer_id);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void showDMABuffer()
|
||||
{
|
||||
i2s_parallel_flip_to_buffer(&I2S1, backbuf_id);
|
||||
|
||||
if (!double_buffering_enabled) return;
|
||||
|
||||
#if SERIAL_DEBUG
|
||||
Serial.printf("Showtime for buffer: %d\n", back_buffer_id);
|
||||
#endif
|
||||
|
||||
i2s_parallel_flip_to_buffer(&I2S1, back_buffer_id);
|
||||
}
|
||||
|
||||
|
||||
|
@ -283,42 +275,46 @@ class RGB64x32MatrixPanel_I2S_DMA : public Adafruit_GFX {
|
|||
min_refresh_rate = rr;
|
||||
}
|
||||
|
||||
int calculated_refresh_rate = 0;
|
||||
|
||||
|
||||
// ------- PRIVATE -------
|
||||
private:
|
||||
|
||||
void allocateDMAbuffers()
|
||||
{
|
||||
matrixUpdateFrames = (frameStruct *)heap_caps_malloc(sizeof(frameStruct) * ESP32_NUM_FRAME_BUFFERS, MALLOC_CAP_DMA);
|
||||
Serial.printf("Allocating DMA Refresh Buffer...\r\nTotal DMA Memory available: %d bytes total. Largest free block: %d bytes\r\n", heap_caps_get_free_size(MALLOC_CAP_DMA), heap_caps_get_largest_free_block(MALLOC_CAP_DMA));
|
||||
/* Pixel data is organized from LSB to MSB sequentially by row, from row 0 to row matrixHeight/matrixRowsInParallel
|
||||
* (two rows of pixels are refreshed in parallel) */
|
||||
frameStruct *matrix_framebuffer_malloc_1;
|
||||
|
||||
} // end initMatrixDMABuffer()
|
||||
#ifdef SPLIT_MEMORY_MODE
|
||||
/* In the case there's a 2nd non-contiguous block of DMA capable SRAM we can use, let us try and use that as well. */
|
||||
frameStruct *matrix_framebuffer_malloc_2;
|
||||
#endif
|
||||
|
||||
// ESP 32 DMA Linked List descriptor
|
||||
int desccount = 0;
|
||||
lldesc_t * dmadesc_a = {0};
|
||||
lldesc_t * dmadesc_b = {0};
|
||||
|
||||
// ESP32-RGB64x32MatrixPanel-I2S-DMA functioning
|
||||
bool everything_OK = false;
|
||||
bool double_buffering_enabled = false;// Do we use double buffer mode? Your project code will have to manually flip between both.
|
||||
int back_buffer_id = 0; // If using double buffer, which one is NOT active (ie. being displayed) to write too?
|
||||
int brightness = 32; // If you get ghosting... reduce brightness level. 60 seems to be the limit before ghosting on a 64 pixel wide physical panel for some panels.
|
||||
int min_refresh_rate = 120; // Probably best to leave as is unless you want to experiment. Framerate has an impact on brightness and also power draw - voltage ripple.
|
||||
int lsbMsbTransitionBit = 0; // For possible color depth calculations
|
||||
|
||||
/* Calculate the memory available for DMA use, do some other stuff, and allocate accordingly */
|
||||
bool allocateDMAmemory();
|
||||
|
||||
/* Setup the DMA Link List chain and initiate the ESP32 DMA engine */
|
||||
void configureDMA(int r1_pin, int g1_pin, int b1_pin, int r2_pin, int g2_pin, int b2_pin, int a_pin, int b_pin, int c_pin, int d_pin, int e_pin, int lat_pin, int oe_pin, int clk_pin); // Get everything setup. Refer to the .c file
|
||||
|
||||
|
||||
// Update a specific pixel in the DMA buffer to a colour
|
||||
/* Update a specific pixel in the DMA buffer to a colour */
|
||||
void updateMatrixDMABuffer(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue);
|
||||
|
||||
// Update the entire DMA buffer (aka. The RGB Panel) a certain colour (wipe the screen basically)
|
||||
/* Update the entire DMA buffer (aka. The RGB Panel) a certain colour (wipe the screen basically) */
|
||||
void updateMatrixDMABuffer(uint8_t red, uint8_t green, uint8_t blue);
|
||||
|
||||
// Pixel data is organized from LSB to MSB sequentially by row, from row 0 to row matrixHeight/matrixRowsInParallel (two rows of pixels are refreshed in parallel)
|
||||
frameStruct *matrixUpdateFrames;
|
||||
|
||||
// Setup
|
||||
bool dma_configuration_success;
|
||||
|
||||
// Internal variables
|
||||
bool doubleBuffer; // Do we use double buffer mode? Your project code will have to manually flip between both.
|
||||
int backbuf_id; // If using double buffer, which one is NOT active (ie. being displayed) to write too?
|
||||
|
||||
int lsbMsbTransitionBit;
|
||||
int refreshRate;
|
||||
int brightness;
|
||||
int min_refresh_rate;
|
||||
|
||||
|
||||
}; // end Class header
|
||||
|
||||
/***************************************************************************************/
|
||||
|
@ -329,36 +325,6 @@ inline void RGB64x32MatrixPanel_I2S_DMA::drawPixel(int16_t x, int16_t y, uint16_
|
|||
drawPixelRGB565( x, y, color);
|
||||
}
|
||||
|
||||
|
||||
|
||||
inline void RGB64x32MatrixPanel_I2S_DMA::drawIcon (int *ico, int16_t x, int16_t y, int16_t cols, int16_t rows) {
|
||||
/* drawIcon draws a C style bitmap.
|
||||
// Example 10x5px bitmap of a yellow sun
|
||||
//
|
||||
int half_sun [50] = {
|
||||
0x0000, 0x0000, 0x0000, 0xffe0, 0x0000, 0x0000, 0xffe0, 0x0000, 0x0000, 0x0000,
|
||||
0x0000, 0xffe0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffe0, 0x0000,
|
||||
0x0000, 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0x0000, 0x0000,
|
||||
0xffe0, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0xffe0,
|
||||
0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0x0000,
|
||||
};
|
||||
|
||||
RGB64x32MatrixPanel_I2S_DMA matrix;
|
||||
|
||||
matrix.drawIcon (half_sun, 0,0,10,5);
|
||||
*/
|
||||
|
||||
int i, j;
|
||||
for (i = 0; i < rows; i++) {
|
||||
for (j = 0; j < cols; j++) {
|
||||
drawPixelRGB565 (x + j, y + i, ico[i * cols + j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
inline void RGB64x32MatrixPanel_I2S_DMA::fillScreen(uint16_t color) // adafruit virtual void override
|
||||
{
|
||||
uint8_t r = ((((color >> 11) & 0x1F) * 527) + 23) >> 6;
|
||||
|
@ -388,7 +354,6 @@ inline void RGB64x32MatrixPanel_I2S_DMA::drawPixelRGB24(int16_t x, int16_t y, rg
|
|||
updateMatrixDMABuffer( x, y, color.red, color.green, color.blue);
|
||||
}
|
||||
|
||||
|
||||
// Pass 8-bit (each) R,G,B, get back 16-bit packed color
|
||||
//https://github.com/squix78/ILI9341Buffer/blob/master/ILI9341_SPI.cpp
|
||||
inline uint16_t RGB64x32MatrixPanel_I2S_DMA::color565(uint8_t r, uint8_t g, uint8_t b) {
|
||||
|
@ -402,7 +367,6 @@ inline uint16_t RGB64x32MatrixPanel_I2S_DMA::color565(uint8_t r, uint8_t g, uint
|
|||
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
|
||||
}
|
||||
|
||||
|
||||
// Promote 3/3/3 RGB to Adafruit_GFX 5/6/5 RRRrrGGGgggBBBbb
|
||||
inline uint16_t RGB64x32MatrixPanel_I2S_DMA::Color333(uint8_t r, uint8_t g, uint8_t b) {
|
||||
|
||||
|
@ -410,4 +374,30 @@ inline uint16_t RGB64x32MatrixPanel_I2S_DMA::Color333(uint8_t r, uint8_t g, uint
|
|||
|
||||
}
|
||||
|
||||
|
||||
inline void RGB64x32MatrixPanel_I2S_DMA::drawIcon (int *ico, int16_t x, int16_t y, int16_t cols, int16_t rows) {
|
||||
/* drawIcon draws a C style bitmap.
|
||||
// Example 10x5px bitmap of a yellow sun
|
||||
//
|
||||
int half_sun [50] = {
|
||||
0x0000, 0x0000, 0x0000, 0xffe0, 0x0000, 0x0000, 0xffe0, 0x0000, 0x0000, 0x0000,
|
||||
0x0000, 0xffe0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffe0, 0x0000,
|
||||
0x0000, 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0x0000, 0x0000,
|
||||
0xffe0, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0xffe0,
|
||||
0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0x0000,
|
||||
};
|
||||
|
||||
RGB64x32MatrixPanel_I2S_DMA matrix;
|
||||
|
||||
matrix.drawIcon (half_sun, 0,0,10,5);
|
||||
*/
|
||||
|
||||
int i, j;
|
||||
for (i = 0; i < rows; i++) {
|
||||
for (j = 0; j < cols; j++) {
|
||||
drawPixelRGB565 (x + j, y + i, ico[i * cols + j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
43
README.md
43
README.md
|
@ -1,16 +1,16 @@
|
|||
# Adafruit_GFX RGB64x32MatrixPanel library for the ESP32, utilising I2S DMA.
|
||||
# HUB75 LED matrix library for the ESP32, utilising DMA
|
||||
|
||||
This ESP32 Arduino library for an RGB LED (HUB 75 type) Matrix Panel, utilises the DMA functionality provided by the ESP32's I2S 'LCD Mode' which basically means that pixel data can be sent straight from memory, via the DMA controller, to the relevant LED Matrix GPIO pins with little CPU overhead.
|
||||
This ESP32 Arduino library for an RGB LED (HUB 75 type) Matrix Panel, utilises the DMA functionality provided by the ESP32's I2S 'LCD Mode' which basically means that pixel data is sent straight from memory, via the DMA controller, to the relevant LED Matrix GPIO pins with little CPU overhead.
|
||||
|
||||
As a result, this library can theoretically provide ~20 bit colour, at various brightness levels without resulting in noticeable flicker.
|
||||
As a result, this library can theoretically provide ~16-24 bit colour, at various brightness levels without noticeable flicker.
|
||||
|
||||
![It's better in real life](image.jpg)
|
||||
|
||||
# Installation
|
||||
|
||||
* Dependency: You will need to install [Adafruit_GFX_Library](https://github.com/adafruit/Adafruit-GFX-Library) from the "Library > Manage Libraries" menu.
|
||||
* Download and unzip this repository into your Arduino/libraries folder (or better still, use the Arudino 'add library from .zip' option.
|
||||
* Library also test to work fine with PlatformIO, install into your PlatformIO projects' lib/ folder as appropriate.
|
||||
* Dependency: You will need to install [GFX_Root](https://github.com/ZinggJM/GFX_Root) from the "Library > Manage Libraries" menu.
|
||||
* Download and unzip this repository into your Arduino/libraries folder (or better still, use the Arduino 'add library from .zip' option.
|
||||
* Library also tested to work fine with PlatformIO, install into your PlatformIO projects' lib/ folder as appropriate.
|
||||
|
||||
# Wiring ESP32 with the LED Matrix Panel
|
||||
|
||||
|
@ -57,26 +57,32 @@ However, if you want to change this, simply provide the wanted pin mapping as pa
|
|||
display.begin(R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN ); // setup the LED matrix
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
The panel must be powered by 5V AC adapter with enough current capacity. (Current varies due to how many LED are turned on at the same time. To drive all the LEDs, you need 5V4A adapter.)
|
||||
|
||||
|
||||
A [typical RGB panel available for purchase](https://www.aliexpress.com/item/256-128mm-64-32-pixels-1-16-Scan-Indoor-3in1-SMD2121-RGB-full-color-P4-led/32810362851.html). This is of the larger P4 (4mm spacing between pixels) type.
|
||||
|
||||
# Usage
|
||||
## Can I chain panels or use with larger panels?
|
||||
|
||||
Refer to the excellent PxMatrix based library for how to wire one of these panels up (in fact, it's probably best to first get your panel working with this library first to be sure): https://github.com/2dom/PxMatrix
|
||||
Yes you can. If you want to use with a 64x64 pixel panel (typically a HUB75*E* panel) you MUST configure a valid *E_PIN* to your ESP32 and connect it to the E pin of the HUB75 panel! Hence the 'E' in 'HUB75E'
|
||||
|
||||
You'll need to adjust the pin configuration in this library of course.
|
||||
|
||||
## Can I chain displays?
|
||||
This library has only been tested with a 64 pixel (wide) and 32 (high) RGB panel. Theoretically, if you want to chain two of these horizontally to make a 128x32 panel you can do so with the cable and then set the MATRIX_WIDTH to '128'.
|
||||
|
||||
All of this is memory permitting of course (dependant on your sketch etc.) ... An ESP32 only has about 100kB of usable DMA memory, so you will have trouble dispalying any more than 32 (height) x 128 (width) pixels.
|
||||
### New Feature
|
||||
|
||||
# Ghosting
|
||||
With version 1.1.0 of the library onwards, there is now a 'feature' to split the framebuffer over two memory 'blocks' (mallocs) to work around the fact that typically the ESP32 upon 'boot up' has 2 x ~100kb chunks of memory available to use (ideally we'd want one massive chunk, but for whatever reasons this isn't the case). This allows non-contiguous memory allocations to be joined-up potentially allowing for 512x64 resolution or greater. No guarantees however.
|
||||
|
||||
```
|
||||
// At the top of 'ESP32-RGB64x32MatrixPanel-I2S-DMA.h':
|
||||
//
|
||||
// Experimental: Split the framebuffer into two smaller memory allocations.
|
||||
// Can allow a bigger resolution due to the fragmented memory
|
||||
// map of the typical Arduino sketch even before setup().
|
||||
#define SPLIT_MEMORY_MODE 1
|
||||
```
|
||||
Refer to [to this](SPLIT_MEMORY_MODE.md) for more details.
|
||||
|
||||
## Ghosting
|
||||
|
||||
If you experience ghosting, you will need to reduce the brightness level, not all RGB Matrix Panels are the same - some seem to display ghosting artefacts at lower brightness levels. In the setup() function do something like:
|
||||
|
||||
|
@ -88,12 +94,11 @@ void setup() {
|
|||
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
The value to pass 'setPanelBrightness' is the RGB Matrix's pixel width or less. i.e. 64 or lower. However values greater than 60 can cause ghosting it seems on some panels. YMMV.
|
||||
The value to pass 'setPanelBrightness' is the RGB Matrix's pixel width or less. i.e. Approx. 50 or lower. Values greater than 60 can cause ghosting it seems on some panels.
|
||||
|
||||
## Credits
|
||||
## Inspiration
|
||||
|
||||
* 'SmartMatrix' project code: https://github.com/pixelmatix/SmartMatrix/tree/teensylc
|
||||
|
||||
|
|
59
SPLIT_MEMORY_MODE.md
Normal file
59
SPLIT_MEMORY_MODE.md
Normal file
|
@ -0,0 +1,59 @@
|
|||
# SPLIT_MEMORY_MODE
|
||||
To enable split framebuffer memory mode, uncomment line:
|
||||
```
|
||||
//#define SPLIT_MEMORY_MODE 1
|
||||
```
|
||||
in 'ESP32-RGB64x32MatrixPanel-I2S-DMA.h'
|
||||
|
||||
## What is it trying to resolve?
|
||||
|
||||
For whatever reason, and this may not be consistent across all ESP32 environments, when `heap_caps_print_heap_info(MALLOC_CAP_DMA)` is executed to print information about the available memory blocks that are DMA capable (which we need for a DMA-enabled pixel framebuffer), you may see something like this:
|
||||
|
||||
```
|
||||
Heap summary for capabilities 0x00000008:
|
||||
At 0x3ffbdb28 len 52 free 4 allocated 0 min_free 4
|
||||
largest_free_block 4 alloc_blocks 0 free_blocks 1 total_blocks 1
|
||||
At 0x3ffb8000 len 6688 free 5256 allocated 1208 min_free 5256
|
||||
largest_free_block 5256 alloc_blocks 11 free_blocks 1 total_blocks 12
|
||||
At 0x3ffb0000 len 25480 free 17172 allocated 8228 min_free 17172
|
||||
largest_free_block 17172 alloc_blocks 2 free_blocks 1 total_blocks 3
|
||||
At 0x3ffae6e0 len 6192 free 6092 allocated 36 min_free 6092
|
||||
largest_free_block 6092 alloc_blocks 1 free_blocks 1 total_blocks 2
|
||||
At 0x3ffaff10 len 240 free 0 allocated 120 min_free 0
|
||||
largest_free_block 0 alloc_blocks 5 free_blocks 1 total_blocks 6
|
||||
At 0x3ffb6388 len 7288 free 0 allocated 6920 min_free 0
|
||||
largest_free_block 0 alloc_blocks 21 free_blocks 0 total_blocks 21
|
||||
At 0x3ffb9a20 len 16648 free 6300 allocated 9680 min_free 348
|
||||
largest_free_block 4980 alloc_blocks 38 free_blocks 4 total_blocks 42
|
||||
At 0x3ffc1818 len 124904 free 124856 allocated 0 min_free 124856
|
||||
largest_free_block 124856 alloc_blocks 0 free_blocks 1 total_blocks 1
|
||||
At 0x3ffe0440 len 15072 free 15024 allocated 0 min_free 15024
|
||||
largest_free_block 15024 alloc_blocks 0 free_blocks 1 total_blocks 1
|
||||
At 0x3ffe4350 len 113840 free 113792 allocated 0 min_free 113792
|
||||
largest_free_block 113792 alloc_blocks 0 free_blocks 1 total_blocks 1
|
||||
Totals:
|
||||
free 288496 allocated 26192 min_free 282544 largest_free_block 124856
|
||||
```
|
||||
|
||||
So what? Well if you look closely, you will probably see two lines (blocks) like this:
|
||||
|
||||
```
|
||||
At 0x3ffc1818 len 124904 free 124856 allocated 0 min_free 124856
|
||||
largest_free_block 124856 alloc_blocks 0 free_blocks 1 total_blocks 1
|
||||
...
|
||||
At 0x3ffe4350 len 113840 free 113792 allocated 0 min_free 113792
|
||||
largest_free_block 113792 alloc_blocks 0 free_blocks 1 total_blocks 1
|
||||
```
|
||||
|
||||
What this means is there are two blocks of DMA capable memory that are about 100kB each.
|
||||
|
||||
The previous library was only able to use the largest single block of DMA capable memory. Now we can join the two largest together.
|
||||
|
||||
Given it's possible to display 128x32 with double buffering in approx. 100kB of RAM. If we use two memory blocks, and disable double buffering (which is now the default), theoretically we should be able to display 256x64 or greater resolution.
|
||||
|
||||
# Caveats
|
||||
When SPLIT_MEMORY_MODE is enabled, the library will divide the memory framebuffer (pixel buffer) in half and split over two blocks of the same size. Therefore, if one of the free DMA memory blocks is SMALLER than 'half' the framebuffer, failure will occur.
|
||||
|
||||
I.e. From the above example, we could not have any half be greater than 113792 bytes.
|
||||
|
||||
Experimentation will be required as available memory is highly dependant on other stuff you have in your sketch. It is best to include and use the 'ESP32-RGB64x32MatrixPanel-I2S-DMA' library as early as possible in your code and analyse the serial output of `heap_caps_print_heap_info(MALLOC_CAP_DMA)` to see what DMA memory blocks are available.
|
|
@ -29,9 +29,11 @@
|
|||
#include "driver/periph_ctrl.h"
|
||||
#include "soc/io_mux_reg.h"
|
||||
#include "rom/lldesc.h"
|
||||
#include "esp_heap_caps.h"
|
||||
//#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;
|
||||
|
@ -61,9 +63,7 @@ static void IRAM_ATTR i2s_isr(void* arg) {
|
|||
if(shiftCompleteCallback)
|
||||
shiftCompleteCallback();
|
||||
}
|
||||
|
||||
#define DMA_MAX (4096-4)
|
||||
|
||||
/*
|
||||
//Calculate the amount of dma descs needed for a buffer desc
|
||||
static int calc_needed_dma_descs_for(i2s_parallel_buffer_desc_t *desc) {
|
||||
int ret=0;
|
||||
|
@ -102,8 +102,10 @@ static void fill_dma_desc(volatile lldesc_t *dmadesc, i2s_parallel_buffer_desc_t
|
|||
|
||||
printf("fill_dma_desc: filled %d descriptors\n", n);
|
||||
}
|
||||
*/
|
||||
|
||||
// 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) {
|
||||
if(size > DMA_MAX) size = DMA_MAX;
|
||||
|
||||
|
@ -128,7 +130,6 @@ static void gpio_setup_out(int gpio, int sig) {
|
|||
gpio_matrix_out(gpio, sig, false, false);
|
||||
}
|
||||
|
||||
|
||||
static void dma_reset(i2s_dev_t *dev) {
|
||||
dev->lc_conf.in_rst=1; dev->lc_conf.in_rst=0;
|
||||
dev->lc_conf.out_rst=1; dev->lc_conf.out_rst=0;
|
||||
|
@ -147,14 +148,14 @@ void i2s_parallel_setup_without_malloc(i2s_dev_t *dev, const i2s_parallel_config
|
|||
sig_data_base=I2S0O_DATA_OUT0_IDX;
|
||||
sig_clk=I2S0O_WS_OUT_IDX;
|
||||
} else {
|
||||
printf("Setting up i2s parallel mode in %d bit mode!\n", cfg->bits);
|
||||
if (cfg->bits==I2S_PARALLEL_BITS_32) {
|
||||
sig_data_base=I2S1O_DATA_OUT0_IDX;
|
||||
} else if (cfg->bits==I2S_PARALLEL_BITS_16) {
|
||||
//Because of... reasons... the 16-bit values for i2s1 appear on d8...d23
|
||||
printf("Setting up i2s parallel mode in 16 bit mode!");
|
||||
sig_data_base=I2S1O_DATA_OUT8_IDX;
|
||||
} else { // I2S_PARALLEL_BITS_8
|
||||
printf("Setting up i2s parallel mode in 8 bit mode -> https://www.esp32.com/viewtopic.php?f=17&t=3188 | https://www.esp32.com/viewtopic.php?f=13&t=3256");
|
||||
//printf("Setting up i2s parallel mode in %d bit mode -> https://www.esp32.com/viewtopic.php?f=17&t=3188 | https://www.esp32.com/viewtopic.php?f=13&t=3256", 8);
|
||||
sig_data_base=I2S1O_DATA_OUT0_IDX;
|
||||
}
|
||||
sig_clk=I2S1O_WS_OUT_IDX;
|
||||
|
@ -251,7 +252,7 @@ void i2s_parallel_setup_without_malloc(i2s_dev_t *dev, const i2s_parallel_config
|
|||
// allocate a level 1 intterupt: lowest priority, as ISR isn't urgent and may take a long time to complete
|
||||
esp_intr_alloc(ETS_I2S1_INTR_SOURCE, (int)(ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL1), i2s_isr, NULL, NULL);
|
||||
|
||||
//Start dma on front buffer
|
||||
//Start dma on front buffer (buffer a)
|
||||
dev->lc_conf.val=I2S_OUT_DATA_BURST_EN | I2S_OUTDSCR_BURST_EN | I2S_OUT_DATA_BURST_EN;
|
||||
dev->out_link.addr=((uint32_t)(&st->dmadesc_a[0]));
|
||||
dev->out_link.start=1;
|
||||
|
@ -281,4 +282,5 @@ bool i2s_parallel_is_previous_buffer_free() {
|
|||
return previousBufferFree;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
@ -13,7 +13,7 @@ extern "C" {
|
|||
#include "rom/lldesc.h"
|
||||
|
||||
typedef enum {
|
||||
I2S_PARALLEL_BITS_8=8, // BUG: Doesn't work.
|
||||
I2S_PARALLEL_BITS_8=8,
|
||||
I2S_PARALLEL_BITS_16=16,
|
||||
I2S_PARALLEL_BITS_32=32,
|
||||
} i2s_parallel_cfg_bits_t;
|
||||
|
@ -29,18 +29,20 @@ typedef struct {
|
|||
int clkspeed_hz;
|
||||
i2s_parallel_cfg_bits_t bits;
|
||||
i2s_parallel_buffer_desc_t *bufa;
|
||||
i2s_parallel_buffer_desc_t *bufb;
|
||||
i2s_parallel_buffer_desc_t *bufb; // only used with double buffering
|
||||
int desccount_a;
|
||||
int desccount_b;
|
||||
int desccount_b; // only used with double buffering
|
||||
lldesc_t * lldesc_a;
|
||||
lldesc_t * lldesc_b;
|
||||
lldesc_t * lldesc_b; // only used with double buffering
|
||||
} i2s_parallel_config_t;
|
||||
|
||||
void i2s_parallel_setup(i2s_dev_t *dev, const i2s_parallel_config_t *cfg);
|
||||
void i2s_parallel_setup_without_malloc(i2s_dev_t *dev, const i2s_parallel_config_t *cfg);
|
||||
void link_dma_desc(volatile lldesc_t *dmadesc, volatile lldesc_t *prevdmadesc, void *memory, size_t size);
|
||||
|
||||
void i2s_parallel_flip_to_buffer(i2s_dev_t *dev, int bufid);
|
||||
bool i2s_parallel_is_previous_buffer_free();
|
||||
void link_dma_desc(volatile lldesc_t *dmadesc, volatile lldesc_t *prevdmadesc, void *memory, size_t size);
|
||||
|
||||
|
||||
|
||||
typedef void (*callback)(void);
|
||||
void setShiftCompleteCallback(callback f);
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"name": "Faptastic",
|
||||
"url": "https://github.com/mrfaptastic/"
|
||||
},
|
||||
"version": "1.0.2",
|
||||
"version": "1.1.0",
|
||||
"frameworks": "arduino",
|
||||
"platforms": "esp32",
|
||||
"examples": [
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name=ESP32 64x32 LED MATRIX HUB75 DMA Display
|
||||
version=1.0.2
|
||||
version=1.1.0
|
||||
author=Faptastic
|
||||
maintainer=Faptastic
|
||||
sentence=Experimental DMA based LED Matrix HUB75 Library
|
||||
|
|
Loading…
Reference in a new issue