#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 void RGB64x32MatrixPanel_I2S_DMA::configureDMA(int r1_pin, int g1_pin, int b1_pin, int r2_pin, int g2_pin, int b2_pin, int a_pin, int b_pin, int c_pin, int d_pin, int e_pin, int lat_pin, int oe_pin, int clk_pin) { // calculate the lowest LSBMSB_TRANSITION_BIT value that will fit in memory int numDescriptorsPerRow; lsbMsbTransitionBit = 0; while(1) { numDescriptorsPerRow = 1; for(int i=lsbMsbTransitionBit + 1; i heap_caps_get_largest_free_block(MALLOC_CAP_DMA)){ assert("Not enough RAM for SmartMatrix descriptors"); Serial.printf("Not enough RAM for SmartMatrix descriptors\r\n"); return; } Serial.printf("Raised lsbMsbTransitionBit to %d/%d to fit in RAM\r\n", lsbMsbTransitionBit, COLOR_DEPTH_BITS - 1); // calculate the lowest LSBMSB_TRANSITION_BIT value that will fit in memory that will meet or exceed the configured refresh rate while(1) { int psPerClock = 1000000000000UL/ESP32_I2S_CLOCK_SPEED; int nsPerLatch = ((PIXELS_PER_LATCH + CLKS_DURING_LATCH) * psPerClock) / 1000; Serial.printf("ns per latch: %d: \r\n", nsPerLatch); // add time to shift out LSBs + LSB-MSB transition bit - this ignores fractions... int nsPerRow = COLOR_DEPTH_BITS * nsPerLatch; // add time to shift out MSBs for(int i=lsbMsbTransitionBit + 1; i 100) // HACK Hard Coded: Minimum frame rate of 150 break; if(lsbMsbTransitionBit < COLOR_DEPTH_BITS - 1) lsbMsbTransitionBit++; else break; } Serial.printf("Raised lsbMsbTransitionBit to %d/%d to meet minimum refresh rate\r\n", lsbMsbTransitionBit, COLOR_DEPTH_BITS - 1); // TODO: completely fill buffer with data before enabling DMA - can't do this now, lsbMsbTransition bit isn't set in the calc class - also this call will probably have no effect as matrixCalcDivider will skip the first call //matrixCalcCallback(); // lsbMsbTransition Bit is now finalized - redo descriptor count in case it changed to hit min refresh rate numDescriptorsPerRow = 1; for(int i=lsbMsbTransitionBit + 1; iclkspeed_hz + 1), must result in >=2. Acceptable values 26.67MHz, 20MHz, 16MHz, 13.34MHz... .bits=MATRIX_I2S_MODE, //MATRIX_I2S_MODE, .bufa=0, .bufb=0, desccount, desccount, dmadesc_a, dmadesc_b }; //Setup I2S i2s_parallel_setup_without_malloc(&I2S1, &cfg); Serial.printf("I2S setup done.\n"); // Just os we know dma_configuration_success = true; } // end initMatrixDMABuff /* Update a specific co-ordinate in the DMA buffer */ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t y_coord, uint8_t red, uint8_t green, uint8_t blue) { if ( !dma_configuration_success) assert("DMA configuration in begin() not performed or completed successfully."); // Need to check that the co-ordinates are within range, or it'll break everything big time. if ( x_coord < 0 || y_coord < 0 || x_coord >= MATRIX_WIDTH || y_coord >= MATRIX_HEIGHT) { // Serial.printf("Invalid: x %d, y %d - r %d, g %d, b %d\n", (int)x_coord, (int)y_coord, (int)red, (int)green, (int)blue ); // x_coord = y_coord = 1; return; } // What half of the HUB75 panel are we painting to? bool paint_top_half = true; if ( y_coord > ROWS_PER_FRAME-1) // co-ords start at zero, y_coord = 15 = 16 (rows per frame) { y_coord -= ROWS_PER_FRAME; // if it's 16, subtract 16. Array position 0 again. paint_top_half = false; } for(int color_depth_idx=0; color_depth_idx lsbMsbTransitionBit || !color_depth_idx) && ((x_coord) >= brightness)) v|=BIT_OE; // For Brightness // special case for the bits *after* LSB through (lsbMsbTransitionBit) - OE is output after data is shifted, so need to set OE to fractional brightness if(color_depth_idx && color_depth_idx <= lsbMsbTransitionBit) { // divide brightness in half for each bit below lsbMsbTransitionBit int lsbBrightness = brightness >> (lsbMsbTransitionBit - color_depth_idx + 1); if((x_coord) >= lsbBrightness) v|=BIT_OE; // For Brightness } /* When using the Adafruit drawPixel, we only have one pixel co-ordinate and colour to draw (duh) * so we can't paint a top and bottom half (or whatever row split the panel is) at the same time. * Need to be smart and check the DMA buffer to see what the other half thinks (pun intended) * and persist this when we refresh. * * The DMA buffer order has also been reversed (refer to the last code in this function) * so we have to check for this and check the correct position of the MATRIX_DATA_STORAGE_TYPE * data. */ int16_t tmp_x_coord = x_coord; if(x_coord%2) { tmp_x_coord -= 1; } else { tmp_x_coord += 1; } // end reordering if (paint_top_half) { // Need to copy what the RGB status is for the bottom pixels // Set the color of the pixel of interest if (green & mask) v|=BIT_G1; if (blue & mask) v|=BIT_B1; if (red & mask) v|=BIT_R1; // Persist what was painted to the other half of the frame equiv. pixel if (p->data[tmp_x_coord] & BIT_R2) v|=BIT_R2; if (p->data[tmp_x_coord] & BIT_G2) v|=BIT_G2; if (p->data[tmp_x_coord] & BIT_B2) v|=BIT_B2; } else { // Do it the other way around // Color to set if (red & mask) v|=BIT_R2; if (green & mask) v|=BIT_G2; if (blue & mask) v|=BIT_B2; // Copy if (p->data[tmp_x_coord] & BIT_R1) v|=BIT_R1; if (p->data[tmp_x_coord] & BIT_G1) v|=BIT_G1; if (p->data[tmp_x_coord] & BIT_B1) v|=BIT_B1; } // paint //Serial.printf("x: %d, y: %d ", x_coord, y_coord ); //Serial.println(v, BIN); // 16 bit parallel mode //Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering if(x_coord%2){ p->data[(x_coord)-1] = v; } else { p->data[(x_coord)+1] = v; } // end reordering } // color depth loop (8) //Show our work! //i2s_parallel_flip_to_buffer(&I2S1, backbuf_id); // If we've linked the DMA output to the same backbuf_id that this function is // currently writing too, then the output will be immediate. Else: flipDMABuffer(), then showDMABuffer() } // updateDMABuffer /* Update the entire buffer with a single specific colour - quicker */ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint8_t red, uint8_t green, uint8_t blue) { for (unsigned int y_coord = 0; y_coord < ROWS_PER_FRAME; y_coord++) // half height - 16 iterations { for(int color_depth_idx=0; color_depth_idx lsbMsbTransitionBit || !color_depth_idx) && ((x_coord) >= brightness)) v|=BIT_OE; // For Brightness // special case for the bits *after* LSB through (lsbMsbTransitionBit) - OE is output after data is shifted, so need to set OE to fractional brightness if(color_depth_idx && color_depth_idx <= lsbMsbTransitionBit) { // divide brightness in half for each bit below lsbMsbTransitionBit int lsbBrightness = brightness >> (lsbMsbTransitionBit - color_depth_idx + 1); if((x_coord) >= lsbBrightness) v|=BIT_OE; // For Brightness } // Top half colours if (green & mask) v|=BIT_G1; if (blue & mask) v|=BIT_B1; if (red & mask) v|=BIT_R1; // Bottom half colours if (red & mask) v|=BIT_R2; if (green & mask) v|=BIT_G2; if (blue & mask) v|=BIT_B2; // 16 bit parallel mode //Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering if(x_coord%2) { p->data[(x_coord)-1] = v; } else { p->data[(x_coord)+1] = v; } // end reordering } // end x_coord iteration } // colour depth loop (8) } // end row iteration //Show our work! //i2s_parallel_flip_to_buffer(&I2S1, backbuf_id); // If we've linked the DMA output to the same backbuf_id that this function is // currently writing too, then the output will be immediate. Else: flipDMABuffer(), then showDMABuffer() } // updateDMABuffer