From 9a5ed4db9e43a42e2ac4ac9b505c2725ee651062 Mon Sep 17 00:00:00 2001 From: mrfaptastic <12006953+mrfaptastic@users.noreply.github.com> Date: Wed, 29 Jul 2020 10:44:38 +0100 Subject: [PATCH] Improved memory checks --- ESP32-RGB64x32MatrixPanel-I2S-DMA.cpp | 319 +++++++++++++------------- ESP32-RGB64x32MatrixPanel-I2S-DMA.h | 7 +- README.md | 2 +- esp32_i2s_parallel.c | 18 +- esp32_i2s_parallel.h | 14 +- 5 files changed, 187 insertions(+), 173 deletions(-) diff --git a/ESP32-RGB64x32MatrixPanel-I2S-DMA.cpp b/ESP32-RGB64x32MatrixPanel-I2S-DMA.cpp index 7c3caf3..4666967 100644 --- a/ESP32-RGB64x32MatrixPanel-I2S-DMA.cpp +++ b/ESP32-RGB64x32MatrixPanel-I2S-DMA.cpp @@ -76,10 +76,10 @@ bool RGB64x32MatrixPanel_I2S_DMA::allocateDMAmemory() 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("FYI: Size of an ESP32 DMA linked list descriptor (lldesc_t) is %d bytes\r\n", sizeof(lldesc_t)); Serial.printf("We're going to need %d bytes of SRAM just for the frame buffer(s).\r\n", _frame_buffer_memory_required); @@ -87,37 +87,53 @@ bool RGB64x32MatrixPanel_I2S_DMA::allocateDMAmemory() #endif // Can we fit the framebuffer into the single DMA capable memory block available? - if ( heap_caps_get_largest_free_block(MALLOC_CAP_DMA) >= _frame_buffer_memory_required ) { // YES - SIMPLE - - // Allocate the framebuffer memory, fail if we can even do this - matrix_framebuffer_malloc_1 = (frameStruct *)heap_caps_malloc(_frame_buffer_memory_required, MALLOC_CAP_DMA); - if ( !matrix_framebuffer_malloc_1 ) { - #if SERIAL_DEBUG - Serial.println("ERROR: Couldn't malloc matrix_framebuffer_malloc_1! Critical fail.\r\n"); - #endif - - return false; - } + if ( heap_caps_get_largest_free_block(MALLOC_CAP_DMA) < _frame_buffer_memory_required ) { - _total_dma_capable_memory_reserved += _frame_buffer_memory_required; - } + #if SERIAL_DEBUG + Serial.printf("######### Insufficent 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_largest_free_block(MALLOC_CAP_DMA)) ); + #endif + + return false; + } + + // Allocate the framebuffer 1 memory, fail if we can even do this + matrix_framebuffer_malloc_1 = (frameStruct *)heap_caps_malloc(_frame_buffer_memory_required, MALLOC_CAP_DMA); + if ( matrix_framebuffer_malloc_1 == NULL ) { + #if SERIAL_DEBUG + Serial.println("ERROR: Couldn't malloc matrix_framebuffer_malloc_1! Critical fail.\r\n"); + #endif + + return false; + } + + _total_dma_capable_memory_reserved += _frame_buffer_memory_required; + + // SPLIT MEMORY MODE #ifdef SPLIT_MEMORY_MODE - Serial.println("SPLIT MEMORY MODE ENABLED!"); + Serial.println("SPLIT MEMORY MODE ENABLED!"); - #if SERIAL_DEBUG - Serial.print("Rows per frame (overall): "); Serial.println(ROWS_PER_FRAME, DEC); - Serial.print("Rows per split framebuffer malloc: "); Serial.println(SPLIT_MEMORY_ROWS_PER_FRAME, DEC); - #endif + #if SERIAL_DEBUG + Serial.print("Rows per frame (overall): "); Serial.println(ROWS_PER_FRAME, DEC); + Serial.print("Rows per split framebuffer malloc: "); Serial.println(SPLIT_MEMORY_ROWS_PER_FRAME, DEC); + #endif - // Can we fit the framebuffer into the single DMA capable memory block available? - if ( heap_caps_get_largest_free_block(MALLOC_CAP_DMA) >= _frame_buffer_memory_required ) { // YES - SIMPLE + // Can we fit the framebuffer into the single DMA capable memory block available? + // Can we fit the framebuffer into the single DMA capable memory block available? + if ( heap_caps_get_largest_free_block(MALLOC_CAP_DMA) < _frame_buffer_memory_required ) { + + #if SERIAL_DEBUG + Serial.printf("######### Insufficent memory for second framebuffer 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_largest_free_block(MALLOC_CAP_DMA)) ); + #endif - // Allocate the framebuffer memory, fail if we can even do this + return false; + } + + // Allocate the framebuffer 2 memory, fail if we can even do this matrix_framebuffer_malloc_2 = (frameStruct *)heap_caps_malloc(_frame_buffer_memory_required, MALLOC_CAP_DMA); - if ( !matrix_framebuffer_malloc_2 ) { + if ( matrix_framebuffer_malloc_2 == NULL ) { #if SERIAL_DEBUG Serial.println("ERROR: Couldn't malloc matrix_framebuffer_malloc_2! Critical fail.\r\n"); #endif @@ -126,7 +142,7 @@ bool RGB64x32MatrixPanel_I2S_DMA::allocateDMAmemory() } _total_dma_capable_memory_reserved += _frame_buffer_memory_required; - } + #endif @@ -245,6 +261,15 @@ bool RGB64x32MatrixPanel_I2S_DMA::allocateDMAmemory() Serial.printf("DMA Memory Available: %d bytes total. Largest free block: %d bytes.\r\n", heap_caps_get_free_size(MALLOC_CAP_DMA), heap_caps_get_largest_free_block(MALLOC_CAP_DMA)); 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 memory blocks available after malloc's: "); + heap_caps_print_heap_info(MALLOC_CAP_DMA); + + delay(1000); + #endif + + return true; } // end initMatrixDMABuffer() @@ -358,10 +383,12 @@ void RGB64x32MatrixPanel_I2S_DMA::configureDMA(int r1_pin, int g1_pin, int b1_ Serial.println("configureDMA(): DMA configuration completed on I2S1.\r\n"); #endif -#if SERIAL_DEBUG - Serial.println("DMA Memory Map after allocations: "); - heap_caps_print_heap_info(MALLOC_CAP_DMA); -#endif + #if SERIAL_DEBUG + Serial.println("DMA Memory Map after DMA LL allocations: "); + heap_caps_print_heap_info(MALLOC_CAP_DMA); + + delay(1000); + #endif // Just os we know everything_OK = true; @@ -375,11 +402,20 @@ void RGB64x32MatrixPanel_I2S_DMA::configureDMA(int r1_pin, int g1_pin, int b1_ /* 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 ( !everything_OK ) { - if ( !everything_OK ) - assert("DMA configuration in begin() not performed or completed successfully."); + #if SERIAL_DEBUG + Serial.println("Cannot updateMatrixDMABuffer as setup failed!"); + #endif + + return; + } + #ifdef SPLIT_MEMORY_MODE + #ifdef SERIAL_DEBUG int tmp_y_coord = y_coord; + #endif + #endif /* 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) @@ -410,11 +446,11 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t #ifdef SPLIT_MEMORY_MODE if (y_coord >= SPLIT_MEMORY_ROWS_PER_FRAME ) { p = &matrix_framebuffer_malloc_2[back_buffer_id].rowdata[(y_coord-SPLIT_MEMORY_ROWS_PER_FRAME)].rowbits[color_depth_idx]; //matrixUpdateFrames location to write to uint16_t's - } - #ifdef SERIAL_DEBUG - // Serial.printf("Using framebuffer_malloc_2. y-coord: %d, matrix row offset: %d \r\n", tmp_y_coord, (y_coord-SPLIT_MEMORY_ROWS_PER_FRAME) ); - #endif + #ifdef SERIAL_DEBUG + // Serial.printf("fb_malloc_2. y-coord: %d, calc. offset: %d \r\n", tmp_y_coord, (y_coord-SPLIT_MEMORY_ROWS_PER_FRAME) ); + #endif + } #endif @@ -467,19 +503,18 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t 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. + /* When using the drawPixel, we are obviously only changing the value of one x,y position, + * however, the HUB75 is wired up such that it is always painting 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. */ - int16_t tmp_x_coord = x_coord; + int tmp_x_coord = x_coord; if(x_coord%2) { tmp_x_coord -= 1; @@ -491,12 +526,9 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t { // 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; + 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) @@ -512,12 +544,9 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t { // 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; + 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) @@ -531,10 +560,6 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t } // 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){ @@ -545,19 +570,14 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t } // 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 +} // updateMatrixDMABuffer (specific co-ords change) /* 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; + 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 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 + + 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 = matrix_frame_parallel_row; + + // 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 = matrix_frame_parallel_row-1; + + if (gpioRowAddress & 0x01) v|=BIT_A; // 1 + if (gpioRowAddress & 0x02) v|=BIT_B; // 2 + if (gpioRowAddress & 0x04) v|=BIT_C; // 4 + if (gpioRowAddress & 0x08) v|=BIT_D; // 8 + if (gpioRowAddress & 0x10) v|=BIT_E; // 16 + + + /* ORIG + // need to disable OE after latch to hide row transition + if((x_coord) == 0) v|=BIT_OE; + + // drive latch while shifting out last bit of RGB data + if((x_coord) == PIXELS_PER_LATCH-1) v|=BIT_LAT; + + // need to turn off OE one clock before latch, otherwise can get ghosting + if((x_coord)==PIXELS_PER_LATCH-1) v|=BIT_OE; + */ + + // need to disable OE after latch to hide row transition + if((x_coord) == 0 ) v|=BIT_OE; + + // drive latch while shifting out last bit of RGB data + if((x_coord) == PIXELS_PER_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 + } + + // Top and bottom matrix MATRIX_ROWS_IN_PARALLEL half colours + if (green & mask) { v|=BIT_G1; v|=BIT_R2; } + if (blue & mask) { v|=BIT_B1; v|=BIT_G2; } + if (red & mask) { v|=BIT_R1; 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 \ No newline at end of file + +} // updateMatrixDMABuffer (full frame paint) \ No newline at end of file diff --git a/ESP32-RGB64x32MatrixPanel-I2S-DMA.h b/ESP32-RGB64x32MatrixPanel-I2S-DMA.h index 3bd94b5..a53a8c6 100644 --- a/ESP32-RGB64x32MatrixPanel-I2S-DMA.h +++ b/ESP32-RGB64x32MatrixPanel-I2S-DMA.h @@ -35,7 +35,7 @@ * */ #ifndef MATRIX_HEIGHT -#define MATRIX_HEIGHT 32 //64 +#define MATRIX_HEIGHT 32 #endif #ifndef MATRIX_WIDTH @@ -210,6 +210,11 @@ class RGB64x32MatrixPanel_I2S_DMA : public GFX { showDMABuffer(); // show backbuf_id of 0 + #if SERIAL_DEBUG + if (!everything_OK) + Serial.println("RGB64x32MatrixPanel_I2S_DMA::begin() failed."); + #endif + return everything_OK; } diff --git a/README.md b/README.md index 2272431..df8d2b9 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ A [typical RGB panel available for purchase](https://www.aliexpress.com/item/256 ## Can I chain panels or use with larger panels? -Yes you can. If you want to use with a 64x64 pixel panel you MUST configure a valid *E_PIN* to your ESP32 and connect it to the E pin of the HUB75 panel! +Yes you can. If you want to use with a 64x64 pixel panel (typically a HUB75*E* panel) you MUST configure a valid *E_PIN* to your ESP32 and connect it to the E pin of the HUB75 panel! Hence the 'E' in 'HUB75E' 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'. diff --git a/esp32_i2s_parallel.c b/esp32_i2s_parallel.c index 00fd8ca..3d6446c 100644 --- a/esp32_i2s_parallel.c +++ b/esp32_i2s_parallel.c @@ -29,9 +29,11 @@ #include "driver/periph_ctrl.h" #include "soc/io_mux_reg.h" #include "rom/lldesc.h" -#include "esp_heap_caps.h" +//#include "esp_heap_caps.h" #include "esp32_i2s_parallel.h" +#define DMA_MAX (4096-4) + typedef struct { volatile lldesc_t *dmadesc_a, *dmadesc_b; int desccount_a, desccount_b; @@ -61,9 +63,7 @@ static void IRAM_ATTR i2s_isr(void* arg) { if(shiftCompleteCallback) shiftCompleteCallback(); } - -#define DMA_MAX (4096-4) - +/* //Calculate the amount of dma descs needed for a buffer desc static int calc_needed_dma_descs_for(i2s_parallel_buffer_desc_t *desc) { int ret=0; @@ -102,8 +102,10 @@ static void fill_dma_desc(volatile lldesc_t *dmadesc, i2s_parallel_buffer_desc_t printf("fill_dma_desc: filled %d descriptors\n", n); } +*/ // size must be less than DMA_MAX - need to handle breaking long transfer into two descriptors before call +// DMA_MAX by the way is the maximum data packet size you can hold in one chunk void link_dma_desc(volatile lldesc_t *dmadesc, volatile lldesc_t *prevdmadesc, void *memory, size_t size) { if(size > DMA_MAX) size = DMA_MAX; @@ -128,7 +130,6 @@ static void gpio_setup_out(int gpio, int sig) { gpio_matrix_out(gpio, sig, false, false); } - static void dma_reset(i2s_dev_t *dev) { dev->lc_conf.in_rst=1; dev->lc_conf.in_rst=0; dev->lc_conf.out_rst=1; dev->lc_conf.out_rst=0; @@ -147,14 +148,14 @@ void i2s_parallel_setup_without_malloc(i2s_dev_t *dev, const i2s_parallel_config sig_data_base=I2S0O_DATA_OUT0_IDX; sig_clk=I2S0O_WS_OUT_IDX; } else { + printf("Setting up i2s parallel mode in %d bit mode!\n", cfg->bits); if (cfg->bits==I2S_PARALLEL_BITS_32) { sig_data_base=I2S1O_DATA_OUT0_IDX; } else if (cfg->bits==I2S_PARALLEL_BITS_16) { //Because of... reasons... the 16-bit values for i2s1 appear on d8...d23 - printf("Setting up i2s parallel mode in 16 bit mode!"); sig_data_base=I2S1O_DATA_OUT8_IDX; } else { // I2S_PARALLEL_BITS_8 - printf("Setting up i2s parallel mode in 8 bit mode -> https://www.esp32.com/viewtopic.php?f=17&t=3188 | https://www.esp32.com/viewtopic.php?f=13&t=3256"); + //printf("Setting up i2s parallel mode in %d bit mode -> https://www.esp32.com/viewtopic.php?f=17&t=3188 | https://www.esp32.com/viewtopic.php?f=13&t=3256", 8); sig_data_base=I2S1O_DATA_OUT0_IDX; } sig_clk=I2S1O_WS_OUT_IDX; @@ -251,7 +252,7 @@ void i2s_parallel_setup_without_malloc(i2s_dev_t *dev, const i2s_parallel_config // allocate a level 1 intterupt: lowest priority, as ISR isn't urgent and may take a long time to complete esp_intr_alloc(ETS_I2S1_INTR_SOURCE, (int)(ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL1), i2s_isr, NULL, NULL); - //Start dma on front buffer + //Start dma on front buffer (buffer a) dev->lc_conf.val=I2S_OUT_DATA_BURST_EN | I2S_OUTDSCR_BURST_EN | I2S_OUT_DATA_BURST_EN; dev->out_link.addr=((uint32_t)(&st->dmadesc_a[0])); dev->out_link.start=1; @@ -281,4 +282,5 @@ bool i2s_parallel_is_previous_buffer_free() { return previousBufferFree; } + #endif diff --git a/esp32_i2s_parallel.h b/esp32_i2s_parallel.h index 6f5f8cc..beb59d8 100644 --- a/esp32_i2s_parallel.h +++ b/esp32_i2s_parallel.h @@ -13,7 +13,7 @@ extern "C" { #include "rom/lldesc.h" typedef enum { - I2S_PARALLEL_BITS_8=8, // BUG: Doesn't work. + I2S_PARALLEL_BITS_8=8, I2S_PARALLEL_BITS_16=16, I2S_PARALLEL_BITS_32=32, } i2s_parallel_cfg_bits_t; @@ -29,18 +29,20 @@ typedef struct { int clkspeed_hz; i2s_parallel_cfg_bits_t bits; i2s_parallel_buffer_desc_t *bufa; - i2s_parallel_buffer_desc_t *bufb; + i2s_parallel_buffer_desc_t *bufb; // only used with double buffering int desccount_a; - int desccount_b; + int desccount_b; // only used with double buffering lldesc_t * lldesc_a; - lldesc_t * lldesc_b; + lldesc_t * lldesc_b; // only used with double buffering } i2s_parallel_config_t; -void i2s_parallel_setup(i2s_dev_t *dev, const i2s_parallel_config_t *cfg); void i2s_parallel_setup_without_malloc(i2s_dev_t *dev, const i2s_parallel_config_t *cfg); +void link_dma_desc(volatile lldesc_t *dmadesc, volatile lldesc_t *prevdmadesc, void *memory, size_t size); + void i2s_parallel_flip_to_buffer(i2s_dev_t *dev, int bufid); bool i2s_parallel_is_previous_buffer_free(); -void link_dma_desc(volatile lldesc_t *dmadesc, volatile lldesc_t *prevdmadesc, void *memory, size_t size); + + typedef void (*callback)(void); void setShiftCompleteCallback(callback f);