Initial Commit

Support for S and C (tbc)
This commit is contained in:
mrfaptastic 2021-09-13 06:57:45 +01:00
parent 6b308cbbfc
commit 6f8d9c0fa2
6 changed files with 539 additions and 125 deletions

View file

@ -1,6 +1,18 @@
#include <Arduino.h> #include <Arduino.h>
#include "ESP32-HUB75-MatrixPanel-I2S-DMA.h" #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 <https://github.com/pixelmatix/SmartMatrix/tree/teensylc> // Credits: Louis Beaudoin <https://github.com/pixelmatix/SmartMatrix/tree/teensylc>
// and Sprite_TM: https://www.esp32.com/viewtopic.php?f=17&t=3188 and https://www.esp32.com/viewtopic.php?f=13&t=3256 // 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. * 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 // 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 // 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 // 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 // switch pointer to a row for a specific color index
row = dma_buff.rowBits[row_idx]->getDataPtr(coloridx, _buff_id); 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... // -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 row[dma_buff.rowBits[row_idx]->width - 1] |= BIT_LAT; // -1 pixel to compensate array index starting at 0
#else #else
@ -688,7 +700,7 @@ void MatrixPanel_I2S_DMA::clearFrameBuffer(bool _buff_id){
do { do {
--_blank; --_blank;
#ifdef ESP32_S2 #ifdef ESP32_SXXX
row[0 + _blank] |= BIT_OE; 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 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 #else
@ -767,7 +779,7 @@ void MatrixPanel_I2S_DMA::brtCtrlOE(int brt, const bool _buff_id){
do { do {
--_blank; --_blank;
#ifdef ESP32_S2 #ifdef ESP32_SXXX
row[0 + _blank] |= BIT_OE; row[0 + _blank] |= BIT_OE;
#else #else
// Original ESP32 WROOM FIFO Ordering Sucks // 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 do { // iterate pixels in a row
int16_t _x = x_coord + --_l; int16_t _x = x_coord + --_l;
#ifdef ESP32_S2 #ifdef ESP32_SXXX
// ESP 32 doesn't need byte flipping for TX FIFO. // ESP 32 doesn't need byte flipping for TX FIFO.
uint16_t &v = p[_x]; uint16_t &v = p[_x];
#else #else
@ -937,7 +949,7 @@ void MatrixPanel_I2S_DMA::vlineDMA(int16_t x_coord, int16_t y_coord, int16_t l,
blue = lumConvTab[blue]; blue = lumConvTab[blue];
#endif #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 // 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; x_coord & 1U ? --x_coord : ++x_coord;
#endif #endif

View file

@ -89,12 +89,7 @@
#include <vector> #include <vector>
#include <memory> #include <memory>
#include "esp_heap_caps.h" #include "esp_heap_caps.h"
#include "esp32_i2s_parallel_dma.h"
#ifdef ESP32_S2
#include "esp32-s2_i2s_parallel_v1.h"
#else
#include "esp32_i2s_parallel_v2.h"
#endif
#ifdef USE_GFX_ROOT #ifdef USE_GFX_ROOT
#include <FastLED.h> #include <FastLED.h>

View file

@ -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 * Description: Multi-ESP32 product DMA setup functions for WROOM & S2, S3 mcu's.
* to better understand the internals of the ESP32 SoC DMA.
* *
* Credits: Based on the merging of three ideas: * Credits:
* 1) https://www.esp32.com/viewtopic.php?f=17&t=3188 for original ref. implementation * 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) * 2) https://github.com/TobleMiner/esp_i2s_parallel for a cleaner implementation
* 3) https://github.com/TobleMiner/esp_i2s_parallel for a cleaner implementation
* *
*/ */
#include <stdbool.h> // Header
#include <stdint.h> #include "esp32_i2s_parallel_dma.h"
#include <stdlib.h>
#include <string.h>
#if defined(ESP32_ORIG) || defined (ESP32_SXXX)
#include <esp_err.h>
// Turn on and off a periphal
#include <driver/periph_ctrl.h>
// GPIO
#include <soc/gpio_periph.h>
#include <hal/gpio_types.h>
#include <driver/gpio.h> #include <driver/gpio.h>
#include <driver/periph_ctrl.h> #include <driver/periph_ctrl.h>
#include <rom/gpio.h> #include <rom/gpio.h>
#include <soc/gpio_sig_map.h> #include <soc/gpio_sig_map.h>
// Header // DMA Linked List Struct
#include <esp32_i2s_parallel_v2.h> #include <soc/lldesc.h>
#include <soc/io_mux_reg.h>
// For I2S<N> frame buffer state management. // I2S
static i2s_parallel_state_t *i2s_state[2]={NULL, NULL}; #include <soc/i2s_struct.h>
#include <soc/i2s_reg.h>
#ifdef ESP32_CXXX
// GDMA
#include <soc/gdma_channel.h>
#include <soc/gdma_periph.h>
#include <soc/gdma_reg.h>
#include <soc/gdma_struct.h>
#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; callback shiftCompleteCallback;
void setShiftCompleteCallback(callback f) { 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) 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 if ( (*(i2s_port_t*)arg) == I2S_NUM_1 ) { // https://www.bogotobogo.com/cplusplus/pointers2_voidpointers_arrays.php
//For I2S1 //For I2S1
SET_PERI_REG_BITS(I2S_INT_CLR_REG(1), I2S_OUT_EOF_INT_CLR_V, 1, I2S_OUT_EOF_INT_CLR_S); 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); 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 // at this point, the previously active buffer is free, go ahead and write to it
previousBufferFree = true; previousBufferFree = true;
if(shiftCompleteCallback) // we've defined a callback function ? if(shiftCompleteCallback) // we've defined a callback function ?
shiftCompleteCallback(); shiftCompleteCallback();
}
} // end irq_hndlr
// For peripheral setup and configuration // 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) { static inline int get_bus_width(i2s_parallel_cfg_bits_t width) {
switch(width) { switch(width) {
@ -97,10 +131,17 @@ static void dma_reset(i2s_dev_t* dev) {
static void fifo_reset(i2s_dev_t* dev) { static void fifo_reset(i2s_dev_t* dev) {
dev->conf.rx_fifo_reset = 1; 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.rx_fifo_reset = 0;
dev->conf.tx_fifo_reset = 1; 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; 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) { 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) { if(port < I2S_NUM_0 || port >= I2S_NUM_MAX) {
return ESP_ERR_INVALID_ARG; 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; volatile int iomux_clock;
int irq_source; int irq_source;
// Initialize I2S peripheral // Initialize I2S0 peripheral
if (port == I2S_NUM_0) { //if (port == I2S_NUM_0) {
periph_module_reset(PERIPH_I2S0_MODULE); periph_module_reset(PERIPH_I2S0_MODULE);
periph_module_enable(PERIPH_I2S0_MODULE); periph_module_enable(PERIPH_I2S0_MODULE);
iomux_clock = I2S0O_WS_OUT_IDX; 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: case I2S_PARALLEL_WIDTH_MAX:
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
} }
/*
} else { } else {
periph_module_reset(PERIPH_I2S1_MODULE); periph_module_reset(PERIPH_I2S1_MODULE);
periph_module_enable(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; return ESP_ERR_INVALID_ARG;
} }
} }
*/
// Setup GPIOs // Setup GPIOs
int bus_width = get_bus_width(conf->sample_width); int bus_width = get_bus_width(conf->sample_width);
// Setup I2S peripheral // Setup I2S peripheral
i2s_dev_t* dev = I2S[port]; i2s_dev_t* dev = I2S;
dev_reset(dev);
// Set i2s mode to LCD mode
dev->conf2.val = 0;
dev->conf2.lcd_en = 1;
dev->conf.tx_slave_mod = 0;
// dev->conf.tx_dma_equal=1; // esp32-s2 only // Setup GPIO's
dev->conf2.lcd_tx_wrx2_en=0; for(int i = 0; i < bus_width; i++) {
dev->conf2.lcd_tx_sdx2_en=0; iomux_set_signal(conf->gpio_bus[i], iomux_signal_base + i);
}
iomux_set_signal(conf->gpio_clk, iomux_clock);
// Enable "One datum will be written twice in LCD mode" - for some reason, // invert clock phase if required
// if we don't do this in 8-bit mode, data is updated on half-clocks not clocks if (conf->clkphase)
if(conf->sample_width == I2S_PARALLEL_WIDTH_8) GPIO.func_out_sel_cfg[conf->gpio_clk].inv_sel = 1;
dev->conf2.lcd_tx_wrx2_en=1;
// Setup i2s clock // Setup i2s clock
dev->sample_rate_conf.val = 0; 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?) // 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.rx_bits_mod = bus_width;
dev->sample_rate_conf.tx_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)
// It's confusing, but the max output the ESP32 can pump out when using I2S *parallel* output is 20Mhz. dev->sample_rate_conf.rx_bck_div_num = 2;
// https://easyvolts.com/2018/08/14/esp32-40msps-oscilloscope-project-is-closed-and-here-is-why/ dev->sample_rate_conf.tx_bck_div_num = 2;
// 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 // Clock configuration
// 16bit parallel I2S @ 20Mhz = calculated clk_div_main (per line ~142) of 2
dev->clkm_conf.val=0; // Clear the clkm_conf struct 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_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
// #ifdef ESP32_SXXX
// Final Mhz output = dev->clkm_conf.clk_sel = 2; // esp32-s2 only
// Output = 80000000L / tx_bck_div_num / (clkm_div_num + (clkm_div_b/clkm_div_a) ) dev->clkm_conf.clk_en = 1;
#endif
#ifdef ESP32_ORIG
dev->clkm_conf.clka_en=0; // Use the 160mhz system clock (PLL_D2_CLK) when '0'
#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! // 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; 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);
// Some fifo conf I don't quite understand // 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.val = 0;
// Dictated by datasheet dev->fifo_conf.rx_data_num = 32; // Thresholds.
dev->fifo_conf.rx_fifo_mod_force_en = 1; dev->fifo_conf.tx_data_num = 32;
dev->fifo_conf.tx_fifo_mod_force_en = 1; 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;
// Not really described for non-pcm modes, although datasheet states it should be set correctly even for LCD mode // 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 // First stage config. Configures how data is loaded into fifo
if(conf->sample_width == I2S_PARALLEL_WIDTH_24) { if(conf->sample_width == I2S_PARALLEL_WIDTH_24) {
@ -258,32 +322,30 @@ 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 // *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; 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; // Dictated by ESP32 datasheet
dev->conf1.tx_stop_en = 0; dev->fifo_conf.rx_fifo_mod_force_en = 1;
dev->conf1.tx_pcm_bypass = 1; dev->fifo_conf.tx_fifo_mod_force_en = 1;
// Second stage config // Second stage config
dev->conf_chan.val = 0; 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.tx_chan_mod = 1;
dev->conf_chan.rx_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 // Allocate I2S status structure for buffer swapping stuff
i2s_state[port] = (i2s_parallel_state_t*) malloc(sizeof(i2s_parallel_state_t)); i2s_state = (i2s_parallel_state_t*) malloc(sizeof(i2s_parallel_state_t));
assert(i2s_state[port] != NULL); assert(i2s_state != NULL);
i2s_parallel_state_t *state = i2s_state[port]; i2s_parallel_state_t *state = i2s_state;
state->desccount_a = conf->desccount_a; state->desccount_a = conf->desccount_a;
state->desccount_b = conf->desccount_b; state->desccount_b = conf->desccount_b;
@ -301,19 +363,15 @@ esp_err_t i2s_parallel_driver_install(i2s_port_t port, i2s_parallel_config_t* co
return err; return err;
} }
dev->timing.val = 0;
// Setup interrupt handler which is focussed only on the (page 322 of Tech. Ref. Manual) // 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" // "I2S_OUT_EOF_INT: Triggered when rxlink has finished sending a packet"
// ... whatever the hell that is supposed to mean... One massive linked list. // ... whatever the hell that is supposed to mean... One massive linked list.
dev->int_ena.out_eof = 1; 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; 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; return ESP_ERR_INVALID_ARG;
} }
i2s_dev_t* dev = I2S[port]; i2s_dev_t* dev = I2S;
// Stop all ongoing DMA operations // Stop all ongoing DMA operations
dev->out_link.stop = 1; dev->out_link.stop = 1;
@ -339,19 +397,19 @@ esp_err_t i2s_parallel_driver_install(i2s_port_t port, i2s_parallel_config_t* co
return ESP_ERR_INVALID_ARG; 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;
dev->out_link.start = 0;
dev->conf.tx_start = 0;
dev_reset(dev);
// Configure DMA burst mode // Configure DMA burst mode
dev->lc_conf.val = I2S_OUT_DATA_BURST_EN | I2S_OUTDSCR_BURST_EN; dev->lc_conf.val = I2S_OUT_DATA_BURST_EN | I2S_OUTDSCR_BURST_EN;
// Set address of DMA descriptor // Set address of DMA descriptor
dev->out_link.addr = (uint32_t) 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->out_link.start = 1;
dev->conf.tx_start = 1; dev->conf.tx_start = 1;
return ESP_OK; 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) { if(port < I2S_NUM_0 || port >= I2S_NUM_MAX) {
return NULL; return NULL;
} }
return I2S[port]; return I2S; // HARCODE THIS TO RETURN &I2S0
} }
// Double buffering flipping // Double buffering flipping
// Flip to a buffer: 0 for bufa, 1 for bufb // Flip to a buffer: 0 for bufa, 1 for bufb
void i2s_parallel_flip_to_buffer(i2s_port_t port, int buffer_id) { void i2s_parallel_flip_to_buffer(i2s_port_t port, int buffer_id) {
if (i2s_state[port] == NULL) { if (i2s_state == NULL) {
return; // :-() return; // :-()
} }
lldesc_t *active_dma_chain; lldesc_t *active_dma_chain;
if (buffer_id == 0) { 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 { } 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 // 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->dmadesc_a[i2s_state->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_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. // we're still shifting out the buffer, so it shouldn't be written to yet.
previousBufferFree = false; previousBufferFree = false;
@ -391,3 +449,5 @@ bool i2s_parallel_is_previous_buffer_free() {
return previousBufferFree; return previousBufferFree;
} }
// End ESP32 original / S2, S3 check
#endif

View file

@ -1,5 +1,6 @@
#pragma once
/* /*
* ESP32_I2S_PARALLEL_V2 (Version 2) * ESP32_I2S_PARALLEL_DMA
*/ */
#pragma once #pragma once
@ -12,18 +13,12 @@
extern "C" { extern "C" {
#endif #endif
#include <stdbool.h>
#include <sys/types.h>
#include <freertos/FreeRTOS.h>
#include <driver/i2s.h>
#include <esp_err.h> #include <esp_err.h>
#include <driver/i2s.h>
#include <rom/lldesc.h> #include <rom/lldesc.h>
#define I2S_PARALLEL_CLOCK_HZ 80000000L // Get MCU Type and Max CLK Hz for MCU
#define DMA_MAX (4096-4) #include <esp32_i2s_parallel_mcu_def.h>
typedef enum { typedef enum {
I2S_PARALLEL_WIDTH_8, 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) { static inline int i2s_parallel_get_memory_width(i2s_port_t port, i2s_parallel_cfg_bits_t width) {
switch(width) { switch(width) {
case I2S_PARALLEL_WIDTH_8: case I2S_PARALLEL_WIDTH_8:
#ifdef ESP32_ORIG
// IS21 supports space saving single byte 8 bit parallel access // IS21 supports space saving single byte 8 bit parallel access
if(port == I2S_NUM_1) { if(port == I2S_NUM_1) {
return 1; return 1;
} }
#else
return 1;
#endif
case I2S_PARALLEL_WIDTH_16: case I2S_PARALLEL_WIDTH_16:
return 2; return 2;
case I2S_PARALLEL_WIDTH_24: case I2S_PARALLEL_WIDTH_24:

View file

@ -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 <esp_err.h>
// Turn on and off a periphal
#include <driver/periph_ctrl.h>
// GPIO
#include <soc/gpio_periph.h>
#include <hal/gpio_types.h>
#include <driver/gpio.h>
#include <driver/periph_ctrl.h>
#include <rom/gpio.h>
#include <soc/gpio_sig_map.h>
// DMA Linked List Struct
#include <soc/lldesc.h>
#include <soc/io_mux_reg.h>
// I2S
#include <soc/i2s_struct.h>
#include <soc/i2s_reg.h>
// GDMA
#include <soc/gdma_channel.h>
#include <soc/gdma_periph.h>
#include <soc/gdma_reg.h>
#include <soc/gdma_struct.h>
// 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

View file

@ -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