#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 150) // 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 }; */ i2s_parallel_config_t cfg={ .gpio_bus={r1_pin, g1_pin, b1_pin, r2_pin, g2_pin, b2_pin, lat_pin, oe_pin, a_pin, b_pin, c_pin, d_pin, e_pin, -1, -1, -1}, .gpio_clk=clk_pin, .clkspeed_hz=ESP32_I2S_CLOCK_SPEED, //ESP32_I2S_CLOCK_SPEED, // formula used is 80000000L/(cfg->clkspeed_hz + 1), must result in >=2. Acceptable values 26.67MHz, 20MHz, 16MHz, 13.34MHz... .bits=MATRIX_I2S_MODE, //MATRIX_I2S_MODE, .bufa=0, .bufb=0, desccount, desccount, dmadesc_a, dmadesc_b }; //Setup I2S i2s_parallel_setup_without_malloc(&I2S1, &cfg); Serial.printf("I2S setup done.\n"); // Just os we know dma_configuration_success = true; } // end initMatrixDMABuff /* Update a specific co-ordinate in the DMA buffer */ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t y_coord, uint8_t red, uint8_t green, uint8_t blue) { if ( !dma_configuration_success) assert("DMA configuration in begin() not performed or completed successfully."); // Need to check that the co-ordinates are within range, or it'll break everything big time. if ( x_coord < 0 || y_coord < 0 || x_coord >= MATRIX_WIDTH || y_coord >= MATRIX_HEIGHT) { 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 } // need to turn off OE one clock before latch, otherwise can get ghosting if((x_coord)==PIXELS_PER_LATCH-1) v|=BIT_OE; /* When using the Adafruit drawPixel, we only have one pixel co-ordinate and 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 // 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 } // need to turn off OE one clock before latch, otherwise can get ghosting if((x_coord)==PIXELS_PER_LATCH-1) v|=BIT_OE; // 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 /* // WORK IN PROGRESS void RGB64x32MatrixPanel_I2S_DMA::writeRGB24Frame2DMABuffer(rgb_24 *framedata, int16_t frame_width = MATRIX_WIDTH, int16_t frame_height = MATRIX_HEIGHT) { if ( !dma_configuration_success) assert("DMA configuration in begin() not performed or completed successfully."); for (unsigned int y = 0; y < ROWS_PER_FRAME; y++) // half height - 16 iterations { unsigned char currentRow = y; for(int j = 0; j < COLOR_DEPTH_BITS; j++) // color depth - 8 iterations { uint16_t mask = (1 << (j)); // 24 bit color //MATRIX_DATA_STORAGE_TYPE *p=matrixUpdateFrames[backbuf_id].rowdata[y].rowbits[pl].data; //matrixUpdateFrames rowBitStruct *p=&matrixUpdateFrames[backbuf_id].rowdata[currentRow].rowbits[j]; //matrixUpdateFrames location to write to int i=0; while(i < PIXELS_PER_LATCH) // row pixels (64) iterations { for(int k=0; k < MATRIX_WIDTH; k++) // row pixel width 64 iterations { int v=0; // the output bitstream //#if (CLKS_DURING_LATCH == 0) // if there is no latch to hold address, output ADDX lines directly to GPIO and latch data at end of cycle int gpioRowAddress = currentRow; // normally output current rows ADDX, special case for LSB, output previous row's ADDX (as previous row is being displayed for one latch cycle) if(j == 0) gpioRowAddress = currentRow-1; if (gpioRowAddress & 0x01) v|=BIT_A; // 1 if (gpioRowAddress & 0x02) v|=BIT_B; // 2 if (gpioRowAddress & 0x04) v|=BIT_C; // 4 if (gpioRowAddress & 0x08) v|=BIT_D; // 8 if (gpioRowAddress & 0x10) v|=BIT_E; // 16 // need to disable OE after latch to hide row transition if((i+k) == 0) v|=BIT_OE; // drive latch while shifting out last bit of RGB data if((i+k) == PIXELS_PER_LATCH-1) v|=BIT_LAT; //#endif // turn off OE after brightness value is reached when displaying MSBs // MSBs always output normal brightness // LSB (!j) outputs normal brightness as MSB from previous row is being displayed if((j > lsbMsbTransitionBit || !j) && ((i+k) >= brightness)) v|=BIT_OE; // special case for the bits *after* LSB through (lsbMsbTransitionBit) - OE is output after data is shifted, so need to set OE to fractional brightness if(j && j <= lsbMsbTransitionBit) { // divide brightness in half for each bit below lsbMsbTransitionBit int lsbBrightness = brightness >> (lsbMsbTransitionBit - j + 1); if((i+k) >= lsbBrightness) v|=BIT_OE; } // need to turn off OE one clock before latch, otherwise can get ghosting if((i+k)==PIXELS_PER_LATCH-1) v|=BIT_OE; //#if 0 // // int c1=getpixel(pix, k, y); // int c2=getpixel(pix, k, y+(MATRIX_HEIGHT/2)); // // if (c1 & (mask<<16)) v|=BIT_R1; // if (c1 & (mask<<8)) v|=BIT_G1; // if (c1 & (mask<<0)) v|=BIT_B1; // if (c2 & (mask<<16)) v|=BIT_R2; // if ( c2 & (mask<<8)) v|=BIT_G2; // if (c2 & (mask<<0)) v|=BIT_B2; // //#else struct rgb_24 c1( 255,0,0); struct rgb_24 c2 = { 255,255,255 }; // struct rgb24 c2 = { 0,0,255 }; if (c1.red & mask) v|=BIT_R1; if (c1.green & mask) v|=BIT_G1; if (c1.blue & mask) v|=BIT_B1; if (c2.red & mask) v|=BIT_R2; if (c2.green & mask) v|=BIT_G2; if (c2.blue & mask) v|=BIT_B2; //#endif // 16 bit parallel mode //Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering if(k%2){ p->data[(i+k)-1] = v; } else { p->data[(i+k)+1] = v; } // end reordering } // end for MATRIX_WIDTH i += MATRIX_WIDTH; } // end pixels per latch loop (64) } // color depth loop (8) } // end half matrix length //Show our work! i2s_parallel_flip_to_buffer(&I2S1, backbuf_id); swapBuffer(); } // updateDMABuffer */ /* void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint16_t x, uint16_t y, uint8_t red, uint8_t green, uint8_t blue) { if ( !dma_configuration_success) assert("DMA configuration in begin() not performed or completed successfully."); // Pixel index sanity checking. int16_t idx = x + y * MATRIX_WIDTH; if (idx < 0 || idx >= MATRIX_HEIGHT * MATRIX_WIDTH) { //Serial.printf("Provided coordinates out of range"); return; } for (unsigned int y=0; y lsbMsbTransitionBit || !j) && ((i+k) >= brightness)) v|=BIT_OE; // special case for the bits *after* LSB through (lsbMsbTransitionBit) - OE is output after data is shifted, so need to set OE to fractional brightness if(j && j <= lsbMsbTransitionBit) { // divide brightness in half for each bit below lsbMsbTransitionBit int lsbBrightness = brightness >> (lsbMsbTransitionBit - j + 1); if((i+k) >= lsbBrightness) v|=BIT_OE; } // need to turn off OE one clock before latch, otherwise can get ghosting if((i+k)==PIXELS_PER_LATCH-1) v|=BIT_OE; //#if 0 // // int c1=getpixel(pix, k, y); // int c2=getpixel(pix, k, y+(MATRIX_HEIGHT/2)); // // if (c1 & (mask<<16)) v|=BIT_R1; // if (c1 & (mask<<8)) v|=BIT_G1; // if (c1 & (mask<<0)) v|=BIT_B1; // if (c2 & (mask<<16)) v|=BIT_R2; // if ( c2 & (mask<<8)) v|=BIT_G2; // if (c2 & (mask<<0)) v|=BIT_B2; // //#else struct rgb24 c1( 255,0,0); struct rgb24 c2 = { 255,255,255 }; // struct rgb24 c2 = { 0,0,255 }; if (c1.red & mask) v|=BIT_R1; if (c1.green & mask) v|=BIT_G1; if (c1.blue & mask) v|=BIT_B1; if (c2.red & mask) v|=BIT_R2; if (c2.green & mask) v|=BIT_G2; if (c2.blue & mask) v|=BIT_B2; //#endif // 16 bit parallel mode //Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering if(k%2){ p->data[(i+k)-1] = v; } else { p->data[(i+k)+1] = v; } // end reordering } // end for MATRIX_WIDTH i += MATRIX_WIDTH; } // end pixels per latch loop (64) } // color depth loop (8) } // end half matrix length //Show our work! i2s_parallel_flip_to_buffer(&I2S1, backbuf_id); swapBuffer(); } // updateDMABuffer */