From b6da8ea09a84004354edf720cbd2c7d76f20d767 Mon Sep 17 00:00:00 2001 From: mrfaptastic <12006953+mrfaptastic@users.noreply.github.com> Date: Sat, 28 Nov 2020 08:39:35 +0000 Subject: Change library name --- ESP32-HUB75-MatrixPanel-I2S-DMA.cpp | 883 +++++++++++++++++++++ ESP32-HUB75-MatrixPanel-I2S-DMA.h | 443 +++++++++++ ESP32-RGB64x32MatrixPanel-I2S-DMA.cpp | 883 --------------------- ESP32-RGB64x32MatrixPanel-I2S-DMA.h | 443 ----------- ESP32-VirtualMatrixPanel-I2S-DMA.h | 2 +- README.md | 12 +- examples/AnimatedGIFPanel/AnimatedGIFPanel.ino | 2 +- examples/AuroraDemo/AuroraDemo.ino | 2 +- examples/BitmapIcons/BitmapIcons.ino | 2 +- examples/ChainedPanels/ChainedPanels.ino | 6 +- examples/ChainedPanels/README.md | 2 +- .../ChainedPanelsAuroraDemo.ino | 2 +- examples/DoubleBufferSwap/DoubleBufferSwap.ino | 2 +- examples/FM6126Panel/FM6126Panel.ino | 2 +- .../Glediator3_TPM2_MatrixPanel.ino | 2 +- examples/PatternPlasma/PatternPlasma.ino | 2 +- examples/testshapes_32x64/testshapes_32x64.ino | 2 +- framebuffer_memory.md | 2 +- library.json | 8 +- library.properties | 8 +- 20 files changed, 1355 insertions(+), 1355 deletions(-) create mode 100644 ESP32-HUB75-MatrixPanel-I2S-DMA.cpp create mode 100644 ESP32-HUB75-MatrixPanel-I2S-DMA.h delete mode 100644 ESP32-RGB64x32MatrixPanel-I2S-DMA.cpp delete mode 100644 ESP32-RGB64x32MatrixPanel-I2S-DMA.h diff --git a/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp b/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp new file mode 100644 index 0000000..e720636 --- /dev/null +++ b/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp @@ -0,0 +1,883 @@ +#include "ESP32-HUB75-MatrixPanel-I2S-DMA.h" + +// Credits: Louis Beaudoin +// and Sprite_TM: https://www.esp32.com/viewtopic.php?f=17&t=3188 and https://www.esp32.com/viewtopic.php?f=13&t=3256 + +/* + + 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 artefacts 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 front buffer/back buffer 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. + +*/ + + +// For development testing only +//#define IGNORE_REFRESH_RATE 1 + + + +uint8_t val2PWM(int val) { + if (val<0) val=0; + if (val>255) val=255; + return lumConvTab[val]; +} + +bool RGB64x32MatrixPanel_I2S_DMA::allocateDMAmemory() +{ + + /*** + * 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 the amount of DMA capable memory that's actually available + #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!"); + } + + Serial.println("DMA memory blocks available before any malloc's: "); + heap_caps_print_heap_info(MALLOC_CAP_DMA); + + 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("The total amount of DMA capable SRAM memory is %d bytes.\r\n", heap_caps_get_free_size(MALLOC_CAP_DMA)); + 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 potentially fit the framebuffer into the DMA capable memory that's available? + if ( heap_caps_get_free_size(MALLOC_CAP_DMA) < _frame_buffer_memory_required ) { + + #if SERIAL_DEBUG + Serial.printf("######### Insufficient 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_free_size(MALLOC_CAP_DMA)) ); + #endif + + return false; + } + + // Alright, theoretically we should be OK, so let us do this, so + // lets allocate a chunk of memory for each row (a row could span multiple panels if chaining is in place) + for (int malloc_num =0; malloc_num < ROWS_PER_FRAME; malloc_num++) + { + matrix_row_framebuffer_malloc[malloc_num] = (rowColorDepthStruct *)heap_caps_malloc( (sizeof(rowColorDepthStruct) * _num_frame_buffers) , MALLOC_CAP_DMA); + // If the ESP crashes here, then we must have a horribly fragmented memory space, or trying to allocate a ludicrous resolution. + #if SERIAL_DEBUG + Serial.printf("Malloc'ing %d bytes of memory @ address %d for frame row %d.\r\n", (sizeof(rowColorDepthStruct) * _num_frame_buffers), matrix_row_framebuffer_malloc[malloc_num], malloc_num); + #endif + if ( matrix_row_framebuffer_malloc[malloc_num] == NULL ) { + Serial.printf("ERROR: Couldn't malloc matrix_row_framebuffer %d! Critical fail.\r\n", malloc_num); + return false; + } + } + + _total_dma_capable_memory_reserved += _frame_buffer_memory_required; + + + /*** + * Step 2: Calculate the amount of memory required for the DMA engine's linked list descriptors. + * Credit to SmartMatrix for this stuff. + */ + + + // Calculate what colour depth is actually possible based on memory available 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 min_refresh_rate) // HACK Hard Coded: 100 + break; + + 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, PIXEL_COLOR_DEPTH_BITS - 1); + #endif + + /*** + * Step 2a: lsbMsbTransition bit is now finalised - recalculate the DMA descriptor count required, which is used for + * memory allocation of the DMA linked list memory structure. + */ + numDMAdescriptorsPerRow = 1; + for(int i=lsbMsbTransitionBit + 1; i DMA_MAX ) { + + #if SERIAL_DEBUG + Serial.println("Split DMA payload required."); + #endif + + numDMAdescriptorsPerRow += PIXEL_COLOR_DEPTH_BITS-1; + // Not if numDMAdescriptorsPerRow is even just one descriptor too large, DMA linked list will not correctly loop. + } + + + /*** + * Step 3: Allocate memory for DMA linked list, linking up each framebuffer row in sequence for GPIO output. + */ + + _dma_linked_list_memory_required = numDMAdescriptorsPerRow * ROWS_PER_FRAME * _num_frame_buffers * sizeof(lldesc_t); + #if SERIAL_DEBUG + Serial.printf("Descriptors for lsbMsbTransitionBit of %d/%d with %d frame rows require %d bytes of DMA RAM with %d numDMAdescriptorsPerRow.\r\n", lsbMsbTransitionBit, PIXEL_COLOR_DEPTH_BITS - 1, ROWS_PER_FRAME, _dma_linked_list_memory_required, numDMAdescriptorsPerRow); + #endif + + _total_dma_capable_memory_reserved += _dma_linked_list_memory_required; + + // 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)) { + Serial.printf("ERROR: Not enough SRAM left over for DMA linked-list descriptor memory reservation! Oh so close!\r\n"); + + return false; + } // linked list descriptors memory check + + // malloc the DMA linked list descriptors that i2s_parallel will need + 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("ERROR: Could not malloc descriptor framebuffer a."); + return false; + } + + 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("ERROR: Could not malloc descriptor framebuffer b."); + return false; + } + } + + Serial.printf("*** ESP32-HUB75-MatrixPanel-I2S-DMA: Memory Allocations Complete *** \r\n"); + Serial.printf("Total memory that was reserved: %d kB.\r\n", _total_dma_capable_memory_reserved/1024); + Serial.printf("... of which was used for the DMA Linked List(s): %d kB.\r\n", _dma_linked_list_memory_required/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("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)); + + + #if SERIAL_DEBUG + Serial.println("DMA capable memory map available after malloc's: "); + heap_caps_print_heap_info(MALLOC_CAP_DMA); + delay(1000); + #endif + + // Just os we know + everything_OK = true; + + 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; + + // HACK: If we need to split the payload in 1/2 so that it doesn't breach DMA_MAX, lets do it by the color_depth. + int num_dma_payload_color_depths = PIXEL_COLOR_DEPTH_BITS; + if ( sizeof(rowColorDepthStruct) > DMA_MAX ) { + num_dma_payload_color_depths = 1; + } + + // Fill DMA linked lists for both frames (as in, halves of the HUB75 panel) and if double buffering is enabled, link it up for both buffers. + for(int row = 0; row < ROWS_PER_FRAME; row++) { + + // Split framebuffer malloc hack 'improvement' + rowColorDepthStruct *fb_malloc_ptr = matrix_row_framebuffer_malloc[row]; + + #if SERIAL_DEBUG + Serial.printf("DMA payload of %d bytes. DMA_MAX is %d.\r\n", sizeof(rowBitStruct) * PIXEL_COLOR_DEPTH_BITS, DMA_MAX); + #endif + + + // first set of data is LSB through MSB, single pass (IF TOTAL SIZE < DMA_MAX) - all color bits are displayed once, which takes care of everything below and inlcluding LSBMSB_TRANSITION_BIT + // NOTE: size must be less than DMA_MAX - worst case for library: 16-bpp with 256 pixels per row would exceed this, need to break into two + link_dma_desc(&dmadesc_a[current_dmadescriptor_offset], previous_dmadesc_a, &(fb_malloc_ptr[0].rowbits[0].data), sizeof(rowBitStruct) * num_dma_payload_color_depths); + previous_dmadesc_a = &dmadesc_a[current_dmadescriptor_offset]; + + if (double_buffering_enabled) { + link_dma_desc(&dmadesc_b[current_dmadescriptor_offset], previous_dmadesc_b, &(fb_malloc_ptr[1].rowbits[0].data), sizeof(rowBitStruct) * num_dma_payload_color_depths); + previous_dmadesc_b = &dmadesc_b[current_dmadescriptor_offset]; } + + current_dmadescriptor_offset++; + + // If the number of pixels per row is to great for the size of a DMA payload, so we need to split what we were going to send above. + if ( sizeof(rowColorDepthStruct) > DMA_MAX ) + { + + #if SERIAL_DEBUG + Serial.printf("Spliting DMA payload for %d color depths into %d byte payloads.\r\n", PIXEL_COLOR_DEPTH_BITS-1, sizeof(rowBitStruct) ); + #endif + + for (int cd = 1; cd < PIXEL_COLOR_DEPTH_BITS; cd++) + { + // first set of data is LSB through MSB, single pass - all color bits are displayed once, which takes care of everything below and inlcluding LSBMSB_TRANSITION_BIT + // TODO: size must be less than DMA_MAX - worst case for library: 16-bpp with 256 pixels per row would exceed this, need to break into two + link_dma_desc(&dmadesc_a[current_dmadescriptor_offset], previous_dmadesc_a, &(fb_malloc_ptr[0].rowbits[cd].data), sizeof(rowBitStruct) ); + previous_dmadesc_a = &dmadesc_a[current_dmadescriptor_offset]; + + if (double_buffering_enabled) { + link_dma_desc(&dmadesc_b[current_dmadescriptor_offset], previous_dmadesc_b, &(fb_malloc_ptr[1].rowbits[cd].data), sizeof(rowBitStruct) ); + previous_dmadesc_b = &dmadesc_b[current_dmadescriptor_offset]; } + + current_dmadescriptor_offset++; + + } // additional linked list items + } // row depth struct + + + for(int i=lsbMsbTransitionBit + 1; iclkspeed_hz + 1), must result in >=2. Acceptable values 26.67MHz, 20MHz, 16MHz, 13.34MHz... + .bits=ESP32_I2S_DMA_MODE, //ESP32_I2S_DMA_MODE, + .bufa=0, + .bufb=0, + desccount, + desccount, + dmadesc_a, + dmadesc_b + }; + + //Setup I2S + i2s_parallel_setup_without_malloc(&I2S1, &cfg); + + #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 + +} // end initMatrixDMABuff + + +/* There are 'bits' set in the frameStruct that we simply don't need to set every single time we change a pixel / DMA buffer co-ordinate. + * For example, the bits that determine the address lines, we don't need to set these every time. Once they're in place, and assuming we + * don't accidently clear them, then we don't need to set them again. + * So to save processing, we strip this logic out to the absolute bare minimum, which is toggling only the R,G,B pixels (bits) per co-ord. + * + * Critical dependency: That 'updateMatrixDMABuffer(uint8_t red, uint8_t green, uint8_t blue)' has been run at least once over the + * entire frameBuffer to ensure all the non R,G,B bitmasks are in place (i.e. like OE, Address Lines etc.) + * + * Note: If you change the brightness with setBrightness() you MUST then clearScreen() and repaint / flush the entire framebuffer. + */ +//#define GO_FOR_SPEED 1 + +#ifdef GO_FOR_SPEED +/* 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) +{ + + // 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; + } + + // https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/ + red = lumConvTab[red]; + green = lumConvTab[green]; + blue = lumConvTab[blue]; + + bool painting_top_frame = true; + if ( y_coord >= ROWS_PER_FRAME) // co-ords start at zero, y_coord = 15 = 16 (rows per frame) + { + y_coord -= ROWS_PER_FRAME; // Subtract the ROWS_PER_FRAME from the pixel co-ord to get the panel ROW (not really the 'y_coord' anymore) + painting_top_frame = false; + } + + // We need to update the correct uint16_t in the rowBitStruct array, that gets sent out in parallel + // 16 bit parallel mode - Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering + int rowBitStruct_x_coord_uint16_t_position = (x_coord % 2) ? (x_coord-1):(x_coord+1); + + // Find the memory address for the malloc for this framebuffer row. + rowColorDepthStruct *fb_row_malloc_ptr = (rowColorDepthStruct *) matrix_row_framebuffer_malloc[y_coord]; + + for(int color_depth_idx=0; color_depth_idxdata[rowBitStruct_x_coord_uint16_t_position] = v; + // NOTE: No need to do this as 'v' is now a reference directly to the frameStruct + + } // color depth loop (8) + +} // updateMatrixDMABuffer (specific co-ords change) + +#else + +/* Update a specific co-ordinate in the DMA buffer */ +/* Original version were we re-create the bitstream from scratch for each x,y co-ordinate / pixel changed. Slightly slower. */ +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 SERIAL_DEBUG + Serial.println("Cannot updateMatrixDMABuffer as setup failed!"); + #endif + + return; + } + + /* LED Brightness Compensation. Because if we do a basic "red & mask" for example, + * we'll NEVER send the dimmest possible colour, due to binary skew. + + i.e. It's almost impossible for color_depth_idx of 0 to be sent out to the MATRIX unless the 'value' of a color is exactly '1' + + */ + red = lumConvTab[red]; + green = lumConvTab[green]; + blue = lumConvTab[blue]; + + + /* 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; + } + + /* When using the drawPixel, we are obviously only changing the value of one x,y position, + * however, the two-scan panels paint 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. + */ + bool painting_top_frame = true; + if ( y_coord >= ROWS_PER_FRAME) // co-ords start at zero, y_coord = 15 = 16 (rows per frame) + { + y_coord -= ROWS_PER_FRAME; // Subtract the ROWS_PER_FRAME from the pixel co-ord to get the panel ROW (not really the 'y_coord' anymore) + painting_top_frame = false; + } + + // We need to update the correct uint16_t in the rowBitStruct array, that gets sent out in parallel + int rowBitStruct_x_coord_uint16_t_position = (x_coord % 2) ? (x_coord-1):(x_coord+1); + + for(int color_depth_idx=0; color_depth_idxdata[rowBitStruct_x_coord_uint16_t_position]; // persist what we already have + 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_ROW-1) v|=BIT_LAT; + + // need to turn off OE one clock before latch, otherwise can get ghosting + if((x_coord)==PIXELS_PER_ROW-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 + } + + /* + // Development / testing code only. + Serial.printf("r value of %d, color depth: %d, mask: %d\r\n", red, color_depth_idx, mask); + if (red & mask) { Serial.println("Success - Binary"); v|=BIT_R1; } + Serial.printf("val2pwm r value: %d\r\n", val2PWM(red)); + if (val2PWM(red) & mask) { Serial.println("Success - PWM"); v|=BIT_R2; } + */ + + + if (painting_top_frame) + { // 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[rowBitStruct_x_coord_uint16_t_position] & BIT_R2) + v|=BIT_R2; + + if (p->data[rowBitStruct_x_coord_uint16_t_position] & BIT_G2) + v|=BIT_G2; + + if (p->data[rowBitStruct_x_coord_uint16_t_position] & 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 / persist + if (p->data[rowBitStruct_x_coord_uint16_t_position] & BIT_R1) + v|=BIT_R1; + + if (p->data[rowBitStruct_x_coord_uint16_t_position] & BIT_G1) + v|=BIT_G1; + + if (p->data[rowBitStruct_x_coord_uint16_t_position] & 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 + */ + + // 16 bit parallel mode + p->data[rowBitStruct_x_coord_uint16_t_position] = v; + + + } // color depth loop (8) + +} // updateMatrixDMABuffer (specific co-ords change) +#endif + + +/* 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; + + /* https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/ */ + /* + red = val2PWM(red); + green = val2PWM(green); + blue = val2PWM(blue); + */ + red = lumConvTab[red]; + green = lumConvTab[green]; + blue = lumConvTab[blue]; + + 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 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 and bottom matrix MATRIX_ROWS_IN_PARALLEL half colours + if (green & mask) { v|=BIT_G1; v|=BIT_G2; } + if (blue & mask) { v|=BIT_B1; v|=BIT_B2; } + if (red & mask) { v|=BIT_R1; v|=BIT_R2; } + + // 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 + +} // updateMatrixDMABuffer (full frame paint) + +/** + * pre-init procedures for specific drivers + * + */ +void RGB64x32MatrixPanel_I2S_DMA::shiftDriver(const shift_driver _drv, const int dma_r1_pin, const int dma_g1_pin, const int dma_b1_pin, const int dma_r2_pin, const int dma_g2_pin, const int dma_b2_pin, const int dma_a_pin, const int dma_b_pin, const int dma_c_pin, const int dma_d_pin, const int dma_e_pin, const int dma_lat_pin, const int dma_oe_pin, const int dma_clk_pin){ + switch (_drv){ + case FM6124: + case FM6126A: + { + #if SERIAL_DEBUG + Serial.println( F("RGB64x32MatrixPanel_I2S_DMA - initializing FM6124 driver...")); + #endif + int C12[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + int C13[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}; + + pinMode(dma_r1_pin, OUTPUT); + pinMode(dma_g1_pin, OUTPUT); + pinMode(dma_b1_pin, OUTPUT); + pinMode(dma_r2_pin, OUTPUT); + pinMode(dma_g2_pin, OUTPUT); + pinMode(dma_b2_pin, OUTPUT); + pinMode(dma_a_pin, OUTPUT); + pinMode(dma_b_pin, OUTPUT); + pinMode(dma_c_pin, OUTPUT); + pinMode(dma_d_pin, OUTPUT); + pinMode(dma_e_pin, OUTPUT); + pinMode(dma_clk_pin, OUTPUT); + pinMode(dma_lat_pin, OUTPUT); + pinMode(dma_oe_pin, OUTPUT); + + // Send Data to control register 11 + digitalWrite(dma_oe_pin, HIGH); // Display reset + digitalWrite(dma_lat_pin, LOW); + digitalWrite(dma_clk_pin, LOW); + for (int l = 0; l < MATRIX_WIDTH; l++){ + int y = l % 16; + digitalWrite(dma_r1_pin, LOW); + digitalWrite(dma_g1_pin, LOW); + digitalWrite(dma_b1_pin, LOW); + digitalWrite(dma_r2_pin, LOW); + digitalWrite(dma_g2_pin, LOW); + digitalWrite(dma_b2_pin, LOW); + + if (C12[y] == 1){ + digitalWrite(dma_r1_pin, HIGH); + digitalWrite(dma_g1_pin, HIGH); + digitalWrite(dma_b1_pin, HIGH); + digitalWrite(dma_r2_pin, HIGH); + digitalWrite(dma_g2_pin, HIGH); + digitalWrite(dma_b2_pin, HIGH); + } + + if (l > MATRIX_WIDTH - 12){ + digitalWrite(dma_lat_pin, HIGH); + } else { + digitalWrite(dma_lat_pin, LOW); + } + + digitalWrite(dma_clk_pin, HIGH); + digitalWrite(dma_clk_pin, LOW); + } + + digitalWrite(dma_lat_pin, LOW); + digitalWrite(dma_clk_pin, LOW); + + // Send Data to control register 12 + for (int l = 0; l < MATRIX_WIDTH; l++){ + int y = l % 16; + digitalWrite(dma_r1_pin, LOW); + digitalWrite(dma_g1_pin, LOW); + digitalWrite(dma_b1_pin, LOW); + digitalWrite(dma_r2_pin, LOW); + digitalWrite(dma_g2_pin, LOW); + digitalWrite(dma_b2_pin, LOW); + + if (C13[y] == 1){ + digitalWrite(dma_r1_pin, HIGH); + digitalWrite(dma_g1_pin, HIGH); + digitalWrite(dma_b1_pin, HIGH); + digitalWrite(dma_r2_pin, HIGH); + digitalWrite(dma_g2_pin, HIGH); + digitalWrite(dma_b2_pin, HIGH); + } + + if (l > MATRIX_WIDTH - 13){ + digitalWrite(dma_lat_pin, HIGH); + } else { + digitalWrite(dma_lat_pin, LOW); + } + digitalWrite(dma_clk_pin, HIGH); + digitalWrite(dma_clk_pin, LOW); + } + + digitalWrite(dma_lat_pin, LOW); + digitalWrite(dma_clk_pin, LOW); + break; + } + case SHIFT: + default: + break; + } +} diff --git a/ESP32-HUB75-MatrixPanel-I2S-DMA.h b/ESP32-HUB75-MatrixPanel-I2S-DMA.h new file mode 100644 index 0000000..5075595 --- /dev/null +++ b/ESP32-HUB75-MatrixPanel-I2S-DMA.h @@ -0,0 +1,443 @@ +#ifndef _ESP32_RGB_64_32_MATRIX_PANEL_I2S_DMA +#define _ESP32_RGB_64_32_MATRIX_PANEL_I2S_DMA + +/***************************************************************************************/ +/* COMPILE-TIME OPTIONS - CONFIGURE AS DESIRED */ +/***************************************************************************************/ +/* Enable serial debugging of the library, to see how memory is allocated etc. */ +//#define SERIAL_DEBUG 1 + +/* Use GFX_Root (https://github.com/mrfaptastic/GFX_Root) instead of + * Adafruit_GFX library. No real benefit unless you don't want Bus_IO & Wire.h library dependencies. + */ +//#define USE_GFX_ROOT 1 + + +/* Physical / Chained HUB75(s) RGB pixel WIDTH and HEIGHT. + * + * 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'. + * + * Also, if you use a 64x64 panel, then set the MATRIX_HEIGHT to '64' and an E_PIN; it will work! + * + * All of this is memory permitting of course (dependant on your sketch etc.) ... + * + */ +#ifndef MATRIX_WIDTH + #define MATRIX_WIDTH 64 // CHANGE THIS VALUE IF CHAINING +#endif + +#ifndef MATRIX_HEIGHT + #define MATRIX_HEIGHT 32 // CHANGE THIS VALUE ONLY IF USING 64px HIGH panel with E PIN +#endif + +/* Best to keep these values as is. */ +#ifndef PIXEL_COLOR_DEPTH_BITS + #define PIXEL_COLOR_DEPTH_BITS 8 // 8bit per RGB color = 24 bit/per pixel, reduce to save RAM +#endif +#ifndef MATRIX_ROWS_IN_PARALLEL + #define MATRIX_ROWS_IN_PARALLEL 2 // Don't change this unless you know what you're doing +#endif + +/* ESP32 Default Pin definition. You can change this, but best if you keep it as is and provide custom pin mappings + * as part of the begin(...) function. + */ +#define R1_PIN_DEFAULT 25 +#define G1_PIN_DEFAULT 26 +#define B1_PIN_DEFAULT 27 +#define R2_PIN_DEFAULT 14 +#define G2_PIN_DEFAULT 12 +#define B2_PIN_DEFAULT 13 + +#define A_PIN_DEFAULT 23 +#define B_PIN_DEFAULT 19 +#define C_PIN_DEFAULT 5 +#define D_PIN_DEFAULT 17 +#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. + +/***************************************************************************************/ +/* Do not change. */ + +#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" + +#ifdef USE_GFX_ROOT + #include "GFX.h" // Adafruit GFX core class -> https://github.com/mrfaptastic/GFX_Root +#else + #include "Adafruit_GFX.h" // Adafruit class with all the other stuff +#endif + +/***************************************************************************************/ +/* Do not change. */ + +// 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 COLOR_CHANNELS_PER_PIXEL 3 +#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 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. +/***************************************************************************************/ + + +/* rowBitStruct + * Note: sizeof(data) must be multiple of 32 bits, as ESP32 DMA linked list buffer address pointer + * must be word-aligned. + */ +struct rowBitStruct { + 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[PIXEL_COLOR_DEPTH_BITS]; +}; + +/* frameStruct + * Note: A 'frameStruct' contains ALL the data for a full-frame (i.e. BOTH 2x16-row frames are + * are contained in parallel within the one uint16_t that is sent in parallel to the HUB75). + * + * This structure isn't actually allocated in one memory block anymore, as the library now allocates + * memory per row (per rowColorDepthStruct) instead. + */ +struct frameStruct { + rowColorDepthStruct rowdata[ROWS_PER_FRAME]; +}; + +typedef struct RGB24 { + RGB24() : RGB24(0,0,0) {} + RGB24(uint8_t r, uint8_t g, uint8_t b) { + red = r; green = g; blue = b; + } + RGB24& operator=(const RGB24& col); + + uint8_t red; + uint8_t green; + uint8_t blue; +} RGB24; + +enum shift_driver {SHIFT=0, FM6124, FM6126A}; + +/***************************************************************************************/ +// Used by val2PWM +//C/p'ed from https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/ +// Example calculator: https://gist.github.com/mathiasvr/19ce1d7b6caeab230934080ae1f1380e +const uint16_t lumConvTab[]={ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 16, 16, 17, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 27, 27, 28, 28, 29, 30, 30, 31, 31, 32, 33, 33, 34, 35, 35, 36, 37, 38, 38, 39, 40, 41, 41, 42, 43, 44, 45, 45, 46, 47, 48, 49, 50, 51, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 80, 81, 82, 83, 84, 86, 87, 88, 90, 91, 92, 93, 95, 96, 98, 99, 100, 102, 103, 105, 106, 107, 109, 110, 112, 113, 115, 116, 118, 120, 121, 123, 124, 126, 128, 129, 131, 133, 134, 136, 138, 139, 141, 143, 145, 146, 148, 150, 152, 154, 156, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 192, 194, 196, 198, 200, 203, 205, 207, 209, 212, 214, 216, 218, 221, 223, 226, 228, 230, 233, 235, 238, 240, 243, 245, 248, 250, 253, 255, 255}; + +/***************************************************************************************/ +#ifdef USE_GFX_ROOT +class RGB64x32MatrixPanel_I2S_DMA : public GFX { +#else +class RGB64x32MatrixPanel_I2S_DMA : public Adafruit_GFX { +#endif + + // ------- PUBLIC ------- + public: + + /** + * 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) +#ifdef USE_GFX_ROOT + : GFX(MATRIX_WIDTH, MATRIX_HEIGHT), double_buffering_enabled(_double_buffer) { +#else + : Adafruit_GFX(MATRIX_WIDTH, MATRIX_HEIGHT), double_buffering_enabled(_double_buffer) { +#endif + + } + + /* 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, const shift_driver _drv=SHIFT) + { + + // Change 'if' to '1' to enable, 0 to not include this Serial output in compiled program + #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); + Serial.printf("Using pin %d for the R2_PIN\n", dma_r2_pin); + Serial.printf("Using pin %d for the G2_PIN\n", dma_g2_pin); + Serial.printf("Using pin %d for the B2_PIN\n", dma_b2_pin); + Serial.printf("Using pin %d for the A_PIN\n", dma_a_pin); + Serial.printf("Using pin %d for the B_PIN\n", dma_b_pin); + 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 + + // initialize some sppecific panel drivers + if (_drv) + shiftDriver(_drv, 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); + + /* 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 + flipDMABuffer(); // flip to backbuffer 1 + clearScreen(); // Must fill the DMA buffer with the initial output bit sequence or the panel will display garbage + flipDMABuffer(); // backbuffer 0 + + // 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 backbuf_id of 0 + + #if SERIAL_DEBUG + if (!everything_OK) + Serial.println("RGB64x32MatrixPanel_I2S_DMA::begin() failed."); + #endif + + return everything_OK; + + } + + // TODO: Disable/Enable auto buffer flipping (useful for lots of drawPixel usage)... + + // Draw pixels + virtual void drawPixel(int16_t x, int16_t y, uint16_t color); // overwrite adafruit implementation + virtual void fillScreen(uint16_t color); // overwrite adafruit implementation + void clearScreen() { fillScreen(0); } + void fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b); + void drawPixelRGB565(int16_t x, int16_t y, uint16_t color); + void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b); + void drawPixelRGB24(int16_t x, int16_t y, RGB24 color); + void drawIcon (int *ico, int16_t x, int16_t y, int16_t cols, int16_t rows); + + // 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! + + // Converts RGB333 to RGB565 + uint16_t color333(uint8_t r, uint8_t g, uint8_t b); // This is what is used by Adafruit GFX! Not sure why they have a capital 'C' for this particular function. + + inline void flipDMABuffer() + { + if ( !double_buffering_enabled) return; + + // 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 + + // Wait before we allow any writing to the buffer. Stop flicker. + while(!i2s_parallel_is_previous_buffer_free()) {} + } + + inline void showDMABuffer() + { + + 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); + + // Wait before we allow any writing to the buffer. Stop flicker. + while(!i2s_parallel_is_previous_buffer_free()) {} + } + + + inline void setPanelBrightness(int b) + { + // Change to set the brightness of the display, range of 1 to matrixWidth (i.e. 1 - 64) + brightness = b; + } + + inline void setMinRefreshRate(int rr) + { + min_refresh_rate = rr; + } + + int calculated_refresh_rate = 0; + + // ------- PRIVATE ------- + private: + + /* 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) + * Memory is allocated (malloc'd) by the row, and not in one massive chunk, for flexibility. + */ + rowColorDepthStruct *matrix_row_framebuffer_malloc[ROWS_PER_FRAME]; + + // ESP 32 DMA Linked List descriptor + int desccount = 0; + lldesc_t * dmadesc_a = {0}; + lldesc_t * dmadesc_b = {0}; + + // ESP32-HUB75-MatrixPanel-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 = 99; // 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 */ + 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) */ + void updateMatrixDMABuffer(uint8_t red, uint8_t green, uint8_t blue); + + /** + * pre-init procedures for specific drivers + * + */ + void shiftDriver(const shift_driver _drv, const int dma_r1_pin, const int dma_g1_pin, const int dma_b1_pin, const int dma_r2_pin, const int dma_g2_pin, const int dma_b2_pin, const int dma_a_pin, const int dma_b_pin, const int dma_c_pin, const int dma_d_pin, const int dma_e_pin, const int dma_lat_pin, const int dma_oe_pin, const int dma_clk_pin); + +}; // end Class header + +/***************************************************************************************/ +// https://stackoverflow.com/questions/5057021/why-are-c-inline-functions-in-the-header +/* 2. functions declared in the header must be marked inline because otherwise, every translation unit which includes the header will contain a definition of the function, and the linker will complain about multiple definitions (a violation of the One Definition Rule). The inline keyword suppresses this, allowing multiple translation units to contain (identical) definitions. */ +inline void RGB64x32MatrixPanel_I2S_DMA::drawPixel(int16_t x, int16_t y, uint16_t color) // adafruit virtual void override +{ + drawPixelRGB565( x, y, color); +} + +inline void RGB64x32MatrixPanel_I2S_DMA::fillScreen(uint16_t color) // adafruit virtual void override +{ + 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(r, g, b); // the RGB only (no pixel coordinate) version of 'updateMatrixDMABuffer' +} + +inline void RGB64x32MatrixPanel_I2S_DMA::fillScreenRGB888(uint8_t r, uint8_t g,uint8_t b) // adafruit virtual void override +{ + updateMatrixDMABuffer(r, g, b); +} + +// 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); +} + +inline void RGB64x32MatrixPanel_I2S_DMA::drawPixelRGB24(int16_t x, int16_t y, RGB24 color) +{ + 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) { + +/* + 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); +} + +// 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) { + + return ((r & 0x7) << 13) | ((r & 0x6) << 10) | ((g & 0x7) << 8) | ((g & 0x7) << 5) | ((b & 0x7) << 2) | ((b & 0x6) >> 1); + +} + + +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 diff --git a/ESP32-RGB64x32MatrixPanel-I2S-DMA.cpp b/ESP32-RGB64x32MatrixPanel-I2S-DMA.cpp deleted file mode 100644 index 69dd1f6..0000000 --- a/ESP32-RGB64x32MatrixPanel-I2S-DMA.cpp +++ /dev/null @@ -1,883 +0,0 @@ -#include "ESP32-RGB64x32MatrixPanel-I2S-DMA.h" - -// Credits: Louis Beaudoin -// and Sprite_TM: https://www.esp32.com/viewtopic.php?f=17&t=3188 and https://www.esp32.com/viewtopic.php?f=13&t=3256 - -/* - - 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 artefacts 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 front buffer/back buffer 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. - -*/ - - -// For development testing only -//#define IGNORE_REFRESH_RATE 1 - - - -uint8_t val2PWM(int val) { - if (val<0) val=0; - if (val>255) val=255; - return lumConvTab[val]; -} - -bool RGB64x32MatrixPanel_I2S_DMA::allocateDMAmemory() -{ - - /*** - * 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 the amount of DMA capable memory that's actually available - #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!"); - } - - Serial.println("DMA memory blocks available before any malloc's: "); - heap_caps_print_heap_info(MALLOC_CAP_DMA); - - 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("The total amount of DMA capable SRAM memory is %d bytes.\r\n", heap_caps_get_free_size(MALLOC_CAP_DMA)); - 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 potentially fit the framebuffer into the DMA capable memory that's available? - if ( heap_caps_get_free_size(MALLOC_CAP_DMA) < _frame_buffer_memory_required ) { - - #if SERIAL_DEBUG - Serial.printf("######### Insufficient 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_free_size(MALLOC_CAP_DMA)) ); - #endif - - return false; - } - - // Alright, theoretically we should be OK, so let us do this, so - // lets allocate a chunk of memory for each row (a row could span multiple panels if chaining is in place) - for (int malloc_num =0; malloc_num < ROWS_PER_FRAME; malloc_num++) - { - matrix_row_framebuffer_malloc[malloc_num] = (rowColorDepthStruct *)heap_caps_malloc( (sizeof(rowColorDepthStruct) * _num_frame_buffers) , MALLOC_CAP_DMA); - // If the ESP crashes here, then we must have a horribly fragmented memory space, or trying to allocate a ludicrous resolution. - #if SERIAL_DEBUG - Serial.printf("Malloc'ing %d bytes of memory @ address %d for frame row %d.\r\n", (sizeof(rowColorDepthStruct) * _num_frame_buffers), matrix_row_framebuffer_malloc[malloc_num], malloc_num); - #endif - if ( matrix_row_framebuffer_malloc[malloc_num] == NULL ) { - Serial.printf("ERROR: Couldn't malloc matrix_row_framebuffer %d! Critical fail.\r\n", malloc_num); - return false; - } - } - - _total_dma_capable_memory_reserved += _frame_buffer_memory_required; - - - /*** - * Step 2: Calculate the amount of memory required for the DMA engine's linked list descriptors. - * Credit to SmartMatrix for this stuff. - */ - - - // Calculate what colour depth is actually possible based on memory available 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 min_refresh_rate) // HACK Hard Coded: 100 - break; - - 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, PIXEL_COLOR_DEPTH_BITS - 1); - #endif - - /*** - * Step 2a: lsbMsbTransition bit is now finalised - recalculate the DMA descriptor count required, which is used for - * memory allocation of the DMA linked list memory structure. - */ - numDMAdescriptorsPerRow = 1; - for(int i=lsbMsbTransitionBit + 1; i DMA_MAX ) { - - #if SERIAL_DEBUG - Serial.println("Split DMA payload required."); - #endif - - numDMAdescriptorsPerRow += PIXEL_COLOR_DEPTH_BITS-1; - // Not if numDMAdescriptorsPerRow is even just one descriptor too large, DMA linked list will not correctly loop. - } - - - /*** - * Step 3: Allocate memory for DMA linked list, linking up each framebuffer row in sequence for GPIO output. - */ - - _dma_linked_list_memory_required = numDMAdescriptorsPerRow * ROWS_PER_FRAME * _num_frame_buffers * sizeof(lldesc_t); - #if SERIAL_DEBUG - Serial.printf("Descriptors for lsbMsbTransitionBit of %d/%d with %d frame rows require %d bytes of DMA RAM with %d numDMAdescriptorsPerRow.\r\n", lsbMsbTransitionBit, PIXEL_COLOR_DEPTH_BITS - 1, ROWS_PER_FRAME, _dma_linked_list_memory_required, numDMAdescriptorsPerRow); - #endif - - _total_dma_capable_memory_reserved += _dma_linked_list_memory_required; - - // 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)) { - Serial.printf("ERROR: Not enough SRAM left over for DMA linked-list descriptor memory reservation! Oh so close!\r\n"); - - return false; - } // linked list descriptors memory check - - // malloc the DMA linked list descriptors that i2s_parallel will need - 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("ERROR: Could not malloc descriptor framebuffer a."); - return false; - } - - 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("ERROR: Could not malloc descriptor framebuffer b."); - return false; - } - } - - Serial.printf("*** ESP32-RGB64x32MatrixPanel-I2S-DMA: Memory Allocations Complete *** \r\n"); - Serial.printf("Total memory that was reserved: %d kB.\r\n", _total_dma_capable_memory_reserved/1024); - Serial.printf("... of which was used for the DMA Linked List(s): %d kB.\r\n", _dma_linked_list_memory_required/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("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)); - - - #if SERIAL_DEBUG - Serial.println("DMA capable memory map available after malloc's: "); - heap_caps_print_heap_info(MALLOC_CAP_DMA); - delay(1000); - #endif - - // Just os we know - everything_OK = true; - - 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; - - // HACK: If we need to split the payload in 1/2 so that it doesn't breach DMA_MAX, lets do it by the color_depth. - int num_dma_payload_color_depths = PIXEL_COLOR_DEPTH_BITS; - if ( sizeof(rowColorDepthStruct) > DMA_MAX ) { - num_dma_payload_color_depths = 1; - } - - // Fill DMA linked lists for both frames (as in, halves of the HUB75 panel) and if double buffering is enabled, link it up for both buffers. - for(int row = 0; row < ROWS_PER_FRAME; row++) { - - // Split framebuffer malloc hack 'improvement' - rowColorDepthStruct *fb_malloc_ptr = matrix_row_framebuffer_malloc[row]; - - #if SERIAL_DEBUG - Serial.printf("DMA payload of %d bytes. DMA_MAX is %d.\r\n", sizeof(rowBitStruct) * PIXEL_COLOR_DEPTH_BITS, DMA_MAX); - #endif - - - // first set of data is LSB through MSB, single pass (IF TOTAL SIZE < DMA_MAX) - all color bits are displayed once, which takes care of everything below and inlcluding LSBMSB_TRANSITION_BIT - // NOTE: size must be less than DMA_MAX - worst case for library: 16-bpp with 256 pixels per row would exceed this, need to break into two - link_dma_desc(&dmadesc_a[current_dmadescriptor_offset], previous_dmadesc_a, &(fb_malloc_ptr[0].rowbits[0].data), sizeof(rowBitStruct) * num_dma_payload_color_depths); - previous_dmadesc_a = &dmadesc_a[current_dmadescriptor_offset]; - - if (double_buffering_enabled) { - link_dma_desc(&dmadesc_b[current_dmadescriptor_offset], previous_dmadesc_b, &(fb_malloc_ptr[1].rowbits[0].data), sizeof(rowBitStruct) * num_dma_payload_color_depths); - previous_dmadesc_b = &dmadesc_b[current_dmadescriptor_offset]; } - - current_dmadescriptor_offset++; - - // If the number of pixels per row is to great for the size of a DMA payload, so we need to split what we were going to send above. - if ( sizeof(rowColorDepthStruct) > DMA_MAX ) - { - - #if SERIAL_DEBUG - Serial.printf("Spliting DMA payload for %d color depths into %d byte payloads.\r\n", PIXEL_COLOR_DEPTH_BITS-1, sizeof(rowBitStruct) ); - #endif - - for (int cd = 1; cd < PIXEL_COLOR_DEPTH_BITS; cd++) - { - // first set of data is LSB through MSB, single pass - all color bits are displayed once, which takes care of everything below and inlcluding LSBMSB_TRANSITION_BIT - // TODO: size must be less than DMA_MAX - worst case for library: 16-bpp with 256 pixels per row would exceed this, need to break into two - link_dma_desc(&dmadesc_a[current_dmadescriptor_offset], previous_dmadesc_a, &(fb_malloc_ptr[0].rowbits[cd].data), sizeof(rowBitStruct) ); - previous_dmadesc_a = &dmadesc_a[current_dmadescriptor_offset]; - - if (double_buffering_enabled) { - link_dma_desc(&dmadesc_b[current_dmadescriptor_offset], previous_dmadesc_b, &(fb_malloc_ptr[1].rowbits[cd].data), sizeof(rowBitStruct) ); - previous_dmadesc_b = &dmadesc_b[current_dmadescriptor_offset]; } - - current_dmadescriptor_offset++; - - } // additional linked list items - } // row depth struct - - - for(int i=lsbMsbTransitionBit + 1; iclkspeed_hz + 1), must result in >=2. Acceptable values 26.67MHz, 20MHz, 16MHz, 13.34MHz... - .bits=ESP32_I2S_DMA_MODE, //ESP32_I2S_DMA_MODE, - .bufa=0, - .bufb=0, - desccount, - desccount, - dmadesc_a, - dmadesc_b - }; - - //Setup I2S - i2s_parallel_setup_without_malloc(&I2S1, &cfg); - - #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 - -} // end initMatrixDMABuff - - -/* There are 'bits' set in the frameStruct that we simply don't need to set every single time we change a pixel / DMA buffer co-ordinate. - * For example, the bits that determine the address lines, we don't need to set these every time. Once they're in place, and assuming we - * don't accidently clear them, then we don't need to set them again. - * So to save processing, we strip this logic out to the absolute bare minimum, which is toggling only the R,G,B pixels (bits) per co-ord. - * - * Critical dependency: That 'updateMatrixDMABuffer(uint8_t red, uint8_t green, uint8_t blue)' has been run at least once over the - * entire frameBuffer to ensure all the non R,G,B bitmasks are in place (i.e. like OE, Address Lines etc.) - * - * Note: If you change the brightness with setBrightness() you MUST then clearScreen() and repaint / flush the entire framebuffer. - */ -//#define GO_FOR_SPEED 1 - -#ifdef GO_FOR_SPEED -/* 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) -{ - - // 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; - } - - // https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/ - red = lumConvTab[red]; - green = lumConvTab[green]; - blue = lumConvTab[blue]; - - bool painting_top_frame = true; - if ( y_coord >= ROWS_PER_FRAME) // co-ords start at zero, y_coord = 15 = 16 (rows per frame) - { - y_coord -= ROWS_PER_FRAME; // Subtract the ROWS_PER_FRAME from the pixel co-ord to get the panel ROW (not really the 'y_coord' anymore) - painting_top_frame = false; - } - - // We need to update the correct uint16_t in the rowBitStruct array, that gets sent out in parallel - // 16 bit parallel mode - Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering - int rowBitStruct_x_coord_uint16_t_position = (x_coord % 2) ? (x_coord-1):(x_coord+1); - - // Find the memory address for the malloc for this framebuffer row. - rowColorDepthStruct *fb_row_malloc_ptr = (rowColorDepthStruct *) matrix_row_framebuffer_malloc[y_coord]; - - for(int color_depth_idx=0; color_depth_idxdata[rowBitStruct_x_coord_uint16_t_position] = v; - // NOTE: No need to do this as 'v' is now a reference directly to the frameStruct - - } // color depth loop (8) - -} // updateMatrixDMABuffer (specific co-ords change) - -#else - -/* Update a specific co-ordinate in the DMA buffer */ -/* Original version were we re-create the bitstream from scratch for each x,y co-ordinate / pixel changed. Slightly slower. */ -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 SERIAL_DEBUG - Serial.println("Cannot updateMatrixDMABuffer as setup failed!"); - #endif - - return; - } - - /* LED Brightness Compensation. Because if we do a basic "red & mask" for example, - * we'll NEVER send the dimmest possible colour, due to binary skew. - - i.e. It's almost impossible for color_depth_idx of 0 to be sent out to the MATRIX unless the 'value' of a color is exactly '1' - - */ - red = lumConvTab[red]; - green = lumConvTab[green]; - blue = lumConvTab[blue]; - - - /* 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; - } - - /* When using the drawPixel, we are obviously only changing the value of one x,y position, - * however, the two-scan panels paint 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. - */ - bool painting_top_frame = true; - if ( y_coord >= ROWS_PER_FRAME) // co-ords start at zero, y_coord = 15 = 16 (rows per frame) - { - y_coord -= ROWS_PER_FRAME; // Subtract the ROWS_PER_FRAME from the pixel co-ord to get the panel ROW (not really the 'y_coord' anymore) - painting_top_frame = false; - } - - // We need to update the correct uint16_t in the rowBitStruct array, that gets sent out in parallel - int rowBitStruct_x_coord_uint16_t_position = (x_coord % 2) ? (x_coord-1):(x_coord+1); - - for(int color_depth_idx=0; color_depth_idxdata[rowBitStruct_x_coord_uint16_t_position]; // persist what we already have - 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_ROW-1) v|=BIT_LAT; - - // need to turn off OE one clock before latch, otherwise can get ghosting - if((x_coord)==PIXELS_PER_ROW-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 - } - - /* - // Development / testing code only. - Serial.printf("r value of %d, color depth: %d, mask: %d\r\n", red, color_depth_idx, mask); - if (red & mask) { Serial.println("Success - Binary"); v|=BIT_R1; } - Serial.printf("val2pwm r value: %d\r\n", val2PWM(red)); - if (val2PWM(red) & mask) { Serial.println("Success - PWM"); v|=BIT_R2; } - */ - - - if (painting_top_frame) - { // 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[rowBitStruct_x_coord_uint16_t_position] & BIT_R2) - v|=BIT_R2; - - if (p->data[rowBitStruct_x_coord_uint16_t_position] & BIT_G2) - v|=BIT_G2; - - if (p->data[rowBitStruct_x_coord_uint16_t_position] & 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 / persist - if (p->data[rowBitStruct_x_coord_uint16_t_position] & BIT_R1) - v|=BIT_R1; - - if (p->data[rowBitStruct_x_coord_uint16_t_position] & BIT_G1) - v|=BIT_G1; - - if (p->data[rowBitStruct_x_coord_uint16_t_position] & 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 - */ - - // 16 bit parallel mode - p->data[rowBitStruct_x_coord_uint16_t_position] = v; - - - } // color depth loop (8) - -} // updateMatrixDMABuffer (specific co-ords change) -#endif - - -/* 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; - - /* https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/ */ - /* - red = val2PWM(red); - green = val2PWM(green); - blue = val2PWM(blue); - */ - red = lumConvTab[red]; - green = lumConvTab[green]; - blue = lumConvTab[blue]; - - 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 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 and bottom matrix MATRIX_ROWS_IN_PARALLEL half colours - if (green & mask) { v|=BIT_G1; v|=BIT_G2; } - if (blue & mask) { v|=BIT_B1; v|=BIT_B2; } - if (red & mask) { v|=BIT_R1; v|=BIT_R2; } - - // 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 - -} // updateMatrixDMABuffer (full frame paint) - -/** - * pre-init procedures for specific drivers - * - */ -void RGB64x32MatrixPanel_I2S_DMA::shiftDriver(const shift_driver _drv, const int dma_r1_pin, const int dma_g1_pin, const int dma_b1_pin, const int dma_r2_pin, const int dma_g2_pin, const int dma_b2_pin, const int dma_a_pin, const int dma_b_pin, const int dma_c_pin, const int dma_d_pin, const int dma_e_pin, const int dma_lat_pin, const int dma_oe_pin, const int dma_clk_pin){ - switch (_drv){ - case FM6124: - case FM6126A: - { - #if SERIAL_DEBUG - Serial.println( F("RGB64x32MatrixPanel_I2S_DMA - initializing FM6124 driver...")); - #endif - int C12[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - int C13[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}; - - pinMode(dma_r1_pin, OUTPUT); - pinMode(dma_g1_pin, OUTPUT); - pinMode(dma_b1_pin, OUTPUT); - pinMode(dma_r2_pin, OUTPUT); - pinMode(dma_g2_pin, OUTPUT); - pinMode(dma_b2_pin, OUTPUT); - pinMode(dma_a_pin, OUTPUT); - pinMode(dma_b_pin, OUTPUT); - pinMode(dma_c_pin, OUTPUT); - pinMode(dma_d_pin, OUTPUT); - pinMode(dma_e_pin, OUTPUT); - pinMode(dma_clk_pin, OUTPUT); - pinMode(dma_lat_pin, OUTPUT); - pinMode(dma_oe_pin, OUTPUT); - - // Send Data to control register 11 - digitalWrite(dma_oe_pin, HIGH); // Display reset - digitalWrite(dma_lat_pin, LOW); - digitalWrite(dma_clk_pin, LOW); - for (int l = 0; l < MATRIX_WIDTH; l++){ - int y = l % 16; - digitalWrite(dma_r1_pin, LOW); - digitalWrite(dma_g1_pin, LOW); - digitalWrite(dma_b1_pin, LOW); - digitalWrite(dma_r2_pin, LOW); - digitalWrite(dma_g2_pin, LOW); - digitalWrite(dma_b2_pin, LOW); - - if (C12[y] == 1){ - digitalWrite(dma_r1_pin, HIGH); - digitalWrite(dma_g1_pin, HIGH); - digitalWrite(dma_b1_pin, HIGH); - digitalWrite(dma_r2_pin, HIGH); - digitalWrite(dma_g2_pin, HIGH); - digitalWrite(dma_b2_pin, HIGH); - } - - if (l > MATRIX_WIDTH - 12){ - digitalWrite(dma_lat_pin, HIGH); - } else { - digitalWrite(dma_lat_pin, LOW); - } - - digitalWrite(dma_clk_pin, HIGH); - digitalWrite(dma_clk_pin, LOW); - } - - digitalWrite(dma_lat_pin, LOW); - digitalWrite(dma_clk_pin, LOW); - - // Send Data to control register 12 - for (int l = 0; l < MATRIX_WIDTH; l++){ - int y = l % 16; - digitalWrite(dma_r1_pin, LOW); - digitalWrite(dma_g1_pin, LOW); - digitalWrite(dma_b1_pin, LOW); - digitalWrite(dma_r2_pin, LOW); - digitalWrite(dma_g2_pin, LOW); - digitalWrite(dma_b2_pin, LOW); - - if (C13[y] == 1){ - digitalWrite(dma_r1_pin, HIGH); - digitalWrite(dma_g1_pin, HIGH); - digitalWrite(dma_b1_pin, HIGH); - digitalWrite(dma_r2_pin, HIGH); - digitalWrite(dma_g2_pin, HIGH); - digitalWrite(dma_b2_pin, HIGH); - } - - if (l > MATRIX_WIDTH - 13){ - digitalWrite(dma_lat_pin, HIGH); - } else { - digitalWrite(dma_lat_pin, LOW); - } - digitalWrite(dma_clk_pin, HIGH); - digitalWrite(dma_clk_pin, LOW); - } - - digitalWrite(dma_lat_pin, LOW); - digitalWrite(dma_clk_pin, LOW); - break; - } - case SHIFT: - default: - break; - } -} diff --git a/ESP32-RGB64x32MatrixPanel-I2S-DMA.h b/ESP32-RGB64x32MatrixPanel-I2S-DMA.h deleted file mode 100644 index c30709a..0000000 --- a/ESP32-RGB64x32MatrixPanel-I2S-DMA.h +++ /dev/null @@ -1,443 +0,0 @@ -#ifndef _ESP32_RGB_64_32_MATRIX_PANEL_I2S_DMA -#define _ESP32_RGB_64_32_MATRIX_PANEL_I2S_DMA - -/***************************************************************************************/ -/* COMPILE-TIME OPTIONS - CONFIGURE AS DESIRED */ -/***************************************************************************************/ -/* Enable serial debugging of the library, to see how memory is allocated etc. */ -//#define SERIAL_DEBUG 1 - -/* Use GFX_Root (https://github.com/mrfaptastic/GFX_Root) instead of - * Adafruit_GFX library. No real benefit unless you don't want Bus_IO & Wire.h library dependencies. - */ -//#define USE_GFX_ROOT 1 - - -/* Physical / Chained HUB75(s) RGB pixel WIDTH and HEIGHT. - * - * 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'. - * - * Also, if you use a 64x64 panel, then set the MATRIX_HEIGHT to '64' and an E_PIN; it will work! - * - * All of this is memory permitting of course (dependant on your sketch etc.) ... - * - */ -#ifndef MATRIX_WIDTH - #define MATRIX_WIDTH 64 // CHANGE THIS VALUE IF CHAINING -#endif - -#ifndef MATRIX_HEIGHT - #define MATRIX_HEIGHT 32 // CHANGE THIS VALUE ONLY IF USING 64px HIGH panel with E PIN -#endif - -/* Best to keep these values as is. */ -#ifndef PIXEL_COLOR_DEPTH_BITS - #define PIXEL_COLOR_DEPTH_BITS 8 // 8bit per RGB color = 24 bit/per pixel, reduce to save RAM -#endif -#ifndef MATRIX_ROWS_IN_PARALLEL - #define MATRIX_ROWS_IN_PARALLEL 2 // Don't change this unless you know what you're doing -#endif - -/* ESP32 Default Pin definition. You can change this, but best if you keep it as is and provide custom pin mappings - * as part of the begin(...) function. - */ -#define R1_PIN_DEFAULT 25 -#define G1_PIN_DEFAULT 26 -#define B1_PIN_DEFAULT 27 -#define R2_PIN_DEFAULT 14 -#define G2_PIN_DEFAULT 12 -#define B2_PIN_DEFAULT 13 - -#define A_PIN_DEFAULT 23 -#define B_PIN_DEFAULT 19 -#define C_PIN_DEFAULT 5 -#define D_PIN_DEFAULT 17 -#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. - -/***************************************************************************************/ -/* Do not change. */ - -#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" - -#ifdef USE_GFX_ROOT - #include "GFX.h" // Adafruit GFX core class -> https://github.com/mrfaptastic/GFX_Root -#else - #include "Adafruit_GFX.h" // Adafruit class with all the other stuff -#endif - -/***************************************************************************************/ -/* Do not change. */ - -// 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 COLOR_CHANNELS_PER_PIXEL 3 -#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 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. -/***************************************************************************************/ - - -/* rowBitStruct - * Note: sizeof(data) must be multiple of 32 bits, as ESP32 DMA linked list buffer address pointer - * must be word-aligned. - */ -struct rowBitStruct { - 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[PIXEL_COLOR_DEPTH_BITS]; -}; - -/* frameStruct - * Note: A 'frameStruct' contains ALL the data for a full-frame (i.e. BOTH 2x16-row frames are - * are contained in parallel within the one uint16_t that is sent in parallel to the HUB75). - * - * This structure isn't actually allocated in one memory block anymore, as the library now allocates - * memory per row (per rowColorDepthStruct) instead. - */ -struct frameStruct { - rowColorDepthStruct rowdata[ROWS_PER_FRAME]; -}; - -typedef struct RGB24 { - RGB24() : RGB24(0,0,0) {} - RGB24(uint8_t r, uint8_t g, uint8_t b) { - red = r; green = g; blue = b; - } - RGB24& operator=(const RGB24& col); - - uint8_t red; - uint8_t green; - uint8_t blue; -} RGB24; - -enum shift_driver {SHIFT=0, FM6124, FM6126A}; - -/***************************************************************************************/ -// Used by val2PWM -//C/p'ed from https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/ -// Example calculator: https://gist.github.com/mathiasvr/19ce1d7b6caeab230934080ae1f1380e -const uint16_t lumConvTab[]={ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 16, 16, 17, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 27, 27, 28, 28, 29, 30, 30, 31, 31, 32, 33, 33, 34, 35, 35, 36, 37, 38, 38, 39, 40, 41, 41, 42, 43, 44, 45, 45, 46, 47, 48, 49, 50, 51, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 80, 81, 82, 83, 84, 86, 87, 88, 90, 91, 92, 93, 95, 96, 98, 99, 100, 102, 103, 105, 106, 107, 109, 110, 112, 113, 115, 116, 118, 120, 121, 123, 124, 126, 128, 129, 131, 133, 134, 136, 138, 139, 141, 143, 145, 146, 148, 150, 152, 154, 156, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 192, 194, 196, 198, 200, 203, 205, 207, 209, 212, 214, 216, 218, 221, 223, 226, 228, 230, 233, 235, 238, 240, 243, 245, 248, 250, 253, 255, 255}; - -/***************************************************************************************/ -#ifdef USE_GFX_ROOT -class RGB64x32MatrixPanel_I2S_DMA : public GFX { -#else -class RGB64x32MatrixPanel_I2S_DMA : public Adafruit_GFX { -#endif - - // ------- PUBLIC ------- - public: - - /** - * 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) -#ifdef USE_GFX_ROOT - : GFX(MATRIX_WIDTH, MATRIX_HEIGHT), double_buffering_enabled(_double_buffer) { -#else - : Adafruit_GFX(MATRIX_WIDTH, MATRIX_HEIGHT), double_buffering_enabled(_double_buffer) { -#endif - - } - - /* 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, const shift_driver _drv=SHIFT) - { - - // Change 'if' to '1' to enable, 0 to not include this Serial output in compiled program - #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); - Serial.printf("Using pin %d for the R2_PIN\n", dma_r2_pin); - Serial.printf("Using pin %d for the G2_PIN\n", dma_g2_pin); - Serial.printf("Using pin %d for the B2_PIN\n", dma_b2_pin); - Serial.printf("Using pin %d for the A_PIN\n", dma_a_pin); - Serial.printf("Using pin %d for the B_PIN\n", dma_b_pin); - 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 - - // initialize some sppecific panel drivers - if (_drv) - shiftDriver(_drv, 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); - - /* 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 - flipDMABuffer(); // flip to backbuffer 1 - clearScreen(); // Must fill the DMA buffer with the initial output bit sequence or the panel will display garbage - flipDMABuffer(); // backbuffer 0 - - // 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 backbuf_id of 0 - - #if SERIAL_DEBUG - if (!everything_OK) - Serial.println("RGB64x32MatrixPanel_I2S_DMA::begin() failed."); - #endif - - return everything_OK; - - } - - // TODO: Disable/Enable auto buffer flipping (useful for lots of drawPixel usage)... - - // Draw pixels - virtual void drawPixel(int16_t x, int16_t y, uint16_t color); // overwrite adafruit implementation - virtual void fillScreen(uint16_t color); // overwrite adafruit implementation - void clearScreen() { fillScreen(0); } - void fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b); - void drawPixelRGB565(int16_t x, int16_t y, uint16_t color); - void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b); - void drawPixelRGB24(int16_t x, int16_t y, RGB24 color); - void drawIcon (int *ico, int16_t x, int16_t y, int16_t cols, int16_t rows); - - // 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! - - // Converts RGB333 to RGB565 - uint16_t color333(uint8_t r, uint8_t g, uint8_t b); // This is what is used by Adafruit GFX! Not sure why they have a capital 'C' for this particular function. - - inline void flipDMABuffer() - { - if ( !double_buffering_enabled) return; - - // 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 - - // Wait before we allow any writing to the buffer. Stop flicker. - while(!i2s_parallel_is_previous_buffer_free()) {} - } - - inline void showDMABuffer() - { - - 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); - - // Wait before we allow any writing to the buffer. Stop flicker. - while(!i2s_parallel_is_previous_buffer_free()) {} - } - - - inline void setPanelBrightness(int b) - { - // Change to set the brightness of the display, range of 1 to matrixWidth (i.e. 1 - 64) - brightness = b; - } - - inline void setMinRefreshRate(int rr) - { - min_refresh_rate = rr; - } - - int calculated_refresh_rate = 0; - - // ------- PRIVATE ------- - private: - - /* 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) - * Memory is allocated (malloc'd) by the row, and not in one massive chunk, for flexibility. - */ - rowColorDepthStruct *matrix_row_framebuffer_malloc[ROWS_PER_FRAME]; - - // 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 = 99; // 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 */ - 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) */ - void updateMatrixDMABuffer(uint8_t red, uint8_t green, uint8_t blue); - - /** - * pre-init procedures for specific drivers - * - */ - void shiftDriver(const shift_driver _drv, const int dma_r1_pin, const int dma_g1_pin, const int dma_b1_pin, const int dma_r2_pin, const int dma_g2_pin, const int dma_b2_pin, const int dma_a_pin, const int dma_b_pin, const int dma_c_pin, const int dma_d_pin, const int dma_e_pin, const int dma_lat_pin, const int dma_oe_pin, const int dma_clk_pin); - -}; // end Class header - -/***************************************************************************************/ -// https://stackoverflow.com/questions/5057021/why-are-c-inline-functions-in-the-header -/* 2. functions declared in the header must be marked inline because otherwise, every translation unit which includes the header will contain a definition of the function, and the linker will complain about multiple definitions (a violation of the One Definition Rule). The inline keyword suppresses this, allowing multiple translation units to contain (identical) definitions. */ -inline void RGB64x32MatrixPanel_I2S_DMA::drawPixel(int16_t x, int16_t y, uint16_t color) // adafruit virtual void override -{ - drawPixelRGB565( x, y, color); -} - -inline void RGB64x32MatrixPanel_I2S_DMA::fillScreen(uint16_t color) // adafruit virtual void override -{ - 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(r, g, b); // the RGB only (no pixel coordinate) version of 'updateMatrixDMABuffer' -} - -inline void RGB64x32MatrixPanel_I2S_DMA::fillScreenRGB888(uint8_t r, uint8_t g,uint8_t b) // adafruit virtual void override -{ - updateMatrixDMABuffer(r, g, b); -} - -// 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); -} - -inline void RGB64x32MatrixPanel_I2S_DMA::drawPixelRGB24(int16_t x, int16_t y, RGB24 color) -{ - 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) { - -/* - 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); -} - -// 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) { - - return ((r & 0x7) << 13) | ((r & 0x6) << 10) | ((g & 0x7) << 8) | ((g & 0x7) << 5) | ((b & 0x7) << 2) | ((b & 0x6) >> 1); - -} - - -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 diff --git a/ESP32-VirtualMatrixPanel-I2S-DMA.h b/ESP32-VirtualMatrixPanel-I2S-DMA.h index 4655866..2a7e569 100644 --- a/ESP32-VirtualMatrixPanel-I2S-DMA.h +++ b/ESP32-VirtualMatrixPanel-I2S-DMA.h @@ -9,7 +9,7 @@ Twitter: https://twitter.com/witnessmenow *******************************************************************/ -#include "ESP32-RGB64x32MatrixPanel-I2S-DMA.h" +#include "ESP32-HUB75-MatrixPanel-I2S-DMA.h" #include diff --git a/README.md b/README.md index e34a12a..526fe37 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ As a result, this library can theoretically provide ~16-24 bit colour, at variou # Wiring ESP32 with the LED Matrix Panel -By default the pin mapping is as follows (defaults defined in ESP32-RGB64x32MatrixPanel-I2S-DMA.h). +By default the pin mapping is as follows (defaults defined in ESP32-HUB75-MatrixPanel-I2S-DMA.h). ``` HUB 75 PANEL ESP 32 PIN @@ -71,13 +71,13 @@ Below is a bare minimum sketch to draw a single white dot in the top left. You m No .begin() before other functions = Crash ``` -#include +#include RGB64x32MatrixPanel_I2S_DMA matrix; void setup() { // MUST DO THIS FIRST! - matrix.begin(); // Use default pins supplied within ESP32-RGB64x32MatrixPanel-I2S-DMA.h + matrix.begin(); // Use default pins supplied within ESP32-HUB75-MatrixPanel-I2S-DMA.h // matrix.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 ); // or custom pins // Draw a single white pixel @@ -131,12 +131,12 @@ Summary: setPanelBrightness(xx) value can be any number from 0 (display off) to ## Power, Power and Power! -Having a good power supply is CRITICAL, and it is highly recommended, for chains of LED Panels to have a 2000uf capacitor soldered to the back of each LED Panel across the [GND and VCC pins](https://github.com/mrfaptastic/ESP32-RGB64x32MatrixPanel-I2S-DMA/issues/39#issuecomment-720780463), otherwise you WILL run into issues with 'flashy' graphics whereby a large amount of LEDs are turned on and off in succession (due to current/power draw peaks and troughs). +Having a good power supply is CRITICAL, and it is highly recommended, for chains of LED Panels to have a 2000uf capacitor soldered to the back of each LED Panel across the [GND and VCC pins](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/39#issuecomment-720780463), otherwise you WILL run into issues with 'flashy' graphics whereby a large amount of LEDs are turned on and off in succession (due to current/power draw peaks and troughs). Refer to this guide written for the [rpi-rgb-led-matrix library](https://github.com/hzeller/rpi-rgb-led-matrix/blob/master/wiring.md#a-word-about-power) for an explanation. -- Refer to this [example](https://github.com/mrfaptastic/ESP32-RGB64x32MatrixPanel-I2S-DMA/issues/39#issuecomment-722691127) issue of what can go wrong with a poor powersupply. -- Refer to [this comment](https://github.com/mrfaptastic/ESP32-RGB64x32MatrixPanel-I2S-DMA/issues/35#issuecomment-726419862) in regards to certain panels not playing nice with voltages, and a 3.3volt signal that the ESP32 GPIO can only provide. +- Refer to this [example](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/39#issuecomment-722691127) issue of what can go wrong with a poor powersupply. +- Refer to [this comment](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/35#issuecomment-726419862) in regards to certain panels not playing nice with voltages, and a 3.3volt signal that the ESP32 GPIO can only provide. ## Inspiration diff --git a/examples/AnimatedGIFPanel/AnimatedGIFPanel.ino b/examples/AnimatedGIFPanel/AnimatedGIFPanel.ino index 112c60f..f78d5ff 100644 --- a/examples/AnimatedGIFPanel/AnimatedGIFPanel.ino +++ b/examples/AnimatedGIFPanel/AnimatedGIFPanel.ino @@ -21,7 +21,7 @@ #define FILESYSTEM SPIFFS #include #include -#include +#include // ---------------------------- diff --git a/examples/AuroraDemo/AuroraDemo.ino b/examples/AuroraDemo/AuroraDemo.ino index 917611d..dbc0dc5 100644 --- a/examples/AuroraDemo/AuroraDemo.ino +++ b/examples/AuroraDemo/AuroraDemo.ino @@ -20,7 +20,7 @@ #define MATRIX_HEIGHT 32 /* -------------------------- Class Initialisation -------------------------- */ -#include +#include RGB64x32MatrixPanel_I2S_DMA matrix; #include diff --git a/examples/BitmapIcons/BitmapIcons.ino b/examples/BitmapIcons/BitmapIcons.ino index 72328c5..310c8a8 100644 --- a/examples/BitmapIcons/BitmapIcons.ino +++ b/examples/BitmapIcons/BitmapIcons.ino @@ -1,5 +1,5 @@ #include -#include +#include #include "Dhole_weather_icons32px.h" /*--------------------- DEBUG -------------------------*/ diff --git a/examples/ChainedPanels/ChainedPanels.ino b/examples/ChainedPanels/ChainedPanels.ino index 3f90042..da3b3f4 100644 --- a/examples/ChainedPanels/ChainedPanels.ino +++ b/examples/ChainedPanels/ChainedPanels.ino @@ -3,7 +3,7 @@ Steps to use ----------- - 1) In ESP32-RGB64x32MatrixPanel-I2S-DMA.h: + 1) In ESP32-HUB75-MatrixPanel-I2S-DMA.h: - Set the MATRIX_HEIGHT to be the y resolution of the physical chained panels in a line (if the panels are 32 x 16, set it to be 16) @@ -25,7 +25,7 @@ Thanks to: * Brian Lough for the original example as raised in this issue: - https://github.com/mrfaptastic/ESP32-RGB64x32MatrixPanel-I2S-DMA/issues/26 + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/26 YouTube: https://www.youtube.com/brianlough Tindie: https://www.tindie.com/stores/brianlough/ @@ -202,7 +202,7 @@ void setup() { if (dma_display.width() != NUM_ROWS*NUM_COLS*PANEL_RES_X ) { - Serial.println(F("\r\nERROR: MATRIX_WIDTH and/or MATRIX_HEIGHT in 'ESP32-RGB64x32MatrixPanel-I2S-DMA.h'\r\nis not configured correctly for the requested VirtualMatrixPanel dimensions!\r\n")); + Serial.println(F("\r\nERROR: MATRIX_WIDTH and/or MATRIX_HEIGHT in 'ESP32-HUB75-MatrixPanel-I2S-DMA.h'\r\nis not configured correctly for the requested VirtualMatrixPanel dimensions!\r\n")); Serial.printf("WIDTH according dma_display is %d, but should be %d. Is your NUM_ROWS and NUM_COLS correct?\r\n", dma_display.width(), NUM_ROWS*NUM_COLS*PANEL_RES_X); return; } diff --git a/examples/ChainedPanels/README.md b/examples/ChainedPanels/README.md index 9385522..7b8e526 100644 --- a/examples/ChainedPanels/README.md +++ b/examples/ChainedPanels/README.md @@ -16,7 +16,7 @@ the matrix, but with VirtualDisplay library looking after the pixel mapping to t ### Steps to Use ### -1) In ESP32-RGB64x32MatrixPanel-I2S-DMA.h: +1) In ESP32-HUB75-MatrixPanel-I2S-DMA.h: - Set the MATRIX_HEIGHT to be the y resolution of the physical chained panels in a line (if the panels are 32 x 16, set it to be 16) - Set the MATRIX_WIDTH to be the sum of the x resolution of all the physical chained panels (i.e. If you have 4 x (32px w x 16px h) panels, 32x4 = 128) diff --git a/examples/ChainedPanelsAuroraDemo/ChainedPanelsAuroraDemo.ino b/examples/ChainedPanelsAuroraDemo/ChainedPanelsAuroraDemo.ino index 1dddd56..9379d7c 100644 --- a/examples/ChainedPanelsAuroraDemo/ChainedPanelsAuroraDemo.ino +++ b/examples/ChainedPanelsAuroraDemo/ChainedPanelsAuroraDemo.ino @@ -46,7 +46,7 @@ int lastPattern = 0; /* -------------------------- Class Initialisation -------------------------- */ -//#include +//#include //RGB64x32MatrixPanel_I2S_DMA matrix; #include diff --git a/examples/DoubleBufferSwap/DoubleBufferSwap.ino b/examples/DoubleBufferSwap/DoubleBufferSwap.ino index 06de58f..8ed410c 100644 --- a/examples/DoubleBufferSwap/DoubleBufferSwap.ino +++ b/examples/DoubleBufferSwap/DoubleBufferSwap.ino @@ -1,4 +1,4 @@ -#include +#include RGB64x32MatrixPanel_I2S_DMA display(true); // Note the TRUE -> Turns of secondary buffer - "double buffering"! // Double buffering is not enabled by default with the library. diff --git a/examples/FM6126Panel/FM6126Panel.ino b/examples/FM6126Panel/FM6126Panel.ino index 8b8bd17..f1820be 100644 --- a/examples/FM6126Panel/FM6126Panel.ino +++ b/examples/FM6126Panel/FM6126Panel.ino @@ -2,7 +2,7 @@ // https://github.com/hzeller/rpi-rgb-led-matrix/issues/746 #include -#include +#include RGB64x32MatrixPanel_I2S_DMA dma_display; diff --git a/examples/Glediator3_TPM2_MatrixPanel/Glediator3_TPM2_MatrixPanel.ino b/examples/Glediator3_TPM2_MatrixPanel/Glediator3_TPM2_MatrixPanel.ino index e44a8f7..dc884a8 100644 --- a/examples/Glediator3_TPM2_MatrixPanel/Glediator3_TPM2_MatrixPanel.ino +++ b/examples/Glediator3_TPM2_MatrixPanel/Glediator3_TPM2_MatrixPanel.ino @@ -1,5 +1,5 @@ /* -------------------------- Class Initialisation -------------------------- */ -#include +#include RGB64x32MatrixPanel_I2S_DMA matrix; #include "TPM2.h" // https://github.com/rstephan/TPM2 diff --git a/examples/PatternPlasma/PatternPlasma.ino b/examples/PatternPlasma/PatternPlasma.ino index b0f5d4b..5f8cb95 100644 --- a/examples/PatternPlasma/PatternPlasma.ino +++ b/examples/PatternPlasma/PatternPlasma.ino @@ -43,7 +43,7 @@ #define OE_PIN 13 -#include +#include RGB64x32MatrixPanel_I2S_DMA dma_display; #include diff --git a/examples/testshapes_32x64/testshapes_32x64.ino b/examples/testshapes_32x64/testshapes_32x64.ino index 0960942..a30d05c 100644 --- a/examples/testshapes_32x64/testshapes_32x64.ino +++ b/examples/testshapes_32x64/testshapes_32x64.ino @@ -1,4 +1,4 @@ -#include +#include RGB64x32MatrixPanel_I2S_DMA dma_display; // Or use an Alternative non-DMA library, i.e: diff --git a/framebuffer_memory.md b/framebuffer_memory.md index 9f31088..982d40f 100644 --- a/framebuffer_memory.md +++ b/framebuffer_memory.md @@ -48,4 +48,4 @@ Given it's possible to display 128x32 with double buffering in approx. 100kB of # Caveats -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. +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-HUB75-MatrixPanel-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. diff --git a/library.json b/library.json index 84c9cad..7311ae3 100644 --- a/library.json +++ b/library.json @@ -1,16 +1,16 @@ { - "name": "ESP32 64x32 LED MATRIX HUB75 DMA Display", + "name": "ESP32 HUB75 LED MATRIX PANEL DMA Display", "keywords": "hub75, esp32, display, dma, rgb matrix", - "description": "An experimental Adafruit GFX compatible library for 64x32 RGB matrix modules (other modules not tested - YMMV), using the ESP's DMA Engine for ultra-fast refresh rates, no-interrupts and therefore very low CPU usage (5%) so you can do other things with your ESP32.", + "description": "An experimental Adafruit GFX compatible library for 64x32 or 64x64 LED matrix modules using the ESP32 DMA Engine for ultra-fast refresh rates, no-interrupts and therefore very low CPU usage.", "repository": { "type": "git", - "url": "https://github.com/mrfaptastic/ESP32-RGB64x32MatrixPanel-I2S-DMA.git" + "url": "https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA.git" }, "authors": { "name": "Faptastic", "url": "https://github.com/mrfaptastic/" }, - "version": "1.2.2", + "version": "1.2.3", "frameworks": "arduino", "platforms": "esp32", "examples": [ diff --git a/library.properties b/library.properties index 6b7f5ef..929662a 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ -name=ESP32 64x32 LED MATRIX HUB75 DMA Display -version=1.2.2 +name=ESP32 HUB75 LED MATRIX PANEL DMA Display +version=1.2.3 author=Faptastic maintainer=Faptastic sentence=Experimental DMA based LED Matrix HUB75 Library -paragraph=An experimental Adafruit GFX compatible library for 64x32 RGB matrix modules (other modules not tested - YMMV), using the ESP's DMA Engine for ultra-fast refresh rates, no-interrupts and very low CPU usage (5%). +paragraph=An experimental Adafruit GFX compatible library for 64x32 or 64x64 LED matrix modules using the ESP32 DMA Engine for ultra-fast refresh rates, no-interrupts and therefore very low CPU usage. category=Display -url=https://github.com/mrfaptastic/ESP32-RGB64x32MatrixPanel-I2S-DMA +url=https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA architectures=esp32 -- cgit v1.3.1