commit
a8810e345c
6 changed files with 597 additions and 162 deletions
|
@ -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
|
||||||
|
|
|
@ -1,5 +1,18 @@
|
||||||
#ifndef _ESP32_RGB_64_32_MATRIX_PANEL_I2S_DMA
|
#ifndef _ESP32_RGB_64_32_MATRIX_PANEL_I2S_DMA
|
||||||
#define _ESP32_RGB_64_32_MATRIX_PANEL_I2S_DMA
|
#define _ESP32_RGB_64_32_MATRIX_PANEL_I2S_DMA
|
||||||
|
/***************************************************************************************/
|
||||||
|
/* Core ESP32 hardware / idf includes! */
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include "esp_heap_caps.h"
|
||||||
|
#include "esp32_i2s_parallel_dma.h"
|
||||||
|
|
||||||
|
#ifdef USE_GFX_ROOT
|
||||||
|
#include <FastLED.h>
|
||||||
|
#include "GFX.h" // Adafruit GFX core class -> https://github.com/mrfaptastic/GFX_Root
|
||||||
|
#elif !defined NO_GFX
|
||||||
|
#include "Adafruit_GFX.h" // Adafruit class with all the other stuff
|
||||||
|
#endif
|
||||||
|
|
||||||
/*******************************************************************************************
|
/*******************************************************************************************
|
||||||
* COMPILE-TIME OPTIONS - MUST BE PROVIDED as part of PlatformIO project build_flags. *
|
* COMPILE-TIME OPTIONS - MUST BE PROVIDED as part of PlatformIO project build_flags. *
|
||||||
|
@ -46,22 +59,44 @@
|
||||||
/* ESP32 Default Pin definition. You can change this, but best if you keep it as is and provide custom pin mappings
|
/* ESP32 Default Pin definition. You can change this, but best if you keep it as is and provide custom pin mappings
|
||||||
* as part of the begin(...) function.
|
* as part of the begin(...) function.
|
||||||
*/
|
*/
|
||||||
#define R1_PIN_DEFAULT 25
|
|
||||||
#define G1_PIN_DEFAULT 26
|
|
||||||
#define B1_PIN_DEFAULT 27
|
|
||||||
#define R2_PIN_DEFAULT 14
|
|
||||||
#define G2_PIN_DEFAULT 12
|
|
||||||
#define B2_PIN_DEFAULT 13
|
|
||||||
|
|
||||||
#define A_PIN_DEFAULT 23
|
#ifdef ESP32_SXXX
|
||||||
#define B_PIN_DEFAULT 19
|
|
||||||
#define C_PIN_DEFAULT 5
|
#define R1_PIN_DEFAULT 45
|
||||||
#define D_PIN_DEFAULT 17
|
#define G1_PIN_DEFAULT 42
|
||||||
#define E_PIN_DEFAULT -1 // IMPORTANT: Change to a valid pin if using a 64x64px panel.
|
#define B1_PIN_DEFAULT 41
|
||||||
|
#define R2_PIN_DEFAULT 40
|
||||||
#define LAT_PIN_DEFAULT 4
|
#define G2_PIN_DEFAULT 39
|
||||||
#define OE_PIN_DEFAULT 15
|
#define B2_PIN_DEFAULT 38
|
||||||
#define CLK_PIN_DEFAULT 16
|
#define A_PIN_DEFAULT 37
|
||||||
|
#define B_PIN_DEFAULT 36
|
||||||
|
#define C_PIN_DEFAULT 35
|
||||||
|
#define D_PIN_DEFAULT 34
|
||||||
|
#define E_PIN_DEFAULT -1 // required for 1/32 scan panels, like 64x64. Any available pin would do, i.e. IO32
|
||||||
|
#define LAT_PIN_DEFAULT 26
|
||||||
|
#define OE_PIN_DEFAULT 21
|
||||||
|
#define CLK_PIN_DEFAULT 33
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define R1_PIN_DEFAULT 25
|
||||||
|
#define G1_PIN_DEFAULT 26
|
||||||
|
#define B1_PIN_DEFAULT 27
|
||||||
|
#define R2_PIN_DEFAULT 14
|
||||||
|
#define G2_PIN_DEFAULT 12
|
||||||
|
#define B2_PIN_DEFAULT 13
|
||||||
|
|
||||||
|
#define A_PIN_DEFAULT 23
|
||||||
|
#define B_PIN_DEFAULT 19
|
||||||
|
#define C_PIN_DEFAULT 5
|
||||||
|
#define D_PIN_DEFAULT 17
|
||||||
|
#define E_PIN_DEFAULT -1 // IMPORTANT: Change to a valid pin if using a 64x64px panel.
|
||||||
|
|
||||||
|
#define LAT_PIN_DEFAULT 4
|
||||||
|
#define OE_PIN_DEFAULT 15
|
||||||
|
#define CLK_PIN_DEFAULT 16
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
// Interesting Fact: We end up using a uint16_t to send data in parallel to the HUB75... but
|
// Interesting Fact: We end up using a uint16_t to send data in parallel to the HUB75... but
|
||||||
// given we only map to 14 physical output wires/bits, we waste 2 bits.
|
// given we only map to 14 physical output wires/bits, we waste 2 bits.
|
||||||
|
@ -84,25 +119,6 @@
|
||||||
|
|
||||||
// #define NO_CIE1931
|
// #define NO_CIE1931
|
||||||
|
|
||||||
/***************************************************************************************/
|
|
||||||
/* Core ESP32 hardware / idf includes! */
|
|
||||||
#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
|
|
||||||
|
|
||||||
#ifdef USE_GFX_ROOT
|
|
||||||
#include <FastLED.h>
|
|
||||||
#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 */
|
/* Definitions below should NOT be ever changed without rewriting library logic */
|
||||||
|
@ -352,8 +368,8 @@ class MatrixPanel_I2S_DMA {
|
||||||
|
|
||||||
/* Propagate the DMA pin configuration, allocate DMA buffs and start data ouput, initialy blank */
|
/* Propagate the DMA pin configuration, allocate DMA buffs and start data ouput, initialy blank */
|
||||||
bool begin(){
|
bool begin(){
|
||||||
|
|
||||||
if (initialized) return true; // we don't do this twice or more!
|
if (initialized) return true; // we don't do this twice or more!
|
||||||
|
|
||||||
// Change 'if' to '1' to enable, 0 to not include this Serial output in compiled program
|
// Change 'if' to '1' to enable, 0 to not include this Serial output in compiled program
|
||||||
#if SERIAL_DEBUG
|
#if SERIAL_DEBUG
|
||||||
|
@ -468,10 +484,10 @@ class MatrixPanel_I2S_DMA {
|
||||||
|
|
||||||
void fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b);
|
void fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b);
|
||||||
void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b);
|
void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b);
|
||||||
|
|
||||||
#ifdef USE_GFX_ROOT
|
#ifdef USE_GFX_ROOT
|
||||||
// 24bpp FASTLED CRGB colour struct support
|
// 24bpp FASTLED CRGB colour struct support
|
||||||
void fillScreen(CRGB color);
|
void fillScreen(CRGB color);
|
||||||
void drawPixel(int16_t x, int16_t y, CRGB color);
|
void drawPixel(int16_t x, int16_t y, CRGB color);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -503,14 +519,14 @@ class MatrixPanel_I2S_DMA {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
i2s_parallel_flip_to_buffer(I2S_NUM_0, back_buffer_id);
|
i2s_parallel_flip_to_buffer(I2S_NUM_0, back_buffer_id);
|
||||||
|
|
||||||
// Wait before we allow any writing to the buffer. Stop flicker.
|
// Wait before we allow any writing to the buffer. Stop flicker.
|
||||||
while(i2s_parallel_is_previous_buffer_free() == false) { }
|
while(i2s_parallel_is_previous_buffer_free() == false) { }
|
||||||
|
|
||||||
// Flip to other buffer as the backbuffer.
|
// Flip to other buffer as the backbuffer.
|
||||||
// i.e. Graphic changes happen to this buffer, but aren't displayed until flipDMABuffer() is called again.
|
// i.e. Graphic changes happen to this buffer, but aren't displayed until flipDMABuffer() is called again.
|
||||||
back_buffer_id ^= 1;
|
back_buffer_id ^= 1;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void setPanelBrightness(int b)
|
inline void setPanelBrightness(int b)
|
||||||
|
|
|
@ -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>
|
|
||||||
#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/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
|
|
||||||
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,
|
// Setup GPIO's
|
||||||
// if we don't do this in 8-bit mode, data is updated on half-clocks not clocks
|
for(int i = 0; i < bus_width; i++) {
|
||||||
if(conf->sample_width == I2S_PARALLEL_WIDTH_8)
|
iomux_set_signal(conf->gpio_bus[i], iomux_signal_base + i);
|
||||||
dev->conf2.lcd_tx_wrx2_en=1;
|
}
|
||||||
|
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
|
// 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)
|
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.
|
#ifdef ESP32_ORIG
|
||||||
// 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
|
|
||||||
dev->clkm_conf.clka_en=0; // Use the 160mhz system clock (PLL_D2_CLK) when '0'
|
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
|
#endif
|
||||||
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) )
|
|
||||||
|
|
||||||
|
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);
|
|
||||||
|
// 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
|
// 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,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
|
// *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.
|
// Dictated by ESP32 datasheet
|
||||||
dev->fifo_conf.tx_data_num = 32;
|
dev->fifo_conf.rx_fifo_mod_force_en = 1;
|
||||||
// Enable DMA support
|
dev->fifo_conf.tx_fifo_mod_force_en = 1;
|
||||||
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
|
// 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
|
||||||
|
|
||||||
|
|
||||||
|
// Device Reset
|
||||||
dev->conf.tx_right_first = 0; //!!invert_clk; // should be false / 0
|
dev_reset(dev);
|
||||||
dev->conf.rx_right_first = 0; //!!invert_clk;
|
dev->conf1.val = 0;
|
||||||
|
dev->conf1.tx_stop_en = 0;
|
||||||
dev->timing.val = 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;
|
||||||
state->dmadesc_a = conf->lldesc_a;
|
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;
|
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,20 +397,20 @@ 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
|
|
@ -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:
|
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