ESP32-HUB75-MatrixPanel-DMA/ESP32-RGB64x32MatrixPanel-I2S-DMA.cpp
mrfaptastic da358dcb1f rename setBrightness to setPanelBrightness
For some strange reason it causes a panic. Some other arduino function must be conflicting.
2019-07-29 12:57:53 +01:00

448 lines
19 KiB
C++

#include "ESP32-RGB64x32MatrixPanel-I2S-DMA.h"
// 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)
{
// 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);
}
int ramrequired = numDescriptorsPerRow * ROWS_PER_FRAME * ESP32_NUM_FRAME_BUFFERS * sizeof(lldesc_t);
ramrequired += 64000; // HACK Hard Coded: Keep at least 64k free!
int largestblockfree = heap_caps_get_largest_free_block(MALLOC_CAP_DMA);
Serial.printf("lsbMsbTransitionBit of %d requires %d RAM, %d available, leaving %d free: \r\n", lsbMsbTransitionBit, ramrequired, largestblockfree, largestblockfree - ramrequired);
if(ramrequired < (largestblockfree))
break;
if(lsbMsbTransitionBit < 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);
// 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);
// add time to shift out LSBs + LSB-MSB transition bit - this ignores fractions...
int nsPerRow = 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);
int nsPerFrame = nsPerRow * ROWS_PER_FRAME;
Serial.printf("nsPerFrame: %d: \r\n", nsPerFrame);
int actualRefreshRate = 1000000000UL/(nsPerFrame);
refreshRate = actualRefreshRate;
Serial.printf("lsbMsbTransitionBit of %d gives %d Hz refresh: \r\n", lsbMsbTransitionBit, actualRefreshRate);
if (actualRefreshRate > 100) // HACK Hard Coded: Minimum frame rate of 150
break;
if(lsbMsbTransitionBit < 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);
// 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);
}
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));
// 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");
if(!dmadesc_a) {
Serial.printf("Could not malloc descriptor buffer a.");
return;
}
lldesc_t * dmadesc_b = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA);
assert("Could not malloc descriptor buffer b.");
if(!dmadesc_b) {
Serial.printf("can't malloc");
return;
}
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));
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++) {
// 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);
for(int i=lsbMsbTransitionBit + 1; i<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);
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);
}
}
}
//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");
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,
.bufa=0,
.bufb=0,
desccount,
desccount,
dmadesc_a,
dmadesc_b
};
//Setup I2S
i2s_parallel_setup_without_malloc(&I2S1, &cfg);
Serial.printf("I2S setup done.\n");
// Just os we know
dma_configuration_success = 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 ( !dma_configuration_success)
assert("DMA configuration in begin() not performed or completed successfully.");
// 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?
bool paint_top_half = true;
if ( y_coord > ROWS_PER_FRAME-1) // 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.
paint_top_half = false;
}
for(int color_depth_idx=0; color_depth_idx<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
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;
// 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;
if (gpioRowAddress & 0x01) v|=BIT_A; // 1
if (gpioRowAddress & 0x02) v|=BIT_B; // 2
if (gpioRowAddress & 0x04) v|=BIT_C; // 4
if (gpioRowAddress & 0x08) v|=BIT_D; // 8
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;
// drive latch while shifting out last bit of RGB data
if((x_coord) == PIXELS_PER_LATCH-1) v|=BIT_LAT;
// 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;
// need to turn off OE one clock before latch, otherwise can get ghosting
if((x_coord)==PIXELS_PER_LATCH-2) v|=BIT_OE;
// turn off OE after brightness value is reached when displaying MSBs
// MSBs always output normal brightness
// LSB (!color_depth_idx) outputs normal brightness as MSB from previous row is being displayed
if((color_depth_idx > lsbMsbTransitionBit || !color_depth_idx) && ((x_coord) >= brightness)) v|=BIT_OE; // For Brightness
// special case for the bits *after* LSB through (lsbMsbTransitionBit) - OE is output after data is shifted, so need to set OE to fractional brightness
if(color_depth_idx && color_depth_idx <= lsbMsbTransitionBit) {
// divide brightness in half for each bit below lsbMsbTransitionBit
int lsbBrightness = brightness >> (lsbMsbTransitionBit - color_depth_idx + 1);
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.
*
* 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;
if(x_coord%2)
{
tmp_x_coord -= 1;
} else {
tmp_x_coord += 1;
} // end reordering
if (paint_top_half)
{ // 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;
// Persist what was painted to the other half of the frame equiv. pixel
if (p->data[tmp_x_coord] & BIT_R2)
v|=BIT_R2;
if (p->data[tmp_x_coord] & BIT_G2)
v|=BIT_G2;
if (p->data[tmp_x_coord] & BIT_B2)
v|=BIT_B2;
}
else
{ // 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;
// Copy
if (p->data[tmp_x_coord] & BIT_R1)
v|=BIT_R1;
if (p->data[tmp_x_coord] & BIT_G1)
v|=BIT_G1;
if (p->data[tmp_x_coord] & BIT_B1)
v|=BIT_B1;
} // 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){
p->data[(x_coord)-1] = v;
} else {
p->data[(x_coord)+1] = v;
} // end reordering
} // 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
/* Update the entire buffer with a single specific colour - quicker */
void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint8_t red, uint8_t green, uint8_t blue)
{
for (unsigned int y_coord = 0; y_coord < ROWS_PER_FRAME; y_coord++) // half height - 16 iterations
{
for(int color_depth_idx=0; color_depth_idx<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
for(int x_coord=0; x_coord < MATRIX_WIDTH; x_coord++) // row pixel width 64 iterations
{
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;
// 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;
if (gpioRowAddress & 0x01) v|=BIT_A; // 1
if (gpioRowAddress & 0x02) v|=BIT_B; // 2
if (gpioRowAddress & 0x04) v|=BIT_C; // 4
if (gpioRowAddress & 0x08) v|=BIT_D; // 8
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;
// drive latch while shifting out last bit of RGB data
if((x_coord) == PIXELS_PER_LATCH-1) v|=BIT_LAT;
// 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;
// need to turn off OE one clock before latch, otherwise can get ghosting
if((x_coord)==PIXELS_PER_LATCH-2) v|=BIT_OE;
// turn off OE after brightness value is reached when displaying MSBs
// MSBs always output normal brightness
// LSB (!color_depth_idx) outputs normal brightness as MSB from previous row is being displayed
if((color_depth_idx > lsbMsbTransitionBit || !color_depth_idx) && ((x_coord) >= brightness)) v|=BIT_OE; // For Brightness
// special case for the bits *after* LSB through (lsbMsbTransitionBit) - OE is output after data is shifted, so need to set OE to fractional brightness
if(color_depth_idx && color_depth_idx <= lsbMsbTransitionBit) {
// divide brightness in half for each bit below lsbMsbTransitionBit
int lsbBrightness = brightness >> (lsbMsbTransitionBit - color_depth_idx + 1);
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;
// 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)
{
p->data[(x_coord)-1] = v;
} else {
p->data[(x_coord)+1] = v;
} // end reordering
} // end x_coord iteration
} // 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