Initial Commit
Support for S and C (tbc)
This commit is contained in:
parent
6b308cbbfc
commit
6f8d9c0fa2
6 changed files with 539 additions and 125 deletions
|
@ -1,6 +1,18 @@
|
|||
#include <Arduino.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>
|
||||
// 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
|
||||
|
|
|
@ -89,12 +89,7 @@
|
|||
#include <vector>
|
||||
#include <memory>
|
||||
#include "esp_heap_caps.h"
|
||||
|
||||
#ifdef ESP32_S2
|
||||
#include "esp32-s2_i2s_parallel_v1.h"
|
||||
#else
|
||||
#include "esp32_i2s_parallel_v2.h"
|
||||
#endif
|
||||
#include "esp32_i2s_parallel_dma.h"
|
||||
|
||||
#ifdef USE_GFX_ROOT
|
||||
#include <FastLED.h>
|
||||
|
|
|
@ -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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// Header
|
||||
#include "esp32_i2s_parallel_dma.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/periph_ctrl.h>
|
||||
|
||||
#include <rom/gpio.h>
|
||||
#include <soc/gpio_sig_map.h>
|
||||
|
||||
// Header
|
||||
#include <esp32_i2s_parallel_v2.h>
|
||||
// DMA Linked List Struct
|
||||
#include <soc/lldesc.h>
|
||||
#include <soc/io_mux_reg.h>
|
||||
|
||||
// For I2S<N> frame buffer state management.
|
||||
static i2s_parallel_state_t *i2s_state[2]={NULL, NULL};
|
||||
// I2S
|
||||
#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;
|
||||
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
|
|
@ -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 <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <driver/i2s.h>
|
||||
#include <esp_err.h>
|
||||
#include <driver/i2s.h>
|
||||
#include <rom/lldesc.h>
|
||||
|
||||
#define I2S_PARALLEL_CLOCK_HZ 80000000L
|
||||
#define DMA_MAX (4096-4)
|
||||
// Get MCU Type and Max CLK Hz for MCU
|
||||
#include <esp32_i2s_parallel_mcu_def.h>
|
||||
|
||||
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:
|
311
esp32_i2s_parallel_dma_cxxx.c
Normal file
311
esp32_i2s_parallel_dma_cxxx.c
Normal 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
|
35
esp32_i2s_parallel_mcu_def.h
Normal file
35
esp32_i2s_parallel_mcu_def.h
Normal 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
|
Loading…
Reference in a new issue