aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormrfaptastic <12006953+mrfaptastic@users.noreply.github.com>2021-02-17 01:00:59 +0000
committermrfaptastic <12006953+mrfaptastic@users.noreply.github.com>2021-02-17 01:00:59 +0000
commit895bb80b438d3373fb5d3424e2ae7a65c1db4e0e (patch)
treefe46276a7faf11b7ca10fab2a8b9a8a19c434dc8
parent249b93ba798c06eabab64023f567c5e3b3a513f0 (diff)
Rewrite DMA driver setup code.
Evolutionary, not revolutionary. Vast improvement in double buffer swap speeds.
-rw-r--r--ESP32-HUB75-MatrixPanel-I2S-DMA.cpp41
-rw-r--r--ESP32-HUB75-MatrixPanel-I2S-DMA.h32
-rw-r--r--esp32_i2s_parallel.c283
-rw-r--r--esp32_i2s_parallel.h65
-rw-r--r--esp32_i2s_parallel_v2.c355
-rw-r--r--esp32_i2s_parallel_v2.h91
6 files changed, 483 insertions, 384 deletions
diff --git a/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp b/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp
index b7dc8b0..6525ef4 100644
--- a/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp
+++ b/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp
@@ -178,8 +178,10 @@ bool MatrixPanel_I2S_DMA::allocateDMAmemory()
else
break;
}
-
- Serial.printf_P(PSTR("Raised lsbMsbTransitionBit to %d/%d to fit in remaining RAM\r\n"), lsbMsbTransitionBit, PIXEL_COLOR_DEPTH_BITS - 1);
+
+ #if SERIAL_DEBUG
+ Serial.printf_P(PSTR("Raised lsbMsbTransitionBit to %d/%d to fit in remaining RAM\r\n"), lsbMsbTransitionBit, PIXEL_COLOR_DEPTH_BITS - 1);
+ #endif
#ifndef IGNORE_REFRESH_RATE
@@ -212,7 +214,10 @@ bool MatrixPanel_I2S_DMA::allocateDMAmemory()
break;
}
+ #if SERIAL_DEBUG
Serial.printf_P(PSTR("Raised lsbMsbTransitionBit to %d/%d to meet minimum refresh rate\r\n"), lsbMsbTransitionBit, PIXEL_COLOR_DEPTH_BITS - 1);
+ #endif
+
#endif
/***
@@ -288,12 +293,6 @@ bool MatrixPanel_I2S_DMA::allocateDMAmemory()
Serial.printf_P(PSTR("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(F("DMA capable memory map available after malloc's: "));
- heap_caps_print_heap_info(MALLOC_CAP_DMA);
- delay(1000);
- #endif
-
// Just os we know
initialized = true;
@@ -414,28 +413,28 @@ void MatrixPanel_I2S_DMA::configureDMA(const HUB75_I2S_CFG& _cfg)
Serial.println(F("Performing I2S setup:"));
#endif
- i2s_parallel_config_t cfg={
+ i2s_parallel_config_t cfg = {
.gpio_bus={_cfg.gpio.r1, _cfg.gpio.g1, _cfg.gpio.b1, _cfg.gpio.r2, _cfg.gpio.g2, _cfg.gpio.b2, _cfg.gpio.lat, _cfg.gpio.oe, _cfg.gpio.a, _cfg.gpio.b, _cfg.gpio.c, _cfg.gpio.d, _cfg.gpio.e, -1, -1, -1},
.gpio_clk=_cfg.gpio.clk,
- .clkspeed_hz=_cfg.i2sspeed, //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=ESP32_I2S_DMA_MODE, //ESP32_I2S_DMA_MODE,
- .bufa=0,
- .bufb=0,
- desccount,
- desccount,
- dmadesc_a,
- dmadesc_b
+ .sample_rate=_cfg.i2sspeed,
+ .sample_width=ESP32_I2S_DMA_MODE,
+ .desccount_a=desccount,
+ .lldesc_a=dmadesc_a,
+ .desccount_b=desccount,
+ .lldesc_b=dmadesc_b
};
- //Setup I2S
- i2s_parallel_setup_without_malloc(&I2S1, &cfg);
+ // Setup I2S
+ i2s_parallel_driver_install(I2S_NUM_1, &cfg);
+ //i2s_parallel_setup_without_malloc(&I2S1, &cfg);
+
+ // Start DMA Output
+ i2s_parallel_send_dma(I2S_NUM_1, &dmadesc_a[0]);
#if SERIAL_DEBUG
Serial.println(F("configureDMA(): DMA configuration completed on I2S1."));
Serial.println(F("DMA Memory Map after DMA LL allocations:"));
heap_caps_print_heap_info(MALLOC_CAP_DMA);
-
- delay(1000);
#endif
} // end initMatrixDMABuff
diff --git a/ESP32-HUB75-MatrixPanel-I2S-DMA.h b/ESP32-HUB75-MatrixPanel-I2S-DMA.h
index 83c43dd..c17a08d 100644
--- a/ESP32-HUB75-MatrixPanel-I2S-DMA.h
+++ b/ESP32-HUB75-MatrixPanel-I2S-DMA.h
@@ -81,9 +81,24 @@
#define COLOR_CHANNELS_PER_PIXEL 3
+
+/***************************************************************************************/
+/* Library Includes! */
+#include <vector>
+#include <memory>
+#include "esp_heap_caps.h"
+#include "esp32_i2s_parallel_v2.h"
+
+#ifdef USE_GFX_ROOT
+ #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 */
-#define ESP32_I2S_DMA_MODE I2S_PARALLEL_BITS_16 // Pump 16 bits out in parallel
+#define ESP32_I2S_DMA_MODE I2S_PARALLEL_WIDTH_16 // From esp32_i2s_parallel_v2.h = 16 bits in parallel
#define ESP32_I2S_DMA_STORAGE_TYPE uint16_t // DMA output of one uint16_t at a time.
#define CLKS_DURING_LATCH 0 // Not (yet) used.
@@ -132,19 +147,6 @@
#endif
/***************************************************************************************/
-// Lib includes
-#include <vector>
-#include <memory>
-#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
-#elif !defined NO_GFX
- #include "Adafruit_GFX.h" // Adafruit class with all the other stuff
-#endif
-
-/***************************************************************************************/
/** @brief - Structure holds raw DMA data to drive TWO full rows of pixels spaning through all chained modules
* Note: sizeof(data) must be multiple of 32 bits, as ESP32 DMA linked list buffer address pointer must be word-aligned
@@ -471,7 +473,7 @@ class MatrixPanel_I2S_DMA {
Serial.printf_P(PSTR("Showtime for buffer: %d\n"), back_buffer_id);
#endif
- i2s_parallel_flip_to_buffer(&I2S1, back_buffer_id);
+ i2s_parallel_flip_to_buffer(I2S_NUM_1, back_buffer_id);
// Wait before we allow any writing to the buffer. Stop flicker.
while(!i2s_parallel_is_previous_buffer_free()) { delay(1); }
diff --git a/esp32_i2s_parallel.c b/esp32_i2s_parallel.c
deleted file mode 100644
index d29b771..0000000
--- a/esp32_i2s_parallel.c
+++ /dev/null
@@ -1,283 +0,0 @@
-// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#if defined(ESP32)
-
-#include <stdio.h>
-#include <stdint.h>
-#include <stddef.h>
-#include <string.h>
-
-#include "freertos/FreeRTOS.h"
-#include "freertos/task.h"
-#include "freertos/semphr.h"
-#include "freertos/queue.h"
-
-#include "soc/i2s_reg.h"
-#include "driver/periph_ctrl.h"
-#include "soc/io_mux_reg.h"
-//#include "esp_heap_caps.h"
-#include "esp32_i2s_parallel.h"
-
-typedef struct {
- volatile lldesc_t *dmadesc_a, *dmadesc_b;
- int desccount_a, desccount_b;
-} i2s_parallel_state_t;
-
-static i2s_parallel_state_t *i2s_state[2]={NULL, NULL};
-
-callback shiftCompleteCallback;
-
-void setShiftCompleteCallback(callback f) {
- shiftCompleteCallback = f;
-}
-
-volatile bool previousBufferFree = true;
-
-static int i2snum(i2s_dev_t *dev) {
- return (dev==&I2S0)?0:1;
-}
-
-// Todo: handle IS20? (this is hard coded for I2S1 only)
-static void IRAM_ATTR i2s_isr(void* arg) {
- REG_WRITE(I2S_INT_CLR_REG(1), (REG_READ(I2S_INT_RAW_REG(1)) & 0xffffffc0) | 0x3f);
-
- // at this point, the previously active buffer is free, go ahead and write to it
- previousBufferFree = true;
-
- if(shiftCompleteCallback)
- shiftCompleteCallback();
-}
-/*
-//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;
- for (int i=0; desc[i].memory!=NULL; i++) {
- ret+=(desc[i].size+DMA_MAX-1)/DMA_MAX;
- }
- return ret;
-}
-
-static void fill_dma_desc(volatile lldesc_t *dmadesc, i2s_parallel_buffer_desc_t *bufdesc) {
- int n=0;
- for (int i=0; bufdesc[i].memory!=NULL; i++) {
- int len=bufdesc[i].size;
- uint8_t *data=(uint8_t*)bufdesc[i].memory;
- while(len) {
- int dmalen=len;
- if (dmalen>DMA_MAX) dmalen=DMA_MAX;
- dmadesc[n].size=dmalen;
- dmadesc[n].length=dmalen;
- dmadesc[n].buf=data;
- dmadesc[n].eof=0;
- dmadesc[n].sosf=0;
- dmadesc[n].owner=1;
- dmadesc[n].qe.stqe_next=(lldesc_t*)&dmadesc[n+1];
- dmadesc[n].offset=0;
- len-=dmalen;
- data+=dmalen;
- n++;
- }
- }
-
- // set EOF bit in last dma descriptor
- dmadesc[n-1].eof=1;
- // link end of list back to beginning so current frame will be refreshed continously
- dmadesc[n-1].qe.stqe_next=(lldesc_t*)&dmadesc[0];
-
- 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;
-
- 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;
-}
-
-static void gpio_setup_out(int gpio, int sig) {
- if (gpio==-1) return;
- PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpio], PIN_FUNC_GPIO);
- gpio_set_direction(gpio, GPIO_MODE_DEF_OUTPUT);
- 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;
-}
-
-static void fifo_reset(i2s_dev_t *dev) {
- dev->conf.rx_fifo_reset=1; dev->conf.rx_fifo_reset=0;
- dev->conf.tx_fifo_reset=1; dev->conf.tx_fifo_reset=0;
-}
-
-void i2s_parallel_setup_without_malloc(i2s_dev_t *dev, const i2s_parallel_config_t *cfg) {
- //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, sig_clk;
- if (dev==&I2S0) {
- 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
- sig_data_base=I2S1O_DATA_OUT8_IDX;
- } else { // I2S_PARALLEL_BITS_8
- //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;
- }
-
- //Route the signals
- for (int x=0; x<cfg->bits; 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 dev
- if (dev==&I2S0) {
- periph_module_enable(PERIPH_I2S0_MODULE);
- } else {
- periph_module_enable(PERIPH_I2S1_MODULE);
- }
- //Initialize I2S dev
- dev->conf.rx_reset=1; dev->conf.rx_reset=0;
- dev->conf.tx_reset=1; dev->conf.tx_reset=0;
- dma_reset(dev);
- fifo_reset(dev);
-
- //Enable LCD mode
- dev->conf2.val=0;
- dev->conf2.lcd_en=1;
-
- // 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(cfg->bits == I2S_PARALLEL_BITS_8)
- dev->conf2.lcd_tx_wrx2_en=1;
-
- dev->sample_rate_conf.val=0;
- dev->sample_rate_conf.rx_bits_mod=cfg->bits;
- dev->sample_rate_conf.tx_bits_mod=cfg->bits;
- dev->sample_rate_conf.rx_bck_div_num=4; //ToDo: Unsure about what this does...
-
- // because conf2.lcd_tx_wrx2_en is set for 8-bit mode, the clock speed is doubled, drop it in half here
- if(cfg->bits == I2S_PARALLEL_BITS_8)
- dev->sample_rate_conf.tx_bck_div_num=2;
- else
- dev->sample_rate_conf.tx_bck_div_num=1; // datasheet says this must be 2 or greater (but 1 seems to work)
-
- dev->clkm_conf.val=0;
- dev->clkm_conf.clka_en=0;
- dev->clkm_conf.clkm_div_a=63;
- dev->clkm_conf.clkm_div_b=63;
- //We ignore the possibility for fractional division here, clkspeed_hz must round up for a fractional clock speed, must result in >= 2
- dev->clkm_conf.clkm_div_num=80000000L/(cfg->clkspeed_hz + 1);
-
- dev->fifo_conf.val=0;
- dev->fifo_conf.rx_fifo_mod_force_en=1;
- dev->fifo_conf.tx_fifo_mod_force_en=1;
- dev->fifo_conf.tx_fifo_mod=1; // 16-bit sigle channel mode
- dev->fifo_conf.rx_data_num=32; //Thresholds.
- dev->fifo_conf.tx_data_num=32;
- dev->fifo_conf.dscr_en=1; // FIFO will pump the data from DMA
-
- dev->conf1.val=0;
- dev->conf1.tx_stop_en=0;
- dev->conf1.tx_pcm_bypass=1;
-
- dev->conf_chan.val=0;
- dev->conf_chan.tx_chan_mod=1; // Mono
- dev->conf_chan.rx_chan_mod=1;
-
- //Invert ws to be active-low... ToDo: make this configurable
- //dev->conf.tx_right_first=1;
- dev->conf.tx_right_first=0;
- //dev->conf.rx_right_first=1;
- dev->conf.rx_right_first=0;
-
- dev->timing.val=0;
-
- //Allocate DMA descriptors
- i2s_state[i2snum(dev)]=malloc(sizeof(i2s_parallel_state_t));
- assert(i2s_state[i2snum(dev)] != NULL);
- i2s_parallel_state_t *st=i2s_state[i2snum(dev)];
-
- st->desccount_a = cfg->desccount_a;
- st->desccount_b = cfg->desccount_b;
- st->dmadesc_a = cfg->lldesc_a;
- st->dmadesc_b = cfg->lldesc_b;
-
- //Reset FIFO/DMA -> needed? Doesn't dma_reset/fifo_reset do this?
-/*
- dev->lc_conf.in_rst=1; dev->lc_conf.out_rst=1; dev->lc_conf.ahbm_rst=1; dev->lc_conf.ahbm_fifo_rst=1;
- dev->lc_conf.in_rst=0; dev->lc_conf.out_rst=0; dev->lc_conf.ahbm_rst=0; dev->lc_conf.ahbm_fifo_rst=0;
- dev->conf.tx_reset=1; dev->conf.tx_fifo_reset=1; dev->conf.rx_fifo_reset=1;
- dev->conf.tx_reset=0; dev->conf.tx_fifo_reset=0; dev->conf.rx_fifo_reset=0;
-*/
- // 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), i2s_isr, NULL, NULL);
-
- //Start dma on front buffer (buffer a)
- dev->lc_conf.val=I2S_OUT_DATA_BURST_EN | I2S_OUTDSCR_BURST_EN;
- dev->out_link.addr=((uint32_t)(&st->dmadesc_a[0]));
- dev->out_link.start=1;
- dev->conf.tx_start=1;
-}
-
-//Flip to a buffer: 0 for bufa, 1 for bufb
-void i2s_parallel_flip_to_buffer(i2s_dev_t *dev, int bufid) {
- int no=i2snum(dev);
- if (i2s_state[no]==NULL) return;
- lldesc_t *active_dma_chain;
- if (bufid==0) {
- active_dma_chain=(lldesc_t*)&i2s_state[no]->dmadesc_a[0];
- } else {
- active_dma_chain=(lldesc_t*)&i2s_state[no]->dmadesc_b[0];
- }
-
- // setup linked list to refresh from new buffer (continuously) when the end of the current list has been reached
- i2s_state[no]->dmadesc_a[i2s_state[no]->desccount_a-1].qe.stqe_next=active_dma_chain;
- i2s_state[no]->dmadesc_b[i2s_state[no]->desccount_b-1].qe.stqe_next=active_dma_chain;
-
- // we're still refreshing the previously buffer, so it shouldn't be written to yet
- previousBufferFree = false;
-}
-
-bool i2s_parallel_is_previous_buffer_free() {
- return previousBufferFree;
-}
-
-
-#endif
diff --git a/esp32_i2s_parallel.h b/esp32_i2s_parallel.h
deleted file mode 100644
index 97eb801..0000000
--- a/esp32_i2s_parallel.h
+++ /dev/null
@@ -1,65 +0,0 @@
-#ifndef I2S_PARALLEL_H
-#define I2S_PARALLEL_H
-
-#if defined(ESP32) || defined(IDF_VER)
-
-#include <stdint.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include "driver/gpio.h"
-#include "soc/i2s_struct.h"
-#if defined(IDF_VER)
- #include "esp32/rom/lldesc.h"
-#elif defined(ESP32)
- #include "rom/lldesc.h"
-#endif
-
-
-#define DMA_MAX (4096-4)
-//#define DMA_MAX (512)
-
-typedef enum {
- I2S_PARALLEL_BITS_8=8,
- I2S_PARALLEL_BITS_16=16,
- I2S_PARALLEL_BITS_32=32,
-} i2s_parallel_cfg_bits_t;
-
-typedef struct {
- void *memory;
- size_t size;
-} i2s_parallel_buffer_desc_t;
-
-typedef struct {
- int gpio_bus[24];
- int gpio_clk;
- int clkspeed_hz;
- i2s_parallel_cfg_bits_t bits;
- i2s_parallel_buffer_desc_t *bufa;
- i2s_parallel_buffer_desc_t *bufb; // only used with double buffering
- int desccount_a;
- int desccount_b; // only used with double buffering
- lldesc_t * lldesc_a;
- lldesc_t * lldesc_b; // only used with double buffering
-} i2s_parallel_config_t;
-
-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();
-
-
-
-typedef void (*callback)(void);
-void setShiftCompleteCallback(callback f);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
-
-#endif
diff --git a/esp32_i2s_parallel_v2.c b/esp32_i2s_parallel_v2.c
new file mode 100644
index 0000000..ba88f10
--- /dev/null
+++ b/esp32_i2s_parallel_v2.c
@@ -0,0 +1,355 @@
+/*
+ * ESP32_I2S_PARALLEL_V2 (Version 2)
+ *
+ * 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.
+ *
+ * Credits: Based on the merging of three ideas:
+ * 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
+ *
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <driver/gpio.h>
+#include <driver/periph_ctrl.h>
+#include <rom/gpio.h>
+#include <soc/gpio_sig_map.h>
+
+// Header
+#include <esp32_i2s_parallel_v2.h>
+
+// For I2S<N> frame buffer state management.
+static i2s_parallel_state_t *i2s_state[2]={NULL, NULL};
+
+callback shiftCompleteCallback;
+void setShiftCompleteCallback(callback f) {
+ shiftCompleteCallback = f;
+}
+
+volatile bool previousBufferFree = true;
+
+static void IRAM_ATTR irq_hndlr(void* arg) { // if we use I2S1 (default)
+ // REG_WRITE(I2S_INT_CLR_REG(1), (REG_READ(I2S_INT_RAW_REG(1)) & 0xffffffc0) | 0x3f);
+
+ 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);
+ } else {
+ // For I2S0
+ SET_PERI_REG_BITS(I2S_INT_CLR_REG(0), I2S_OUT_EOF_INT_CLR_V, 1, I2S_OUT_EOF_INT_CLR_S);
+ }
+
+ // 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();
+}
+
+
+// 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) {
+ 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 iomux_set_signal(int gpio, int signal) {
+ if(gpio < 0) {
+ return;
+ }
+ PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpio], PIN_FUNC_GPIO);
+ gpio_set_direction(gpio, GPIO_MODE_DEF_OUTPUT);
+ gpio_matrix_out(gpio, signal, 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;
+}
+
+static void fifo_reset(i2s_dev_t* dev) {
+ dev->conf.rx_fifo_reset = 1;
+// while(dev->state.rx_fifo_reset_back);
+ dev->conf.rx_fifo_reset = 0;
+ dev->conf.tx_fifo_reset = 1;
+// while(dev->state.tx_fifo_reset_back);
+ dev->conf.tx_fifo_reset = 0;
+}
+
+static void dev_reset(i2s_dev_t* dev) {
+ fifo_reset(dev);
+ dma_reset(dev);
+ dev->conf.rx_reset=1;
+ dev->conf.tx_reset=1;
+ dev->conf.rx_reset=0;
+ dev->conf.tx_reset=0;
+}
+
+// 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* conf) {
+ if(port < I2S_NUM_0 || port >= I2S_NUM_MAX) {
+ return ESP_ERR_INVALID_ARG;
+ }
+ if(conf->sample_width < I2S_PARALLEL_WIDTH_8 || conf->sample_width >= I2S_PARALLEL_WIDTH_MAX) {
+ return ESP_ERR_INVALID_ARG;
+ }
+ if(conf->sample_rate > I2S_PARALLEL_CLOCK_HZ || conf->sample_rate < 1) {
+ return ESP_ERR_INVALID_ARG;
+ }
+ uint32_t clk_div_main = I2S_PARALLEL_CLOCK_HZ / conf->sample_rate / i2s_parallel_get_memory_width(port, conf->sample_width);
+ if(clk_div_main < 2 || clk_div_main > 0xFF) {
+ return ESP_ERR_INVALID_ARG;
+ }
+
+ volatile int iomux_signal_base;
+ volatile int iomux_clock;
+ int irq_source;
+
+ // Initialize I2S peripheral
+ if (port == I2S_NUM_0) {
+ periph_module_reset(PERIPH_I2S0_MODULE);
+ periph_module_enable(PERIPH_I2S0_MODULE);
+ iomux_clock = I2S0O_WS_OUT_IDX;
+ irq_source = ETS_I2S0_INTR_SOURCE;
+
+ switch(conf->sample_width) {
+ case I2S_PARALLEL_WIDTH_8:
+ case I2S_PARALLEL_WIDTH_16:
+ iomux_signal_base = I2S0O_DATA_OUT8_IDX;
+ break;
+ case I2S_PARALLEL_WIDTH_24:
+ iomux_signal_base = I2S0O_DATA_OUT0_IDX;
+ break;
+ case I2S_PARALLEL_WIDTH_MAX:
+ return ESP_ERR_INVALID_ARG;
+ }
+ } else {
+ periph_module_reset(PERIPH_I2S1_MODULE);
+ periph_module_enable(PERIPH_I2S1_MODULE);
+ iomux_clock = I2S1O_WS_OUT_IDX;
+ irq_source = ETS_I2S1_INTR_SOURCE;
+
+ switch(conf->sample_width) {
+ case I2S_PARALLEL_WIDTH_16:
+ iomux_signal_base = I2S1O_DATA_OUT8_IDX;
+ break;
+ case I2S_PARALLEL_WIDTH_8:
+ case I2S_PARALLEL_WIDTH_24:
+ iomux_signal_base = I2S1O_DATA_OUT0_IDX;
+ break;
+ case I2S_PARALLEL_WIDTH_MAX:
+ 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;
+
+ // 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 i2s clock
+ dev->sample_rate_conf.val = 0;
+
+ // 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 = 1;
+
+ // No support for fractional dividers (could probably be ported from official serial i2s driver though)
+ dev->clkm_conf.val=0; // Clear the clkm_conf struct
+ dev->clkm_conf.clka_en=0; // Use the 160mhz system clock (PLL_D2_CLK) when '0'
+ dev->clkm_conf.clkm_div_a=1; // Page 310 of Technical Reference Manual - Clock denominator
+ dev->clkm_conf.clkm_div_b=1; // Page 310 of Technical Reference Manual - Clock numerator
+
+ // 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."
+ //printf("esp32_i2s_parallel_2.c > I2S clock divider is %d \n", clk_div_main*2);
+ // 10Mhz requested will = clk_div_main of 4 for some reason, so *2 = 8, which = 80/8 = 10Mhz gpio output.
+ dev->clkm_conf.clkm_div_num = clk_div_main*2;
+
+
+ // 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) {
+ // Mode 0, single 32-bit channel, linear 32 bit load to fifo
+ dev->fifo_conf.tx_fifo_mod = 3;
+ } else {
+ // Mode 1, single 16-bit channel, load 16 bit sample(*) into fifo and pad to 32 bit with zeros
+ // *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;
+
+ // Second stage config
+ dev->conf_chan.val = 0;
+ // Tx in mono mode, read 32 bit per sample from fifo
+ dev->conf_chan.tx_chan_mod = 1;
+ dev->conf_chan.rx_chan_mod = 1;
+
+
+ dev->conf.tx_right_first = 0; //!!invert_clk; // should be false / 0
+ dev->conf.rx_right_first = 0; //!!invert_clk;
+
+ dev->timing.val = 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];
+
+ state->desccount_a = conf->desccount_a;
+ state->desccount_b = conf->desccount_b;
+ state->dmadesc_a = conf->lldesc_a;
+ state->dmadesc_b = conf->lldesc_b;
+ state->i2s_interrupt_port_arg = port; // need to keep this somewhere in static memory for the ISR
+
+ // Get ISR setup
+ esp_err_t err = esp_intr_alloc(irq_source,
+ (int)(ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL1),
+ irq_hndlr,
+ &state->i2s_interrupt_port_arg, NULL);
+
+ if(err) {
+ return err;
+ }
+
+ // 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);
+
+ 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;
+ }
+
+ 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);
+
+ // 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
+ dev->out_link.start = 1;
+ dev->conf.tx_start = 1;
+
+ 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[port];
+}
+
+// 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) {
+ return; // :-()
+ }
+
+ lldesc_t *active_dma_chain;
+ if (buffer_id == 0) {
+ active_dma_chain=(lldesc_t*)&i2s_state[port]->dmadesc_a[0];
+ } else {
+ active_dma_chain=(lldesc_t*)&i2s_state[port]->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;
+
+ // 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;
+}
+
diff --git a/esp32_i2s_parallel_v2.h b/esp32_i2s_parallel_v2.h
new file mode 100644
index 0000000..16b094d
--- /dev/null
+++ b/esp32_i2s_parallel_v2.h
@@ -0,0 +1,91 @@
+/*
+ * ESP32_I2S_PARALLEL_V2 (Version 2)
+ */
+
+#pragma once
+
+#if defined(ESP32) || defined(IDF_VER)
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include <freertos/FreeRTOS.h>
+#include <driver/i2s.h>
+#include <esp_err.h>
+#include <rom/lldesc.h>
+
+#define I2S_PARALLEL_CLOCK_HZ 80000000L
+#define DMA_MAX (4096-4)
+
+typedef enum {
+ I2S_PARALLEL_WIDTH_8,
+ I2S_PARALLEL_WIDTH_16,
+ I2S_PARALLEL_WIDTH_24,
+ I2S_PARALLEL_WIDTH_MAX
+} i2s_parallel_cfg_bits_t;
+
+typedef struct {
+ int gpio_bus[24]; // The parallel GPIOs to use, set gpio to -1 to disable
+ int gpio_clk;
+ int sample_rate; // 'clockspeed'
+ int sample_width;
+ int desccount_a;
+ lldesc_t * lldesc_a;
+ int desccount_b; // only used with double buffering
+ lldesc_t * lldesc_b; // only used with double buffering
+} i2s_parallel_config_t;
+
+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:
+ // IS21 supports space saving single byte 8 bit parallel access
+ if(port == I2S_NUM_1) {
+ return 1;
+ }
+ case I2S_PARALLEL_WIDTH_16:
+ return 2;
+ case I2S_PARALLEL_WIDTH_24:
+ return 4;
+ default:
+ return -ESP_ERR_INVALID_ARG;
+ }
+}
+
+// DMA Linked List Creation
+void link_dma_desc(volatile lldesc_t *dmadesc, volatile lldesc_t *prevdmadesc, void *memory, size_t size);
+
+// I2S DMA Peripheral Setup Functions
+esp_err_t i2s_parallel_driver_install(i2s_port_t port, i2s_parallel_config_t* conf);
+esp_err_t i2s_parallel_send_dma(i2s_port_t port, lldesc_t* dma_descriptor);
+i2s_dev_t* i2s_parallel_get_dev(i2s_port_t port);
+
+// For frame buffer flipping / double buffering
+typedef struct {
+ volatile lldesc_t *dmadesc_a, *dmadesc_b;
+ int desccount_a, desccount_b;
+ i2s_port_t i2s_interrupt_port_arg;
+} i2s_parallel_state_t;
+
+void i2s_parallel_flip_to_buffer(i2s_port_t port, int bufid);
+bool i2s_parallel_is_previous_buffer_free();
+
+// Callback function for when whole length of DMA chain has been sent out.
+typedef void (*callback)(void);
+void setShiftCompleteCallback(callback f);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+