diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index feca2dfdf..336acfbb5 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -86,6 +86,7 @@ jobs: name: Build with ESP-IDF ${{ matrix.idf_ver }} for ${{ matrix.idf_target }} runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: # The version names here correspond to the versions of espressif/idf Docker image. # See https://hub.docker.com/r/espressif/idf/tags and diff --git a/CMakeLists.txt b/CMakeLists.txt index 198200807..3a1de72e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,6 +82,9 @@ set(LIBRARY_SRCS libraries/BluetoothSerial/src/BTScanResultsSet.cpp libraries/DNSServer/src/DNSServer.cpp libraries/EEPROM/src/EEPROM.cpp + libraries/ESP_I2S/src/ESP_I2S.cpp + libraries/ESP_SR/src/ESP_SR.cpp + libraries/ESP_SR/src/esp32-hal-sr.c libraries/ESPmDNS/src/ESPmDNS.cpp libraries/Ethernet/src/ETH.cpp libraries/FFat/src/FFat.cpp @@ -91,7 +94,6 @@ set(LIBRARY_SRCS libraries/HTTPUpdate/src/HTTPUpdate.cpp libraries/LittleFS/src/LittleFS.cpp libraries/Insights/src/Insights.cpp - libraries/I2S/src/I2S.cpp libraries/NetBIOS/src/NetBIOS.cpp libraries/Preferences/src/Preferences.cpp libraries/RainMaker/src/RMaker.cpp @@ -179,6 +181,8 @@ set(includedirs libraries/BluetoothSerial/src libraries/DNSServer/src libraries/EEPROM/src + libraries/ESP_I2S/src + libraries/ESP_SR/src libraries/ESP32/src libraries/ESPmDNS/src libraries/Ethernet/src @@ -188,7 +192,6 @@ set(includedirs libraries/HTTPUpdate/src libraries/LittleFS/src libraries/Insights/src - libraries/I2S/src libraries/NetBIOS/src libraries/Preferences/src libraries/RainMaker/src diff --git a/libraries/ESP32/examples/I2S/HiFreq_ADC/.skip.esp32s3 b/libraries/ESP32/examples/I2S/HiFreq_ADC/.skip.esp32s3 deleted file mode 100644 index e69de29bb..000000000 diff --git a/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino b/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino deleted file mode 100644 index a1f5c3088..000000000 --- a/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino +++ /dev/null @@ -1,136 +0,0 @@ -/* - This example demonstrates I2S ADC capability to sample high frequency analog signals. - The PWM signal generated with ledc is only for ease of use when first trying out. - To sample the generated signal connect default pins 27(PWM) and 32(Sampling) together. - If you do not wish to generate PWM simply comment out the definition of constant GENERATE_PWM - Try to change the PWM_DUTY_PERCENT and see how to averaged value changes. - - The maximum for I2S ADC sampling frequency is 5MHz (value 5000000), however there will be many values repeated because the real - sampling frequency is much lower - - - By default this example will print values compatible with Arduino plotter - 1. signal - all values - 2. signal - averaged value - - You can change the number of sample over which is the signal averaged by changing value of AVERAGE_EVERY_N_SAMPLES - If you comment the definition altogether the averaging will not be performed nor printed. - - If you do not wish to print every value, simply comment definition of constant PRINT_ALL_VALUES - - Note: ESP prints messages at startup which will pollute Arduino IDE Serial plotter legend. - To avoid this pollution, start the plotter after startup (op restart) -*/ -#include - -// I2S -#define I2S_SAMPLE_RATE (277777) // Max sampling frequency = 277.777 kHz -#define ADC_INPUT (ADC1_CHANNEL_4) //pin 32 -#define I2S_DMA_BUF_LEN (1024) - -// PWM -#define GENERATE_PWM -#define OUTPUT_PIN (27) -#define PWM_FREQUENCY ((I2S_SAMPLE_RATE)/4) -#define PWM_DUTY_PERCENT (50) -#define PWM_RESOLUTION_BITS (2) // Lower bit resolution enables higher frequency -#define PWM_DUTY_VALUE ((((1<<(PWM_RESOLUTION_BITS)))*(PWM_DUTY_PERCENT))/100) // Duty value used for setup function based on resolution - -// Sample post processing -#define PRINT_ALL_VALUES -#define AVERAGE_EVERY_N_SAMPLES (100) - -void i2sInit(){ - i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN), - .sample_rate = I2S_SAMPLE_RATE, // The format of the signal using ADC_BUILT_IN - .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // is fixed at 12bit, stereo, MSB - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, - .communication_format = I2S_COMM_FORMAT_STAND_I2S, - .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, - .dma_buf_count = 8, - .dma_buf_len = I2S_DMA_BUF_LEN, - .use_apll = false, - .tx_desc_auto_clear = false, - .fixed_mclk = 0, - .mclk_multiple = I2S_MCLK_MULTIPLE_128, - .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT - }; - Serial.printf("Attempting to setup I2S ADC with sampling frequency %d Hz\n", I2S_SAMPLE_RATE); - if(ESP_OK != i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL)){ - Serial.printf("Error installing I2S. Halt!"); - while(1); - } - if(ESP_OK != i2s_set_adc_mode(ADC_UNIT_1, ADC_INPUT)){ - Serial.printf("Error setting up ADC. Halt!"); - while(1); - } - if(ESP_OK != adc1_config_channel_atten(ADC_INPUT, ADC_ATTEN_DB_11)){ - Serial.printf("Error setting up ADC attenuation. Halt!"); - while(1); - } - - if(ESP_OK != i2s_adc_enable(I2S_NUM_0)){ - Serial.printf("Error enabling ADC. Halt!"); - while(1); - } - Serial.printf("I2S ADC setup ok\n"); -} - -void setup() { - Serial.begin(115200); - -#ifdef GENERATE_PWM - // PWM setup - Serial.printf("Setting up PWM: frequency = %d; resolution bits %d; Duty cycle = %d; duty value = %d, Output pin = %d\n", PWM_FREQUENCY, PWM_RESOLUTION_BITS, PWM_DUTY_PERCENT, PWM_DUTY_VALUE, OUTPUT_PIN); - ledcAttach(OUTPUT_PIN, PWM_FREQUENCY, PWM_RESOLUTION_BITS); - uint32_t freq = ledcReadFreq(OUTPUT_PIN); - - if(freq != PWM_FREQUENCY){ - Serial.printf("Error setting up PWM. Halt!"); - while(1); - } - ledcWrite(OUTPUT_PIN, PWM_DUTY_VALUE); - Serial.printf("PWM setup ok\n"); -#endif - - // Initialize the I2S peripheral - i2sInit(); -} - -void loop(){ -// The 4 high bits are the channel, and the data is inverted - size_t bytes_read; - uint16_t buffer[I2S_DMA_BUF_LEN] = {0}; - -#ifdef AVERAGE_EVERY_N_SAMPLES - uint32_t read_counter = 0; - uint32_t averaged_reading = 0; - uint64_t read_sum = 0; -#endif - - while(1){ - i2s_read(I2S_NUM_0, &buffer, sizeof(buffer), &bytes_read, 15); - //Serial.printf("read %d Bytes\n", bytes_read); - - for(int i = 0; i < bytes_read/2; ++i){ -#ifdef PRINT_ALL_VALUES - //Serial.printf("[%d] = %d\n", i, buffer[i] & 0x0FFF); // Print with indexes - Serial.printf("Signal:%d ", buffer[i] & 0x0FFF); // Print compatible with Arduino Plotter -#endif -#ifdef AVERAGE_EVERY_N_SAMPLES - read_sum += buffer[i] & 0x0FFF; - ++read_counter; - if(read_counter == AVERAGE_EVERY_N_SAMPLES){ - averaged_reading = read_sum / AVERAGE_EVERY_N_SAMPLES; - //Serial.printf("averaged_reading = %d over %d samples\n", averaged_reading, read_counter); // Print with additional info - Serial.printf("Averaged_signal:%ld", averaged_reading); // Print compatible with Arduino Plotter - read_counter = 0; - read_sum = 0; - } -#endif -#if defined(PRINT_ALL_VALUES) || defined (AVERAGE_EVERY_N_SAMPLES) - Serial.printf("\n"); -#endif - } // for - } // while -} diff --git a/libraries/ESP_I2S/keywords.txt b/libraries/ESP_I2S/keywords.txt new file mode 100644 index 000000000..f30b7ad1d --- /dev/null +++ b/libraries/ESP_I2S/keywords.txt @@ -0,0 +1,21 @@ +####################################### +# Syntax Coloring Map For ESP_I2S +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +ESP_I2S KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +onEvent KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +SR_EVENT_WAKEWORD LITERAL1 diff --git a/libraries/ESP_I2S/library.properties b/libraries/ESP_I2S/library.properties new file mode 100755 index 000000000..d4e3e4090 --- /dev/null +++ b/libraries/ESP_I2S/library.properties @@ -0,0 +1,9 @@ +name=ESP_I2S +version=1.0.0 +author=me-no-dev +maintainer=me-no-dev +sentence=Library for ESP I2S communication +paragraph=Supports ESP32 Arduino platforms. +category=Sound +url=https://github.com/espressif/arduino-esp32/ +architectures=esp32 \ No newline at end of file diff --git a/libraries/ESP_I2S/src/ESP_I2S.cpp b/libraries/ESP_I2S/src/ESP_I2S.cpp new file mode 100644 index 000000000..112c85aac --- /dev/null +++ b/libraries/ESP_I2S/src/ESP_I2S.cpp @@ -0,0 +1,807 @@ +#include "ESP_I2S.h" + +#if SOC_I2S_SUPPORTED + +#include "esp32-hal-periman.h" +#include "wav_header.h" +#include "mp3dec.h" + +#define I2S_READ_CHUNK_SIZE 1920 + +#define I2S_DEFAULT_CFG() { \ + .id = I2S_NUM_AUTO, \ + .role = I2S_ROLE_MASTER, \ + .dma_desc_num = 6, \ + .dma_frame_num = 240, \ + .auto_clear = true, \ +} + +#define I2S_STD_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode) \ + { \ + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(_sample_rate), \ + .slot_cfg = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \ + .gpio_cfg = { \ + .mclk = (gpio_num_t)_mclk, \ + .bclk = (gpio_num_t)_bclk, \ + .ws = (gpio_num_t)_ws, \ + .dout = (gpio_num_t)_dout, \ + .din = (gpio_num_t)_din, \ + .invert_flags = { \ + .mclk_inv = _mclk_inv, \ + .bclk_inv = _bclk_inv, \ + .ws_inv = _ws_inv, \ + }, \ + }, \ + } + +#if SOC_I2S_SUPPORTS_TDM +#define I2S_TDM_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode, _mask) \ + { \ + .clk_cfg = I2S_TDM_CLK_DEFAULT_CONFIG(_sample_rate), \ + .slot_cfg = I2S_TDM_PHILIP_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode, _mask), \ + .gpio_cfg = { \ + .mclk = (gpio_num_t)_mclk, \ + .bclk = (gpio_num_t)_bclk, \ + .ws = (gpio_num_t)_ws, \ + .dout = (gpio_num_t)_dout, \ + .din = (gpio_num_t)_din, \ + .invert_flags = { \ + .mclk_inv = _mclk_inv, \ + .bclk_inv = _bclk_inv, \ + .ws_inv = _ws_inv, \ + }, \ + }, \ + } +#endif +#if SOC_I2S_SUPPORTS_PDM_TX +#if (SOC_I2S_PDM_MAX_TX_LINES > 1) + #define I2S_PDM_TX_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode) \ + { \ + .clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate), \ + .slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \ + .gpio_cfg = { \ + .clk = (gpio_num_t)_tx_clk, \ + .dout = (gpio_num_t)_tx_dout0, \ + .dout2 = (gpio_num_t)_tx_dout1, \ + .invert_flags = { \ + .clk_inv = _tx_clk_inv, \ + }, \ + }, \ + } +#else + #define I2S_PDM_TX_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode) \ + { \ + .clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate), \ + .slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \ + .gpio_cfg = { \ + .clk = (gpio_num_t)_tx_clk, \ + .dout = (gpio_num_t)_tx_dout0, \ + .invert_flags = { \ + .clk_inv = _tx_clk_inv, \ + }, \ + }, \ + } +#endif +#endif + +#if SOC_I2S_SUPPORTS_PDM_RX +#if (SOC_I2S_PDM_MAX_RX_LINES > 1) + #define I2S_PDM_RX_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode) \ + { \ + .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(_sample_rate), \ + .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \ + .gpio_cfg = { \ + .clk = (gpio_num_t)_rx_clk, \ + .dins = { \ + (gpio_num_t)_rx_din0, \ + (gpio_num_t)_rx_din1, \ + (gpio_num_t)_rx_din2, \ + (gpio_num_t)_rx_din3, \ + }, \ + .invert_flags = { \ + .clk_inv = _rx_clk_inv, \ + }, \ + }, \ + } +#else + #define I2S_PDM_RX_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode) \ + { \ + .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(_sample_rate), \ + .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \ + .gpio_cfg = { \ + .clk = (gpio_num_t)_rx_clk, \ + .din = (gpio_num_t)_rx_din0, \ + .invert_flags = { \ + .clk_inv = _rx_clk_inv, \ + }, \ + }, \ + } +#endif +#endif + +#define I2S_ERROR_CHECK_RETURN(x,r) do { last_error = (x); if(unlikely(last_error != ESP_OK)){ log_e("ERROR: %s", esp_err_to_name(last_error)); return (r); } } while(0) +#define I2S_ERROR_CHECK_RETURN_FALSE(x) I2S_ERROR_CHECK_RETURN(x,false) + +// Default read, no resmpling and temp buffer necessary +static esp_err_t i2s_channel_read_default(i2s_chan_handle_t handle, char * tmp_buf, void * dst, size_t len, size_t *bytes_read, uint32_t timeout_ms){ + return i2s_channel_read(handle, (char *)dst, len, bytes_read, timeout_ms); +} + +// Resample the 32bit SPH0645 microphone data into 16bit. SPH0645 is actually 18 bit, but this trick helps save some space +static esp_err_t i2s_channel_read_32_to_16(i2s_chan_handle_t handle, char * read_buff, void * dst, size_t len, size_t *bytes_read, uint32_t timeout_ms){ + size_t out_len = 0; + size_t read_buff_len = len * 2; + if(read_buff == NULL){ + log_e("Temp buffer is NULL!"); + return ESP_FAIL; + } + esp_err_t err = i2s_channel_read(handle, read_buff, read_buff_len, &out_len, timeout_ms); + if (err != ESP_OK){ + *bytes_read = 0; + return err; + } + out_len /= 4; + uint16_t * ds = (uint16_t*)dst; + uint32_t * src = (uint32_t*)read_buff; + for (size_t i = 0; i < out_len; i++){ + ds[i] = src[i] >> 16; + } + *bytes_read = out_len * 2; + return ESP_OK; +} + +// Resample the 16bit stereo microphone data into 16bit mono. +static esp_err_t i2s_channel_read_16_stereo_to_mono(i2s_chan_handle_t handle, char * read_buff, void * dst, size_t len, size_t *bytes_read, uint32_t timeout_ms){ + size_t out_len = 0; + size_t read_buff_len = len * 2; + if(read_buff == NULL){ + log_e("Temp buffer is NULL!"); + return ESP_FAIL; + } + esp_err_t err = i2s_channel_read(handle, read_buff, read_buff_len, &out_len, timeout_ms); + if (err != ESP_OK){ + *bytes_read = 0; + return err; + } + out_len /= 2; + uint16_t * ds = (uint16_t*)dst; + uint16_t * src = (uint16_t*)read_buff; + for (size_t i = 0; i < out_len; i+=2){ + *ds++ = src[i]; + } + *bytes_read = out_len; + return ESP_OK; +} + +I2SClass::I2SClass(){ + last_error = ESP_OK; + + tx_chan = NULL; + tx_sample_rate = 0; + tx_data_bit_width = I2S_DATA_BIT_WIDTH_16BIT; + tx_slot_mode = I2S_SLOT_MODE_STEREO; + + rx_fn = i2s_channel_read_default; + rx_transform = I2S_RX_TRANSFORM_NONE; + rx_transform_buf = NULL; + rx_transform_buf_len = 0; + + rx_chan = NULL; + rx_sample_rate = 0; + rx_data_bit_width = I2S_DATA_BIT_WIDTH_16BIT; + rx_slot_mode = I2S_SLOT_MODE_STEREO; + + _mclk = -1; + _bclk = -1; + _ws = -1; + _dout = -1; + _din = -1; + _mclk_inv = false; + _bclk_inv = false; + _ws_inv = false; + +#if SOC_I2S_SUPPORTS_PDM_TX + _tx_clk = -1; + _tx_dout0 = -1; + _tx_dout1 = -1; + _tx_clk_inv = false; +#endif +#if SOC_I2S_SUPPORTS_PDM_RX + _rx_clk = -1; + _rx_din0 = -1; + _rx_din1 = -1; + _rx_din2 = -1; + _rx_din3 = -1; + _rx_clk_inv = false; +#endif +} + +I2SClass::~I2SClass(){ + end(); +} + +bool I2SClass::i2sDetachBus(void * bus_pointer){ + I2SClass *bus = (I2SClass *) bus_pointer; + if(bus->tx_chan != NULL || bus->tx_chan != NULL){ + bus->end(); + } + return true; +} + +// Set pins for STD and TDM mode +void I2SClass::setPins(int8_t bclk, int8_t ws, int8_t dout, int8_t din, int8_t mclk){ + _mclk = mclk; + _bclk = bclk; + _ws = ws; + _dout = dout; + _din = din; +} + +void I2SClass::setInverted(bool bclk, bool ws, bool mclk){ + _mclk_inv = mclk; + _bclk_inv = bclk; + _ws_inv = ws; +} + +// Set pins for PDM TX mode +#if SOC_I2S_SUPPORTS_PDM_TX +void I2SClass::setPinsPdmTx(int8_t clk, int8_t dout0, int8_t dout1){ + _tx_clk = clk; + _tx_dout0 = dout0; +#if (SOC_I2S_PDM_MAX_TX_LINES > 1) + _tx_dout1 = dout1; +#endif +} +#endif + +// Set pins for PDM RX mode +#if SOC_I2S_SUPPORTS_PDM_RX +void I2SClass::setPinsPdmRx(int8_t clk, int8_t din0, int8_t din1, int8_t din2, int8_t din3){ + _rx_clk = clk; + _rx_din0 = din0; +#if (SOC_I2S_PDM_MAX_RX_LINES > 1) + _rx_din1 = din1; + _rx_din2 = din2; + _rx_din3 = din3; +#endif +} +#endif + +#if SOC_I2S_SUPPORTS_PDM_TX || SOC_I2S_SUPPORTS_PDM_RX +void I2SClass::setInvertedPdm(bool clk){ +#if SOC_I2S_SUPPORTS_PDM_TX + _tx_clk_inv = clk; +#endif +#if SOC_I2S_SUPPORTS_PDM_RX + _rx_clk_inv = clk; +#endif +} +#endif + +bool I2SClass::initSTD(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch){ + // Peripheral manager deinit previous peripheral if pin was used + if (_mclk >= 0) if (!perimanSetPinBus(_mclk, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_bclk >= 0) if (!perimanSetPinBus(_bclk, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_ws >= 0) if (!perimanSetPinBus(_ws, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_dout >= 0) if (!perimanSetPinBus(_dout, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_din >= 0) if (!perimanSetPinBus(_din, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + + // I2S configuration + i2s_chan_config_t chan_cfg = I2S_DEFAULT_CFG(); + if (_dout >= 0 && _din >= 0) { + I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, &rx_chan)); + } else if (_dout >= 0) { + I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, NULL)); + } else if (_din >= 0) { + I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, NULL, &rx_chan)); + } + + i2s_std_config_t i2s_config = I2S_STD_CHAN_CFG(rate, bits_cfg, ch); + if (tx_chan != NULL) { + tx_sample_rate = rate; + tx_data_bit_width = bits_cfg; + tx_slot_mode = ch; + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_std_mode(tx_chan, &i2s_config)); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(tx_chan)); + } + if (rx_chan != NULL) { + rx_sample_rate = rate; + rx_data_bit_width = bits_cfg; + rx_slot_mode = ch; + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_std_mode(rx_chan, &i2s_config)); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan)); + } + + // Peripheral manager set bus type to I2S + if (_mclk >= 0) if (!perimanSetPinBus(_mclk, ESP32_BUS_TYPE_I2S_STD, (void *)(this))){ goto err; } + if (_bclk >= 0) if (!perimanSetPinBus(_bclk, ESP32_BUS_TYPE_I2S_STD, (void *)(this))){ goto err; } + if (_ws >= 0) if (!perimanSetPinBus(_ws, ESP32_BUS_TYPE_I2S_STD, (void *)(this))){ goto err; } + if (_dout >= 0) if (!perimanSetPinBus(_dout, ESP32_BUS_TYPE_I2S_STD, (void *)(this))){ goto err; } + if (_din >= 0) if (!perimanSetPinBus(_din, ESP32_BUS_TYPE_I2S_STD, (void *)(this))){ goto err; } + + return true; +err: + log_e("Failed to set all pins bus to I2S_STD"); + I2SClass::i2sDetachBus((void *)(this)); + return false; +} + +#if SOC_I2S_SUPPORTS_TDM +bool I2SClass::initTDM(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask){ + // Peripheral manager deinit previous peripheral if pin was used + if (_mclk >= 0) if (!perimanSetPinBus(_mclk, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_bclk >= 0) if (!perimanSetPinBus(_bclk, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_ws >= 0) if (!perimanSetPinBus(_ws, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_dout >= 0) if (!perimanSetPinBus(_dout, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_din >= 0) if (!perimanSetPinBus(_din, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + + // I2S configuration + i2s_chan_config_t chan_cfg = I2S_DEFAULT_CFG(); + if (_dout >= 0 && _din >= 0) { + I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, &rx_chan)); + } else if (_dout >= 0) { + I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, NULL)); + } else if (_din >= 0) { + I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, NULL, &rx_chan)); + } + + i2s_tdm_config_t i2s_tdm_config = I2S_TDM_CHAN_CFG(rate, bits_cfg, ch, (i2s_tdm_slot_mask_t)slot_mask); + if (tx_chan != NULL) { + tx_sample_rate = rate; + tx_data_bit_width = bits_cfg; + tx_slot_mode = ch; + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_tdm_mode(tx_chan, &i2s_tdm_config)); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(tx_chan)); + } + if (rx_chan != NULL) { + rx_sample_rate = rate; + rx_data_bit_width = bits_cfg; + rx_slot_mode = ch; + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_tdm_mode(rx_chan, &i2s_tdm_config)); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan)); + } + + // Peripheral manager set bus type to I2S + if (_mclk >= 0) if (!perimanSetPinBus(_mclk, ESP32_BUS_TYPE_I2S_TDM, (void *)(this))){ goto err; } + if (_bclk >= 0) if (!perimanSetPinBus(_bclk, ESP32_BUS_TYPE_I2S_TDM, (void *)(this))){ goto err; } + if (_ws >= 0) if (!perimanSetPinBus(_ws, ESP32_BUS_TYPE_I2S_TDM, (void *)(this))){ goto err; } + if (_dout >= 0) if (!perimanSetPinBus(_dout, ESP32_BUS_TYPE_I2S_TDM, (void *)(this))){ goto err; } + if (_din >= 0) if (!perimanSetPinBus(_din, ESP32_BUS_TYPE_I2S_TDM, (void *)(this))){ goto err; } + + return true; +err: + log_e("Failed to set all pins bus to I2S_TDM"); + I2SClass::i2sDetachBus((void *)(this)); + return false; +} +#endif + +#if SOC_I2S_SUPPORTS_PDM_TX +bool I2SClass::initPDMtx(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch){ + // Peripheral manager deinit previous peripheral if pin was used + if (_tx_clk >= 0) if (!perimanSetPinBus(_tx_clk, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_tx_dout0 >= 0) if (!perimanSetPinBus(_tx_dout0, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_tx_dout1 >= 0) if (!perimanSetPinBus(_tx_dout1, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + + // I2S configuration + i2s_chan_config_t chan_cfg = I2S_DEFAULT_CFG(); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, NULL)); + + i2s_pdm_tx_config_t i2s_pdm_tx_config = I2S_PDM_TX_CHAN_CFG(rate, bits_cfg, ch); + if (tx_chan != NULL) { + tx_sample_rate = rate; + tx_data_bit_width = bits_cfg; + tx_slot_mode = ch; + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_pdm_tx_mode(tx_chan, &i2s_pdm_tx_config)); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(tx_chan)); + } + + // Peripheral manager set bus type to I2S + if (_tx_clk >= 0) if (!perimanSetPinBus(_tx_clk, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; } + if (_tx_dout0 >= 0) if (!perimanSetPinBus(_tx_dout0, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; } + if (_tx_dout1 >= 0) if (!perimanSetPinBus(_tx_dout1, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; } + + return true; +err: + log_e("Failed to set all pins bus to I2S_TDM"); + I2SClass::i2sDetachBus((void *)(this)); + return false; +} +#endif + +#if SOC_I2S_SUPPORTS_PDM_RX +bool I2SClass::initPDMrx(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch){ + // Peripheral manager deinit previous peripheral if pin was used + if (_rx_clk >= 0) if (!perimanSetPinBus(_rx_clk, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_rx_din0 >= 0) if (!perimanSetPinBus(_rx_din0, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_rx_din1 >= 0) if (!perimanSetPinBus(_rx_din1, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_rx_din2 >= 0) if (!perimanSetPinBus(_rx_din2, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_rx_din3 >= 0) if (!perimanSetPinBus(_rx_din3, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + + // I2S configuration + i2s_chan_config_t chan_cfg = I2S_DEFAULT_CFG(); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, NULL, &rx_chan)); + + i2s_pdm_rx_config_t i2s_pdf_rx_config = I2S_PDM_RX_CHAN_CFG(rate, bits_cfg, ch); + if (rx_chan != NULL) { + rx_sample_rate = rate; + rx_data_bit_width = bits_cfg; + rx_slot_mode = ch; + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_pdm_rx_mode(rx_chan, &i2s_pdf_rx_config)); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan)); + } + + // Peripheral manager set bus type to I2S + if (_rx_clk >= 0) if (!perimanSetPinBus(_rx_clk, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; } + if (_rx_din0 >= 0) if (!perimanSetPinBus(_rx_din0, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; } + if (_rx_din1 >= 0) if (!perimanSetPinBus(_rx_din1, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; } + if (_rx_din2 >= 0) if (!perimanSetPinBus(_rx_din2, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; } + if (_rx_din3 >= 0) if (!perimanSetPinBus(_rx_din3, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; } + + return true; +err: + log_e("Failed to set all pins bus to I2S_TDM"); + I2SClass::i2sDetachBus((void *)(this)); + return false; +} +#endif + +bool I2SClass::begin(i2s_mode_t mode, uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch +#if SOC_I2S_SUPPORTS_TDM + , int8_t slot_mask +#endif +){ + /* Setup I2S peripheral */ + if (mode >= I2S_MODE_MAX){ + log_e("Invalid I2S mode selected."); + return false; + } + _mode = mode; + + bool init = false; + switch (_mode){ + case I2S_MODE_STD: + init = initSTD(rate, bits_cfg, ch); + break; +#if SOC_I2S_SUPPORTS_TDM + case I2S_MODE_TDM: + init = initTDM(rate, bits_cfg, ch, slot_mask); + break; +#endif +#if SOC_I2S_SUPPORTS_PDM_TX + case I2S_MODE_PDM_TX: + init = initPDMtx(rate, bits_cfg, ch); + break; +#endif +#if SOC_I2S_SUPPORTS_PDM_RX + case I2S_MODE_PDM_RX: + init = initPDMrx(rate, bits_cfg, ch); + break; +#endif + default: + break; + } + + if (init == false){ + log_e("I2S initialization failed."); + return false; + } + return true; +} + +bool I2SClass::end(){ + if (tx_chan != NULL){ + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(tx_chan)); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_del_channel(tx_chan)); + tx_chan = NULL; + } + if (rx_chan != NULL){ + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(rx_chan)); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_del_channel(rx_chan)); + rx_chan = NULL; + } + if(rx_transform_buf != NULL){ + free(rx_transform_buf); + rx_transform_buf = NULL; + rx_transform_buf_len = 0; + } + + //Peripheral manager deinit used pins + switch (_mode){ + case I2S_MODE_STD: +#if SOC_I2S_SUPPORTS_TDM + case I2S_MODE_TDM: +#endif + perimanSetPinBus(_mclk, ESP32_BUS_TYPE_INIT, NULL); + perimanSetPinBus(_bclk, ESP32_BUS_TYPE_INIT, NULL); + perimanSetPinBus(_ws, ESP32_BUS_TYPE_INIT, NULL); + if (_dout >= 0) perimanSetPinBus(_dout, ESP32_BUS_TYPE_INIT, NULL); + if (_din >= 0) perimanSetPinBus(_din, ESP32_BUS_TYPE_INIT, NULL); + break; +#if SOC_I2S_SUPPORTS_PDM_TX + case I2S_MODE_PDM_TX: + perimanSetPinBus(_tx_clk, ESP32_BUS_TYPE_INIT, NULL); + if (_tx_dout0 >= 0) perimanSetPinBus(_tx_dout0, ESP32_BUS_TYPE_INIT, NULL); + if (_tx_dout1 >= 0) perimanSetPinBus(_tx_dout1, ESP32_BUS_TYPE_INIT, NULL); + break; +#endif +#if SOC_I2S_SUPPORTS_PDM_RX + case I2S_MODE_PDM_RX: + perimanSetPinBus(_rx_clk, ESP32_BUS_TYPE_INIT, NULL); + if (_rx_din0 >= 0) perimanSetPinBus(_rx_din0, ESP32_BUS_TYPE_INIT, NULL); + if (_rx_din1 >= 0) perimanSetPinBus(_rx_din1, ESP32_BUS_TYPE_INIT, NULL); + if (_rx_din2 >= 0) perimanSetPinBus(_rx_din2, ESP32_BUS_TYPE_INIT, NULL); + if (_rx_din3 >= 0) perimanSetPinBus(_rx_din3, ESP32_BUS_TYPE_INIT, NULL); + break; +#endif + default: + break; + } + return true; +} + +bool I2SClass::configureTX(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch){ + /* Setup I2S channels */ + if (tx_chan != NULL) { + if(tx_sample_rate == rate && tx_data_bit_width == bits_cfg && tx_slot_mode == ch){ + return true; + } + i2s_std_config_t i2s_config = I2S_STD_CHAN_CFG(rate, bits_cfg, ch); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(tx_chan)); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_clock(tx_chan, &i2s_config.clk_cfg)); + tx_sample_rate = rate; + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_slot(tx_chan, &i2s_config.slot_cfg)); + tx_data_bit_width = bits_cfg; + tx_slot_mode = ch; + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(tx_chan)); + return true; + } + return false; +} + +bool I2SClass::configureRX(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, i2s_rx_transform_t transform){ + /* Setup I2S channels */ + if (rx_chan != NULL) { + if(rx_sample_rate != rate || rx_data_bit_width != bits_cfg || rx_slot_mode != ch){ + i2s_std_config_t i2s_config = I2S_STD_CHAN_CFG(rate, bits_cfg, ch); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(rx_chan)); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_clock(rx_chan, &i2s_config.clk_cfg)); + rx_sample_rate = rate; + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_slot(rx_chan, &i2s_config.slot_cfg)); + rx_data_bit_width = bits_cfg; + rx_slot_mode = ch; + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan)); + return transformRX(transform); + } + if(rx_transform != transform){ + return transformRX(transform); + } + return true; + } + return false; +} + +size_t I2SClass::readBytes(char *buffer, size_t size){ + size_t bytes_read = 0; + size_t total_size = 0; + last_error = ESP_FAIL; + if(rx_chan == NULL){ + return total_size; + } + while (total_size < size) { + bytes_read = size - total_size; + if(rx_transform_buf != NULL && bytes_read > I2S_READ_CHUNK_SIZE){ bytes_read = I2S_READ_CHUNK_SIZE; } + I2S_ERROR_CHECK_RETURN(rx_fn(rx_chan, rx_transform_buf, (char*)(buffer + total_size), bytes_read, &bytes_read, _timeout), 0); + total_size += bytes_read; + } + return total_size; +} + +size_t I2SClass::write(uint8_t *buffer, size_t size){ + size_t written = 0; + size_t bytes_sent = 0; + last_error = ESP_FAIL; + if(tx_chan == NULL){ + return written; + } + while(written < size){ + bytes_sent = size - written; + esp_err_t err = i2s_channel_write(tx_chan, (char*)(buffer + written), bytes_sent, &bytes_sent, _timeout); + setWriteError(err); + I2S_ERROR_CHECK_RETURN(err, written); + written += bytes_sent; + } + return written; +} + +i2s_chan_handle_t I2SClass::txChan(){ return tx_chan; } +uint32_t I2SClass::txSampleRate(){ return tx_sample_rate; } +i2s_data_bit_width_t I2SClass::txDataWidth(){ return tx_data_bit_width; } +i2s_slot_mode_t I2SClass::txSlotMode(){ return tx_slot_mode; } + +i2s_chan_handle_t I2SClass::rxChan(){ return rx_chan; } +uint32_t I2SClass::rxSampleRate(){ return rx_sample_rate; } +i2s_data_bit_width_t I2SClass::rxDataWidth(){ return rx_data_bit_width; } +i2s_slot_mode_t I2SClass::rxSlotMode(){ return rx_slot_mode; } + +int I2SClass::lastError(){ + return (int)last_error; +} + +int I2SClass::available(){ + if(rx_chan == NULL){ + return -1; + } + return I2S_READ_CHUNK_SIZE;// / (rx_data_bit_width/8); +}; + +int I2SClass::peek(){ + return -1; +}; + +int I2SClass::read(){ + int out = 0; + if(readBytes((char *)&out, rx_data_bit_width/8) == (rx_data_bit_width/8)){ + return out; + } + return -1; +}; + +size_t I2SClass::write(uint8_t d){ + return write(&d, 1); +} + +bool I2SClass::transformRX(i2s_rx_transform_t transform){ + switch(transform){ + case I2S_RX_TRANSFORM_NONE: + allocTranformRX(0); + rx_fn = i2s_channel_read_default; + break; + + case I2S_RX_TRANSFORM_32_TO_16: + if(rx_data_bit_width != I2S_DATA_BIT_WIDTH_32BIT){ + log_e("Wrong data width. Should be 32bit"); + return false; + } + if(!allocTranformRX(I2S_READ_CHUNK_SIZE * 2)){ + return false; + } + rx_fn = i2s_channel_read_32_to_16; + rx_data_bit_width = I2S_DATA_BIT_WIDTH_16BIT; + break; + + case I2S_RX_TRANSFORM_16_STEREO_TO_MONO: + if(rx_slot_mode != I2S_SLOT_MODE_STEREO){ + log_e("Wrong slot mode. Should be Stereo"); + return false; + } + if(!allocTranformRX(I2S_READ_CHUNK_SIZE * 2)){ + return false; + } + rx_fn = i2s_channel_read_16_stereo_to_mono; + rx_slot_mode = I2S_SLOT_MODE_MONO; + break; + + default: + log_e("Unknown RX Transform %d", transform); + return false; + } + rx_transform = transform; + return true; +} + +bool I2SClass::allocTranformRX(size_t buf_len){ + char* buf = NULL; + if(buf_len == 0){ + if(rx_transform_buf != NULL){ + free(rx_transform_buf); + rx_transform_buf = NULL; + rx_transform_buf_len = 0; + } + return true; + } + if(rx_transform_buf == NULL || rx_transform_buf_len != buf_len){ + buf = (char*)malloc(buf_len); + if(buf == NULL){ + log_e("malloc %u failed!", buf_len); + return false; + } + if(rx_transform_buf != NULL){ + free(rx_transform_buf); + } + rx_transform_buf = buf; + rx_transform_buf_len = buf_len; + } + return true; +} + +const int WAVE_HEADER_SIZE = PCM_WAV_HEADER_SIZE; + +//Record PCM WAV with current RX settings +uint8_t * I2SClass::recordWAV(size_t rec_seconds, size_t * out_size){ + uint32_t sample_rate = rxSampleRate(); + uint16_t sample_width = (uint16_t)rxDataWidth(); + uint16_t num_channels = (uint16_t)rxSlotMode(); + size_t rec_size = rec_seconds * ((sample_rate * (sample_width / 8)) * num_channels); + const pcm_wav_header_t wav_header = PCM_WAV_HEADER_DEFAULT(rec_size, sample_width, sample_rate, num_channels); + *out_size = 0; + + log_d("Record WAV: rate:%lu, bits:%u, channels:%u, size:%lu", sample_rate, sample_width, num_channels, rec_size); + + uint8_t * wav_buf = (uint8_t*)malloc(rec_size + WAVE_HEADER_SIZE); + if(wav_buf == NULL){ + log_e("Failed to allocate WAV buffer with size %u", rec_size + WAVE_HEADER_SIZE); + return NULL; + } + memcpy(wav_buf, &wav_header, WAVE_HEADER_SIZE); + size_t wav_size = readBytes((char*)(wav_buf + WAVE_HEADER_SIZE), rec_size); + if(wav_size < rec_size){ + log_e("Recorded %u bytes from %u", wav_size, rec_size); + } else if(lastError()){ + log_e("Read Failed! %d", lastError()); + } else { + *out_size = rec_size + WAVE_HEADER_SIZE; + return wav_buf; + } + free(wav_buf); + return NULL; +} + +void I2SClass::playWAV(uint8_t * data, size_t len){ + pcm_wav_header_t * header = (pcm_wav_header_t*)data; + if(header->fmt_chunk.audio_format != 1){ + log_e("Audio format is not PCM!"); + return; + } + wav_data_chunk_t * data_chunk = &header->data_chunk; + size_t data_offset = 0; + while(memcmp(data_chunk->subchunk_id, "data", 4) != 0){ + log_d("Skip chunk: %c%c%c%c, len: %lu", data_chunk->subchunk_id[0], data_chunk->subchunk_id[1], data_chunk->subchunk_id[2], data_chunk->subchunk_id[3], data_chunk->subchunk_size + 8); + data_offset += data_chunk->subchunk_size + 8; + data_chunk = (wav_data_chunk_t *)(data + WAVE_HEADER_SIZE + data_offset - 8); + } + log_d("Play WAV: rate:%lu, bits:%d, channels:%d, size:%lu", header->fmt_chunk.sample_rate, header->fmt_chunk.bits_per_sample, header->fmt_chunk.num_of_channels, data_chunk->subchunk_size); + configureTX(header->fmt_chunk.sample_rate, (i2s_data_bit_width_t)header->fmt_chunk.bits_per_sample, (i2s_slot_mode_t)header->fmt_chunk.num_of_channels); + write(data + WAVE_HEADER_SIZE + data_offset, data_chunk->subchunk_size); +} + +bool I2SClass::playMP3(uint8_t *src, size_t src_len) +{ + int16_t outBuf[MAX_NCHAN * MAX_NGRAN * MAX_NSAMP]; + uint8_t *readPtr=NULL; + int bytesAvailable=0, err=0, offset=0; + MP3FrameInfo frameInfo; + HMP3Decoder decoder=NULL; + + bytesAvailable = src_len; + readPtr = src; + + decoder = MP3InitDecoder(); + if (decoder == NULL){ + log_e("Could not allocate decoder"); + return false; + } + + do { + offset = MP3FindSyncWord(readPtr, bytesAvailable); + if (offset < 0) { + break; + } + readPtr += offset; + bytesAvailable -= offset; + err = MP3Decode(decoder, &readPtr, &bytesAvailable, outBuf, 0); + if (err) { + log_e("Decode ERROR: %d", err); + MP3FreeDecoder(decoder); + return false; + } else { + MP3GetLastFrameInfo(decoder, &frameInfo); + configureTX(frameInfo.samprate, (i2s_data_bit_width_t)frameInfo.bitsPerSample, (i2s_slot_mode_t)frameInfo.nChans); + write((uint8_t*)outBuf, (size_t)((frameInfo.bitsPerSample / 8) * frameInfo.outputSamps)); + } + } while (true); + MP3FreeDecoder(decoder); + return true; +} + +#endif /* SOC_I2S_SUPPORTED */ diff --git a/libraries/ESP_I2S/src/ESP_I2S.h b/libraries/ESP_I2S/src/ESP_I2S.h new file mode 100644 index 000000000..f291b9ade --- /dev/null +++ b/libraries/ESP_I2S/src/ESP_I2S.h @@ -0,0 +1,145 @@ +#pragma once + +#include "soc/soc_caps.h" +#if SOC_I2S_SUPPORTED + +#include "Arduino.h" +#include "esp_err.h" +#include "driver/i2s_std.h" +#if SOC_I2S_SUPPORTS_TDM +#include "driver/i2s_tdm.h" +#endif +#if SOC_I2S_SUPPORTS_PDM_TX || SOC_I2S_SUPPORTS_PDM_RX +#include "driver/i2s_pdm.h" +#endif + +typedef esp_err_t (*i2s_channel_read_fn)(i2s_chan_handle_t handle, char * tmp_buf, void *dest, size_t size, size_t *bytes_read, uint32_t timeout_ms); + +typedef enum { + I2S_MODE_STD, +#if SOC_I2S_SUPPORTS_TDM + I2S_MODE_TDM, +#endif +#if SOC_I2S_SUPPORTS_PDM_TX + I2S_MODE_PDM_TX, +#endif +#if SOC_I2S_SUPPORTS_PDM_RX + I2S_MODE_PDM_RX, +#endif + I2S_MODE_MAX +} i2s_mode_t; + +typedef enum { + I2S_RX_TRANSFORM_NONE, + I2S_RX_TRANSFORM_32_TO_16, + I2S_RX_TRANSFORM_16_STEREO_TO_MONO, + I2S_RX_TRANSFORM_MAX +} i2s_rx_transform_t; + +class I2SClass: public Stream { + public: + I2SClass(); + ~I2SClass(); + + //STD + TDM mode + void setPins(int8_t bclk, int8_t ws, int8_t dout, int8_t din=-1, int8_t mclk=-1); + void setInverted(bool bclk, bool ws, bool mclk=false); + + //PDM TX + PDM RX mode +#if SOC_I2S_SUPPORTS_PDM_TX + void setPinsPdmTx(int8_t clk, int8_t dout0, int8_t dout1=-1); +#endif +#if SOC_I2S_SUPPORTS_PDM_RX + void setPinsPdmRx(int8_t clk, int8_t din0, int8_t din1=-1, int8_t din2=-1, int8_t din3=-1); +#endif +#if SOC_I2S_SUPPORTS_PDM_TX || SOC_I2S_SUPPORTS_PDM_RX + void setInvertedPdm(bool clk); +#endif + + bool begin(i2s_mode_t mode, uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch +#if SOC_I2S_SUPPORTS_TDM + , int8_t slot_mask=-1 +#endif + ); + bool configureTX(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch); + bool configureRX(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, i2s_rx_transform_t transform=I2S_RX_TRANSFORM_NONE); + bool end(); + + size_t readBytes(char *buffer, size_t size); + size_t write(uint8_t *buffer, size_t size); + + i2s_chan_handle_t txChan(); + uint32_t txSampleRate(); + i2s_data_bit_width_t txDataWidth(); + i2s_slot_mode_t txSlotMode(); + + i2s_chan_handle_t rxChan(); + uint32_t rxSampleRate(); + i2s_data_bit_width_t rxDataWidth(); + i2s_slot_mode_t rxSlotMode(); + + int lastError(); + + int available(); + int peek(); + int read(); + size_t write(uint8_t d); + + // Record short PCM WAV to memory with current RX settings. Returns buffer that must be freed by the user. + uint8_t * recordWAV(size_t rec_seconds, size_t * out_size); + // Play short PCM WAV from memory + void playWAV(uint8_t * data, size_t len); + // Play short MP3 from memory + bool playMP3(uint8_t *src, size_t src_len); + + + private: + esp_err_t last_error; + i2s_mode_t _mode; + + i2s_chan_handle_t tx_chan; + uint32_t tx_sample_rate; + i2s_data_bit_width_t tx_data_bit_width; + i2s_slot_mode_t tx_slot_mode; + + i2s_channel_read_fn rx_fn; + i2s_rx_transform_t rx_transform; + char * rx_transform_buf; + size_t rx_transform_buf_len; + + i2s_chan_handle_t rx_chan; + uint32_t rx_sample_rate; + i2s_data_bit_width_t rx_data_bit_width; + i2s_slot_mode_t rx_slot_mode; + + //STD and TDM mode + int8_t _mclk, _bclk, _ws, _dout, _din; + bool _mclk_inv, _bclk_inv, _ws_inv; + + //PDM mode +#if SOC_I2S_SUPPORTS_PDM_RX + int8_t _rx_clk, _rx_din0, _rx_din1, _rx_din2, _rx_din3; //TODO: soc_caps.h 1/4 + bool _rx_clk_inv; +#endif +#if SOC_I2S_SUPPORTS_PDM_TX + int8_t _tx_clk, _tx_dout0, _tx_dout1; + bool _tx_clk_inv; +#endif + + bool allocTranformRX(size_t buf_len); + bool transformRX(i2s_rx_transform_t transform); + + static bool i2sDetachBus(void * bus_pointer); + bool initSTD(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch); +#if SOC_I2S_SUPPORTS_TDM + bool initTDM(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask); +#endif +#if SOC_I2S_SUPPORTS_PDM_TX + bool initPDMtx(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch); +#endif +#if SOC_I2S_SUPPORTS_PDM_RX + bool initPDMrx(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch); +#endif +}; + +#endif /* SOC_I2S_SUPPORTED */ \ No newline at end of file diff --git a/libraries/ESP_I2S/src/wav_header.h b/libraries/ESP_I2S/src/wav_header.h new file mode 100644 index 000000000..3969cac40 --- /dev/null +++ b/libraries/ESP_I2S/src/wav_header.h @@ -0,0 +1,91 @@ +#pragma once +#include + +/** + * @brief Header structure for WAV file with only one data chunk + * + * @note See this for reference: http://soundfile.sapp.org/doc/WaveFormat/ + * + * @note Assignment to variables in this struct directly is only possible for little endian architectures + * (including Xtensa & RISC-V) + */ + +typedef struct { + char chunk_id[4]; /*!< Contains the letters "RIFF" in ASCII form */ + uint32_t chunk_size; /*!< This is the size of the rest of the chunk following this number */ + char chunk_format[4]; /*!< Contains the letters "WAVE" */ +} __attribute__((packed)) wav_descriptor_chunk_t; /*!< Canonical WAVE format starts with the RIFF header */ + +typedef struct { + char subchunk_id[4]; /*!< Contains the letters "fmt " */ + uint32_t subchunk_size; /*!< PCM = 16, This is the size of the rest of the Subchunk which follows this number */ + uint16_t audio_format; /*!< PCM = 1, values other than 1 indicate some form of compression */ + uint16_t num_of_channels; /*!< Mono = 1, Stereo = 2, etc. */ + uint32_t sample_rate; /*!< 8000, 44100, etc. */ + uint32_t byte_rate; /*!< ==SampleRate * NumChannels * BitsPerSample s/ 8 */ + uint16_t block_align; /*!< ==NumChannels * BitsPerSample / 8 */ + uint16_t bits_per_sample; /*!< 8 bits = 8, 16 bits = 16, etc. */ +} __attribute__((packed)) pcm_wav_fmt_chunk_t; /*!< The "fmt " subchunk describes the sound data's format */ + +typedef struct { + char subchunk_id[4]; /*!< Contains the letters "fmt " */ + uint32_t subchunk_size; /*!< ALAW/MULAW = 18, This is the size of the rest of the Subchunk which follows this number */ + uint16_t audio_format; /*!< ALAW = 6, MULAW = 7, values other than 1 indicate some form of compression */ + uint16_t num_of_channels; /*!< ALAW/MULAW = 1, Mono = 1, Stereo = 2, etc. */ + uint32_t sample_rate; /*!< ALAW/MULAW = 8000, 8000, 44100, etc. */ + uint32_t byte_rate; /*!< ALAW/MULAW = 8000, ==SampleRate * NumChannels * BitsPerSample s/ 8 */ + uint16_t block_align; /*!< ALAW/MULAW = 1, ==NumChannels * BitsPerSample / 8 */ + uint16_t bits_per_sample; /*!< ALAW/MULAW = 8, 8 bits = 8, 16 bits = 16, etc. */ + uint16_t ext_size; /*!< ALAW/MULAW = 0, Size of the extension (0 or 22) */ +} __attribute__((packed)) non_pcm_wav_fmt_chunk_t; /*!< The "fmt " subchunk describes the sound data's format */ + +typedef struct { + char subchunk_id[4]; /*!< Contains the letters "data" */ + uint32_t subchunk_size; /*!< ==NumSamples * NumChannels * BitsPerSample / 8 */ +} __attribute__((packed)) wav_data_chunk_t; /*!< The "data" subchunk contains the size of the data and the actual sound */ + +typedef struct { + wav_descriptor_chunk_t descriptor_chunk; /*!< Canonical WAVE format starts with the RIFF header */ + pcm_wav_fmt_chunk_t fmt_chunk; /*!< The "fmt " subchunk describes the sound data's format */ + wav_data_chunk_t data_chunk; /*!< The "data" subchunk contains the size of the data and the actual sound */ +} __attribute__((packed)) pcm_wav_header_t; + +typedef struct { + wav_descriptor_chunk_t descriptor_chunk; /*!< Canonical WAVE format starts with the RIFF header */ + non_pcm_wav_fmt_chunk_t fmt_chunk; /*!< The "fmt " subchunk describes the sound data's format */ + wav_data_chunk_t data_chunk; /*!< The "data" subchunk contains the size of the data and the actual sound */ +} __attribute__((packed)) non_pcm_wav_header_t; + +#define WAVE_FORMAT_PCM 1 // PCM +#define WAVE_FORMAT_IEEE_FLOAT 3 // IEEE float +#define WAVE_FORMAT_ALAW 6 // 8-bit ITU-T G.711 A-law +#define WAVE_FORMAT_MULAW 7 // 8-bit ITU-T G.711 ยต-law + +#define PCM_WAV_HEADER_SIZE 44 +#define NON_PCM_WAV_HEADER_SIZE 46 + +/** + * @brief Default header for PCM format WAV files + * + */ +#define PCM_WAV_HEADER_DEFAULT(wav_sample_size, wav_sample_bits, wav_sample_rate, wav_channel_num) { \ + .descriptor_chunk = { \ + .chunk_id = {'R', 'I', 'F', 'F'}, \ + .chunk_size = (wav_sample_size) + sizeof(pcm_wav_header_t) - 8, \ + .chunk_format = {'W', 'A', 'V', 'E'} \ + }, \ + .fmt_chunk = { \ + .subchunk_id = {'f', 'm', 't', ' '}, \ + .subchunk_size = 16, /* 16 for PCM */ \ + .audio_format = WAVE_FORMAT_PCM, /* 1 for PCM */ \ + .num_of_channels = (uint16_t)(wav_channel_num), \ + .sample_rate = (wav_sample_rate), \ + .byte_rate = (wav_sample_bits) * (wav_sample_rate) * (wav_channel_num) / 8, \ + .block_align = (uint16_t)((wav_sample_bits) * (wav_channel_num) / 8), \ + .bits_per_sample = (uint16_t)(wav_sample_bits)\ + }, \ + .data_chunk = { \ + .subchunk_id = {'d', 'a', 't', 'a'}, \ + .subchunk_size = (wav_sample_size) \ + } \ +} diff --git a/libraries/ESP32/examples/I2S/HiFreq_ADC/.skip.esp32c3 b/libraries/ESP_SR/examples/Basic/.skip.esp32 similarity index 100% rename from libraries/ESP32/examples/I2S/HiFreq_ADC/.skip.esp32c3 rename to libraries/ESP_SR/examples/Basic/.skip.esp32 diff --git a/libraries/I2S/examples/ADCPlotter/.skip.esp32c3 b/libraries/ESP_SR/examples/Basic/.skip.esp32c3 similarity index 100% rename from libraries/I2S/examples/ADCPlotter/.skip.esp32c3 rename to libraries/ESP_SR/examples/Basic/.skip.esp32c3 diff --git a/libraries/ESP32/examples/I2S/HiFreq_ADC/.skip.esp32c6 b/libraries/ESP_SR/examples/Basic/.skip.esp32c6 similarity index 100% rename from libraries/ESP32/examples/I2S/HiFreq_ADC/.skip.esp32c6 rename to libraries/ESP_SR/examples/Basic/.skip.esp32c6 diff --git a/libraries/ESP32/examples/I2S/HiFreq_ADC/.skip.esp32h2 b/libraries/ESP_SR/examples/Basic/.skip.esp32h2 similarity index 100% rename from libraries/ESP32/examples/I2S/HiFreq_ADC/.skip.esp32h2 rename to libraries/ESP_SR/examples/Basic/.skip.esp32h2 diff --git a/libraries/ESP32/examples/I2S/HiFreq_ADC/.skip.esp32s2 b/libraries/ESP_SR/examples/Basic/.skip.esp32s2 similarity index 100% rename from libraries/ESP32/examples/I2S/HiFreq_ADC/.skip.esp32s2 rename to libraries/ESP_SR/examples/Basic/.skip.esp32s2 diff --git a/libraries/ESP_SR/examples/Basic/Basic.ino b/libraries/ESP_SR/examples/Basic/Basic.ino new file mode 100644 index 000000000..c5e7641e7 --- /dev/null +++ b/libraries/ESP_SR/examples/Basic/Basic.ino @@ -0,0 +1,92 @@ + +#include "ESP_I2S.h" +#include "ESP_SR.h" + +#define I2S_PIN_BCK 17 +#define I2S_PIN_WS 47 +#define I2S_PIN_DIN 16 + +#define LIGHT_PIN 40 +#define FAN_PIN 41 + +I2SClass i2s; + +// Generated using the following command: +// python3 tools/gen_sr_commands.py "Turn on the light,Switch on the light;Turn off the light,Switch off the light,Go dark;Start fan;Stop fan" +enum { + SR_CMD_TURN_ON_THE_LIGHT, + SR_CMD_TURN_OFF_THE_LIGHT, + SR_CMD_START_FAN, + SR_CMD_STOP_FAN, +}; +static const sr_cmd_t sr_commands[] = { + { 0, "Turn on the light", "TkN nN jc LiT"}, + { 0, "Switch on the light", "SWgp nN jc LiT"}, + { 1, "Turn off the light", "TkN eF jc LiT"}, + { 1, "Switch off the light", "SWgp eF jc LiT"}, + { 1, "Go dark", "Gb DnRK"}, + { 2, "Start fan", "STnRT FaN"}, + { 3, "Stop fan", "STnP FaN"}, +}; + +void onSrEvent(sr_event_t event, int command_id, int phrase_id){ + switch(event){ + case SR_EVENT_WAKEWORD: + Serial.println("WakeWord Detected!"); + break; + case SR_EVENT_WAKEWORD_CHANNEL: + Serial.printf("WakeWord Channel %d Verified!\n", command_id); + ESP_SR.setMode(SR_MODE_COMMAND); // Switch to Command detection + break; + case SR_EVENT_TIMEOUT: + Serial.println("Timeout Detected!"); + ESP_SR.setMode(SR_MODE_WAKEWORD); // Switch back to WakeWord detection + break; + case SR_EVENT_COMMAND: + Serial.printf("Command %d Detected! %s\n", command_id, sr_commands[phrase_id].str); + switch(command_id){ + case SR_CMD_TURN_ON_THE_LIGHT: + digitalWrite(LIGHT_PIN, HIGH); + break; + case SR_CMD_TURN_OFF_THE_LIGHT: + digitalWrite(LIGHT_PIN, LOW); + break; + case SR_CMD_START_FAN: + digitalWrite(FAN_PIN, HIGH); + break; + case SR_CMD_STOP_FAN: + digitalWrite(FAN_PIN, LOW); + break; + default: + Serial.println("Unknown Command!"); + break; + } + ESP_SR.setMode(SR_MODE_COMMAND); // Allow for more commands to be given, before timeout + // ESP_SR.setMode(SR_MODE_WAKEWORD); // Switch back to WakeWord detection + break; + default: + Serial.println("Unknown Event!"); + break; + } +} + +void setup(){ + Serial.begin(115200); + + pinMode(LIGHT_PIN, OUTPUT); + digitalWrite(LIGHT_PIN, LOW); + pinMode(FAN_PIN, OUTPUT); + digitalWrite(FAN_PIN, LOW); + + i2s.setPins(I2S_PIN_BCK, I2S_PIN_WS, -1, I2S_PIN_DIN); + i2s.setTimeout(1000); + i2s.begin(I2S_MODE_STD, 16000, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO); + + + ESP_SR.onEvent(onSrEvent); + ESP_SR.begin(i2s, sr_commands, sizeof(sr_commands) / sizeof(sr_cmd_t), SR_CHANNELS_STEREO, SR_MODE_WAKEWORD); +} + +void loop(){ + +} diff --git a/libraries/ESP_SR/keywords.txt b/libraries/ESP_SR/keywords.txt new file mode 100644 index 000000000..30a392518 --- /dev/null +++ b/libraries/ESP_SR/keywords.txt @@ -0,0 +1,40 @@ +####################################### +# Syntax Coloring Map For ESP_SR +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +ESP_SR KEYWORD1 +ESP_SR_Class KEYWORD1 +sr_cmd_t KEYWORD1 +sr_event_t KEYWORD1 +sr_mode_t KEYWORD1 +sr_channels_t KEYWORD1 +sr_cb KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +onEvent KEYWORD2 +setMode KEYWORD2 +pause KEYWORD2 +resume KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +SR_EVENT_WAKEWORD LITERAL1 +SR_EVENT_WAKEWORD_CHANNEL LITERAL1 +SR_EVENT_COMMAND LITERAL1 +SR_EVENT_TIMEOUT LITERAL1 +SR_MODE_OFF LITERAL1 +SR_MODE_WAKEWORD LITERAL1 +SR_MODE_COMMAND LITERAL1 +SR_MODE_MAX LITERAL1 +SR_CHANNELS_MONO LITERAL1 +SR_CHANNELS_STEREO LITERAL1 +SR_CHANNELS_MAX LITERAL1 diff --git a/libraries/ESP_SR/library.properties b/libraries/ESP_SR/library.properties new file mode 100755 index 000000000..518cc915f --- /dev/null +++ b/libraries/ESP_SR/library.properties @@ -0,0 +1,9 @@ +name=ESP_SR +version=1.0.0 +author=me-no-dev +maintainer=me-no-dev +sentence=Library for ESP Sound Recognition +paragraph=Supports ESP32 Arduino platforms. +category=Sound +url=https://github.com/espressif/arduino-esp32/ +architectures=esp32 \ No newline at end of file diff --git a/libraries/ESP_SR/src/ESP_SR.cpp b/libraries/ESP_SR/src/ESP_SR.cpp new file mode 100644 index 000000000..f020d915f --- /dev/null +++ b/libraries/ESP_SR/src/ESP_SR.cpp @@ -0,0 +1,72 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "sdkconfig.h" +#if CONFIG_IDF_TARGET_ESP32S3 && (CONFIG_USE_WAKENET || CONFIG_USE_MULTINET) +#include "ESP_SR.h" + +static esp_err_t on_sr_fill(void * arg, void * out, size_t len, size_t *bytes_read, uint32_t timeout_ms){ + return ((ESP_SR_Class*)arg)->_fill(out, len, bytes_read, timeout_ms); +} + +static void on_sr_event(void * arg, sr_event_t event, int command_id, int phrase_id){ + ((ESP_SR_Class*)arg)->_sr_event(event, command_id, phrase_id); +} + +ESP_SR_Class::ESP_SR_Class() + : cb(NULL) + , i2s(NULL) +{ + +} + +ESP_SR_Class::~ESP_SR_Class(){ + end(); +} + +void ESP_SR_Class::onEvent(sr_cb event_cb){ + cb = event_cb; +} + +bool ESP_SR_Class::begin(I2SClass & _i2s, const sr_cmd_t * sr_commands, size_t sr_commands_len, sr_channels_t rx_chan, sr_mode_t mode){ + i2s = &_i2s; + esp_err_t err = sr_start(on_sr_fill, this, rx_chan, mode, sr_commands, sr_commands_len, on_sr_event, this); + return (err == ESP_OK); +} + +bool ESP_SR_Class::end(void){ + return sr_stop() == ESP_OK; +} + +bool ESP_SR_Class::setMode(sr_mode_t mode){ + return sr_set_mode(mode) == ESP_OK; +} + +bool ESP_SR_Class::pause(void){ + return sr_pause() == ESP_OK; +} + +bool ESP_SR_Class::resume(void){ + return sr_resume() == ESP_OK; +} + +void ESP_SR_Class::_sr_event(sr_event_t event, int command_id, int phrase_id){ + if(cb){ + cb(event, command_id, phrase_id); + } +} + +esp_err_t ESP_SR_Class::_fill(void * out, size_t len, size_t *bytes_read, uint32_t timeout_ms){ + if(i2s == NULL){ + return ESP_FAIL; + } + i2s->setTimeout(timeout_ms); + *bytes_read = i2s->readBytes((char *)out, len); + return (esp_err_t)i2s->lastError(); +} + +ESP_SR_Class ESP_SR; + +#endif // CONFIG_IDF_TARGET_ESP32S3 diff --git a/libraries/ESP_SR/src/ESP_SR.h b/libraries/ESP_SR/src/ESP_SR.h new file mode 100644 index 000000000..4af4276b6 --- /dev/null +++ b/libraries/ESP_SR/src/ESP_SR.h @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#pragma once +#include "sdkconfig.h" +#if CONFIG_IDF_TARGET_ESP32S3 && (CONFIG_USE_WAKENET || CONFIG_USE_MULTINET) + +#include "ESP_I2S.h" +#include "esp32-hal-sr.h" + +typedef void (*sr_cb)(sr_event_t event, int command_id, int phrase_id); + +class ESP_SR_Class { + private: + sr_cb cb; + I2SClass * i2s; + public: + ESP_SR_Class(); + ~ESP_SR_Class(); + + void onEvent(sr_cb cb); + bool begin(I2SClass & i2s, const sr_cmd_t * sr_commands, size_t sr_commands_len, sr_channels_t rx_chan=SR_CHANNELS_STEREO, sr_mode_t mode=SR_MODE_WAKEWORD); + bool end(void); + bool setMode(sr_mode_t mode); + bool pause(void); + bool resume(void); + + void _sr_event(sr_event_t event, int command_id, int phrase_id); + esp_err_t _fill(void * out, size_t len, size_t *bytes_read, uint32_t timeout_ms); +}; + +extern ESP_SR_Class ESP_SR; + + +#endif // CONFIG_IDF_TARGET_ESP32S3 diff --git a/libraries/ESP_SR/src/esp32-hal-sr.c b/libraries/ESP_SR/src/esp32-hal-sr.c new file mode 100644 index 000000000..d33a52d1c --- /dev/null +++ b/libraries/ESP_SR/src/esp32-hal-sr.c @@ -0,0 +1,445 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "sdkconfig.h" +#if CONFIG_IDF_TARGET_ESP32S3 && (CONFIG_USE_WAKENET || CONFIG_USE_MULTINET) + +#if !defined(ARDUINO_PARTITION_esp_sr_32) && !defined(ARDUINO_PARTITION_esp_sr_16) && !defined(ARDUINO_PARTITION_esp_sr_8) +#warning Compatible partition must be selected for ESP_SR to work +#endif + +#include +#include +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/event_groups.h" +#include "freertos/task.h" +#include "esp_task_wdt.h" +#include "esp_check.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_mn_speech_commands.h" +#include "esp_process_sdkconfig.h" +#include "esp_afe_sr_models.h" +#include "esp_mn_models.h" +#include "esp_wn_iface.h" +#include "esp_wn_models.h" +#include "esp_afe_sr_iface.h" +#include "esp_mn_iface.h" +#include "model_path.h" + +#include "driver/i2s_common.h" +#include "esp32-hal-sr.h" +#include "esp32-hal-log.h" + +#undef ESP_GOTO_ON_FALSE +#define ESP_GOTO_ON_FALSE(a, err_code, goto_tag, format, ...) do { \ + if (unlikely(!(a))) { \ + log_e(format, ##__VA_ARGS__); \ + ret = err_code; \ + goto goto_tag; \ + } \ + } while (0) + +#undef ESP_RETURN_ON_FALSE +#define ESP_RETURN_ON_FALSE(a, err_code, format, ...) do { \ + if (unlikely(!(a))) { \ + log_e(format, ##__VA_ARGS__); \ + return err_code; \ + } \ + } while(0) + +#define NEED_DELETE BIT0 +#define FEED_DELETED BIT1 +#define DETECT_DELETED BIT2 +#define PAUSE_FEED BIT3 +#define PAUSE_DETECT BIT4 +#define RESUME_FEED BIT5 +#define RESUME_DETECT BIT6 + +typedef struct { + wakenet_state_t wakenet_mode; + esp_mn_state_t state; + int command_id; + int phrase_id; +} sr_result_t; + +typedef struct { + model_iface_data_t *model_data; + const esp_mn_iface_t *multinet; + const esp_afe_sr_iface_t *afe_handle; + esp_afe_sr_data_t *afe_data; + int16_t *afe_in_buffer; + sr_mode_t mode; + uint8_t i2s_rx_chan_num; + sr_event_cb user_cb; + void * user_cb_arg; + sr_fill_cb fill_cb; + void * fill_cb_arg; + TaskHandle_t feed_task; + TaskHandle_t detect_task; + TaskHandle_t handle_task; + QueueHandle_t result_que; + EventGroupHandle_t event_group; +} sr_data_t; + +static int SR_CHANNEL_NUM = 3; + +static srmodel_list_t *models = NULL; +static sr_data_t *g_sr_data = NULL; + +esp_err_t sr_set_mode(sr_mode_t mode); + +void sr_handler_task(void *pvParam) +{ + while (true) { + sr_result_t result; + if(xQueueReceive(g_sr_data->result_que, &result, portMAX_DELAY) != pdTRUE){ + continue; + } + + if (WAKENET_DETECTED == result.wakenet_mode) { + if(g_sr_data->user_cb){ + g_sr_data->user_cb(g_sr_data->user_cb_arg, SR_EVENT_WAKEWORD, -1, -1); + } + continue; + } + + if (WAKENET_CHANNEL_VERIFIED == result.wakenet_mode) { + if(g_sr_data->user_cb){ + g_sr_data->user_cb(g_sr_data->user_cb_arg, SR_EVENT_WAKEWORD_CHANNEL, result.command_id, -1); + } + continue; + } + + if (ESP_MN_STATE_DETECTED == result.state) { + if(g_sr_data->user_cb){ + g_sr_data->user_cb(g_sr_data->user_cb_arg, SR_EVENT_COMMAND, result.command_id, result.phrase_id); + } + continue; + } + + if (ESP_MN_STATE_TIMEOUT == result.state) { + if(g_sr_data->user_cb){ + g_sr_data->user_cb(g_sr_data->user_cb_arg, SR_EVENT_TIMEOUT, -1, -1); + } + continue; + } + } + vTaskDelete(NULL); +} + +static void audio_feed_task(void *arg) +{ + size_t bytes_read = 0; + int audio_chunksize = g_sr_data->afe_handle->get_feed_chunksize(g_sr_data->afe_data); + log_i("audio_chunksize=%d, feed_channel=%d", audio_chunksize, SR_CHANNEL_NUM); + + /* Allocate audio buffer and check for result */ + int16_t *audio_buffer = heap_caps_malloc(audio_chunksize * sizeof(int16_t) * SR_CHANNEL_NUM, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + if (NULL == audio_buffer) { + esp_system_abort("No mem for audio buffer"); + } + g_sr_data->afe_in_buffer = audio_buffer; + + while (true) { + EventBits_t bits = xEventGroupGetBits(g_sr_data->event_group); + if (NEED_DELETE & bits) { + xEventGroupSetBits(g_sr_data->event_group, FEED_DELETED); + break; + } + if (PAUSE_FEED & bits) { + xEventGroupWaitBits(g_sr_data->event_group, PAUSE_FEED | RESUME_FEED, 1, 1, portMAX_DELAY); + } + + /* Read audio data from I2S bus */ + //ToDo: handle error + if(g_sr_data->fill_cb == NULL){ + vTaskDelay(100); + continue; + } + esp_err_t err = g_sr_data->fill_cb(g_sr_data->fill_cb_arg, (char *)audio_buffer, audio_chunksize * g_sr_data->i2s_rx_chan_num * sizeof(int16_t), &bytes_read, portMAX_DELAY); + if(err != ESP_OK){ + vTaskDelay(100); + continue; + } + + /* Channel Adjust */ + if(g_sr_data->i2s_rx_chan_num == 1){ + for (int i = audio_chunksize - 1; i >= 0; i--) { + audio_buffer[i * SR_CHANNEL_NUM + 2] = 0; + audio_buffer[i * SR_CHANNEL_NUM + 1] = 0; + audio_buffer[i * SR_CHANNEL_NUM + 0] = audio_buffer[i]; + } + } else if(g_sr_data->i2s_rx_chan_num == 2){ + for (int i = audio_chunksize - 1; i >= 0; i--) { + audio_buffer[i * SR_CHANNEL_NUM + 2] = 0; + audio_buffer[i * SR_CHANNEL_NUM + 1] = audio_buffer[i * 2 + 1]; + audio_buffer[i * SR_CHANNEL_NUM + 0] = audio_buffer[i * 2 + 0]; + } + } else { + vTaskDelay(100); + continue; + } + + /* Feed samples of an audio stream to the AFE_SR */ + g_sr_data->afe_handle->feed(g_sr_data->afe_data, audio_buffer); + } + vTaskDelete(NULL); +} + +static void audio_detect_task(void *arg) +{ + int afe_chunksize = g_sr_data->afe_handle->get_fetch_chunksize(g_sr_data->afe_data); + int mu_chunksize = g_sr_data->multinet->get_samp_chunksize(g_sr_data->model_data); + assert(mu_chunksize == afe_chunksize); + log_i("------------detect start------------"); + + while (true) { + EventBits_t bits = xEventGroupGetBits(g_sr_data->event_group); + if (NEED_DELETE & bits) { + xEventGroupSetBits(g_sr_data->event_group, DETECT_DELETED); + break; + } + if (PAUSE_DETECT & bits) { + xEventGroupWaitBits(g_sr_data->event_group, PAUSE_DETECT | RESUME_DETECT, 1, 1, portMAX_DELAY); + } + + afe_fetch_result_t* res = g_sr_data->afe_handle->fetch(g_sr_data->afe_data); + if (!res || res->ret_value == ESP_FAIL) { + continue; + } + + if(g_sr_data->mode == SR_MODE_WAKEWORD){ + if (res->wakeup_state == WAKENET_DETECTED) { + log_d("wakeword detected"); + sr_result_t result = { + .wakenet_mode = WAKENET_DETECTED, + .state = ESP_MN_STATE_DETECTING, + .command_id = 0, + .phrase_id = 0, + }; + xQueueSend(g_sr_data->result_que, &result, 0); + } + else if (res->wakeup_state == WAKENET_CHANNEL_VERIFIED) { + sr_set_mode(SR_MODE_OFF); + log_d("AFE_FETCH_CHANNEL_VERIFIED, channel index: %d", res->trigger_channel_id); + sr_result_t result = { + .wakenet_mode = WAKENET_CHANNEL_VERIFIED, + .state = ESP_MN_STATE_DETECTING, + .command_id = res->trigger_channel_id, + .phrase_id = 0, + }; + xQueueSend(g_sr_data->result_que, &result, 0); + } + } + + if (g_sr_data->mode == SR_MODE_COMMAND) { + + esp_mn_state_t mn_state = ESP_MN_STATE_DETECTING; + mn_state = g_sr_data->multinet->detect(g_sr_data->model_data, res->data); + + if (ESP_MN_STATE_DETECTING == mn_state) { + continue; + } + + if (ESP_MN_STATE_TIMEOUT == mn_state) { + sr_set_mode(SR_MODE_OFF); + log_d("Time out"); + sr_result_t result = { + .wakenet_mode = WAKENET_NO_DETECT, + .state = mn_state, + .command_id = 0, + .phrase_id = 0, + }; + xQueueSend(g_sr_data->result_que, &result, 0); + continue; + } + + if (ESP_MN_STATE_DETECTED == mn_state) { + sr_set_mode(SR_MODE_OFF); + esp_mn_results_t *mn_result = g_sr_data->multinet->get_results(g_sr_data->model_data); + for (int i = 0; i < mn_result->num; i++) { + log_d("TOP %d, command_id: %d, phrase_id: %d, prob: %f", + i + 1, mn_result->command_id[i], mn_result->phrase_id[i], mn_result->prob[i]); + } + + int sr_command_id = mn_result->command_id[0]; + int sr_phrase_id = mn_result->phrase_id[0]; + log_d("Deteted command : %d, phrase: %d", sr_command_id, sr_phrase_id); + sr_result_t result = { + .wakenet_mode = WAKENET_NO_DETECT, + .state = mn_state, + .command_id = sr_command_id, + .phrase_id = sr_phrase_id, + }; + xQueueSend(g_sr_data->result_que, &result, 0); + continue; + } + log_e("Exception unhandled"); + } + } + vTaskDelete(NULL); +} + +esp_err_t sr_set_mode(sr_mode_t mode) +{ + ESP_RETURN_ON_FALSE(NULL != g_sr_data, ESP_ERR_INVALID_STATE, "SR is not running"); + switch(mode){ + case SR_MODE_OFF: + if(g_sr_data->mode == SR_MODE_WAKEWORD){ + g_sr_data->afe_handle->disable_wakenet(g_sr_data->afe_data); + } + break; + case SR_MODE_WAKEWORD: + if(g_sr_data->mode != SR_MODE_WAKEWORD){ + g_sr_data->afe_handle->enable_wakenet(g_sr_data->afe_data); + } + break; + case SR_MODE_COMMAND: + if(g_sr_data->mode == SR_MODE_WAKEWORD){ + g_sr_data->afe_handle->disable_wakenet(g_sr_data->afe_data); + } + break; + default: + return ESP_FAIL; + } + g_sr_data->mode = mode; + return ESP_OK; +} + +esp_err_t sr_start(sr_fill_cb fill_cb, void * fill_cb_arg, sr_channels_t rx_chan, sr_mode_t mode, const sr_cmd_t sr_commands[], size_t cmd_number, sr_event_cb cb, void * cb_arg) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_FALSE(NULL == g_sr_data, ESP_ERR_INVALID_STATE, "SR already running"); + + g_sr_data = heap_caps_calloc(1, sizeof(sr_data_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + ESP_RETURN_ON_FALSE(NULL != g_sr_data, ESP_ERR_NO_MEM, "Failed create sr data"); + + g_sr_data->result_que = xQueueCreate(3, sizeof(sr_result_t)); + ESP_GOTO_ON_FALSE(NULL != g_sr_data->result_que, ESP_ERR_NO_MEM, err, "Failed create result queue"); + + g_sr_data->event_group = xEventGroupCreate(); + ESP_GOTO_ON_FALSE(NULL != g_sr_data->event_group, ESP_ERR_NO_MEM, err, "Failed create event_group"); + + BaseType_t ret_val; + g_sr_data->user_cb = cb; + g_sr_data->user_cb_arg = cb_arg; + g_sr_data->fill_cb = fill_cb; + g_sr_data->fill_cb_arg = fill_cb_arg; + g_sr_data->i2s_rx_chan_num = rx_chan + 1; + g_sr_data->mode = mode; + + // Init Model + log_d("init model"); + models = esp_srmodel_init("model"); + + // Load WakeWord Detection + g_sr_data->afe_handle = (esp_afe_sr_iface_t *)&ESP_AFE_SR_HANDLE; + afe_config_t afe_config = AFE_CONFIG_DEFAULT(); + afe_config.wakenet_model_name = esp_srmodel_filter(models, ESP_WN_PREFIX, "hiesp"); + afe_config.aec_init = false; + log_d("load wakenet '%s'", afe_config.wakenet_model_name); + g_sr_data->afe_data = g_sr_data->afe_handle->create_from_config(&afe_config); + + // Load Custom Command Detection + char *mn_name = esp_srmodel_filter(models, ESP_MN_PREFIX, ESP_MN_ENGLISH); + log_d("load multinet '%s'", mn_name); + g_sr_data->multinet = esp_mn_handle_from_name(mn_name); + log_d("load model_data '%s'", mn_name); + g_sr_data->model_data = g_sr_data->multinet->create(mn_name, 5760); + + + // Add commands + esp_mn_commands_alloc((esp_mn_iface_t *)g_sr_data->multinet, (model_iface_data_t *)g_sr_data->model_data); + log_i("add %d commands", cmd_number); + for (size_t i = 0; i < cmd_number; i++) { + esp_mn_commands_add(sr_commands[i].command_id, (char *)(sr_commands[i].phoneme)); + log_i(" cmd[%d] phrase[%d]:'%s'", sr_commands[i].command_id, i, sr_commands[i].str); + } + + // Load commands + esp_mn_error_t *err_id = esp_mn_commands_update(); + if(err_id){ + for (int i = 0; i < err_id->num; i++) { + log_e("err cmd id:%d", err_id->phrases[i]->command_id); + } + } + + //Start tasks + log_d("start tasks"); + ret_val = xTaskCreatePinnedToCore(&audio_feed_task, "SR Feed Task", 4 * 1024, NULL, 5, &g_sr_data->feed_task, 0); + ESP_GOTO_ON_FALSE(pdPASS == ret_val, ESP_FAIL, err, "Failed create audio feed task"); + vTaskDelay(10); + ret_val = xTaskCreatePinnedToCore(&audio_detect_task, "SR Detect Task", 8 * 1024, NULL, 5, &g_sr_data->detect_task, 1); + ESP_GOTO_ON_FALSE(pdPASS == ret_val, ESP_FAIL, err, "Failed create audio detect task"); + ret_val = xTaskCreatePinnedToCore(&sr_handler_task, "SR Handler Task", 6 * 1024, NULL, configMAX_PRIORITIES - 1, &g_sr_data->handle_task, 1); + //ret_val = xTaskCreatePinnedToCore(&sr_handler_task, "SR Handler Task", 6 * 1024, NULL, configMAX_PRIORITIES - 1, &g_sr_data->handle_task, 0); + ESP_GOTO_ON_FALSE(pdPASS == ret_val, ESP_FAIL, err, "Failed create audio handler task"); + + return ESP_OK; +err: + sr_stop(); + return ret; +} + +esp_err_t sr_stop(void) +{ + ESP_RETURN_ON_FALSE(NULL != g_sr_data, ESP_ERR_INVALID_STATE, "SR is not running"); + + /** + * Waiting for all task stoped + * TODO: A task creation failure cannot be handled correctly now + * */ + vTaskDelete(g_sr_data->handle_task); + xEventGroupSetBits(g_sr_data->event_group, NEED_DELETE); + xEventGroupWaitBits(g_sr_data->event_group, NEED_DELETE | FEED_DELETED | DETECT_DELETED, 1, 1, portMAX_DELAY); + + if (g_sr_data->result_que) { + vQueueDelete(g_sr_data->result_que); + g_sr_data->result_que = NULL; + } + + if (g_sr_data->event_group) { + vEventGroupDelete(g_sr_data->event_group); + g_sr_data->event_group = NULL; + } + + if (g_sr_data->model_data) { + g_sr_data->multinet->destroy(g_sr_data->model_data); + } + + if (g_sr_data->afe_data) { + g_sr_data->afe_handle->destroy(g_sr_data->afe_data); + } + + if (g_sr_data->afe_in_buffer) { + heap_caps_free(g_sr_data->afe_in_buffer); + } + + heap_caps_free(g_sr_data); + g_sr_data = NULL; + return ESP_OK; +} + +esp_err_t sr_pause(void) +{ + ESP_RETURN_ON_FALSE(NULL != g_sr_data, ESP_ERR_INVALID_STATE, "SR is not running"); + xEventGroupSetBits(g_sr_data->event_group, PAUSE_FEED | PAUSE_DETECT); + return ESP_OK; +} + +esp_err_t sr_resume(void) +{ + ESP_RETURN_ON_FALSE(NULL != g_sr_data, ESP_ERR_INVALID_STATE, "SR is not running"); + xEventGroupSetBits(g_sr_data->event_group, RESUME_FEED | RESUME_DETECT); + return ESP_OK; +} + +#endif // CONFIG_IDF_TARGET_ESP32S3 diff --git a/libraries/ESP_SR/src/esp32-hal-sr.h b/libraries/ESP_SR/src/esp32-hal-sr.h new file mode 100644 index 000000000..33d22a79d --- /dev/null +++ b/libraries/ESP_SR/src/esp32-hal-sr.h @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#pragma once +#include "sdkconfig.h" +#if CONFIG_IDF_TARGET_ESP32S3 && (CONFIG_USE_WAKENET || CONFIG_USE_MULTINET) + +#include "driver/i2s_types.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SR_CMD_STR_LEN_MAX 64 +#define SR_CMD_PHONEME_LEN_MAX 64 + +typedef struct sr_cmd_t { + int command_id; + char str[SR_CMD_STR_LEN_MAX]; + char phoneme[SR_CMD_PHONEME_LEN_MAX]; +} sr_cmd_t; + +typedef enum { + SR_EVENT_WAKEWORD,//WakeWord Detected + SR_EVENT_WAKEWORD_CHANNEL,//WakeWord Channel Verified + SR_EVENT_COMMAND,//Command Detected + SR_EVENT_TIMEOUT,//Command Timeout + SR_EVENT_MAX +} sr_event_t; + +typedef enum { + SR_MODE_OFF,//Detection Off + SR_MODE_WAKEWORD,//WakeWord Detection + SR_MODE_COMMAND,//Command Detection + SR_MODE_MAX +} sr_mode_t; + +typedef enum { + SR_CHANNELS_MONO, + SR_CHANNELS_STEREO, + SR_CHANNELS_MAX +} sr_channels_t; + +typedef void (*sr_event_cb)(void * arg, sr_event_t event, int command_id, int phrase_id); +typedef esp_err_t (*sr_fill_cb)(void * arg, void * out, size_t len, size_t *bytes_read, uint32_t timeout_ms); + +esp_err_t sr_start(sr_fill_cb fill_cb, void * fill_cb_arg, sr_channels_t rx_chan, sr_mode_t mode, const sr_cmd_t * sr_commands, size_t cmd_number, sr_event_cb cb, void * cb_arg); +esp_err_t sr_stop(void); +esp_err_t sr_pause(void); +esp_err_t sr_resume(void); +esp_err_t sr_set_mode(sr_mode_t mode); + +// static const sr_cmd_t sr_commands[] = { +// {0, "Turn On the Light", "TkN nN jc LiT"}, +// {0, "Switch On the Light", "SWgp nN jc LiT"}, +// {1, "Switch Off the Light", "SWgp eF jc LiT"}, +// {1, "Turn Off the Light", "TkN eF jc LiT"}, +// {2, "Turn Red", "TkN RfD"}, +// {3, "Turn Green", "TkN GRmN"}, +// {4, "Turn Blue", "TkN BLo"}, +// {5, "Customize Color", "KcSTcMiZ KcLk"}, +// {6, "Sing a song", "Sgl c Sel"}, +// {7, "Play Music", "PLd MYoZgK"}, +// {8, "Next Song", "NfKST Sel"}, +// {9, "Pause Playing", "PeZ PLdgl"}, +// }; + +#ifdef __cplusplus +} +#endif + +#endif // CONFIG_IDF_TARGET_ESP32S3 diff --git a/libraries/ESP_SR/tools/gen_sr_commands.py b/libraries/ESP_SR/tools/gen_sr_commands.py new file mode 100644 index 000000000..f3629b71b --- /dev/null +++ b/libraries/ESP_SR/tools/gen_sr_commands.py @@ -0,0 +1,73 @@ +# pip3 install g2p_en +from g2p_en import G2p +import argparse + +# python3 gen_sr_commands.py "Turn on the light,Switch on the light;Turn off the light,Switch off the light,Go dark;Start fan;Stop fan;Volume down,Turn down;Mute sound;Next song;Pause playback" +# enum { +# SR_CMD_TURN_ON_THE_LIGHT, +# SR_CMD_TURN_OFF_THE_LIGHT, +# SR_CMD_START_FAN, +# SR_CMD_STOP_FAN, +# SR_CMD_VOLUME_DOWN, +# SR_CMD_MUTE_SOUND, +# SR_CMD_NEXT_SONG, +# SR_CMD_PAUSE_PLAYBACK, +# }; +# static const sr_cmd_t sr_commands[] = { +# { 0, "Turn on the light", "TkN nN jc LiT"}, +# { 0, "Switch on the light", "SWgp nN jc LiT"}, +# { 1, "Turn off the light", "TkN eF jc LiT"}, +# { 1, "Switch off the light", "SWgp eF jc LiT"}, +# { 1, "Go dark", "Gb DnRK"}, +# { 2, "Start fan", "STnRT FaN"}, +# { 3, "Stop fan", "STnP FaN"}, +# { 4, "Volume down", "VnLYoM DtN"}, +# { 4, "Turn down", "TkN DtN"}, +# { 5, "Mute sound", "MYoT StND"}, +# { 6, "Next song", "NfKST Sel"}, +# { 7, "Pause playback", "PeZ PLdBaK"}, +# }; + +def english_g2p(text): + g2p = G2p() + out = "static const sr_cmd_t sr_commands[] = {\n" + enum = "enum {\n" + alphabet={"AE1": "a", "N": "N", " ": " ", "OW1": "b", "V": "V", "AH0": "c", "L": "L", "F": "F", "EY1": "d", "S": "S", "B": "B", "R": "R", "AO1": "e", "D": "D", "AH1": "c", "EH1": "f", "OW0": "b", "IH0": "g", "G": "G", "HH": "h", "K": "K", "IH1": "g", "W": "W", "AY1": "i", "T": "T", "M": "M", "Z": "Z", "DH": "j", "ER0": "k", "P": "P", "NG": "l", "IY1": "m", "AA1": "n", "Y": "Y", "UW1": "o", "IY0": "m", "EH2": "f", "CH": "p", "AE0": "a", "JH": "q", "ZH": "r", "AA2": "n", "SH": "s", "AW1": "t", "OY1": "u", "AW2": "t", "IH2": "g", "AE2": "a", "EY2": "d", "ER1": "k", "TH": "v", "UH1": "w", "UW2": "o", "OW2": "b", "AY2": "i", "UW0": "o", "AH2": "c", "EH0": "f", "AW0": "t", "AO2": "e", "AO0": "e", "UH0": "w", "UH2": "w", "AA0": "n", "AY0": "i", "IY2": "m", "EY0": "d", "ER2": "k", "OY2": "u", "OY0": "u"} + + cmd_id = 0 + phrase_id = 0 + text_list = text.split(";") + for item in text_list: + item = item.split(",") + phrase_id = 0 + for phrase in item: + labels = g2p(phrase) + phoneme = "" + for char in labels: + if char not in alphabet: + print("skip %s, not found in alphabet") + continue + else: + phoneme += alphabet[char] + out += " { "+str(cmd_id)+", \""+phrase+"\", \""+phoneme+"\"},\n" + if phrase_id == 0: + enum += " SR_CMD_"+phrase.upper().replace(" ", "_")+",\n" + phrase_id += 1 + cmd_id += 1 + out += "};" + enum += "};" + # print(text) + print(enum) + print(out) + + return out + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(prog="English Speech Commands G2P") + parser.add_argument("text", type=str, default=None, help="input text") + args = parser.parse_args() + + if args.text is not None: + english_g2p(args.text) + diff --git a/libraries/I2S/examples/ADCPlotter/.skip.esp32c6 b/libraries/I2S/examples/ADCPlotter/.skip.esp32c6 deleted file mode 100644 index e69de29bb..000000000 diff --git a/libraries/I2S/examples/ADCPlotter/.skip.esp32h2 b/libraries/I2S/examples/ADCPlotter/.skip.esp32h2 deleted file mode 100644 index e69de29bb..000000000 diff --git a/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino b/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino deleted file mode 100644 index 5f3bd93ca..000000000 --- a/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino +++ /dev/null @@ -1,86 +0,0 @@ -/* - This example is only for ESP devices. - - This example demonstrates usage of integrated Digital to Analog Converter (DAC) - You can display sound wave from audio device, or just measure voltage. - - To display audio prepare circuit found in following link or drafted as ASCII art - https://forum.arduino.cc/index.php?topic=567581.0 - (!) Note that unlike in the link we are connecting the supply line to 3.3V (not 5V) - because ADC can measure only up to around 3V. Anything above 3V will be very inaccurate. - - ^ +3.3V - | - _ - | |10k - |_| - | | |10uF - GPIO34-------------*------------| |----------- line in -(Default ADC pin) | +| | - | - _ - | |10k - |_| - | - | - V GND - -Connect hot wire of your audio to Line in and GNd wire of audio cable to common ground (GND) - -Second option to measure voltage on trimmer / potentiometer has following connection - ^ +3.3V - | - _ - | | - GPIO34----------->| | -(Default ADC pin) |_| - | - | - _ - | | optional resistor - |_| - | - | - V GND - Optional resistor will decrease read value. - - Steps to run: - 1. Select target board: - Tools -> Board -> ESP32 Arduino -> your board - 2. Upload sketch - Press upload button (arrow in top left corner) - When you see in console line like this: "Connecting........_____.....__" - On your board press and hold Boot button and press EN button shortly. Now you can release both buttons. - You should see lines like this: "Writing at 0x00010000... (12 %)" with rising percentage on each line. - If this fails, try the board buttons right after pressing upload button, or reconnect the USB cable. - 3. Open plotter - Tools -> Serial Plotter - Enjoy - -Created by Tomas Pilny -on 17th June 2021 -*/ - -#include - -void setup() { - // Open serial communications and wait for port to open: - // A baud rate of 115200 is used instead of 9600 for a faster data rate - // on non-native USB ports - Serial.begin(115200); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - // start I2S at 8 kHz with 32-bits per sample - if (!I2S.begin(ADC_DAC_MODE, 8000, 16)) { - Serial.println("Failed to initialize I2S!"); - while (1); // do nothing - } -} - -void loop() { - // read a sample - int sample = I2S.read(); - Serial.println(sample); -} diff --git a/libraries/I2S/examples/FullDuplex/.skip.esp32c3 b/libraries/I2S/examples/FullDuplex/.skip.esp32c3 deleted file mode 100644 index e69de29bb..000000000 diff --git a/libraries/I2S/examples/FullDuplex/.skip.esp32c6 b/libraries/I2S/examples/FullDuplex/.skip.esp32c6 deleted file mode 100644 index e69de29bb..000000000 diff --git a/libraries/I2S/examples/FullDuplex/.skip.esp32h2 b/libraries/I2S/examples/FullDuplex/.skip.esp32h2 deleted file mode 100644 index e69de29bb..000000000 diff --git a/libraries/I2S/examples/FullDuplex/FullDuplex.ino b/libraries/I2S/examples/FullDuplex/FullDuplex.ino deleted file mode 100644 index 9b3625fbd..000000000 --- a/libraries/I2S/examples/FullDuplex/FullDuplex.ino +++ /dev/null @@ -1,59 +0,0 @@ -/* - This example is only for ESP - This example demonstrates simultaneous usage of microphone and speaker using single I2S module. - The application transfers data from input to output - - Circuit: - * ESP32 - * GND connected GND - * VIN connected 5V - * SCK 5 - * FS 25 - * DIN 35 - * DOUT 26 - * I2S microphone - * I2S decoder + headphones / speaker - - created 8 October 2021 - by Tomas Pilny - */ - -#include -const long sampleRate = 16000; -const int bitsPerSample = 32; -uint8_t *buffer; - -void setup() { - Serial.begin(115200); - //I2S.setAllPins(5, 25, 35, 26); // you can change default pins; order of pins = (CLK, WS, IN, OUT) - if(!I2S.setDuplex()){ - Serial.println("ERROR - could not set duplex"); - while(true){ - vTaskDelay(10); // Cannot continue - } - } - if (!I2S.begin(I2S_PHILIPS_MODE, sampleRate, bitsPerSample)) { - Serial.println("Failed to initialize I2S!"); - while(true){ - vTaskDelay(10); // Cannot continue - } - } - buffer = (uint8_t*) malloc(I2S.getBufferSize() * (bitsPerSample / 8)); - if(buffer == NULL){ - Serial.println("Failed to allocate buffer!"); - while(true){ - vTaskDelay(10); // Cannot continue - } - } - Serial.println("Setup done"); -} - -void loop() { - //I2S.write(I2S.read()); // primitive implementation sample-by-sample - - // Buffer based implementation - I2S.read(buffer, I2S.getBufferSize() * (bitsPerSample / 8)); - I2S.write(buffer, I2S.getBufferSize() * (bitsPerSample / 8)); - - //optimistic_yield(1000); // yield if last yield occurred before CPU clock cycles ago -} diff --git a/libraries/I2S/examples/InputSerialPlotter/.skip.esp32c3 b/libraries/I2S/examples/InputSerialPlotter/.skip.esp32c3 deleted file mode 100644 index e69de29bb..000000000 diff --git a/libraries/I2S/examples/InputSerialPlotter/.skip.esp32c6 b/libraries/I2S/examples/InputSerialPlotter/.skip.esp32c6 deleted file mode 100644 index e69de29bb..000000000 diff --git a/libraries/I2S/examples/InputSerialPlotter/.skip.esp32h2 b/libraries/I2S/examples/InputSerialPlotter/.skip.esp32h2 deleted file mode 100644 index e69de29bb..000000000 diff --git a/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino b/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino deleted file mode 100644 index db9f7d0d4..000000000 --- a/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino +++ /dev/null @@ -1,44 +0,0 @@ -/* - This example reads audio data from an Invensense's ICS43432 I2S microphone - breakout board, and prints out the samples to the Serial console. The - Serial Plotter built into the Arduino IDE can be used to plot the audio - data (Tools -> Serial Plotter) - - Circuit: - * Arduino/Genuino Zero, MKR family and Nano 33 IoT - * ICS43432: - * GND connected GND - * 3.3V connected to 3.3V (Zero, Nano, ESP32), VCC (MKR) - * WS connected to pin 0 (Zero) or 3 (MKR), A2 (Nano) or 25 (ESP32) - * CLK connected to pin 1 (Zero) or 2 (MKR), A3 (Nano) or 5 (ESP32) - * SD connected to pin 9 (Zero) or A6 (MKR), 4 (Nano) or 26 (ESP32) - created 17 November 2016 - by Sandeep Mistry - */ - -#include - -void setup() { - // Open serial communications and wait for port to open: - // A baud rate of 115200 is used instead of 9600 for a faster data rate - // on non-native USB ports - Serial.begin(115200); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - // start I2S at 8 kHz with 32-bits per sample - if (!I2S.begin(I2S_PHILIPS_MODE, 8000, 32)) { - Serial.println("Failed to initialize I2S!"); - while (1); // do nothing - } -} - -void loop() { - // read a sample - int sample = I2S.read(); - - if (sample && sample != -1 && sample != 1) { - Serial.println(sample); - } -} diff --git a/libraries/I2S/examples/SimpleTone/.skip.esp32c3 b/libraries/I2S/examples/SimpleTone/.skip.esp32c3 deleted file mode 100644 index e69de29bb..000000000 diff --git a/libraries/I2S/examples/SimpleTone/.skip.esp32c6 b/libraries/I2S/examples/SimpleTone/.skip.esp32c6 deleted file mode 100644 index e69de29bb..000000000 diff --git a/libraries/I2S/examples/SimpleTone/.skip.esp32h2 b/libraries/I2S/examples/SimpleTone/.skip.esp32h2 deleted file mode 100644 index e69de29bb..000000000 diff --git a/libraries/I2S/examples/SimpleTone/SimpleTone.ino b/libraries/I2S/examples/SimpleTone/SimpleTone.ino deleted file mode 100644 index 49cae7723..000000000 --- a/libraries/I2S/examples/SimpleTone/SimpleTone.ino +++ /dev/null @@ -1,79 +0,0 @@ -/* - This example generates a square wave based tone at a specified frequency - and sample rate. Then outputs the data using the I2S interface to a - MAX08357 I2S Amp Breakout board. - - I2S Circuit: - * Arduino/Genuino Zero, MKR family and Nano 33 IoT - * MAX08357: - * GND connected GND - * VIN connected 5V - * LRC connected to pin 0 (Zero) or 3 (MKR), A2 (Nano) or 25 (ESP32) - * BCLK connected to pin 1 (Zero) or 2 (MKR), A3 (Nano) or 5 (ESP32) - * DIN connected to pin 9 (Zero) or A6 (MKR), 4 (Nano) or 26 (ESP32) - - DAC Circuit: - * ESP32 or ESP32-S2 - * Audio amplifier - - Note: - - ESP32 has DAC on GPIO pins 25 and 26. - - ESP32-S2 has DAC on GPIO pins 17 and 18. - - Connect speaker(s) or headphones. - - created 17 November 2016 - by Sandeep Mistry - For ESP extended - Tomas Pilny - 2nd September 2021 - */ - -#include -const int frequency = 440; // frequency of square wave in Hz -const int amplitude = 500; // amplitude of square wave -const int sampleRate = 8000; // sample rate in Hz -const int bps = 16; - -const int halfWavelength = (sampleRate / frequency); // half wavelength of square wave - -int32_t sample = amplitude; // current sample value -int count = 0; - -i2s_mode_t mode = I2S_PHILIPS_MODE; // I2S decoder is needed -// i2s_mode_t mode = ADC_DAC_MODE; // Audio amplifier is needed - -// Mono channel input -// This is ESP specific implementation - -// samples will be automatically copied to both channels inside I2S driver -// If you want to have true mono output use I2S_PHILIPS_MODE and interlay -// second channel with 0-value samples. -// The order of channels is RIGH followed by LEFT -//i2s_mode_t mode = I2S_RIGHT_JUSTIFIED_MODE; // I2S decoder is needed - -void setup() { - Serial.begin(115200); - Serial.println("I2S simple tone"); - - // start I2S at the sample rate with 16-bits per sample - if (!I2S.begin(mode, sampleRate, bps)) { - Serial.println("Failed to initialize I2S!"); - while (1); // do nothing - } -} - -void loop() { - if (count % halfWavelength == 0 ) { - // invert the sample every half wavelength count multiple to generate square wave - sample = -1 * sample; - } - - if(mode == I2S_PHILIPS_MODE || mode == ADC_DAC_MODE){ // write the same sample twice, once for Right and once for Left channel - I2S.write(sample); // Right channel - I2S.write(sample); // Left channel - }else if(mode == I2S_RIGHT_JUSTIFIED_MODE || mode == I2S_LEFT_JUSTIFIED_MODE){ - // write the same only once - it will be automatically copied to the other channel - I2S.write(sample); - } - - // increment the counter for the next sample - count++; -} diff --git a/libraries/I2S/keywords.txt b/libraries/I2S/keywords.txt deleted file mode 100644 index ad1b8028d..000000000 --- a/libraries/I2S/keywords.txt +++ /dev/null @@ -1,61 +0,0 @@ -####################################### -# Syntax Coloring Map I2S -####################################### - -####################################### -# Datatypes (KEYWORD1) -####################################### - -I2S KEYWORD1 - -####################################### -# Methods and Functions (KEYWORD2) -####################################### -I2SClass KEYWORD2 -begin KEYWORD2 -end KEYWORD2 - -onReceive KEYWORD2 -onTransmit KEYWORD2 - -setSckPin KEYWORD2 -setFsPin KEYWORD2 -setDataInPin KEYWORD2 -setDataOutPin KEYWORD2 -setAllPins KEYWORD2 - -getSckPin KEYWORD2 -getFsPin KEYWORD2 -getDataPin KEYWORD2 -getDataInPin KEYWORD2 -getDataOutPin KEYWORD2 - -setDuplex KEYWORD2 -setSimplex KEYWORD2 -isDuplex KEYWORD2 - -setBufferSize KEYWORD2 -getBufferSize KEYWORD2 - -write KEYWORD2 -availableForWrite KEYWORD2 - -read KEYWORD2 -available KEYWORD2 - -gpioToAdcUnit KEYWORD2 -gpioToAdcChannel KEYWORD2 - -####################################### -# Constants (LITERAL1) -####################################### -I2S_PHILIPS_MODE LITERAL1 -I2S_RIGHT_JUSTIFIED_MODE LITERAL1 -I2S_LEFT_JUSTIFIED_MODE LITERAL1 -I2S_ADC_DAC LITERAL1 -I2S_PDM LITERAL1 - -PIN_I2S_SCK LITERAL1 -PIN_I2S_FS LITERAL1 -PIN_I2S_SD LITERAL1 -PIN_I2S_SD_OUT LITERAL1 diff --git a/libraries/I2S/library.properties b/libraries/I2S/library.properties deleted file mode 100644 index bb77e3061..000000000 --- a/libraries/I2S/library.properties +++ /dev/null @@ -1,9 +0,0 @@ -name=I2S -version=1.0 -author=Tomas Pilny -maintainer=Tomas Pilny -sentence=Enables the communication with devices that use the Inter-IC Sound (I2S) Bus. Specific implementation for ESP. -paragraph= -category=Communication -url=http://www.arduino.cc/en/Reference/I2S -architectures=esp32 diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp deleted file mode 100644 index 7414067eb..000000000 --- a/libraries/I2S/src/I2S.cpp +++ /dev/null @@ -1,1222 +0,0 @@ -/* - Copyright (c) 2016 Arduino LLC. All right reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include -#include -#include "I2S.h" -#include "freertos/semphr.h" - -#define _I2S_EVENT_QUEUE_LENGTH 16 -#define _I2S_DMA_BUFFER_COUNT 2 // BUFFER COUNT must be between 2 and 128 -#define I2S_INTERFACES_COUNT SOC_I2S_NUM - -#ifndef I2S_DEVICE - #define I2S_DEVICE 0 -#endif - -#ifndef I2S_CLOCK_GENERATOR - #define I2S_CLOCK_GENERATOR 0 // does nothing for ESP -#endif - -I2SClass::I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, uint8_t sckPin, uint8_t fsPin) : - _deviceIndex(deviceIndex), - _sdPin(sdPin), // shared data pin - _inSdPin(PIN_I2S_SD_IN), // input data pin - _outSdPin(PIN_I2S_SD), // output data pin - _sckPin(sckPin), // clock pin - _fsPin(fsPin), // frame (word) select pin - - _state(I2S_STATE_IDLE), - _bitsPerSample(0), - _sampleRate(0), - _mode(I2S_PHILIPS_MODE), - - _buffer_byte_size(0), - - _driverInstalled(false), - _initialized(false), - _callbackTaskHandle(NULL), - _i2sEventQueue(NULL), - _i2s_general_mutex(NULL), - _input_ring_buffer(NULL), - _output_ring_buffer(NULL), - _i2s_dma_buffer_size(128), // Number of frames in each DMA buffer. Frame size = number of channels * Bytes per sample; Must be between 8 and 1024 - _driveClock(true), - _peek_buff(0), - _peek_buff_valid(false), - _nesting_counter(0), - - _onTransmit(NULL), - _onReceive(NULL) -{ - _i2s_general_mutex = xSemaphoreCreateMutex(); - if(_i2s_general_mutex == NULL){ - log_e("I2S could not create internal mutex!"); - } -} - -int I2SClass::_createCallbackTask(){ - int stack_size = 20000; - if(_callbackTaskHandle != NULL){ - log_e("Callback task already exists!"); - return 0; // ERR - } - - xTaskCreate( - onDmaTransferComplete, // Function to implement the task - "onDmaTransferComplete", // Name of the task - stack_size, // Stack size in words - NULL, // Task input parameter - 2, // Priority of the task - &_callbackTaskHandle // Task handle. - ); - if(_callbackTaskHandle == NULL){ - log_e("Could not create callback task"); - return 0; // ERR - } - return 1; // OK -} - -int I2SClass::_installDriver(){ - if(_driverInstalled){ - log_e("I2S driver is already installed"); - return 0; // ERR - } - - esp_i2s::i2s_mode_t i2s_mode = (esp_i2s::i2s_mode_t)(esp_i2s::I2S_MODE_RX | esp_i2s::I2S_MODE_TX); - - if(_driveClock){ - i2s_mode = (esp_i2s::i2s_mode_t)(i2s_mode | esp_i2s::I2S_MODE_MASTER); - }else{ - i2s_mode = (esp_i2s::i2s_mode_t)(i2s_mode | esp_i2s::I2S_MODE_SLAVE); - } - - if(_mode == ADC_DAC_MODE){ - #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) - if(_bitsPerSample != 16){ // ADC/DAC can only work in 16-bit sample mode - log_e("ERROR invalid bps for ADC/DAC. Allowed only 16, requested %d", _bitsPerSample); - return 0; // ERR - } - i2s_mode = (esp_i2s::i2s_mode_t)(i2s_mode | esp_i2s::I2S_MODE_DAC_BUILT_IN | esp_i2s::I2S_MODE_ADC_BUILT_IN); - #else - log_e("This chip does not support ADC / DAC mode"); - return 0; // ERR - #endif - }else if(_mode == I2S_PHILIPS_MODE || - _mode == I2S_RIGHT_JUSTIFIED_MODE || - _mode == I2S_LEFT_JUSTIFIED_MODE){ // End of ADC/DAC mode; start of Normal Philips mode - if(_bitsPerSample != 8 && _bitsPerSample != 16 && _bitsPerSample != 24 && _bitsPerSample != 32){ - log_e("Invalid bits per sample for normal mode (requested %d)\nAllowed bps = 8 | 16 | 24 | 32", _bitsPerSample); - return 0; // ERR - } - if(_bitsPerSample == 24){ - log_w("Original Arduino library does not support 24 bits per sample.\nKeep that in mind if you should switch back to Arduino"); - } - }else if(_mode == PDM_STEREO_MODE || _mode == PDM_MONO_MODE){ // end of Normal Philips mode; start of PDM mode - #if (SOC_I2S_SUPPORTS_PDM_TX && SOC_I2S_SUPPORTS_PDM_RX) - i2s_mode = (esp_i2s::i2s_mode_t)(i2s_mode | esp_i2s::I2S_MODE_PDM); - #else - log_e("This chip does not support PDM"); - return 0; // ERR - #endif - } // Mode - esp_i2s::i2s_config_t i2s_config = { - .mode = i2s_mode, - .sample_rate = _sampleRate, - .bits_per_sample = (esp_i2s::i2s_bits_per_sample_t)_bitsPerSample, - .channel_format = esp_i2s::I2S_CHANNEL_FMT_RIGHT_LEFT, - .communication_format = (esp_i2s::i2s_comm_format_t)(esp_i2s::I2S_COMM_FORMAT_STAND_I2S), - .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2, - .dma_buf_count = _I2S_DMA_BUFFER_COUNT, - .dma_buf_len = _i2s_dma_buffer_size, - .use_apll = false, - #warning The following values are new and need to be checked - .tx_desc_auto_clear = true, - .fixed_mclk = 0, - .mclk_multiple = esp_i2s::I2S_MCLK_MULTIPLE_128, - .bits_per_chan = esp_i2s::I2S_BITS_PER_CHAN_DEFAULT -#if SOC_I2S_SUPPORTS_TDM - ,.chan_mask = esp_i2s::I2S_CHANNEL_STEREO, - .total_chan = 2, - .left_align = false, - .big_edin = false, - .bit_order_msb = false, - .skip_msk = false -#endif // SOC_I2S_SUPPORTS_TDM - }; - - if(_driveClock == false){ - i2s_config.use_apll = true; - i2s_config.fixed_mclk = 512*_sampleRate; - } - - // Install and start i2s driver - while(ESP_OK != esp_i2s::i2s_driver_install((esp_i2s::i2s_port_t) _deviceIndex, &i2s_config, _I2S_EVENT_QUEUE_LENGTH, &_i2sEventQueue)){ - // increase buffer size - if(2*_i2s_dma_buffer_size <= 1024){ - log_w("WARNING i2s driver install failed.\nTrying to increase I2S DMA buffer size from %d to %d\n", _i2s_dma_buffer_size, 2*_i2s_dma_buffer_size); - setBufferSize(2*_i2s_dma_buffer_size); - }else if(_i2s_dma_buffer_size < 1024){ - log_w("WARNING i2s driver install failed.\nTrying to decrease I2S DMA buffer size from %d to 1024\n", _i2s_dma_buffer_size); - setBufferSize(1024); - }else{ // install failed with max buffer size - log_e("ERROR i2s driver install failed"); - return 0; // ERR - } - } //try installing with increasing size - - if(_mode == I2S_RIGHT_JUSTIFIED_MODE || _mode == I2S_LEFT_JUSTIFIED_MODE || _mode == PDM_MONO_MODE){ // mono/single channel - // Set the clock for MONO. Stereo is not supported yet. - if(ESP_OK != esp_i2s::i2s_set_clk((esp_i2s::i2s_port_t) _deviceIndex, _sampleRate, (esp_i2s::i2s_bits_per_sample_t)_bitsPerSample, esp_i2s::I2S_CHANNEL_MONO)){ - log_e("Setting the I2S Clock has failed!\n"); - return 0; // ERR - } - } // mono channel mode - -#if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) - if(_mode == ADC_DAC_MODE){ - esp_i2s::i2s_set_dac_mode(esp_i2s::I2S_DAC_CHANNEL_BOTH_EN); - esp_i2s::adc_unit_t adc_unit; - if(!_gpioToAdcUnit((gpio_num_t)_inSdPin, &adc_unit)){ - log_e("pin to adc unit conversion failed"); - return 0; // ERR - } - esp_i2s::adc_channel_t adc_channel; - if(!_gpioToAdcChannel((gpio_num_t)_inSdPin, &adc_channel)){ - log_e("pin to adc channel conversion failed"); - return 0; // ERR - } - if(ESP_OK != esp_i2s::i2s_set_adc_mode(adc_unit, (esp_i2s::adc1_channel_t)adc_channel)){ - log_e("i2s_set_adc_mode failed"); - return 0; // ERR - } - if(ESP_OK != esp_i2s::i2s_set_pin((esp_i2s::i2s_port_t) _deviceIndex, NULL)){ - log_e("i2s_set_pin failed"); - return 0; // ERR - } - - if(adc_unit == esp_i2s::ADC_UNIT_1){ - esp_i2s::adc1_config_width(esp_i2s::ADC_WIDTH_BIT_12); - esp_i2s::adc1_config_channel_atten((esp_i2s::adc1_channel_t)adc_channel, esp_i2s::ADC_ATTEN_DB_11); - }else if(adc_unit == esp_i2s::ADC_UNIT_2){ - esp_i2s::adc2_config_channel_atten((esp_i2s::adc2_channel_t)adc_channel, esp_i2s::ADC_ATTEN_DB_11); - } - - esp_i2s::i2s_adc_enable((esp_i2s::i2s_port_t) _deviceIndex); - _driverInstalled = true; - }else // End of ADC/DAC mode -#endif // SOC_I2S_SUPPORTS_ADC_DAC - if(_mode == I2S_PHILIPS_MODE || _mode == I2S_RIGHT_JUSTIFIED_MODE || _mode == I2S_LEFT_JUSTIFIED_MODE || _mode == PDM_STEREO_MODE || _mode == PDM_MONO_MODE){ // if I2S mode - _driverInstalled = true; // IDF I2S driver must be installed before calling _applyPinSetting - if(!_applyPinSetting()){ - log_e("could not apply pin setting during driver install"); - _uninstallDriver(); - return 0; // ERR - } - } // if I2S _mode - return 1; // OK -} - -// Init in MASTER mode: the SCK and FS pins are driven as outputs using the sample rate -int I2SClass::begin(int mode, int sampleRate, int bitsPerSample){ - _take_if_not_holding(); - // master mode (driving clock and frame select pins - output) - int ret = begin(mode, sampleRate, bitsPerSample, true); - _give_if_top_call(); - return ret; -} - -// Init in SLAVE mode: the SCK and FS pins are inputs, other side controls sample rate -int I2SClass::begin(int mode, int bitsPerSample){ - _take_if_not_holding(); - // slave mode (not driving clock and frame select pin - input) - int ret = begin(mode, 96000, bitsPerSample, false); - _give_if_top_call(); - return ret; -} - - -// Core function -int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock){ - _take_if_not_holding(); - if(_initialized){ - log_e("ERROR: Object already initialized! Call I2S.end() to disable"); - _give_if_top_call(); - return 0; // ERR - } - _driveClock = driveClock; - _mode = mode; - _sampleRate = (uint32_t)sampleRate; - _bitsPerSample = bitsPerSample; - - // There is work in progress on this library. - if(_bitsPerSample == 16 && _sampleRate > 16000 && driveClock){ - log_w("This sample rate is not officially supported - audio might be noisy.\nTry using sample rate below or equal to 16000"); - } - if(_bitsPerSample != 16){ - log_w("This bit-per-sample is not officially supported - audio quality might suffer.\nTry using 16bps, with sample rate below or equal 16000"); - } - if(_mode != I2S_PHILIPS_MODE){ - log_w("This mode is not officially supported - audio quality might suffer.\nAt the moment the only supported mode is I2S_PHILIPS_MODE"); - } - - if (_state != I2S_STATE_IDLE && _state != I2S_STATE_DUPLEX) { - log_e("Error: unexpected _state (%d)", _state); - _give_if_top_call(); - return 0; // ERR - } - - switch (mode) { - case I2S_PHILIPS_MODE: - case I2S_RIGHT_JUSTIFIED_MODE: - case I2S_LEFT_JUSTIFIED_MODE: - - #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) - case ADC_DAC_MODE: - #endif - - case PDM_STEREO_MODE: - case PDM_MONO_MODE: - break; - - default: // invalid mode - log_e("ERROR: unknown mode"); - _give_if_top_call(); - return 0; // ERR - } - - if(!_installDriver()){ - log_e("ERROR: failed to install driver"); - end(); - _give_if_top_call(); - return 0; // ERR - } - - _buffer_byte_size = _i2s_dma_buffer_size * (_bitsPerSample / 8) * _I2S_DMA_BUFFER_COUNT * 2; - _input_ring_buffer = xRingbufferCreate(_buffer_byte_size, RINGBUF_TYPE_BYTEBUF); - _output_ring_buffer = xRingbufferCreate(_buffer_byte_size, RINGBUF_TYPE_BYTEBUF); - if(_input_ring_buffer == NULL || _output_ring_buffer == NULL){ - log_e("ERROR: could not create one or both internal buffers. Requested size = %d\n", _buffer_byte_size); - _give_if_top_call(); - return 0; // ERR - } - - if(!_createCallbackTask()){ - log_e("ERROR: failed to create callback task"); - end(); - _give_if_top_call(); - return 0; // ERR - } - _initialized = true; - _give_if_top_call(); - return 1; // OK -} - -int I2SClass::_applyPinSetting(){ - if(_driverInstalled){ - esp_i2s::i2s_pin_config_t pin_config = { - .mck_io_num = I2S_PIN_NO_CHANGE, - .bck_io_num = _sckPin, - .ws_io_num = _fsPin, - .data_out_num = I2S_PIN_NO_CHANGE, - .data_in_num = I2S_PIN_NO_CHANGE - }; - if (_state == I2S_STATE_DUPLEX){ // duplex - pin_config.data_out_num = _outSdPin; - pin_config.data_in_num = _inSdPin; - }else{ // simplex - if(_state == I2S_STATE_RECEIVER){ - pin_config.data_out_num = I2S_PIN_NO_CHANGE; - pin_config.data_in_num = _sdPin; - }else if(_state == I2S_STATE_TRANSMITTER){ - pin_config.data_out_num = _sdPin; - pin_config.data_in_num = I2S_PIN_NO_CHANGE; - }else{ - pin_config.data_out_num = I2S_PIN_NO_CHANGE; - pin_config.data_in_num = _sdPin; - } - } - if(ESP_OK != esp_i2s::i2s_set_pin((esp_i2s::i2s_port_t) _deviceIndex, &pin_config)){ - log_e("i2s_set_pin failed; attempted settings: SCK=%d; FS=%d; DIN=%d; DOUT=%d", pin_config.bck_io_num, pin_config.ws_io_num, pin_config.data_in_num, pin_config.data_out_num); - return 0; // ERR - }else{ - return 1; // OK - } - } // if(_driverInstalled) - return 1; // OK -} - -void I2SClass::_setSckPin(int sckPin){ - _take_if_not_holding(); - if(sckPin >= 0){ - _sckPin = sckPin; - }else{ - _sckPin = PIN_I2S_SCK; - } - _give_if_top_call(); -} - -int I2SClass::setSckPin(int sckPin){ - _take_if_not_holding(); - _setSckPin(sckPin); - int ret = _applyPinSetting(); - _applyPinSetting(); - _give_if_top_call(); - return ret; -} - -void I2SClass::_setFsPin(int fsPin){ - if(fsPin >= 0){ - _fsPin = fsPin; - }else{ - _fsPin = PIN_I2S_FS; - } -} - -int I2SClass::setFsPin(int fsPin){ - _take_if_not_holding(); - _setFsPin(fsPin); - int ret = _applyPinSetting(); - _give_if_top_call(); - return ret; -} - -// shared data pin for simplex -void I2SClass::_setDataPin(int sdPin){ - if(sdPin >= 0){ - _sdPin = sdPin; - }else{ - _sdPin = PIN_I2S_SD; - } -} - -// shared data pin for simplex -int I2SClass::setDataPin(int sdPin){ - _take_if_not_holding(); - _setDataPin(sdPin); - int ret = _applyPinSetting(); - _give_if_top_call(); - return ret; -} - -void I2SClass::_setDataInPin(int inSdPin){ - if(inSdPin >= 0){ - _inSdPin = inSdPin; - }else{ - _inSdPin = PIN_I2S_SD_IN; - } -} - -int I2SClass::setDataInPin(int inSdPin){ - _take_if_not_holding(); - _setDataInPin(inSdPin); - int ret = _applyPinSetting(); - _give_if_top_call(); - return ret; -} - -void I2SClass::_setDataOutPin(int outSdPin){ - if(outSdPin >= 0){ - _outSdPin = outSdPin; - }else{ - _outSdPin = PIN_I2S_SD; - } -} - -int I2SClass::setDataOutPin(int outSdPin){ - _take_if_not_holding(); - _setDataOutPin(outSdPin); - int ret = _applyPinSetting(); - _give_if_top_call(); - return ret; -} - -int I2SClass::setAllPins(){ - _take_if_not_holding(); - int ret = setAllPins(PIN_I2S_SCK, PIN_I2S_FS, PIN_I2S_SD, PIN_I2S_SD_OUT, PIN_I2S_SD_IN); - _give_if_top_call(); - return ret; -} - -int I2SClass::setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin){ - _take_if_not_holding(); - _setSckPin(sckPin); - _setFsPin(fsPin); - _setDataPin(sdPin); - _setDataOutPin(outSdPin); - _setDataInPin(inSdPin); - int ret = _applyPinSetting(); - _give_if_top_call(); - return ret; -} - -int I2SClass::setDuplex(){ - _take_if_not_holding(); - _state = I2S_STATE_DUPLEX; - _give_if_top_call(); - return 1; -} - -int I2SClass::setSimplex(){ - _take_if_not_holding(); - _state = I2S_STATE_IDLE; - _give_if_top_call(); - return 1; -} - -int I2SClass::isDuplex(){ - _take_if_not_holding(); - int ret = (int)(_state == I2S_STATE_DUPLEX); - _give_if_top_call(); - return ret; -} - -int I2SClass::getSckPin(){ - _take_if_not_holding(); - int ret = _sckPin; - _give_if_top_call(); - return ret; -} - -int I2SClass::getFsPin(){ - _take_if_not_holding(); - int ret = _fsPin; - _give_if_top_call(); - return ret; -} - -int I2SClass::getDataPin(){ - _take_if_not_holding(); - int ret = _sdPin; - _give_if_top_call(); - return ret; -} - -int I2SClass::getDataInPin(){ - _take_if_not_holding(); - int ret = _inSdPin; - _give_if_top_call(); - return ret; -} - -int I2SClass::getDataOutPin(){ - _take_if_not_holding(); - int ret = _outSdPin; - _give_if_top_call(); - return ret; -} - -void I2SClass::_uninstallDriver(){ - if(_driverInstalled){ - #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) - if(_mode == ADC_DAC_MODE){ - esp_i2s::i2s_adc_disable((esp_i2s::i2s_port_t) _deviceIndex); - } - #endif - esp_i2s::i2s_driver_uninstall((esp_i2s::i2s_port_t) _deviceIndex); - - if(_state != I2S_STATE_DUPLEX){ - _state = I2S_STATE_IDLE; - } - _driverInstalled = false; - } // if(_driverInstalled) -} - -void I2SClass::end(){ - _take_if_not_holding(); - if(xTaskGetCurrentTaskHandle() != _callbackTaskHandle){ - if(_callbackTaskHandle){ - vTaskDelete(_callbackTaskHandle); - _callbackTaskHandle = NULL; // prevent secondary termination to non-existing task - } - _uninstallDriver(); - _onTransmit = NULL; - _onReceive = NULL; - if(_input_ring_buffer != NULL){ - vRingbufferDelete(_input_ring_buffer); - _input_ring_buffer = NULL; - } - if(_output_ring_buffer != NULL){ - vRingbufferDelete(_output_ring_buffer); - _output_ring_buffer = NULL; - } - _initialized = false; - }else{ - log_w("WARNING: ending I2SClass from callback task not permitted, but attempted!"); - } - _give_if_top_call(); -} - -// Bytes available to read -int I2SClass::available(){ - _take_if_not_holding(); - int ret = 0; - if(_input_ring_buffer != NULL){ - ret = _buffer_byte_size - (int)xRingbufferGetCurFreeSize(_input_ring_buffer); - } - _give_if_top_call(); - return ret; -} - -union i2s_sample_t { - uint8_t b8; - int16_t b16; - int32_t b32; -}; - -int I2SClass::read(){ - _take_if_not_holding(); - i2s_sample_t sample; - sample.b32 = 0; - if(_initialized){ - read(&sample, _bitsPerSample / 8); - - if (_bitsPerSample == 32) { - _give_if_top_call(); - return sample.b32; - } else if (_bitsPerSample == 16) { - _give_if_top_call(); - return sample.b16; - } else if (_bitsPerSample == 8) { - _give_if_top_call(); - return sample.b8; - } else { - _give_if_top_call(); - return 0; // sample value - } - } // if(_initialized) - _give_if_top_call(); - return 0; // sample value -} - -int I2SClass::read(void* buffer, size_t size){ - _take_if_not_holding(); - size_t requested_size = size; - if(_initialized){ - if(!_enableReceiver()){ - _give_if_top_call(); - return 0; // There was an error switching to receiver - } // _enableReceiver succeeded ? - - size_t item_size = 0; - void *tmp_buffer; - if(_input_ring_buffer != NULL){ - if(_peek_buff_valid){ - memcpy(buffer, &_peek_buff, _bitsPerSample/8); - _peek_buff_valid = false; - requested_size -= _bitsPerSample/8; - } - tmp_buffer = xRingbufferReceiveUpTo(_input_ring_buffer, &item_size, pdMS_TO_TICKS(1000), requested_size); - if(tmp_buffer != NULL){ - memcpy(buffer, tmp_buffer, item_size); - #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) - if(_mode == ADC_DAC_MODE){ - for(size_t i = 0; i < item_size / 2; ++i){ - ((uint16_t*)buffer)[i] = ((uint16_t*)buffer)[i] & 0x0FFF; - } - } // ADC/DAC mode - #endif - vRingbufferReturnItem(_input_ring_buffer, tmp_buffer); - _give_if_top_call(); - return item_size; - }else{ - log_w("input buffer is empty - timed out"); - _give_if_top_call(); - return 0; // 0 Bytes read / ERR - } // tmp buffer not NULL ? - } // ring buffer not NULL ? - } // if(_initialized) - _give_if_top_call(); - return 0; // 0 Bytes read / ERR -} - -size_t I2SClass::write(uint8_t data){ - _take_if_not_holding(); - size_t ret = 0; - if(_initialized){ - ret = write_blocking((int32_t*)&data, 1); - } - _give_if_top_call(); - return ret; -} - -size_t I2SClass::write(int32_t sample){ - _take_if_not_holding(); - size_t ret = 0; - if(_initialized){ - ret = write_blocking(&sample, _bitsPerSample/8); - } - _give_if_top_call(); - return ret; -} - -size_t I2SClass::write(const uint8_t *buffer, size_t size){ - _take_if_not_holding(); - size_t ret = 0; - if(_initialized){ - ret = write((const void*)buffer, size); - } - _give_if_top_call(); - return ret; -} - -size_t I2SClass::write(const void *buffer, size_t size){ - _take_if_not_holding(); - size_t ret = 0; - if(_initialized){ - //size_t ret = write_blocking(buffer, size); - ret = write_nonblocking(buffer, size); - } // if(_initialized) - _give_if_top_call(); - return ret; -} - -// blocking version of write -// This version of write will wait indefinitely to write requested samples -// into output buffer -size_t I2SClass::write_blocking(const void *buffer, size_t size){ - _take_if_not_holding(); - if(_initialized){ - if(!_enableTransmitter()){ - _give_if_top_call(); - return 0; // There was an error switching to transmitter - } // _enableTransmitter succeeded ? - - if(_output_ring_buffer != NULL){ - int ret = xRingbufferSend(_output_ring_buffer, buffer, size, portMAX_DELAY); - if(pdTRUE == ret){ - _give_if_top_call(); - return size; - }else{ - log_e("xRingbufferSend() with infinite wait returned with error"); - _give_if_top_call(); - return 0; - } // ring buffer send ok ? - } // ring buffer not NULL ? - } // if(_initialized) - return 0; - log_w("I2S not initialized"); - _give_if_top_call(); - return 0; -} - -// non-blocking version of write -// In case there is not enough space in buffer to write requested size -// this function will try to flush the buffer and write requested data with 0 time-out -size_t I2SClass::write_nonblocking(const void *buffer, size_t size){ - _take_if_not_holding(); - if(_initialized){ - if(_state != I2S_STATE_TRANSMITTER && _state != I2S_STATE_DUPLEX){ - if(!_enableTransmitter()){ - _give_if_top_call(); - return 0; // There was an error switching to transmitter - } - } - if(availableForWrite() < size){ - flush(); - } - if(_output_ring_buffer != NULL){ - if(pdTRUE == xRingbufferSend(_output_ring_buffer, buffer, size, 0)){ - _give_if_top_call(); - return size; - }else{ - log_w("I2S could not write all data into ring buffer!"); - _give_if_top_call(); - return 0; - } - } - } // if(_initialized) - return 0; - _give_if_top_call(); // this should not be needed -} - -/* - Read 1 sample from internal buffer and return it. - Repeated peeks will return the same sample until read is called. -*/ -int I2SClass::peek(){ - _take_if_not_holding(); - int ret = 0; - if(_initialized && _input_ring_buffer != NULL && !_peek_buff_valid){ - size_t item_size = 0; - void *item = NULL; - - item = xRingbufferReceiveUpTo(_input_ring_buffer, &item_size, 0, _bitsPerSample/8); // fetch 1 sample - if (item != NULL && item_size == _bitsPerSample/8){ - _peek_buff = *((int*)item); - vRingbufferReturnItem(_input_ring_buffer, item); - _peek_buff_valid = true; - } - - } // if(_initialized) - if(_peek_buff_valid){ - ret = _peek_buff; - } - _give_if_top_call(); - return ret; -} - -void I2SClass::flush(){ - _take_if_not_holding(); - if(_initialized){ - const size_t single_dma_buf = _i2s_dma_buffer_size*(_bitsPerSample/8)*2; - size_t item_size = 0; - void *item = NULL; - if(_output_ring_buffer != NULL){ - item = xRingbufferReceiveUpTo(_output_ring_buffer, &item_size, 0, single_dma_buf); - if (item != NULL){ - _fix_and_write(item, item_size); - vRingbufferReturnItem(_output_ring_buffer, item); - } - } - } // if(_initialized) - _give_if_top_call(); -} - -// Bytes available to write -int I2SClass::availableForWrite(){ - _take_if_not_holding(); - int ret = 0; - if(_initialized){ - if(_output_ring_buffer != NULL){ - ret = (int)xRingbufferGetCurFreeSize(_output_ring_buffer); - } - } // if(_initialized) - _give_if_top_call(); - return ret; -} - -void I2SClass::onTransmit(void(*function)(void)){ - _take_if_not_holding(); - _onTransmit = function; - _give_if_top_call(); -} - -void I2SClass::onReceive(void(*function)(void)){ - _take_if_not_holding(); - _onReceive = function; - _give_if_top_call(); -} - -int I2SClass::setBufferSize(int bufferSize){ - _take_if_not_holding(); - int ret = 0; - if(bufferSize >= 8 && bufferSize <= 1024){ - _i2s_dma_buffer_size = bufferSize; - }else{ - log_e("setBufferSize: wrong input! Buffer size must be between 8 and 1024. Requested %d", bufferSize); - _give_if_top_call(); - return 0; // ERR - } // check requested buffer size - - if(_initialized){ - _uninstallDriver(); - ret = _installDriver(); - _give_if_top_call(); - return ret; - }else{ // check requested buffer size - _give_if_top_call(); - return 1; // It's ok to change buffer size for uninitialized driver - new size will be used on begin() - } // if(_initialized) - _give_if_top_call(); - return 0; // ERR -} - -int I2SClass::getBufferSize(){ - _take_if_not_holding(); - int ret = _i2s_dma_buffer_size; - _give_if_top_call(); - return ret; -} - -int I2SClass::_enableTransmitter(){ - if(_state != I2S_STATE_DUPLEX && _state != I2S_STATE_TRANSMITTER){ - _state = I2S_STATE_TRANSMITTER; - return _applyPinSetting(); - } - return 1; // Ok -} - -int I2SClass::_enableReceiver(){ - if(_state != I2S_STATE_DUPLEX && _state != I2S_STATE_RECEIVER){ - _state = I2S_STATE_RECEIVER; - return _applyPinSetting(); - } - return 1; // Ok -} - -void I2SClass::_tx_done_routine(uint8_t* prev_item){ - static bool prev_item_valid = false; - const size_t single_dma_buf = _i2s_dma_buffer_size*(_bitsPerSample/8)*2; // *2 for stereo - it has double number of samples for 2 channels - static size_t item_size = 0; - static size_t prev_item_size = 0; - static void *item = NULL; - static int prev_item_offset = 0; - static size_t bytes_written = 0; - - if(prev_item_valid){ // use item from previous round - _fix_and_write(prev_item+prev_item_offset, prev_item_size, &bytes_written); - if(prev_item_size == bytes_written){ - prev_item_valid = false; - } // write size check - prev_item_offset = bytes_written; - prev_item_size -= bytes_written; - } // prev_item_valid - - if(_output_ring_buffer != NULL && (_buffer_byte_size - xRingbufferGetCurFreeSize(_output_ring_buffer) >= single_dma_buf)){ // fill up the I2S DMA buffer - bytes_written = 0; - item_size = 0; - if(_buffer_byte_size - xRingbufferGetCurFreeSize(_output_ring_buffer) >= _i2s_dma_buffer_size*(_bitsPerSample/8)){ // don't read from almost empty buffer - item = xRingbufferReceiveUpTo(_output_ring_buffer, &item_size, pdMS_TO_TICKS(0), single_dma_buf); - if (item != NULL){ - _fix_and_write(item, item_size, &bytes_written); - if(item_size != bytes_written){ // save item that was not written correctly for later - memcpy(prev_item, (void*)&((uint8_t*)item)[bytes_written], item_size-bytes_written); - prev_item_size = item_size - bytes_written; - prev_item_offset = 0; - prev_item_valid = true; - } // save item that was not written correctly for later - vRingbufferReturnItem(_output_ring_buffer, item); - } // Check received item - } // don't read from almost empty buffer - } // fill up the I2S DMA buffer - if(_onTransmit){ - _onTransmit(); - } // user callback -} - -void I2SClass::_rx_done_routine(){ - size_t bytes_read = 0; - const size_t single_dma_buf = _i2s_dma_buffer_size*(_bitsPerSample/8); - - if(_input_ring_buffer != NULL){ - uint8_t *_inputBuffer = (uint8_t*)malloc(_i2s_dma_buffer_size*4); - size_t avail = xRingbufferGetCurFreeSize(_input_ring_buffer); - if(avail > 0){ - esp_err_t ret = esp_i2s::i2s_read((esp_i2s::i2s_port_t) _deviceIndex, _inputBuffer, avail <= single_dma_buf ? avail : single_dma_buf, (size_t*) &bytes_read, 0); - if(ret != ESP_OK){ - log_w("i2s_read returned with error %d", ret); - } - _post_read_data_fix(_inputBuffer, &bytes_read); - } - - if(bytes_read > 0){ // when read more than 0, then send to ring buffer - if(pdTRUE != xRingbufferSend(_input_ring_buffer, _inputBuffer, bytes_read, 0)){ - log_w("I2S failed to send item from DMA to internal buffer\n"); - } // xRingbufferSendComplete - } // if(bytes_read > 0) - free(_inputBuffer); - if (_onReceive && avail < _buffer_byte_size){ // when user callback is registered && and there is some data in ring buffer to read - _onReceive(); - } // user callback - } -} - -void I2SClass::_onTransferComplete(){ - uint8_t prev_item[_i2s_dma_buffer_size*4]; - esp_i2s::i2s_event_t i2s_event; - - while(true){ - xQueueReceive(_i2sEventQueue, &i2s_event, portMAX_DELAY); - if(i2s_event.type == esp_i2s::I2S_EVENT_TX_DONE){ - _tx_done_routine(prev_item); - }else if(i2s_event.type == esp_i2s::I2S_EVENT_RX_DONE){ - _rx_done_routine(); - } // RX Done - } // infinite loop -} - -void I2SClass::onDmaTransferComplete(void*){ - I2S._onTransferComplete(); -} - -void I2SClass::_take_if_not_holding(){ - TaskHandle_t mutex_holder = xSemaphoreGetMutexHolder(_i2s_general_mutex); - if(mutex_holder != NULL && mutex_holder == xTaskGetCurrentTaskHandle()){ - ++_nesting_counter; - return; // we are already holding this mutex - no need to take it - } - - // we are not holding the mutex - wait for it and take it - if(xSemaphoreTake(_i2s_general_mutex, portMAX_DELAY) != pdTRUE ){ - log_e("I2S internal mutex take returned with error"); - } - //_give_if_top_call(); // call after this function -} - -void I2SClass::_give_if_top_call(){ - if(_nesting_counter){ - --_nesting_counter; - }else{ - if(xSemaphoreGive(_i2s_general_mutex) != pdTRUE){ - log_e("I2S internal mutex give error"); - } - } -} - - -// Fixes data in-situ received from esp i2s driver. After fixing they reflect what was on the bus. -// input - bytes as received from i2s_read - this serves as input and output buffer -// size - number of bytes (this may be changed during operation) -void I2SClass::_post_read_data_fix(void *input, size_t *size){ - ulong dst_ptr = 0; - switch(_bitsPerSample){ - case 8: - for(int i = 0; i < *size; i+=4){ - ((uint8_t*)input)[dst_ptr++] = ((uint8_t*)input)[i+3]; - ((uint8_t*)input)[dst_ptr++] = ((uint8_t*)input)[i+1]; - } - *size /= 2; - break; - case 16: - uint16_t tmp; - for(int i = 0; i < *size/2; i+=2){ - tmp = ((uint16_t*)input)[i]; - ((uint16_t*)input)[dst_ptr++] = ((uint16_t*)input)[i+1]; - ((uint16_t*)input)[dst_ptr++] = tmp; - } - break; - default: ; // Do nothing - } // switch -} - -// Prepares data and writes them to IDF i2s driver. -// This counters possible bug in ESP IDF I2S driver -// output - bytes to be sent -// size - number of bytes in original buffer -// bytes_written - number of bytes used from original buffer -// actual_bytes_written - number of bytes written by i2s_write after fix -void I2SClass::_fix_and_write(void *output, size_t size, size_t *bytes_written, size_t *actual_bytes_written){ - ulong src_ptr = 0; - uint8_t* buff = NULL; - size_t buff_size = size; - switch(_bitsPerSample){ - case 8: - buff_size = size *2; - buff = (uint8_t*)calloc(buff_size, sizeof(uint8_t)); - if(buff == NULL){ - log_e("callock error"); - if(bytes_written != NULL){ *bytes_written = 0; } - return; - } - for(int i = 0; i < buff_size ; i+=4){ - ((uint8_t*)buff)[i+3] = (uint16_t)((uint8_t*)output)[src_ptr++]; - ((uint8_t*)buff)[i+1] = (uint16_t)((uint8_t*)output)[src_ptr++]; - } - break; - case 16: - buff = (uint8_t*)malloc(buff_size); - if(buff == NULL){ - log_e("malloc error"); - if(bytes_written != NULL){ *bytes_written = 0; } - return; - } - for(int i = 0; i < size/2; i += 2 ){ - ((uint16_t*)buff)[i] = ((uint16_t*)output)[i+1]; // [1] <- [0] - ((uint16_t*)buff)[i+1] = ((uint16_t*)output)[i]; // [0] <- [1] - } - break; - case 24: - buff = (uint8_t*)output; - break; - case 32: - buff = (uint8_t*)output; - break; - default: ; // Do nothing - } // switch - - size_t _bytes_written; - esp_err_t ret = esp_i2s::i2s_write((esp_i2s::i2s_port_t) _deviceIndex, buff, buff_size, &_bytes_written, 0); // fixed - if(ret != ESP_OK){ - log_e("Error: writing data to i2s - function returned with err code %d", ret); - } - if(ret == ESP_OK && buff_size != _bytes_written){ - log_w("Warning: writing data to i2s - written %d B instead of requested %d B", _bytes_written, buff_size); - } - // free if the buffer was actually allocated - if(_bitsPerSample == 8 || _bitsPerSample == 16){ - free(buff); - } - if(bytes_written != NULL){ - *bytes_written = _bitsPerSample == 8 ? _bytes_written/2 : _bytes_written; - } - if(actual_bytes_written != NULL){ - *actual_bytes_written = _bytes_written; - } -} - - -#if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) -int I2SClass::_gpioToAdcUnit(gpio_num_t gpio_num, esp_i2s::adc_unit_t* adc_unit){ - switch(gpio_num){ -#if CONFIG_IDF_TARGET_ESP32 - // ADC 1 - case GPIO_NUM_36: - case GPIO_NUM_37: - case GPIO_NUM_38: - case GPIO_NUM_39: - case GPIO_NUM_32: - case GPIO_NUM_33: - case GPIO_NUM_34: - case GPIO_NUM_35: - *adc_unit = esp_i2s::ADC_UNIT_1; - return 1; // OK - - // ADC 2 - case GPIO_NUM_0: - log_w("GPIO 0 for ADC should not be used for dev boards due to external auto program circuits."); - case GPIO_NUM_4: - case GPIO_NUM_2: - case GPIO_NUM_15: - case GPIO_NUM_13: - case GPIO_NUM_12: - case GPIO_NUM_14: - case GPIO_NUM_27: - case GPIO_NUM_25: - case GPIO_NUM_26: - *adc_unit = esp_i2s::ADC_UNIT_2; - return 1; // OK -#endif - -#if (CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3) - case GPIO_NUM_1: - case GPIO_NUM_2: - case GPIO_NUM_3: - case GPIO_NUM_4: - case GPIO_NUM_5: - case GPIO_NUM_6: - case GPIO_NUM_7: - case GPIO_NUM_8: - case GPIO_NUM_9: - case GPIO_NUM_10: - *adc_unit = esp_i2s::ADC_UNIT_1; - return 1; // OK -#endif - -#if CONFIG_IDF_TARGET_ESP32S2 - case GPIO_NUM_11: - case GPIO_NUM_12: - case GPIO_NUM_13: - case GPIO_NUM_14: - case GPIO_NUM_15: - case GPIO_NUM_16: - case GPIO_NUM_17: - case GPIO_NUM_18: - case GPIO_NUM_19: - case GPIO_NUM_20: - *adc_unit = esp_i2s::ADC_UNIT_2; - return 1; // OK -#endif - -#if (CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2) - case GPIO_NUM_0: - case GPIO_NUM_1: - case GPIO_NUM_2: - case GPIO_NUM_3: - case GPIO_NUM_4: - *adc_unit = esp_i2s::ADC_UNIT_1; - return 1; // OK - case GPIO_NUM_5: - *adc_unit = esp_i2s::ADC_UNIT_2; - return 1; // OK -#endif - default: - log_e("GPIO %d not usable for ADC!", gpio_num); - log_i("Please refer to documentation https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/gpio.html"); - return 0; // ERR - } -} - -int I2SClass::_gpioToAdcChannel(gpio_num_t gpio_num, esp_i2s::adc_channel_t* adc_channel){ - switch(gpio_num){ -#if CONFIG_IDF_TARGET_ESP32 - // ADC 1 - case GPIO_NUM_36: *adc_channel = esp_i2s::ADC_CHANNEL_0; return 1; // OK - case GPIO_NUM_37: *adc_channel = esp_i2s::ADC_CHANNEL_1; return 1; // OK - case GPIO_NUM_38: *adc_channel = esp_i2s::ADC_CHANNEL_2; return 1; // OK - case GPIO_NUM_39: *adc_channel = esp_i2s::ADC_CHANNEL_3; return 1; // OK - case GPIO_NUM_32: *adc_channel = esp_i2s::ADC_CHANNEL_4; return 1; // OK - case GPIO_NUM_33: *adc_channel = esp_i2s::ADC_CHANNEL_5; return 1; // OK - case GPIO_NUM_34: *adc_channel = esp_i2s::ADC_CHANNEL_6; return 1; // OK - case GPIO_NUM_35: *adc_channel = esp_i2s::ADC_CHANNEL_7; return 1; // OK - - // ADC 2 - case GPIO_NUM_0: - log_w("GPIO 0 for ADC should not be used for dev boards due to external auto program circuits."); - *adc_channel = esp_i2s::ADC_CHANNEL_1; return 1; // OK - case GPIO_NUM_4: *adc_channel = esp_i2s::ADC_CHANNEL_0; return 1; // OK - case GPIO_NUM_2: *adc_channel = esp_i2s::ADC_CHANNEL_2; return 1; // OK - case GPIO_NUM_15: *adc_channel = esp_i2s::ADC_CHANNEL_3; return 1; // OK - case GPIO_NUM_13: *adc_channel = esp_i2s::ADC_CHANNEL_4; return 1; // OK - case GPIO_NUM_12: *adc_channel = esp_i2s::ADC_CHANNEL_5; return 1; // OK - case GPIO_NUM_14: *adc_channel = esp_i2s::ADC_CHANNEL_6; return 1; // OK - case GPIO_NUM_27: *adc_channel = esp_i2s::ADC_CHANNEL_7; return 1; // OK - case GPIO_NUM_25: *adc_channel = esp_i2s::ADC_CHANNEL_8; return 1; // OK - case GPIO_NUM_26: *adc_channel = esp_i2s::ADC_CHANNEL_9; return 1; // OK -#endif - -#if (CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3) - case GPIO_NUM_1: *adc_channel = esp_i2s::ADC_CHANNEL_0; return 1; // OK - case GPIO_NUM_2: *adc_channel = esp_i2s::ADC_CHANNEL_1; return 1; // OK - case GPIO_NUM_3: *adc_channel = esp_i2s::ADC_CHANNEL_2; return 1; // OK - case GPIO_NUM_4: *adc_channel = esp_i2s::ADC_CHANNEL_3; return 1; // OK - case GPIO_NUM_5: *adc_channel = esp_i2s::ADC_CHANNEL_4; return 1; // OK - case GPIO_NUM_6: *adc_channel = esp_i2s::ADC_CHANNEL_5; return 1; // OK - case GPIO_NUM_7: *adc_channel = esp_i2s::ADC_CHANNEL_6; return 1; // OK - case GPIO_NUM_8: *adc_channel = esp_i2s::ADC_CHANNEL_7; return 1; // OK - case GPIO_NUM_9: *adc_channel = esp_i2s::ADC_CHANNEL_8; return 1; // OK - case GPIO_NUM_10: *adc_channel = esp_i2s::ADC_CHANNEL_9; return 1; // OK -#endif - -#if CONFIG_IDF_TARGET_ESP32S2 - case GPIO_NUM_11: *adc_channel = esp_i2s::ADC_CHANNEL_0; return 1; // OK - case GPIO_NUM_12: *adc_channel = esp_i2s::ADC_CHANNEL_1; return 1; // OK - case GPIO_NUM_13: *adc_channel = esp_i2s::ADC_CHANNEL_2; return 1; // OK - case GPIO_NUM_14: *adc_channel = esp_i2s::ADC_CHANNEL_3; return 1; // OK - case GPIO_NUM_15: *adc_channel = esp_i2s::ADC_CHANNEL_4; return 1; // OK - case GPIO_NUM_16: *adc_channel = esp_i2s::ADC_CHANNEL_5; return 1; // OK - case GPIO_NUM_17: *adc_channel = esp_i2s::ADC_CHANNEL_6; return 1; // OK - case GPIO_NUM_18: *adc_channel = esp_i2s::ADC_CHANNEL_7; return 1; // OK - case GPIO_NUM_19: *adc_channel = esp_i2s::ADC_CHANNEL_8; return 1; // OK - case GPIO_NUM_20: *adc_channel = esp_i2s::ADC_CHANNEL_9; return 1; // OK -#endif - -#if (CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2) - case GPIO_NUM_0: *adc_channel = esp_i2s::ADC_CHANNEL_0; return 1; // OK - case GPIO_NUM_1: *adc_channel = esp_i2s::ADC_CHANNEL_1; return 1; // OK - case GPIO_NUM_2: *adc_channel = esp_i2s::ADC_CHANNEL_2; return 1; // OK - case GPIO_NUM_3: *adc_channel = esp_i2s::ADC_CHANNEL_3; return 1; // OK - case GPIO_NUM_4: *adc_channel = esp_i2s::ADC_CHANNEL_4; return 1; // OK - case GPIO_NUM_5: *adc_channel = esp_i2s::ADC_CHANNEL_0; return 1; // OK -#endif - default: - log_e("GPIO %d not usable for ADC!", gpio_num); - log_i("Please refer to documentation https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/gpio.html"); - return 0; // ERR - } -} -#endif // SOC_I2S_SUPPORTS_ADC_DAC - -#if I2S_INTERFACES_COUNT > 0 - I2SClass I2S(I2S_DEVICE, I2S_CLOCK_GENERATOR, PIN_I2S_SD, PIN_I2S_SCK, PIN_I2S_FS); // default - half duplex -#endif - -#if I2S_INTERFACES_COUNT > 1 - // TODO set default pins for second module - //I2SClass I2S1(I2S_DEVICE+1, I2S_CLOCK_GENERATOR, PIN_I2S_SD, PIN_I2S_SCK, PIN_I2S_FS); // default - half duplex -#endif diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h deleted file mode 100644 index 623fa8917..000000000 --- a/libraries/I2S/src/I2S.h +++ /dev/null @@ -1,195 +0,0 @@ -/* - Copyright (c) 2016 Arduino LLC. All right reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef _I2S_H_INCLUDED -#define _I2S_H_INCLUDED - -#include -#include "freertos/ringbuf.h" - -namespace esp_i2s { - #include "driver/i2s.h" // ESP specific i2s driver -} - -// Default pins -#ifndef PIN_I2S_SCK - #define PIN_I2S_SCK 14 -#endif - -#ifndef PIN_I2S_FS - #if CONFIG_IDF_TARGET_ESP32S2 - #define PIN_I2S_FS 27 - #else - #define PIN_I2S_FS 25 - #endif -#endif - -#ifndef PIN_I2S_SD - #define PIN_I2S_SD 26 -#endif - -#ifndef PIN_I2S_SD_OUT - #define PIN_I2S_SD_OUT 26 -#endif - -#ifndef PIN_I2S_SD_IN - #define PIN_I2S_SD_IN 35 // Pin 35 is only input! -#endif - -typedef enum { - I2S_PHILIPS_MODE, - I2S_RIGHT_JUSTIFIED_MODE, - I2S_LEFT_JUSTIFIED_MODE, - ADC_DAC_MODE, - PDM_STEREO_MODE, - PDM_MONO_MODE -} i2s_mode_t; - -class I2SClass : public Stream -{ -public: - // The device index and pins must map to the "COM" pads in Table 6-1 of the datasheet - I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, uint8_t sckPin, uint8_t fsPin); - - // Init in MASTER mode: the SCK and FS pins are driven as outputs using the sample rate - int begin(int mode, int sampleRate, int bitsPerSample); - - // Init in SLAVE mode: the SCK and FS pins are inputs, other side controls sample rate - int begin(int mode, int bitsPerSample); - - // change pin setup and mode (default is Half Duplex) - // Can be called only on initialized object (after begin) - int setSckPin(int sckPin); - int setFsPin(int fsPin); - int setDataPin(int sdPin); // shared data pin for simplex - int setDataOutPin(int outSdPin); - int setDataInPin(int inSdPin); - - int setAllPins(); - int setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin); - - int getSckPin(); - int getFsPin(); - int getDataPin(); - int getDataOutPin(); - int getDataInPin(); - - int setDuplex(); - int setSimplex(); - int isDuplex(); - - void end(); - - // from Stream - virtual int available(); - virtual int read(); - virtual int peek(); - virtual void flush(); - - // from Print - virtual size_t write(uint8_t); - virtual size_t write(const uint8_t *buffer, size_t size); - - virtual int availableForWrite(); - - int read(void* buffer, size_t size); - - //size_t write(int); - size_t write(int32_t); - size_t write(const void *buffer, size_t size); - size_t write_blocking(const void *buffer, size_t size); - size_t write_nonblocking(const void *buffer, size_t size); - - void onTransmit(void(*)(void)); - void onReceive(void(*)(void)); - - int setBufferSize(int bufferSize); - int getBufferSize(); -private: - #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) - int _gpioToAdcUnit(gpio_num_t gpio_num, esp_i2s::adc_unit_t* adc_unit); - int _gpioToAdcChannel(gpio_num_t gpio_num, esp_i2s::adc_channel_t* adc_channel); - #endif - int begin(int mode, int sampleRate, int bitsPerSample, bool driveClock); - - int _enableTransmitter(); - int _enableReceiver(); - void _onTransferComplete(); - - int _createCallbackTask(); - - static void onDmaTransferComplete(void*); - int _installDriver(); - void _uninstallDriver(); - void _setSckPin(int sckPin); - void _setFsPin(int fsPin); - void _setDataPin(int sdPin); - void _setDataOutPin(int outSdPin); - void _setDataInPin(int inSdPin); - int _applyPinSetting(); - -private: - typedef enum { - I2S_STATE_IDLE, - I2S_STATE_TRANSMITTER, - I2S_STATE_RECEIVER, - I2S_STATE_DUPLEX - } i2s_state_t; - - int _deviceIndex; - int _sdPin; - int _inSdPin; - int _outSdPin; - int _sckPin; - int _fsPin; - - i2s_state_t _state; - int _bitsPerSample; - uint32_t _sampleRate; - int _mode; - - uint16_t _buffer_byte_size; - - bool _driverInstalled; // Is IDF I2S driver installed? - bool _initialized; // Is everything initialized (callback task, I2S driver, ring buffers)? - TaskHandle_t _callbackTaskHandle; - QueueHandle_t _i2sEventQueue; - SemaphoreHandle_t _i2s_general_mutex; - RingbufHandle_t _input_ring_buffer; - RingbufHandle_t _output_ring_buffer; - int _i2s_dma_buffer_size; - bool _driveClock; - uint32_t _peek_buff; - bool _peek_buff_valid; - - void _tx_done_routine(uint8_t* prev_item); - void _rx_done_routine(); - - uint16_t _nesting_counter; - void _take_if_not_holding(); - void _give_if_top_call(); - void _post_read_data_fix(void *input, size_t *size); - void _fix_and_write(void *output, size_t size, size_t *bytes_written = NULL, size_t *actual_bytes_written = NULL); - - void (*_onTransmit)(void); - void (*_onReceive)(void); -}; - -extern I2SClass I2S; - -#endif