diff --git a/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp b/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp index a0bee63..bca89c8 100644 --- a/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp +++ b/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp @@ -1,6 +1,18 @@ #include #include "ESP32-HUB75-MatrixPanel-I2S-DMA.h" + +#if defined(ESP32_SXXX) + #pragma message "Compiling for ESP32-Sx MCUs" +#elif defined(ESP32_CXXX) + #pragma message "Compiling for ESP32-Cx MCUs" +#elif CONFIG_IDF_TARGET_ESP32 || defined(ESP32) + #pragma message "Compiling for original 520kB SRAM ESP32." +#else + #error "Compiling for something unknown!" +#endif + + // 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 @@ -483,7 +495,7 @@ void IRAM_ATTR MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16 * data. */ -#ifndef ESP32_S2 +#ifndef ESP32_SXXX // 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 // Irrelevant for ESP32-S2 the way the FIFO ordering works is different - refer to page 679 of S2 technical reference manual @@ -672,7 +684,7 @@ void MatrixPanel_I2S_DMA::clearFrameBuffer(bool _buff_id){ // switch pointer to a row for a specific color index row = dma_buff.rowBits[row_idx]->getDataPtr(coloridx, _buff_id); - #ifdef ESP32_S2 + #ifdef ESP32_SXXX // -1 works better on ESP32-S2 ? Because bytes get sent out in order... row[dma_buff.rowBits[row_idx]->width - 1] |= BIT_LAT; // -1 pixel to compensate array index starting at 0 #else @@ -688,7 +700,7 @@ void MatrixPanel_I2S_DMA::clearFrameBuffer(bool _buff_id){ do { --_blank; - #ifdef ESP32_S2 + #ifdef ESP32_SXXX row[0 + _blank] |= BIT_OE; row[dma_buff.rowBits[row_idx]->width - _blank - 1 ] |= BIT_OE; // (LAT pulse is (width-2) -1 pixel to compensate array index starting at 0 #else @@ -767,7 +779,7 @@ void MatrixPanel_I2S_DMA::brtCtrlOE(int brt, const bool _buff_id){ do { --_blank; - #ifdef ESP32_S2 + #ifdef ESP32_SXXX row[0 + _blank] |= BIT_OE; #else // Original ESP32 WROOM FIFO Ordering Sucks @@ -896,7 +908,7 @@ void MatrixPanel_I2S_DMA::hlineDMA(int16_t x_coord, int16_t y_coord, int16_t l, do { // iterate pixels in a row int16_t _x = x_coord + --_l; -#ifdef ESP32_S2 +#ifdef ESP32_SXXX // ESP 32 doesn't need byte flipping for TX FIFO. uint16_t &v = p[_x]; #else @@ -937,7 +949,7 @@ void MatrixPanel_I2S_DMA::vlineDMA(int16_t x_coord, int16_t y_coord, int16_t l, blue = lumConvTab[blue]; #endif -#ifndef ESP32_S2 +#ifndef ESP32_SXXX // Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering x_coord & 1U ? --x_coord : ++x_coord; #endif diff --git a/ESP32-HUB75-MatrixPanel-I2S-DMA.h b/ESP32-HUB75-MatrixPanel-I2S-DMA.h index 66cc20a..8d1c0a9 100644 --- a/ESP32-HUB75-MatrixPanel-I2S-DMA.h +++ b/ESP32-HUB75-MatrixPanel-I2S-DMA.h @@ -1,5 +1,18 @@ #ifndef _ESP32_RGB_64_32_MATRIX_PANEL_I2S_DMA #define _ESP32_RGB_64_32_MATRIX_PANEL_I2S_DMA +/***************************************************************************************/ +/* Core ESP32 hardware / idf includes! */ +#include +#include +#include "esp_heap_caps.h" +#include "esp32_i2s_parallel_dma.h" + +#ifdef USE_GFX_ROOT + #include + #include "GFX.h" // Adafruit GFX core class -> https://github.com/mrfaptastic/GFX_Root +#elif !defined NO_GFX + #include "Adafruit_GFX.h" // Adafruit class with all the other stuff +#endif /******************************************************************************************* * COMPILE-TIME OPTIONS - MUST BE PROVIDED as part of PlatformIO project build_flags. * @@ -46,22 +59,44 @@ /* 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 +#ifdef ESP32_SXXX + + #define R1_PIN_DEFAULT 45 + #define G1_PIN_DEFAULT 42 + #define B1_PIN_DEFAULT 41 + #define R2_PIN_DEFAULT 40 + #define G2_PIN_DEFAULT 39 + #define B2_PIN_DEFAULT 38 + #define A_PIN_DEFAULT 37 + #define B_PIN_DEFAULT 36 + #define C_PIN_DEFAULT 35 + #define D_PIN_DEFAULT 34 + #define E_PIN_DEFAULT -1 // required for 1/32 scan panels, like 64x64. Any available pin would do, i.e. IO32 + #define LAT_PIN_DEFAULT 26 + #define OE_PIN_DEFAULT 21 + #define CLK_PIN_DEFAULT 33 + +#else + + #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 + +#endif // 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. @@ -84,25 +119,6 @@ // #define NO_CIE1931 -/***************************************************************************************/ -/* Core ESP32 hardware / idf includes! */ -#include -#include -#include "esp_heap_caps.h" - -#ifdef ESP32_S2 - #include "esp32-s2_i2s_parallel_v1.h" -#else - #include "esp32_i2s_parallel_v2.h" -#endif - -#ifdef USE_GFX_ROOT - #include - #include "GFX.h" // Adafruit GFX core class -> https://github.com/mrfaptastic/GFX_Root -#elif !defined NO_GFX - #include "Adafruit_GFX.h" // Adafruit class with all the other stuff -#endif - /***************************************************************************************/ /* Definitions below should NOT be ever changed without rewriting library logic */ @@ -352,8 +368,8 @@ class MatrixPanel_I2S_DMA { /* Propagate the DMA pin configuration, allocate DMA buffs and start data ouput, initialy blank */ bool begin(){ - - if (initialized) return true; // we don't do this twice or more! + + if (initialized) return true; // we don't do this twice or more! // Change 'if' to '1' to enable, 0 to not include this Serial output in compiled program #if SERIAL_DEBUG @@ -468,10 +484,10 @@ class MatrixPanel_I2S_DMA { void fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b); void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b); - + #ifdef USE_GFX_ROOT - // 24bpp FASTLED CRGB colour struct support - void fillScreen(CRGB color); + // 24bpp FASTLED CRGB colour struct support + void fillScreen(CRGB color); void drawPixel(int16_t x, int16_t y, CRGB color); #endif @@ -503,14 +519,14 @@ class MatrixPanel_I2S_DMA { #endif i2s_parallel_flip_to_buffer(I2S_NUM_0, back_buffer_id); - + // Wait before we allow any writing to the buffer. Stop flicker. while(i2s_parallel_is_previous_buffer_free() == false) { } - + // Flip to other buffer as the backbuffer. - // i.e. Graphic changes happen to this buffer, but aren't displayed until flipDMABuffer() is called again. + // i.e. Graphic changes happen to this buffer, but aren't displayed until flipDMABuffer() is called again. back_buffer_id ^= 1; - + } inline void setPanelBrightness(int b) diff --git a/esp32_i2s_parallel_v2.c b/esp32_i2s_parallel_dma.c similarity index 73% rename from esp32_i2s_parallel_v2.c rename to esp32_i2s_parallel_dma.c index f21111e..6797733 100644 --- a/esp32_i2s_parallel_v2.c +++ b/esp32_i2s_parallel_dma.c @@ -1,33 +1,59 @@ /* - * ESP32_I2S_PARALLEL_V2 (Version 2) + * ESP32_I2S_PARALLEL_DMA (Version 3) * - * Author: Mrfaptastic - https://github.com/mrfaptastic/ + * Author: Mrfaptastic @ https://github.com/mrfaptastic/ * - * Description: Pointless reimplementation of ESP32 DMA setup for no reason than - * to better understand the internals of the ESP32 SoC DMA. + * Description: Multi-ESP32 product DMA setup functions for WROOM & S2, S3 mcu's. * - * Credits: Based on the merging of three ideas: + * Credits: * 1) https://www.esp32.com/viewtopic.php?f=17&t=3188 for original ref. implementation - * 2) https://www.esp32.com/viewtopic.php?f=18&p=55305#p55305 for APLL overclock (no longer used) - * 3) https://github.com/TobleMiner/esp_i2s_parallel for a cleaner implementation + * 2) https://github.com/TobleMiner/esp_i2s_parallel for a cleaner implementation * */ - -#include -#include -#include -#include +// Header +#include "esp32_i2s_parallel_dma.h" + +#if defined(ESP32_ORIG) || defined (ESP32_SXXX) + + +#include + + // Turn on and off a periphal +#include + +// GPIO +#include +#include #include #include + #include #include -// Header -#include +// DMA Linked List Struct +#include +#include -// For I2S frame buffer state management. -static i2s_parallel_state_t *i2s_state[2]={NULL, NULL}; +// I2S +#include +#include + + +#ifdef ESP32_CXXX + // GDMA + #include + #include + #include + #include +#endif + +// For I2S state management. +static i2s_parallel_state_t *i2s_state = NULL; + +// ESP32-S2,S3,C3 only has IS20 +// Original ESP32 has two I2S's, but we'll stick with the lowest common denominator. +static i2s_dev_t* I2S = &I2S0; // Device to use for this library, change if you want. callback shiftCompleteCallback; void setShiftCompleteCallback(callback f) { @@ -38,6 +64,8 @@ volatile bool previousBufferFree = true; static void IRAM_ATTR irq_hndlr(void* arg) { // if we use I2S1 (default) +#ifdef ESP_ORIG + if ( (*(i2s_port_t*)arg) == I2S_NUM_1 ) { // https://www.bogotobogo.com/cplusplus/pointers2_voidpointers_arrays.php //For I2S1 SET_PERI_REG_BITS(I2S_INT_CLR_REG(1), I2S_OUT_EOF_INT_CLR_V, 1, I2S_OUT_EOF_INT_CLR_S); @@ -46,16 +74,22 @@ static void IRAM_ATTR irq_hndlr(void* arg) { // if we use I2S1 (default) SET_PERI_REG_BITS(I2S_INT_CLR_REG(0), I2S_OUT_EOF_INT_CLR_V, 1, I2S_OUT_EOF_INT_CLR_S); } +#else + // Other ESP32 MCU's only have one I2S + SET_PERI_REG_BITS(I2S_INT_CLR_REG(0), I2S_OUT_EOF_INT_CLR_V, 1, I2S_OUT_EOF_INT_CLR_S); + +#endif + // at this point, the previously active buffer is free, go ahead and write to it previousBufferFree = true; if(shiftCompleteCallback) // we've defined a callback function ? shiftCompleteCallback(); -} + +} // end irq_hndlr // For peripheral setup and configuration -static i2s_dev_t* I2S[I2S_NUM_MAX] = {&I2S0, &I2S1}; static inline int get_bus_width(i2s_parallel_cfg_bits_t width) { switch(width) { @@ -97,10 +131,17 @@ static void dma_reset(i2s_dev_t* dev) { static void fifo_reset(i2s_dev_t* dev) { dev->conf.rx_fifo_reset = 1; -// while(dev->state.rx_fifo_reset_back); + +#ifdef ESP32_SXXX + while(dev->conf.rx_fifo_reset_st); // esp32-s2 only +#endif dev->conf.rx_fifo_reset = 0; + dev->conf.tx_fifo_reset = 1; -// while(dev->state.tx_fifo_reset_back); +#ifdef ESP32_SXXX + while(dev->conf.tx_fifo_reset_st); // esp32-s2 only +#endif + dev->conf.tx_fifo_reset = 0; } @@ -137,6 +178,9 @@ void link_dma_desc(volatile lldesc_t *dmadesc, volatile lldesc_t *prevdmadesc, v esp_err_t i2s_parallel_driver_install(i2s_port_t port, i2s_parallel_config_t* conf) { + + port = I2S_NUM_0; /// override. + if(port < I2S_NUM_0 || port >= I2S_NUM_MAX) { return ESP_ERR_INVALID_ARG; } @@ -155,8 +199,8 @@ esp_err_t i2s_parallel_driver_install(i2s_port_t port, i2s_parallel_config_t* co volatile int iomux_clock; int irq_source; - // Initialize I2S peripheral - if (port == I2S_NUM_0) { + // Initialize I2S0 peripheral + //if (port == I2S_NUM_0) { periph_module_reset(PERIPH_I2S0_MODULE); periph_module_enable(PERIPH_I2S0_MODULE); iomux_clock = I2S0O_WS_OUT_IDX; @@ -173,6 +217,7 @@ esp_err_t i2s_parallel_driver_install(i2s_port_t port, i2s_parallel_config_t* co case I2S_PARALLEL_WIDTH_MAX: return ESP_ERR_INVALID_ARG; } + /* } else { periph_module_reset(PERIPH_I2S1_MODULE); periph_module_enable(PERIPH_I2S1_MODULE); @@ -191,27 +236,23 @@ esp_err_t i2s_parallel_driver_install(i2s_port_t port, i2s_parallel_config_t* co return ESP_ERR_INVALID_ARG; } } - + */ // Setup GPIOs int bus_width = get_bus_width(conf->sample_width); // Setup I2S peripheral - i2s_dev_t* dev = I2S[port]; - dev_reset(dev); - - // Set i2s mode to LCD mode - dev->conf2.val = 0; - dev->conf2.lcd_en = 1; - dev->conf.tx_slave_mod = 0; + i2s_dev_t* dev = I2S; - // dev->conf.tx_dma_equal=1; // esp32-s2 only - dev->conf2.lcd_tx_wrx2_en=0; - dev->conf2.lcd_tx_sdx2_en=0; - // Enable "One datum will be written twice in LCD mode" - for some reason, - // if we don't do this in 8-bit mode, data is updated on half-clocks not clocks - if(conf->sample_width == I2S_PARALLEL_WIDTH_8) - dev->conf2.lcd_tx_wrx2_en=1; + // Setup GPIO's + for(int i = 0; i < bus_width; i++) { + iomux_set_signal(conf->gpio_bus[i], iomux_signal_base + i); + } + iomux_set_signal(conf->gpio_clk, iomux_clock); + + // invert clock phase if required + if (conf->clkphase) + GPIO.func_out_sel_cfg[conf->gpio_clk].inv_sel = 1; // Setup i2s clock dev->sample_rate_conf.val = 0; @@ -219,35 +260,58 @@ esp_err_t i2s_parallel_driver_install(i2s_port_t port, i2s_parallel_config_t* co // Third stage config, width of data to be written to IO (I think this should always be the actual data width?) dev->sample_rate_conf.rx_bits_mod = bus_width; dev->sample_rate_conf.tx_bits_mod = bus_width; - dev->sample_rate_conf.rx_bck_div_num = 1; - dev->sample_rate_conf.tx_bck_div_num = 2; // datasheet says this must be 2 or greater (but 1 seems to work) + + dev->sample_rate_conf.rx_bck_div_num = 2; + dev->sample_rate_conf.tx_bck_div_num = 2; + + // Clock configuration + dev->clkm_conf.val=0; // Clear the clkm_conf struct + +#ifdef ESP32_SXXX + dev->clkm_conf.clk_sel = 2; // esp32-s2 only + dev->clkm_conf.clk_en = 1; +#endif - // It's confusing, but the max output the ESP32 can pump out when using I2S *parallel* output is 20Mhz. - // https://easyvolts.com/2018/08/14/esp32-40msps-oscilloscope-project-is-closed-and-here-is-why/ - // and https://github.com/espressif/esp-idf/issues/2251 - // Igor - "Frequencies above 20MHz do not work in I2S mode." - - // 16bit parallel I2S @ 10Mhz = calculated clk_div_main (per line ~142) of 4 - // 16bit parallel I2S @ 20Mhz = calculated clk_div_main (per line ~142) of 2 - dev->clkm_conf.val=0; // Clear the clkm_conf struct +#ifdef ESP32_ORIG dev->clkm_conf.clka_en=0; // Use the 160mhz system clock (PLL_D2_CLK) when '0' - dev->clkm_conf.clkm_div_b=0; // Page 310 of Technical Reference Manual - Clock numerator - dev->clkm_conf.clkm_div_a=1; // Page 310 of Technical Reference Manual - Clock denominator - - // - // Final Mhz output = - // Output = 80000000L / tx_bck_div_num / (clkm_div_num + (clkm_div_b/clkm_div_a) ) +#endif + dev->clkm_conf.clkm_div_b=0; // Clock numerator + dev->clkm_conf.clkm_div_a=1; // Clock denominator + + // Note: clkm_div_num must only be set here AFTER clkm_div_b, clkm_div_a, etc. Or weird things happen! + // On original ESP32, max I2S DMA parallel speed is 20Mhz. dev->clkm_conf.clkm_div_num = clk_div_main; - - //printf("esp32_i2s_parallel_2.c > I2S clock divider is %d \n", clk_div_main*2); + + + // I2S conf2 reg + dev->conf2.val = 0; + dev->conf2.lcd_en = 1; + dev->conf2.lcd_tx_wrx2_en=0; + dev->conf2.lcd_tx_sdx2_en=0; + + // I2S conf reg + dev->conf.val = 0; + +#ifdef ESP32_SXXX + dev->conf.tx_dma_equal=1; // esp32-s2 only + dev->conf.pre_req_en=1; // esp32-s2 only - enable I2S to prepare data earlier? wtf? +#endif + + // Now start setting up DMA FIFO + dev->fifo_conf.val = 0; + dev->fifo_conf.rx_data_num = 32; // Thresholds. + dev->fifo_conf.tx_data_num = 32; + dev->fifo_conf.dscr_en = 1; + +#ifdef ESP32_ORIG + + // Enable "One datum will be written twice in LCD mode" - for some reason, + // if we don't do this in 8-bit mode, data is updated on half-clocks not clocks + if(conf->sample_width == I2S_PARALLEL_WIDTH_8) + dev->conf2.lcd_tx_wrx2_en=1; - // Some fifo conf I don't quite understand - dev->fifo_conf.val = 0; - // Dictated by datasheet - dev->fifo_conf.rx_fifo_mod_force_en = 1; - dev->fifo_conf.tx_fifo_mod_force_en = 1; // Not really described for non-pcm modes, although datasheet states it should be set correctly even for LCD mode // First stage config. Configures how data is loaded into fifo if(conf->sample_width == I2S_PARALLEL_WIDTH_24) { @@ -258,33 +322,31 @@ esp_err_t i2s_parallel_driver_install(i2s_port_t port, i2s_parallel_config_t* co // *Actually a 32 bit read where two samples are read at once. Length of fifo must thus still be word-alligned dev->fifo_conf.tx_fifo_mod = 1; } - // Probably relevant for buffering from the DMA controller - dev->fifo_conf.rx_data_num = 32; // Thresholds. - dev->fifo_conf.tx_data_num = 32; - // Enable DMA support - dev->fifo_conf.dscr_en = 1; - - dev->conf1.val = 0; - dev->conf1.tx_stop_en = 0; - dev->conf1.tx_pcm_bypass = 1; + + // Dictated by ESP32 datasheet + dev->fifo_conf.rx_fifo_mod_force_en = 1; + dev->fifo_conf.tx_fifo_mod_force_en = 1; // Second stage config dev->conf_chan.val = 0; - // Tx in mono mode, read 32 bit per sample from fifo + + // 16-bit single channel data dev->conf_chan.tx_chan_mod = 1; dev->conf_chan.rx_chan_mod = 1; + +#endif + - - dev->conf.tx_right_first = 0; //!!invert_clk; // should be false / 0 - dev->conf.rx_right_first = 0; //!!invert_clk; - - dev->timing.val = 0; + // Device Reset + dev_reset(dev); + dev->conf1.val = 0; + dev->conf1.tx_stop_en = 0; // Allocate I2S status structure for buffer swapping stuff - i2s_state[port] = (i2s_parallel_state_t*) malloc(sizeof(i2s_parallel_state_t)); - assert(i2s_state[port] != NULL); - i2s_parallel_state_t *state = i2s_state[port]; - + i2s_state = (i2s_parallel_state_t*) malloc(sizeof(i2s_parallel_state_t)); + assert(i2s_state != NULL); + i2s_parallel_state_t *state = i2s_state; + state->desccount_a = conf->desccount_a; state->desccount_b = conf->desccount_b; state->dmadesc_a = conf->lldesc_a; @@ -301,20 +363,16 @@ esp_err_t i2s_parallel_driver_install(i2s_port_t port, i2s_parallel_config_t* co return err; } + + dev->timing.val = 0; + + // Setup interrupt handler which is focussed only on the (page 322 of Tech. Ref. Manual) // "I2S_OUT_EOF_INT: Triggered when rxlink has finished sending a packet" // ... whatever the hell that is supposed to mean... One massive linked list. dev->int_ena.out_eof = 1; - // Setup GPIO's at the end to avoid spurious crap being sent out - for(int i = 0; i < bus_width; i++) { - iomux_set_signal(conf->gpio_bus[i], iomux_signal_base + i); - } - iomux_set_signal(conf->gpio_clk, iomux_clock); - // invert clock phase if required - if (conf->clkphase) - GPIO.func_out_sel_cfg[conf->gpio_clk].inv_sel = 1; - + return ESP_OK; } @@ -323,7 +381,7 @@ esp_err_t i2s_parallel_driver_install(i2s_port_t port, i2s_parallel_config_t* co return ESP_ERR_INVALID_ARG; } - i2s_dev_t* dev = I2S[port]; + i2s_dev_t* dev = I2S; // Stop all ongoing DMA operations dev->out_link.stop = 1; @@ -339,20 +397,20 @@ esp_err_t i2s_parallel_driver_install(i2s_port_t port, i2s_parallel_config_t* co return ESP_ERR_INVALID_ARG; } - i2s_dev_t* dev = I2S[port]; - // Stop all ongoing DMA operations - dev->out_link.stop = 1; - dev->out_link.start = 0; - dev->conf.tx_start = 0; - dev_reset(dev); + i2s_dev_t* dev = I2S; + // Configure DMA burst mode dev->lc_conf.val = I2S_OUT_DATA_BURST_EN | I2S_OUTDSCR_BURST_EN; + // Set address of DMA descriptor dev->out_link.addr = (uint32_t) dma_descriptor; - // Start DMA operation + + // Start DMA operation + dev->out_link.stop = 0; dev->out_link.start = 1; - dev->conf.tx_start = 1; + + dev->conf.tx_start = 1; return ESP_OK; } @@ -361,27 +419,27 @@ i2s_dev_t* i2s_parallel_get_dev(i2s_port_t port) { if(port < I2S_NUM_0 || port >= I2S_NUM_MAX) { return NULL; } - return I2S[port]; + return I2S; // HARCODE THIS TO RETURN &I2S0 } // Double buffering flipping // Flip to a buffer: 0 for bufa, 1 for bufb void i2s_parallel_flip_to_buffer(i2s_port_t port, int buffer_id) { - if (i2s_state[port] == NULL) { + if (i2s_state == NULL) { return; // :-() } lldesc_t *active_dma_chain; if (buffer_id == 0) { - active_dma_chain=(lldesc_t*)&i2s_state[port]->dmadesc_a[0]; + active_dma_chain=(lldesc_t*)&i2s_state->dmadesc_a[0]; } else { - active_dma_chain=(lldesc_t*)&i2s_state[port]->dmadesc_b[0]; + active_dma_chain=(lldesc_t*)&i2s_state->dmadesc_b[0]; } // setup linked list to refresh from new buffer (continuously) when the end of the current list has been reached - i2s_state[port]->dmadesc_a[i2s_state[port]->desccount_a-1].qe.stqe_next = active_dma_chain; - i2s_state[port]->dmadesc_b[i2s_state[port]->desccount_b-1].qe.stqe_next = active_dma_chain; + i2s_state->dmadesc_a[i2s_state->desccount_a-1].qe.stqe_next = active_dma_chain; + i2s_state->dmadesc_b[i2s_state->desccount_b-1].qe.stqe_next = active_dma_chain; // we're still shifting out the buffer, so it shouldn't be written to yet. previousBufferFree = false; @@ -391,3 +449,5 @@ bool i2s_parallel_is_previous_buffer_free() { return previousBufferFree; } +// End ESP32 original / S2, S3 check +#endif \ No newline at end of file diff --git a/esp32_i2s_parallel_v2.h b/esp32_i2s_parallel_dma.h similarity index 92% rename from esp32_i2s_parallel_v2.h rename to esp32_i2s_parallel_dma.h index e344b9a..00e6097 100644 --- a/esp32_i2s_parallel_v2.h +++ b/esp32_i2s_parallel_dma.h @@ -1,5 +1,6 @@ +#pragma once /* - * ESP32_I2S_PARALLEL_V2 (Version 2) + * ESP32_I2S_PARALLEL_DMA */ #pragma once @@ -12,18 +13,12 @@ extern "C" { #endif - - -#include -#include - -#include -#include #include +#include #include -#define I2S_PARALLEL_CLOCK_HZ 80000000L -#define DMA_MAX (4096-4) +// Get MCU Type and Max CLK Hz for MCU +#include typedef enum { I2S_PARALLEL_WIDTH_8, @@ -47,10 +42,16 @@ typedef struct { static inline int i2s_parallel_get_memory_width(i2s_port_t port, i2s_parallel_cfg_bits_t width) { switch(width) { case I2S_PARALLEL_WIDTH_8: + +#ifdef ESP32_ORIG // IS21 supports space saving single byte 8 bit parallel access if(port == I2S_NUM_1) { return 1; } +#else + return 1; +#endif + case I2S_PARALLEL_WIDTH_16: return 2; case I2S_PARALLEL_WIDTH_24: diff --git a/esp32_i2s_parallel_dma_cxxx.c b/esp32_i2s_parallel_dma_cxxx.c new file mode 100644 index 0000000..f8d8702 --- /dev/null +++ b/esp32_i2s_parallel_dma_cxxx.c @@ -0,0 +1,311 @@ +/* + * ESP32_I2S_PARALLEL_DMA (Version 3) + * + * Author: Mrfaptastic @ https://github.com/mrfaptastic/ + * + * Description: Multi-ESP32 product DMA setup functions for ESP32 C3/H2 RISC-V chips + * + */ + +// Header +#include "esp32_i2s_parallel_dma.h" + +#if defined(ESP32_CXXX) + + +#include + + // Turn on and off a periphal +#include + +// GPIO +#include +#include +#include +#include + +#include +#include + +// DMA Linked List Struct +#include +#include + +// I2S +#include +#include + +// GDMA +#include +#include +#include +#include + +// For I2S state management. +static i2s_parallel_state_t *i2s_state = NULL; + +// ESP32-S2,S3,C3 only has IS20 +// Original ESP32 has two I2S's, but we'll stick with the lowest common denominator. +static i2s_dev_t* I2S = &I2S0; // Device to use for this library, change if you want. + +callback shiftCompleteCallback; +void setShiftCompleteCallback(callback f) { + shiftCompleteCallback = f; +} + +volatile bool previousBufferFree = true; + +static void IRAM_ATTR gdma_irq_handler(void* arg) { // if we use I2S1 (default) + + GDMA.intr[0].clr.out_eof = 1; + + // at this point, the previously active buffer is free, go ahead and write to it + // previousBufferFree = true; + // if(shiftCompleteCallback) // we've defined a callback function ? + // shiftCompleteCallback(); + + // at this point, the previously active buffer is free, go ahead and write to it + previousBufferFree = true; + + if(shiftCompleteCallback) // we've defined a callback function ? + shiftCompleteCallback(); + +} // end irq_hndlr + + +// For peripheral setup and configuration + +static inline int get_bus_width(i2s_parallel_cfg_bits_t width) { + switch(width) { + case I2S_PARALLEL_WIDTH_8: + return 8; + case I2S_PARALLEL_WIDTH_16: + return 16; + case I2S_PARALLEL_WIDTH_24: + return 24; + default: + return -ESP_ERR_INVALID_ARG; + } +} + +static void gpio_setup_out(int gpio, int sig) { + if(gpio < 0) { + return; + } + + // Configure GPIO + // https://github.com/espressif/esp-idf/blob/d5f58ab13551cd883e8d8478ba367b6e4543ffec/examples/peripherals/gpio/generic_gpio/main/gpio_example_main.c + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_DEF_OUTPUT; + io_conf.pin_bit_mask = (1ULL << gpio); + io_conf.pull_down_en = 1; + io_conf.pull_up_en = 0; + + gpio_config(&io_conf); + + // Set IOMUX to GPIO + //gpio_iomux_out(gpio, sig, false); // ?? is sig right? + gpio_matrix_out(gpio, sig, false, false); + + // Drive Strength to MAX + gpio_set_drive_capability((gpio_num_t)gpio, (gpio_drive_cap_t)3); +} + + +// DMA Linked List +// 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; + + dmadesc->size = size; + dmadesc->length = size; + dmadesc->buf = memory; + dmadesc->eof = 0; + dmadesc->sosf = 0; + dmadesc->owner = 1; + dmadesc->qe.stqe_next = 0; // will need to set this elsewhere + dmadesc->offset = 0; + + // link previous to current + if(prevdmadesc) + prevdmadesc->qe.stqe_next = (lldesc_t*)dmadesc; +} + + + +esp_err_t i2s_parallel_driver_install(i2s_port_t port, i2s_parallel_config_t* cfg) { + + port = I2S_NUM_0; /// override. + + if(port < I2S_NUM_0 || port >= I2S_NUM_MAX) { + return ESP_ERR_INVALID_ARG; + } + if(cfg->sample_width < I2S_PARALLEL_WIDTH_8 || cfg->sample_width >= I2S_PARALLEL_WIDTH_MAX) { + return ESP_ERR_INVALID_ARG; + } + if(cfg->sample_rate > I2S_PARALLEL_CLOCK_HZ || cfg->sample_rate < 1) { + return ESP_ERR_INVALID_ARG; + } + uint32_t clk_div_main = I2S_PARALLEL_CLOCK_HZ / cfg->sample_rate / i2s_parallel_get_memory_width(port, cfg->sample_width); + if(clk_div_main < 2 || clk_div_main > 0xFF) { + return ESP_ERR_INVALID_ARG; + } + + // Setup GPIOs + int bus_width = get_bus_width(cfg->sample_width); + + i2s_dev_t *i2s_dev = I2S; // There's only one I2S device on C3.... + + //Figure out which signal numbers to use for routing + //printf("Setting up parallel I2S bus at I2S%d\n", i2snum(dev)); + int sig_data_base = I2SO_SD_OUT_IDX; + int sig_clk = I2SO_WS_OUT_IDX; + + //Route the signals + for (int x=0; x < bus_width; x++) { + gpio_setup_out(cfg->gpio_bus[x], sig_data_base+x); + } + + //ToDo: Clk/WS may need inversion? + gpio_setup_out(cfg->gpio_clk, sig_clk); + + // Power on I2S1 (or 0) + periph_module_enable(PERIPH_I2S1_MODULE); + + // Now it's apparently I2S0 !? + //i2s_dev_t *i2s_dev = &I2S0; + + // Reset RX (not that we use it) + i2s_dev->rx_conf.val = 0; + i2s_dev->rx_conf.rx_reset=1; i2s_dev->rx_conf.rx_reset=0; + i2s_dev->rx_conf.rx_fifo_reset=1; i2s_dev->rx_conf.rx_fifo_reset=0; + + // Reset TX (what we care about) + i2s_dev->tx_conf.val = 0; + i2s_dev->tx_conf.tx_reset=1; i2s_dev->tx_conf.tx_reset=0; + i2s_dev->tx_conf.tx_fifo_reset=1; i2s_dev->tx_conf.tx_fifo_reset=0; + i2s_dev->tx_conf.tx_chan_equal=1; + + // Device setup + i2s_dev->tx_conf1.val = 0; + i2s_dev->tx_conf1.tx_bits_mod=16;//cfg->bits; + + i2s_dev->rx_conf1.val = 0; + i2s_dev->rx_conf1.rx_bits_mod=16; //cfg->bits; + + i2s_dev->tx_conf1.tx_bck_div_num=2; + i2s_dev->rx_conf1.rx_bck_div_num=2; + + i2s_dev->tx_clkm_conf.val=0; + i2s_dev->tx_clkm_conf.tx_clk_sel=2; // 160mhz + + // clock speed + i2s_dev->tx_clkm_conf.tx_clkm_div_num=160/16; // 10Mhz + //i2s_dev->tx_clkm_div_conf.val = 0; // + i2s_dev->tx_clkm_div_conf.tx_clkm_div_x = 0; // > ? + i2s_dev->tx_clkm_div_conf.tx_clkm_div_y = 0; // + i2s_dev->tx_clkm_div_conf.tx_clkm_div_yn1 = 0; + i2s_dev->tx_clkm_div_conf.tx_clkm_div_z = 0; + + i2s_dev->tx_clkm_conf.tx_clk_active=1; // Start + + + //Allocate DMA descriptors + i2s_state = malloc(sizeof(i2s_parallel_state_t)); + assert(i2s_state != NULL); + i2s_parallel_state_t *st= i2s_state; + + st->desccount_a = cfg->desccount_a; + st->desccount_b = cfg->desccount_b; + st->dmadesc_a = cfg->lldesc_a; + st->dmadesc_b = cfg->lldesc_b; + + // setup I2S Interrupt + // SET_PERI_REG_BITS(I2S_INT_ENA_REG(1), I2S_OUT_EOF_INT_ENA_V, 1, I2S_OUT_EOF_INT_ENA_S); + // 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), gdma_irq_handler, NULL, NULL); + GDMA.intr[0].ena.out_eof = 1; //? + + + // Reset GDMA device + GDMA.channel[0].out.out_conf0.out_rst = 1; // REG_SET_BIT(DMA_OUT_CONF0_CH0_REG, DMA_OUT_RST_CH0); + GDMA.channel[0].out.out_conf0.out_rst = 0; // REG_CLR_BIT(DMA_OUT_CONF0_CH0_REG, DMA_OUT_RST_CH0); + // GDMA.channel[0].out.out_conf0.out_eof_mode = 1; ? + GDMA.misc_conf.ahbm_rst_inter = 1; + GDMA.misc_conf.ahbm_rst_inter = 0; + + // Setup interrupt + + // Setup outlink + GDMA.channel[0].out.out_link.addr = ((uint32_t)(&st->dmadesc_a[0]));// Set a vlaue here + GDMA.channel[0].out.out_peri_sel.sel = SOC_GDMA_TRIG_PERIPH_I2S0; // 3 = I2S0 + GDMA.channel[0].out.out_conf0.out_data_burst_en = 1; + GDMA.channel[0].out.out_conf0.outdscr_burst_en = 1; + GDMA.channel[0].out.out_link.start = 1; + + while (!GDMA.intr->raw.out_eof) { } // check status + + i2s_dev->tx_conf.tx_start = 1; + + return ESP_OK; +} + + esp_err_t i2s_parallel_stop_dma(i2s_port_t port) { + if(port < I2S_NUM_0 || port >= I2S_NUM_MAX) { + return ESP_ERR_INVALID_ARG; + } + + // Not implemented + return ESP_OK; +} + + + esp_err_t i2s_parallel_send_dma(i2s_port_t port, lldesc_t* dma_descriptor) { + if(port < I2S_NUM_0 || port >= I2S_NUM_MAX) { + return ESP_ERR_INVALID_ARG; + } + + // Not implemented + return ESP_OK; +} + +i2s_dev_t* i2s_parallel_get_dev(i2s_port_t port) { + if(port < I2S_NUM_0 || port >= I2S_NUM_MAX) { + return NULL; + } + return I2S; // HARCODE THIS TO RETURN &I2S0 +} + +// Double buffering flipping +// Flip to a buffer: 0 for bufa, 1 for bufb +void i2s_parallel_flip_to_buffer(i2s_port_t port, int buffer_id) { + + if (i2s_state == NULL) { + return; // :-() + } + + lldesc_t *active_dma_chain; + if (buffer_id == 0) { + active_dma_chain=(lldesc_t*)&i2s_state->dmadesc_a[0]; + } else { + active_dma_chain=(lldesc_t*)&i2s_state->dmadesc_b[0]; + } + + // setup linked list to refresh from new buffer (continuously) when the end of the current list has been reached + i2s_state->dmadesc_a[i2s_state->desccount_a-1].qe.stqe_next = active_dma_chain; + i2s_state->dmadesc_b[i2s_state->desccount_b-1].qe.stqe_next = active_dma_chain; + + // we're still shifting out the buffer, so it shouldn't be written to yet. + previousBufferFree = false; +} + +bool i2s_parallel_is_previous_buffer_free() { + return previousBufferFree; +} + +// End ESP32 original / S2, S3 check +#endif \ No newline at end of file diff --git a/esp32_i2s_parallel_mcu_def.h b/esp32_i2s_parallel_mcu_def.h new file mode 100644 index 0000000..199d8fa --- /dev/null +++ b/esp32_i2s_parallel_mcu_def.h @@ -0,0 +1,35 @@ +#pragma once + +/* Abstract the Espressif IDF ESP32 MCU variant compile-time defines + * into another list for the purposes of this library. + * + * i.e. I couldn't be bothered having to update the library when they + * release the ESP32S4,5,6,7, n+1 etc. if they are all fundamentally + * the same architecture. + */ +#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + + #define ESP32_SXXX 1 + + #define I2S_PARALLEL_CLOCK_HZ 160000000L + #define DMA_MAX (4096-4) + +#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 + + #define ESP32_CXXX 1 + + #define I2S_PARALLEL_CLOCK_HZ 160000000L + #define DMA_MAX (4096-4) + +#elif CONFIG_IDF_TARGET_ESP32 || defined(ESP32) + + // 2016 model that started it all, and this library. The best. + #define ESP32_ORIG 1 + + #define I2S_PARALLEL_CLOCK_HZ 80000000L + #define DMA_MAX (4096-4) + +#else + #error "No ESP32 or ESP32 Espressif IDF compile-time defines detected. WTH!?" + +#endif \ No newline at end of file