New Version

This commit is contained in:
mrfaptastic 2018-10-23 01:00:47 +01:00
parent 3245a37e3e
commit 12aff19666
5 changed files with 921 additions and 3 deletions

View file

@ -0,0 +1,453 @@
#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()
{
// 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 += 100000; // HACK Hard Coded: Keep at least 100k 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 > 200) // HACK Hard Coded: Minimum frame rate of 160
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
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-ordinate is within range, or it'll break everything big time.
if ( x_coord < 0 || y_coord < 0 || x_coord > MATRIX_WIDTH || y_coord > MATRIX_HEIGHT)
{
return;
}
// What half of the HUB75 panel are we painting too?
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
// 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;
// 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;
// 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;
}
// need to turn off OE one clock before latch, otherwise can get ghosting
if((x_coord)==PIXELS_PER_LATCH-1) v|=BIT_OE;
/* When using the Adafruit drawPixel, we only have one pixel co-ordinate and color 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 persis this when we refresh.
*
* The DMA buffer order has also been reversed (fer to the last code in this function)
* so we have to check for this and check ther correct possition 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
// 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);
//swapBuffer();
} // updateDMABuffer
/*
void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint16_t x, uint16_t y, uint8_t red, uint8_t green, uint8_t blue)
{
if ( !dma_configuration_success)
assert("DMA configuration in begin() not performed or completed successfully.");
// Pixel index sanity checking.
int16_t idx = x + y * MATRIX_WIDTH;
if (idx < 0 || idx >= MATRIX_HEIGHT * MATRIX_WIDTH)
{
//Serial.printf("Provided coordinates out of range");
return;
}
for (unsigned int y=0; y<MATRIX_HEIGHT/MATRIX_ROWS_IN_PARALLEL; y++) // half height - 16 iterations
{
unsigned char currentRow = y;
for(int j=0; j<COLOR_DEPTH_BITS; j++) // color depth - 8 iterations
{
uint16_t mask = (1 << (j)); // 24 bit color
//MATRIX_DATA_STORAGE_TYPE *p=matrixUpdateFrames[backbuf_id].rowdata[y].rowbits[pl].data; //matrixUpdateFrames
rowBitStruct *p=&matrixUpdateFrames[backbuf_id].rowdata[currentRow].rowbits[j]; //matrixUpdateFrames location to write to
int i=0;
while(i < PIXELS_PER_LATCH) // row pixels (64) iterations
{
for(int k=0; k < MATRIX_WIDTH; k++) // row pixel width 64 iterations
{
int v=0; // the output bitstream
//#if (CLKS_DURING_LATCH == 0)
// if there is no latch to hold address, output ADDX lines directly to GPIO and latch data at end of cycle
int gpioRowAddress = currentRow;
// 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(j == 0)
gpioRowAddress = currentRow-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
// need to disable OE after latch to hide row transition
if((i+k) == 0) v|=BIT_OE;
// drive latch while shifting out last bit of RGB data
if((i+k) == PIXELS_PER_LATCH-1) v|=BIT_LAT;
//#endif
// turn off OE after brightness value is reached when displaying MSBs
// MSBs always output normal brightness
// LSB (!j) outputs normal brightness as MSB from previous row is being displayed
if((j > lsbMsbTransitionBit || !j) && ((i+k) >= brightness)) v|=BIT_OE;
// 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(j && j <= lsbMsbTransitionBit) {
// divide brightness in half for each bit below lsbMsbTransitionBit
int lsbBrightness = brightness >> (lsbMsbTransitionBit - j + 1);
if((i+k) >= lsbBrightness) v|=BIT_OE;
}
// need to turn off OE one clock before latch, otherwise can get ghosting
if((i+k)==PIXELS_PER_LATCH-1) v|=BIT_OE;
//#if 0
//
// int c1=getpixel(pix, k, y);
// int c2=getpixel(pix, k, y+(MATRIX_HEIGHT/2));
//
// if (c1 & (mask<<16)) v|=BIT_R1;
// if (c1 & (mask<<8)) v|=BIT_G1;
// if (c1 & (mask<<0)) v|=BIT_B1;
// if (c2 & (mask<<16)) v|=BIT_R2;
// if ( c2 & (mask<<8)) v|=BIT_G2;
// if (c2 & (mask<<0)) v|=BIT_B2;
//
//#else
struct rgb24 c1( 255,0,0);
struct rgb24 c2 = { 255,255,255 };
// struct rgb24 c2 = { 0,0,255 };
if (c1.red & mask)
v|=BIT_R1;
if (c1.green & mask)
v|=BIT_G1;
if (c1.blue & mask)
v|=BIT_B1;
if (c2.red & mask)
v|=BIT_R2;
if (c2.green & mask)
v|=BIT_G2;
if (c2.blue & mask)
v|=BIT_B2;
//#endif
// 16 bit parallel mode
//Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering
if(k%2){
p->data[(i+k)-1] = v;
} else {
p->data[(i+k)+1] = v;
} // end reordering
} // end for MATRIX_WIDTH
i += MATRIX_WIDTH;
} // end pixels per latch loop (64)
} // color depth loop (8)
} // end half matrix length
//Show our work!
i2s_parallel_flip_to_buffer(&I2S1, backbuf_id);
swapBuffer();
} // updateDMABuffer
*/

View file

@ -0,0 +1,266 @@
#ifndef _ESP32_RGB_64_32_MATRIX_PANEL_I2S_DMA
#define _ESP32_RGB_64_32_MATRIX_PANEL_I2S_DMA
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#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.
Finally, the binary code modulated intensity of a LED does not correspond to the intensity as seen by human eyes. To correct for that, a
luminance correction is used. See val2pwm.c for more info.
Note: Because every subframe contains one bit of grayscale information, they are also referred to as 'bitplanes' by the code below.
*/
/***************************************************************************************/
/* ESP32 Pin Definition. You can change this, but best if you keep it as is... */
#define R1_PIN 25
#define G1_PIN 26
#define B1_PIN 27
#define R2_PIN 14
#define G2_PIN 12
#define B2_PIN 13
#define A_PIN 23
#define B_PIN 22
#define C_PIN 5
#define D_PIN 17
#define E_PIN -1
#define LAT_PIN 4
#define OE_PIN 15
#define CLK_PIN 16
/***************************************************************************************/
/* HUB75 RGB Panel definitions and DMA Config. It's best you don't change any of this. */
#define MATRIX_HEIGHT 32
#define MATRIX_WIDTH 64
#define MATRIX_ROWS_IN_PARALLEL 2
// Panel Upper half RGB (numbering according to order in DMA gpio_bus configuration)
#define BIT_R1 (1<<0)
#define BIT_G1 (1<<1)
#define BIT_B1 (1<<2)
// Panel Lower half RGB
#define BIT_R2 (1<<3)
#define BIT_G2 (1<<4)
#define BIT_B2 (1<<5)
// Panel Control Signals
#define BIT_LAT (1<<6)
#define BIT_OE (1<<7)
// Panel GPIO Pin Addresses (A, B, C, D etc..)
#define BIT_A (1<<8)
#define BIT_B (1<<9)
#define BIT_C (1<<10)
#define BIT_D (1<<11)
#define BIT_E (1<<12)
// RGB Panel Constants / Calculated Values
#define PIXELS_PER_LATCH ((MATRIX_WIDTH * MATRIX_HEIGHT) / MATRIX_HEIGHT) // = 64
#define COLOR_CHANNELS_PER_PIXEL 3
#define COLOR_DEPTH_BITS (COLOR_DEPTH/COLOR_CHANNELS_PER_PIXEL) // = 8
#define ROWS_PER_FRAME (MATRIX_HEIGHT/MATRIX_ROWS_IN_PARALLEL) // = 2
/***************************************************************************************/
/* You really don't want to change this stuff */
#define CLKS_DURING_LATCH 0 // ADDX is output directly using GPIO
#define MATRIX_I2S_MODE I2S_PARALLEL_BITS_16
#define MATRIX_DATA_STORAGE_TYPE uint16_t
#define ESP32_NUM_FRAME_BUFFERS 2
#define ESP32_OE_OFF_CLKS_AFTER_LATCH 1
#define ESP32_I2S_CLOCK_SPEED (20000000UL)
#define COLOR_DEPTH 24
/***************************************************************************************/
// 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) / 32) + CLKS_DURING_LATCH];
// this evaluates to just MATRIX_DATA_STORAGE_TYPE data[64] really;
// and array of 64 uint16_t's
};
struct rowColorDepthStruct {
rowBitStruct rowbits[COLOR_DEPTH_BITS];
};
struct frameStruct {
rowColorDepthStruct rowdata[ROWS_PER_FRAME];
};
/***************************************************************************************/
class RGB64x32MatrixPanel_I2S_DMA : public Adafruit_GFX {
// ------- PUBLIC -------
public:
RGB64x32MatrixPanel_I2S_DMA(bool _doubleBuffer = false) // doublebuffer always enabled, option makes no difference
: Adafruit_GFX(MATRIX_WIDTH, MATRIX_HEIGHT), doubleBuffer(_doubleBuffer) {
allocateDMAbuffers();
backbuf_id = 0;
brightness = 64;
}
void begin(void)
{
configureDMA(); //DMA and I2S configuration and setup
// Need to wipe the contents of the matrix buffers or weird things happen.
for (int y=0;y<MATRIX_HEIGHT; y++)
for (int x=0;x<MATRIX_WIDTH; x++)
updateMatrixDMABuffer( x, y, 0, 0, 0);
}
// Draw pixels
virtual void drawPixel(int16_t x, int16_t y, uint16_t color); // adafruit implementation
inline void drawPixelRGB565(int16_t x, int16_t y, uint16_t color);
inline void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b);
// Color 444 is a 4 bit scale, so 0 to 15, color 565 takes a 0-255 bit value, so scale up by 255/15 (i.e. 17)!
uint16_t color444(uint8_t r, uint8_t g, uint8_t b) { return color565(r*17,g*17,b*17); }
// Converts RGB888 to RGB565
uint16_t color565(uint8_t r, uint8_t g, uint8_t b); // This is what is used by Adafruit GFX!
void swapBuffer() {
backbuf_id ^=1;
}
void setBrightness(int _brightness)
{
brightness = _brightness;
}
// ------- PRIVATE -------
private:
void allocateDMAbuffers()
{
matrixUpdateFrames = (frameStruct *)heap_caps_malloc(sizeof(frameStruct) * ESP32_NUM_FRAME_BUFFERS, MALLOC_CAP_DMA);
Serial.printf("Allocating Refresh Buffer:\r\nDMA 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));
} // end initMatrixDMABuffer()
void configureDMA(); // Get everything setup. Refer to the .c file
// Paint a pixel to the DMA buffer directly
void updateMatrixDMABuffer(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue);
// Internal variables
bool dma_configuration_success;
bool doubleBuffer;
// 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;
int lsbMsbTransitionBit;
int refreshRate;
int backbuf_id; // which buffer is the DMA backbuffer, as in, which one is not active so we can write to it
int brightness;
}; // end Class header
/***************************************************************************************/
inline void RGB64x32MatrixPanel_I2S_DMA::drawPixel(int16_t x, int16_t y, uint16_t color)
{
drawPixelRGB565( x, y, color);
}
// For adafruit
inline void RGB64x32MatrixPanel_I2S_DMA::drawPixelRGB565(int16_t x, int16_t y, uint16_t color)
{
uint8_t r = ((((color >> 11) & 0x1F) * 527) + 23) >> 6;
uint8_t g = ((((color >> 5) & 0x3F) * 259) + 33) >> 6;
uint8_t b = (((color & 0x1F) * 527) + 23) >> 6;
updateMatrixDMABuffer( x, y, r, g, b);
}
inline void RGB64x32MatrixPanel_I2S_DMA::drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g,uint8_t b)
{
updateMatrixDMABuffer( x, y, r, g, b);
}
// 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) {
Serial.printf("Got r value of %d\n", r);
Serial.printf("Got g value of %d\n", g);
Serial.printf("Got b value of %d\n", b);
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
#endif

View file

@ -0,0 +1,152 @@
//#include <P3RGB64x32MatrixPanel.h>
//P3RGB64x32MatrixPanel display2;
#include "ESP32-RGB64x32MatrixPanel-I2S-DMA.h"
RGB64x32MatrixPanel_I2S_DMA dma_display;
void setup() {
Serial.begin(115200);
Serial.println("*****************************************************");
Serial.println(" HELLO !");
Serial.println("*****************************************************");
dma_display.begin();
for (int y = 0; y < dma_display.height(); y++)
{
dma_display.drawFastHLine(0, y-1, dma_display.width(), dma_display.color565(255-(15*y), 0, 0));
delay(100);
}
delay(1000);
for (int y = 0; y < dma_display.height(); y++)
{
dma_display.drawFastHLine(0, y-1, dma_display.width(), dma_display.color565(128, 255-(15*y), 0));
delay(100);
}
delay(2000);
// draw a pixel in solid white
dma_display.drawPixel(0, 0, dma_display.color444(15, 15, 15));
delay(500);
// fix the screen with green
dma_display.fillRect(0, 0, dma_display.width(), dma_display.height(), dma_display.color444(0, 15, 0));
delay(500);
// draw a box in yellow
dma_display.drawRect(0, 0, dma_display.width(), dma_display.height(), dma_display.color444(15, 15, 0));
delay(500);
// draw an 'X' in red
dma_display.drawLine(0, 0, dma_display.width()-1, dma_display.height()-1, dma_display.color444(15, 0, 0));
dma_display.drawLine(dma_display.width()-1, 0, 0, dma_display.height()-1, dma_display.color444(15, 0, 0));
delay(500);
// draw a blue circle
dma_display.drawCircle(10, 10, 10, dma_display.color444(0, 0, 15));
delay(500);
// fill a violet circle
dma_display.fillCircle(40, 21, 10, dma_display.color444(15, 0, 15));
delay(500);
// fill the screen with 'black'
dma_display.fillScreen(dma_display.color444(0, 0, 0));
// draw some text!
dma_display.setTextSize(1); // size 1 == 8 pixels high
dma_display.setTextWrap(false); // Don't wrap at end of line - will do ourselves
dma_display.setCursor(5, 0); // start at top left, with 8 pixel of spacing
uint8_t w = 0;
char *str = "ESP32 DMA";
for (w=0; w<9; w++) {
dma_display.setTextColor(dma_display.color565(255, 0, 255));
dma_display.print(str[w]);
}
dma_display.setCursor(2, 4); // next line
for (w=9; w<18; w++) {
dma_display.setTextColor(Wheel(w));
dma_display.print(str[w]);
}
dma_display.println();
//dma_display.setTextColor(dma_display.Color333(4,4,4));
//dma_display.println("Industries");
dma_display.setTextColor(dma_display.color444(15,15,15));
dma_display.println("LED MATRIX!");
// print each letter with a rainbow color
dma_display.setTextColor(dma_display.color444(15,0,0));
dma_display.print('3');
dma_display.setTextColor(dma_display.color444(15,4,0));
dma_display.print('2');
dma_display.setTextColor(dma_display.color444(15,15,0));
dma_display.print('x');
dma_display.setTextColor(dma_display.color444(8,15,0));
dma_display.print('6');
dma_display.setTextColor(dma_display.color444(0,15,0));
dma_display.print('4');
dma_display.setCursor(34, 24);
dma_display.setTextColor(dma_display.color444(0,15,15));
dma_display.print("*");
dma_display.setTextColor(dma_display.color444(0,8,15));
dma_display.print('R');
dma_display.setTextColor(dma_display.color444(0,0,15));
dma_display.print('G');
dma_display.setTextColor(dma_display.color444(8,0,15));
dma_display.print("B");
dma_display.setTextColor(dma_display.color444(15,0,8));
dma_display.println("*");
delay(2000);
/*
for (int i = 15; i > 0; i--)
{
// fade out
dma_display.fillScreen(dma_display.color565(0, 0, i*17));
delay(250);
}
for (int i = 15; i > 0; i--)
{
// draw a blue circle
dma_display.drawCircle(10, 10, 10, dma_display.color565(i*17, 0, 0));
delay(250);
}
*/
// whew!
}
void loop() {
// do nothing
}
// Input a value 0 to 24 to get a color value.
// The colours are a transition r - g - b - back to r.
uint16_t Wheel(byte WheelPos) {
if(WheelPos < 8) {
return dma_display.color444(15 - WheelPos*2, WheelPos*2, 0);
} else if(WheelPos < 16) {
WheelPos -= 8;
return dma_display.color444(0, 15-WheelPos*2, WheelPos*2);
} else {
WheelPos -= 16;
return dma_display.color444(0, WheelPos*2, 7 - WheelPos*2);
}
}

View file

@ -1,8 +1,55 @@
Currently WIP. Dirty demo code only. # Adafruit_GFX comapatable RGB64x32MatrixPanel library for ESP32 based on experimental ESP32 I2S DMA!
ESP32 Arduino library for P3 64x32 RGB LED Matrix Panel, which leverages the DMA functionality of the ESP32's I2S 'LCD Mode' which bascially means that pixel data can be sent straight from memory, via the DMA controller, to the relevant GPIO pins (RGB Matrix) with no overhead to either CPU! Most other library require the CPU to constantly bit-bang the GPIO (either with SPI as well), which results in flickery outcomes when using either CPU and decreased performance of your sketch.
This software is released under the MIT License, see LICENSE.txt.
# Installation
You need to install [Adafruit_GFX_Library](https://github.com/adafruit/Adafruit-GFX-Library) from the "Library > Manage Libraries" menu.
## Patching GPIO to avoid eratta of ESP32
You should patch the `tools/sdk/ld/esp32.peripherals.ld` to avoid random pixel noise on the display as following:
```
/* PROVIDE ( GPIO = 0x3ff44000 ); */
PROVIDE ( GPIO = 0x60004000 );
```
Please refer section 3.3. in https://www.espressif.com/sites/default/files/documentation/eco_and_workarounds_for_bugs_in_esp32_en.pdf for more details.
# Wiring ESP32 with the LED Matrix Panel
The panel has HUB75 compatible pins. The library provides two constructors.
One with default pin wiring like:
```
+-----------+ Panel - ESP32 pins
| R1 G1 | R1 - IO25 G1 - IO26
| B1 GND | B1 - IO27
| R2 G2 | R2 - IO14 G2 - IO12
| B2 GND | B2 - IO13
| A B | A - IO23 B - IO22
| C D | C - IO 5 D - IO17
| CLK LAT | CLK - IO16 LAT - IO 4
| OE GND | OE - IO15
+-----------+
```
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.)
# Usage
Work in progress at the moment. Run the .ino file once hooked up.
# Notes
Currently WIP. Dirty demo code only.
Seems to work extremly well and drive a RGB panel with 24bit color at very high refresh rates. Seems to work extremly well and drive a RGB panel with 24bit color at very high refresh rates.
Based off 'SmartMatrix' code. Based off highly modified 'SmartMatrix' code.
Credits: Credits:
https://www.esp32.com/viewtopic.php?f=17&t=3188 https://www.esp32.com/viewtopic.php?f=17&t=3188

View file

@ -13,7 +13,7 @@ extern "C" {
#include "rom/lldesc.h" #include "rom/lldesc.h"
typedef enum { typedef enum {
I2S_PARALLEL_BITS_8=8, I2S_PARALLEL_BITS_8=8, // BUG: Doesn't work.
I2S_PARALLEL_BITS_16=16, I2S_PARALLEL_BITS_16=16,
I2S_PARALLEL_BITS_32=32, I2S_PARALLEL_BITS_32=32,
} i2s_parallel_cfg_bits_t; } i2s_parallel_cfg_bits_t;