From 306cf89eff27ca902ccb40c55e6cb3c4ac366406 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Fri, 12 Aug 2022 10:26:38 +0200 Subject: [PATCH 01/46] Initial support for second I2S - not tested --- libraries/I2S/README.md | 7 ++ libraries/I2S/src/I2S.cpp | 151 +++++++++++++++++++++----------------- libraries/I2S/src/I2S.h | 29 +++++++- 3 files changed, 118 insertions(+), 69 deletions(-) create mode 100644 libraries/I2S/README.md diff --git a/libraries/I2S/README.md b/libraries/I2S/README.md new file mode 100644 index 00000000000..6573606bbe0 --- /dev/null +++ b/libraries/I2S/README.md @@ -0,0 +1,7 @@ +# I2S / IIS / Inter-IC Sound + +This library is base od Arduino I2S library (see doc [here](https://docs.arduino.cc/learn/built-in-libraries/i2s)) +This library mimics the behavior and extends possibilities with ESP-specific functionalities. +For example Arduino I2S can operate on 8, 16 and 32 bits per sample, ESP adds 24 bits per sample. +In this document you will find description of functionality which is not available in Arduino and therefore is not described in the Arduino documentation. + diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index 78dc5c202df..4d029b4228f 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -23,11 +23,6 @@ #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 @@ -66,14 +61,14 @@ I2SClass::I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, u { _i2s_general_mutex = xSemaphoreCreateMutex(); if(_i2s_general_mutex == NULL){ - log_e("I2S could not create internal mutex!"); + log_e("(I2S#%d) I2S could not create internal mutex!", _deviceIndex); } } int I2SClass::_createCallbackTask(){ int stack_size = 20000; if(_callbackTaskHandle != NULL){ - log_e("Callback task already exists!"); + log_e("(I2S#%d) Callback task already exists!", _deviceIndex); return 0; // ERR } @@ -81,12 +76,12 @@ int I2SClass::_createCallbackTask(){ onDmaTransferComplete, // Function to implement the task "onDmaTransferComplete", // Name of the task stack_size, // Stack size in words - NULL, // Task input parameter + (void *)_deviceIndex, // Task input parameter 2, // Priority of the task &_callbackTaskHandle // Task handle. ); if(_callbackTaskHandle == NULL){ - log_e("Could not create callback task"); + log_e("(I2S#%d) Could not create callback task", _deviceIndex); return 0; // ERR } return 1; // OK @@ -94,7 +89,12 @@ int I2SClass::_createCallbackTask(){ int I2SClass::_installDriver(){ if(_driverInstalled){ - log_e("I2S driver is already installed"); + log_e("(I2S#%d) I2S driver is already installed", _deviceIndex); + return 0; // ERR + } + + if(_deviceIndex >= SOC_I2S_NUM){ + log_e("Max allowed I2S device number is %d but requested %d", SOC_I2S_NUM-1, _deviceIndex); return 0; // ERR } @@ -109,29 +109,29 @@ int I2SClass::_installDriver(){ 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); + log_e("(I2S#%d) ERROR invalid bps for ADC/DAC. Allowed only 16, requested %d", _deviceIndex, _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"); + log_e("(I2S#%d) This chip does not support ADC / DAC mode", _deviceIndex); 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); + log_e("(I2S#%d) Invalid bits per sample for normal mode (requested %d)\nAllowed bps = 8 | 16 | 24 | 32", _deviceIndex, _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"); + log_w("(I2S#%d) Original Arduino library does not support 24 bits per sample.\nKeep that in mind if you should switch back to Arduino", _deviceIndex); } }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"); + log_e("(I2S#%d) This chip does not support PDM", _deviceIndex); return 0; // ERR #endif } // Mode @@ -156,21 +156,22 @@ int I2SClass::_installDriver(){ 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); + log_w("(I2S#%d) WARNING i2s driver install failed.\nTrying to increase I2S DMA buffer size from %d to %d\n", _deviceIndex, _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); + log_w("(I2S#%d) WARNING i2s driver install failed.\nTrying to decrease I2S DMA buffer size from %d to 1024\n", _deviceIndex, _i2s_dma_buffer_size); setBufferSize(1024); }else{ // install failed with max buffer size - log_e("ERROR i2s driver install failed"); + log_e("(I2S#%d) ERROR i2s driver install failed", _deviceIndex); return 0; // ERR } } //try installing with increasing size +// TODO add check for unit or PDM support - only unit 0 support, unit 1 cannot un in PDM 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"); + log_e("(I2S#%d) Setting the I2S Clock has failed!\n", _deviceIndex); return 0; // ERR } } // mono channel mode @@ -180,20 +181,20 @@ int I2SClass::_installDriver(){ 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"); + log_e("(I2S#%d) pin to adc unit conversion failed", _deviceIndex); 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"); + log_e("(I2S#%d) pin to adc channel conversion failed", _deviceIndex); 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"); + log_e("(I2S#%d) i2s_set_adc_mode failed", _deviceIndex); return 0; // ERR } if(ESP_OK != esp_i2s::i2s_set_pin((esp_i2s::i2s_port_t) _deviceIndex, NULL)){ - log_e("i2s_set_pin failed"); + log_e("(I2S#%d) i2s_set_pin failed", _deviceIndex); return 0; // ERR } @@ -211,7 +212,7 @@ int I2SClass::_installDriver(){ 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"); + log_e("(I2S#%d) could not apply pin setting during driver install", _deviceIndex); _uninstallDriver(); return 0; // ERR } @@ -242,28 +243,29 @@ int I2SClass::begin(int mode, int bitsPerSample){ 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"); + log_e("(I2S#%d) ERROR: Object already initialized! Call I2S.end() to disable", _deviceIndex); _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"); + log_w("(I2S#%d) This sample rate is not officially supported - audio might be noisy.\nTry using sample rate below or equal to 16000", _deviceIndex); } 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"); + log_w("(I2S#%d) This bit-per-sample is not officially supported - audio quality might suffer.\nTry using 16bps, with sample rate below or equal 16000", _deviceIndex); } 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"); + log_w("(I2S#%d) This mode is not officially supported - audio quality might suffer.\nAt the moment the only supported mode is I2S_PHILIPS_MODE", _deviceIndex); } if (_state != I2S_STATE_IDLE && _state != I2S_STATE_DUPLEX) { - log_e("Error: unexpected _state (%d)", _state); + log_e("(I2S#%d) Error: unexpected _state (%d)", _deviceIndex, _state); _give_if_top_call(); return 0; // ERR } @@ -282,13 +284,12 @@ int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock break; default: // invalid mode - log_e("ERROR: unknown mode"); + log_e("(I2S#%d) ERROR: unknown mode", _deviceIndex); _give_if_top_call(); return 0; // ERR } - if(!_installDriver()){ - log_e("ERROR: failed to install driver"); + log_e("(I2S#%d) ERROR: failed to install driver", _deviceIndex); end(); _give_if_top_call(); return 0; // ERR @@ -298,13 +299,13 @@ int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock _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); + log_e("(I2S#%d) ERROR: could not create one or both internal buffers. Requested size = %d\n", _deviceIndex, _buffer_byte_size); _give_if_top_call(); return 0; // ERR } if(!_createCallbackTask()){ - log_e("ERROR: failed to create callback task"); + log_e("(I2S#%d) ERROR: failed to create callback task", _deviceIndex); end(); _give_if_top_call(); return 0; // ERR @@ -338,7 +339,7 @@ int I2SClass::_applyPinSetting(){ } } 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); + log_e("(I2S#%d) i2s_set_pin failed; attempted settings: SCK=%d; FS=%d; DIN=%d; DOUT=%d", _deviceIndex, 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 @@ -543,7 +544,7 @@ void I2SClass::end(){ } _initialized = false; }else{ - log_w("WARNING: ending I2SClass from callback task not permitted, but attempted!"); + log_w("(I2S#%d) WARNING: ending I2SClass from callback task not permitted, but attempted!", _deviceIndex); } _give_if_top_call(); } @@ -621,7 +622,7 @@ int I2SClass::read(void* buffer, size_t size){ _give_if_top_call(); return item_size; }else{ - log_w("input buffer is empty - timed out"); + log_w("(I2S#%d) input buffer is empty - timed out", _deviceIndex); _give_if_top_call(); return 0; // 0 Bytes read / ERR } // tmp buffer not NULL ? @@ -689,14 +690,14 @@ size_t I2SClass::write_blocking(const void *buffer, size_t size){ _give_if_top_call(); return size; }else{ - log_e("xRingbufferSend() with infinite wait returned with error"); + log_e("(I2S#%d) xRingbufferSend() with infinite wait returned with error", _deviceIndex); _give_if_top_call(); return 0; } // ring buffer send ok ? } // ring buffer not NULL ? } // if(_initialized) return 0; - log_w("I2S not initialized"); + log_w("(I2S#%d) I2S not initialized", _deviceIndex); _give_if_top_call(); return 0; } @@ -721,7 +722,7 @@ size_t I2SClass::write_nonblocking(const void *buffer, size_t size){ _give_if_top_call(); return size; }else{ - log_w("I2S could not write all data into ring buffer!"); + log_w("(I2S#%d) I2S could not write all data into ring buffer!", _deviceIndex); _give_if_top_call(); return 0; } @@ -805,7 +806,7 @@ int I2SClass::setBufferSize(int bufferSize){ 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); + log_e("(I2S#%d) setBufferSize: wrong input! Buffer size must be between 8 and 1024. Requested %d", _deviceIndex, bufferSize); _give_if_top_call(); return 0; // ERR } // check requested buffer size @@ -896,14 +897,14 @@ void I2SClass::_rx_done_routine(){ 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); + log_w("(I2S#%d) i2s_read returned with error %d", _deviceIndex, 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"); + log_w("(I2S#%d) I2S failed to send item from DMA to internal buffer\n", _deviceIndex); } // xRingbufferSendComplete } // if(bytes_read > 0) free(_inputBuffer); @@ -917,18 +918,33 @@ 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 + if(_i2sEventQueue == NULL){ + log_e("(I2S#%d) Event Queue is NULL!", _deviceIndex); + }else{ + 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 + } // _i2sEventQueue NULL check } -void I2SClass::onDmaTransferComplete(void*){ - I2S._onTransferComplete(); +void I2SClass::onDmaTransferComplete(void *deviceIndex){ + uint8_t *index; + index = (uint8_t*) deviceIndex; + if(*index == 0){ + I2S._onTransferComplete(); + } +#if SOC_I2S_NUM > 1 + if(*index == 1){ + I2S1._onTransferComplete(); + } +#endif + log_w("(I2S#%d) Deleting callback task from inside!", _deviceIndex); + vTaskDelete(NULL); } void I2SClass::_take_if_not_holding(){ @@ -940,7 +956,7 @@ void I2SClass::_take_if_not_holding(){ // 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"); + log_e("(I2S#%d) I2S internal mutex take returned with error", _deviceIndex); } //_give_if_top_call(); // call after this function } @@ -950,7 +966,7 @@ void I2SClass::_give_if_top_call(){ --_nesting_counter; }else{ if(xSemaphoreGive(_i2s_general_mutex) != pdTRUE){ - log_e("I2S internal mutex give error"); + log_e("(I2S#%d) I2S internal mutex give error", _deviceIndex); } } } @@ -996,7 +1012,7 @@ void I2SClass::_fix_and_write(void *output, size_t size, size_t *bytes_written, buff_size = size *2; buff = (uint8_t*)calloc(buff_size, sizeof(uint8_t)); if(buff == NULL){ - log_e("callock error"); + log_e("(I2S#%d) callock error", _deviceIndex); if(bytes_written != NULL){ *bytes_written = 0; } return; } @@ -1008,7 +1024,7 @@ void I2SClass::_fix_and_write(void *output, size_t size, size_t *bytes_written, case 16: buff = (uint8_t*)malloc(buff_size); if(buff == NULL){ - log_e("malloc error"); + log_e("(I2S#%d) malloc error", _deviceIndex); if(bytes_written != NULL){ *bytes_written = 0; } return; } @@ -1029,10 +1045,10 @@ void I2SClass::_fix_and_write(void *output, size_t size, size_t *bytes_written, 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); + log_e("(I2S#%d) Error: writing data to i2s - function returned with err code %d", _deviceIndex, 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); + log_w("(I2S#%d) Warning: writing data to i2s - written %d B instead of requested %d B", _deviceIndex, _bytes_written, buff_size); } // free if the buffer was actually allocated if(_bitsPerSample == 8 || _bitsPerSample == 16){ @@ -1065,7 +1081,7 @@ int I2SClass::_gpioToAdcUnit(gpio_num_t gpio_num, esp_i2s::adc_unit_t* adc_unit) // 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."); + log_w("(I2S#%d) GPIO 0 for ADC should not be used for dev boards due to external auto program circuits.", _deviceIndex); case GPIO_NUM_4: case GPIO_NUM_2: case GPIO_NUM_15: @@ -1122,7 +1138,7 @@ int I2SClass::_gpioToAdcUnit(gpio_num_t gpio_num, esp_i2s::adc_unit_t* adc_unit) return 1; // OK #endif default: - log_e("GPIO %d not usable for ADC!", gpio_num); + log_e("(I2S#%d) GPIO %d not usable for ADC!", _deviceIndex, 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 } @@ -1143,7 +1159,7 @@ int I2SClass::_gpioToAdcChannel(gpio_num_t gpio_num, esp_i2s::adc_channel_t* adc // 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."); + log_w("(I2S#%d) GPIO 0 for ADC should not be used for dev boards due to external auto program circuits.", _deviceIndex); *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 @@ -1191,18 +1207,17 @@ int I2SClass::_gpioToAdcChannel(gpio_num_t gpio_num, esp_i2s::adc_channel_t* adc 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_e("(I2S#%d) GPIO %d not usable for ADC!", _deviceIndex, 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 +#if SOC_I2S_NUM > 0 + I2SClass I2S(0, 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 +#if SOC_I2S_NUM > 1 + I2SClass I2S1(1, I2S_CLOCK_GENERATOR, PIN_I2S1_SD, PIN_I2S1_SCK, PIN_I2S1_FS); // default - half duplex #endif diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index 623fa8917b4..e1d6808ed7f 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -27,6 +27,7 @@ namespace esp_i2s { } // Default pins +//I2S0 #ifndef PIN_I2S_SCK #define PIN_I2S_SCK 14 #endif @@ -51,6 +52,29 @@ namespace esp_i2s { #define PIN_I2S_SD_IN 35 // Pin 35 is only input! #endif +#if SOC_I2S_NUM > 1 + // I2S1 + #ifndef PIN_I2S1_SCK + #define PIN_I2S1_SCK 15 + #endif + + #ifndef PIN_I2S1_FS + #define PIN_I2S1_FS 16 + #endif + + #ifndef PIN_I2S1_SD + #define PIN_I2S1_SD 17 + #endif + + #ifndef PIN_I2S1_SD_OUT + #define PIN_I2S1_SD_OUT 17 + #endif + + #ifndef PIN_I2S1_SD_IN + #define PIN_I2S1_SD_IN 18 + #endif +#endif + typedef enum { I2S_PHILIPS_MODE, I2S_RIGHT_JUSTIFIED_MODE, @@ -133,7 +157,7 @@ class I2SClass : public Stream int _createCallbackTask(); - static void onDmaTransferComplete(void*); + static void onDmaTransferComplete(void *device_index); int _installDriver(); void _uninstallDriver(); void _setSckPin(int sckPin); @@ -191,5 +215,8 @@ class I2SClass : public Stream }; extern I2SClass I2S; +#if SOC_I2S_NUM > 1 + extern I2SClass I2S1; +#endif #endif From d87aa62aa654e32f886c9745134a04dbb6b1943e Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Fri, 12 Aug 2022 15:40:16 +0200 Subject: [PATCH 02/46] Initial support for 24 bits per second - not tested yet! --- libraries/I2S/src/I2S.cpp | 20 ++++++++++++++++---- libraries/I2S/src/I2S.h | 2 +- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index 4d029b4228f..bb43d31c739 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -76,7 +76,7 @@ int I2SClass::_createCallbackTask(){ onDmaTransferComplete, // Function to implement the task "onDmaTransferComplete", // Name of the task stack_size, // Stack size in words - (void *)_deviceIndex, // Task input parameter + (void *)&_deviceIndex, // Task input parameter 2, // Priority of the task &_callbackTaskHandle // Task handle. ); @@ -167,7 +167,6 @@ int I2SClass::_installDriver(){ } } //try installing with increasing size -// TODO add check for unit or PDM support - only unit 0 support, unit 1 cannot un in PDM 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)){ @@ -576,6 +575,9 @@ int I2SClass::read(){ if (_bitsPerSample == 32) { _give_if_top_call(); return sample.b32; + } else if (_bitsPerSample == 24) { + _give_if_top_call(); + return sample.b32; } else if (_bitsPerSample == 16) { _give_if_top_call(); return sample.b16; @@ -758,6 +760,7 @@ int I2SClass::peek(){ return ret; } +// TODO flush everything instead of one buffer void I2SClass::flush(){ _take_if_not_holding(); if(_initialized){ @@ -765,6 +768,7 @@ void I2SClass::flush(){ size_t item_size = 0; void *item = NULL; if(_output_ring_buffer != NULL){ + // TODO while available data to fill entire DMA buff - keep flushing item = xRingbufferReceiveUpTo(_output_ring_buffer, &item_size, 0, single_dma_buf); if (item != NULL){ _fix_and_write(item, item_size); @@ -943,7 +947,7 @@ void I2SClass::onDmaTransferComplete(void *deviceIndex){ I2S1._onTransferComplete(); } #endif - log_w("(I2S#%d) Deleting callback task from inside!", _deviceIndex); + log_w("(I2S#%d) Deleting callback task from inside!", index); vTaskDelete(NULL); } @@ -1007,6 +1011,7 @@ void I2SClass::_fix_and_write(void *output, size_t size, size_t *bytes_written, ulong src_ptr = 0; uint8_t* buff = NULL; size_t buff_size = size; + uint32_t offset = 1; // 24bit specific switch(_bitsPerSample){ case 8: buff_size = size *2; @@ -1034,7 +1039,14 @@ void I2SClass::_fix_and_write(void *output, size_t size, size_t *bytes_written, } break; case 24: - buff = (uint8_t*)output; + buff_size = (size/3)*4; // Increase by 1/3 + buff = (uint8_t*)calloc(buff_size, sizeof(uint8_t)); + for(int i = 0; i < size/3; i+=3){ + // LSB in buffer's 4-Byte Word is 0 to compensate IDF driver behavior + buff[i+offset] = ((uint8_t*)output)[i]; + buff[i+offset+1] = ((uint8_t*)output)[i+1]; + buff[i+offset+2] = ((uint8_t*)output)[i+2]; + ++offset; break; case 32: buff = (uint8_t*)output; diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index e1d6808ed7f..71a6f80f42b 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -126,7 +126,7 @@ class I2SClass : public Stream virtual void flush(); // from Print - virtual size_t write(uint8_t); + virtual size_t write(uint8_t data); virtual size_t write(const uint8_t *buffer, size_t size); virtual int availableForWrite(); From 54e955469167951b7e526e1390883bd04a2e3c0f Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Mon, 15 Aug 2022 10:02:11 +0200 Subject: [PATCH 03/46] SimpleTone supports all available bitrates --- libraries/I2S/examples/SimpleTone/SimpleTone.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/I2S/examples/SimpleTone/SimpleTone.ino b/libraries/I2S/examples/SimpleTone/SimpleTone.ino index dd312cf8a82..bd5a820b1d1 100644 --- a/libraries/I2S/examples/SimpleTone/SimpleTone.ino +++ b/libraries/I2S/examples/SimpleTone/SimpleTone.ino @@ -29,13 +29,13 @@ #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 amplitude = ((1< Date: Wed, 21 Sep 2022 15:18:45 +0200 Subject: [PATCH 04/46] Minor changes to fix compilation errors --- libraries/I2S/src/I2S.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index bb43d31c739..f4ccada5eac 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -1047,11 +1047,13 @@ void I2SClass::_fix_and_write(void *output, size_t size, size_t *bytes_written, buff[i+offset+1] = ((uint8_t*)output)[i+1]; buff[i+offset+2] = ((uint8_t*)output)[i+2]; ++offset; + } break; case 32: buff = (uint8_t*)output; break; - default: ; // Do nothing + default: + break; // Do nothing } // switch size_t _bytes_written; From f2fdca7b729626f16882ca73f29e175c5893ee11 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Wed, 21 Sep 2022 15:51:00 +0200 Subject: [PATCH 05/46] Remapped pins --- libraries/I2S/src/I2S.h | 82 +++++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 27 deletions(-) diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index 71a6f80f42b..7ac278f26db 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -27,51 +27,79 @@ namespace esp_i2s { } // Default pins -//I2S0 +//I2S0 is available for all ESP32 SoCs +//SCK WS SD(OUT) SDIN +// 1 4 19 21 #ifndef PIN_I2S_SCK - #define PIN_I2S_SCK 14 + #define PIN_I2S_SCK 1 #endif #ifndef PIN_I2S_FS - #if CONFIG_IDF_TARGET_ESP32S2 - #define PIN_I2S_FS 27 - #else - #define PIN_I2S_FS 25 - #endif + #define PIN_I2S_FS 4 #endif #ifndef PIN_I2S_SD - #define PIN_I2S_SD 26 + #define PIN_I2S_SD 19 #endif #ifndef PIN_I2S_SD_OUT - #define PIN_I2S_SD_OUT 26 + #define PIN_I2S_SD_OUT 19 #endif #ifndef PIN_I2S_SD_IN - #define PIN_I2S_SD_IN 35 // Pin 35 is only input! + #define PIN_I2S_SD_IN 21 #endif #if SOC_I2S_NUM > 1 - // I2S1 - #ifndef PIN_I2S1_SCK - #define PIN_I2S1_SCK 15 - #endif - - #ifndef PIN_I2S1_FS - #define PIN_I2S1_FS 16 - #endif - - #ifndef PIN_I2S1_SD - #define PIN_I2S1_SD 17 - #endif - - #ifndef PIN_I2S1_SD_OUT - #define PIN_I2S1_SD_OUT 17 + // I2S1 is available only for ESP32 and ESP32-S3 + #if CONFIG_IDF_TARGET_ESP32 + // ESP32 pins + //SCK WS SD(OUT) SDIN + // 18 22 23 25 + #ifndef PIN_I2S1_SCK + #define PIN_I2S1_SCK 18 + #endif + + #ifndef PIN_I2S1_FS + #define PIN_I2S1_FS 22 + #endif + + #ifndef PIN_I2S1_SD + #define PIN_I2S1_SD 23 + #endif + + #ifndef PIN_I2S1_SD_OUT + #define PIN_I2S1_SD_OUT 23 + #endif + + #ifndef PIN_I2S1_SD_IN + #define PIN_I2S1_SD_IN 25 + #endif #endif - #ifndef PIN_I2S1_SD_IN - #define PIN_I2S1_SD_IN 18 + #if CONFIG_IDF_TARGET_ESP32S3 + // ESP32-S3 pins + //SCK WS SD(OUT) SDIN + // 36 37 39 40 + #ifndef PIN_I2S1_SCK + #define PIN_I2S1_SCK 36 + #endif + + #ifndef PIN_I2S1_FS + #define PIN_I2S1_FS 37 + #endif + + #ifndef PIN_I2S1_SD + #define PIN_I2S1_SD 39 + #endif + + #ifndef PIN_I2S1_SD_OUT + #define PIN_I2S1_SD_OUT 39 + #endif + + #ifndef PIN_I2S1_SD_IN + #define PIN_I2S1_SD_IN 40 + #endif #endif #endif From 578e871e91539ec2ceb8f55c3d9834a1f1f6e355 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Tue, 27 Sep 2022 15:59:33 +0200 Subject: [PATCH 06/46] Changed default pins --- libraries/I2S/src/I2S.h | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index 7ac278f26db..1cd769109a2 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -29,25 +29,38 @@ namespace esp_i2s { // Default pins //I2S0 is available for all ESP32 SoCs //SCK WS SD(OUT) SDIN -// 1 4 19 21 +// 19 21 22 23 ESP32 +// 19 21 4 5 ESP32-x (C3,S2,S3) #ifndef PIN_I2S_SCK - #define PIN_I2S_SCK 1 + #define PIN_I2S_SCK 19 #endif #ifndef PIN_I2S_FS - #define PIN_I2S_FS 4 + #define PIN_I2S_FS 21 #endif #ifndef PIN_I2S_SD - #define PIN_I2S_SD 19 + #if CONFIG_IDF_TARGET_ESP32 + #define PIN_I2S_SD 22 + #else + #define PIN_I2S_SD 4 + #endif #endif #ifndef PIN_I2S_SD_OUT - #define PIN_I2S_SD_OUT 19 + #if CONFIG_IDF_TARGET_ESP32 + #define PIN_I2S_SD_OUT 22 + #else + #define PIN_I2S_SD_OUT 4 + #endif #endif #ifndef PIN_I2S_SD_IN - #define PIN_I2S_SD_IN 21 + #if CONFIG_IDF_TARGET_ESP32 + #define PIN_I2S_SD_IN 23 + #else + #define PIN_I2S_SD_IN 5 + #endif #endif #if SOC_I2S_NUM > 1 @@ -55,7 +68,7 @@ namespace esp_i2s { #if CONFIG_IDF_TARGET_ESP32 // ESP32 pins //SCK WS SD(OUT) SDIN - // 18 22 23 25 + // 18 22 25 26 #ifndef PIN_I2S1_SCK #define PIN_I2S1_SCK 18 #endif @@ -65,15 +78,15 @@ namespace esp_i2s { #endif #ifndef PIN_I2S1_SD - #define PIN_I2S1_SD 23 + #define PIN_I2S1_SD 25 #endif #ifndef PIN_I2S1_SD_OUT - #define PIN_I2S1_SD_OUT 23 + #define PIN_I2S1_SD_OUT 25 #endif #ifndef PIN_I2S1_SD_IN - #define PIN_I2S1_SD_IN 25 + #define PIN_I2S1_SD_IN 26 #endif #endif From b376699432308b4b4a331a1ed9fbca27cf97da39 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Tue, 27 Sep 2022 16:48:16 +0200 Subject: [PATCH 07/46] Added notes into README as a guide for propper documentation --- libraries/I2S/README.md | 64 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/libraries/I2S/README.md b/libraries/I2S/README.md index 6573606bbe0..cecd28f906c 100644 --- a/libraries/I2S/README.md +++ b/libraries/I2S/README.md @@ -1,7 +1,69 @@ # I2S / IIS / Inter-IC Sound -This library is base od Arduino I2S library (see doc [here](https://docs.arduino.cc/learn/built-in-libraries/i2s)) +This library is based on Arduino I2S library (see doc [here](https://docs.arduino.cc/learn/built-in-libraries/i2s)) This library mimics the behavior and extends possibilities with ESP-specific functionalities. For example Arduino I2S can operate on 8, 16 and 32 bits per sample, ESP adds 24 bits per sample. In this document you will find description of functionality which is not available in Arduino and therefore is not described in the Arduino documentation. +note: Arduino describes: +I2S.begin(mode, sampleRate, bitsPerSample); // controller device +I2S.begin(mode, bitsPerSample); // peripheral device + +I2S.end() + +I2S.available() + +I2S.write(val) // blocking +I2S.write(buf, len) // not blocking + +I2S.availableForWrite() + +onTransmit(handler) + +onReceive(handler) + +Modes: I2S_PHILIPS_MODE, + I2S_RIGHT_JUSTIFIED_MODE, + I2S_LEFT_JUSTIFIED_MODE + +// What we have + + +modes: + I2S_PHILIPS_MODE, (similar to PCM) + I2S_RIGHT_JUSTIFIED_MODE, + I2S_LEFT_JUSTIFIED_MODE, + ++++++++++++++++++++++++++++++ + ADC_DAC_MODE outputting and inputting raw analog signal - see example ADCPlotter + PDM_STEREO_MODE, not to be confused with PCM. PDM is oversampled 1bit audio + PDM_MONO_MODE + +ESP32 = 2x I2S = I2S + I2S1 +flush +virtual size_t write(const uint8_t *buffer, size_t size); = more efficient +same for read + size_t write_blocking(const void *buffer, size_t size); + size_t write_nonblocking(const void *buffer, size_t size); + + internall buffers + int setBufferSize(int bufferSize); + int getBufferSize(); + + variable pins: + 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(); \ No newline at end of file From dc1769e55b15f17db51f375efa4813d5432df403 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Fri, 30 Sep 2022 12:17:04 +0200 Subject: [PATCH 08/46] WIP Backup of documentation update --- libraries/I2S/README.md | 120 ++++++++++++++++++++++++++++------------ libraries/I2S/src/I2S.h | 110 ++++++++++++++++++++++++++++-------- 2 files changed, 171 insertions(+), 59 deletions(-) diff --git a/libraries/I2S/README.md b/libraries/I2S/README.md index cecd28f906c..2bf0c24a676 100644 --- a/libraries/I2S/README.md +++ b/libraries/I2S/README.md @@ -1,54 +1,74 @@ -# I2S / IIS / Inter-IC Sound +# I2S / IIS / Inter-IC Sound This library is based on Arduino I2S library (see doc [here](https://docs.arduino.cc/learn/built-in-libraries/i2s)) This library mimics the behavior and extends possibilities with ESP-specific functionalities. -For example Arduino I2S can operate on 8, 16 and 32 bits per sample, ESP adds 24 bits per sample. -In this document you will find description of functionality which is not available in Arduino and therefore is not described in the Arduino documentation. -note: Arduino describes: -I2S.begin(mode, sampleRate, bitsPerSample); // controller device -I2S.begin(mode, bitsPerSample); // peripheral device +The main differences are: +Property Arduino ESP32 extends +bits per sample 8,16,32 24 +data channels 1 (simplex) 2(duplex) +modes Philips PDM, ADC/DAC +I2S modules 1 2 (only for ESP32, other SoC have also only 1) +pins fixed configurable -I2S.end() +In this document you will find overview for this version of library and description of class functions. -I2S.available() +Since all ESP32 code runs on FreeRTOS (operating system handling execution time on CPUs and allowing pseudo-parallel execution) this library uses 1 task which monitors messages from underlying IDF I2S driver which sends them whenever it sends sample from buffer to data line, or when it fills buffer with samples received from data line. +Since Arduino utilizes single sample writes and reads heavily, which would case unbearable lags there had to be implemented another layer of ring buffers (one for transmitted data and one for received data). The user of this library writes, or reads from this buffer via functions `write` and `read` (exact variants are described below). +The task, upon receiving the message from IDF I2S driver performs either flush data from transmit ring buffer to IDF I2S driver or received data from IDF I2S driver to receive ring buffer. +Usage of the ring buffers also enabled implementation of functions `available`, `availableForWrite`, `peak` and single sample write/read. -I2S.write(val) // blocking -I2S.write(buf, len) // not blocking +The IDF I2S driver uses multiple sets of buffer of the same size. The size of ring buffer equals to sum of all those buffers. By default the number of IDF I2S buffers is 2. Usually there should be no need to change this, however if you choose to change it, change the constant `_I2S_DMA_BUFFER_COUNT` in the library, in `src/I2S.cpp`: -I2S.availableForWrite() +The size of IDF I2S buffer is by default 128. The unit used for this buffer is a frame. A frame means the data of all channels in a WS cycle. For example dual channel 16 bits per sample and default buffer size = 2 * 2 * 128 = 512 Bytes. -onTransmit(handler) +From this we can get also size of the ring buffers. As stated their size is equal to the sum of IDF I2S buffers. For the example of dual channel, 16 bps the size of each ring buffer is 1024 B. +The function `setBufferSize` allows you to change the size in frames (see detailed description below). -onReceive(handler) +On Arduino and most ESPs you can use object `I2S` to use the I2S module. on ESP32 and ESP32-S3 you have the option to use addititonal object `I2S1`. +I2S module functionality on each SoC differs, please refer to [IDF documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html?highlight=dac#overview-of-all-modes) for an overview. -Modes: I2S_PHILIPS_MODE, - I2S_RIGHT_JUSTIFIED_MODE, - I2S_LEFT_JUSTIFIED_MODE +## Pins +ESP I2S has fully configurable pins. There is a group of setter and getter functions for each pin separately and one setter for all pins at once (detailed description below). Calling the setter will take effect immediately and does not need driver restart. +Default pins are setup as follows: +### Common I2S object: +| SCK | WS | SD(OUT) | SDIN | SoC | +| --- | -- | ------- | ---- |:---------------------------- | +| 19 | 21 | 22 | 23 | ESP32 | +| 19 | 21 | 4 | 5 | ESP32-C3, ESP32-S2, ESP32-S3 | -// What we have + -modes: - I2S_PHILIPS_MODE, (similar to PCM) - I2S_RIGHT_JUSTIFIED_MODE, - I2S_LEFT_JUSTIFIED_MODE, - ++++++++++++++++++++++++++++++ - ADC_DAC_MODE outputting and inputting raw analog signal - see example ADCPlotter - PDM_STEREO_MODE, not to be confused with PCM. PDM is oversampled 1bit audio - PDM_MONO_MODE +### I2S1 object: +| SCK | WS | SD(OUT) | SDIN | SoC | +| --- | -- | ------- | ---- |:-------- | +| 18 | 22 | 25 | 26 | ESP32 | +| 36 | 37 | 39 | 40 | ESP32-S3 | -ESP32 = 2x I2S = I2S + I2S1 -flush -virtual size_t write(const uint8_t *buffer, size_t size); = more efficient -same for read - size_t write_blocking(const void *buffer, size_t size); - size_t write_nonblocking(const void *buffer, size_t size); - internall buffers - int setBufferSize(int bufferSize); - int getBufferSize(); +## Duplex / Simplex +Arduino has only one data pin and the driver is switching between receiving and transmitting upon calling `read` or `write`. On all ESP32s each I2S module has 2 independent data lines, one for transmitting and one for receiving. +For backward compatibility with Arduino we are using DataPin for multiplexed data and DataOutPin + DataInPin for independent duplex communication. +The default mode is simplex and the shared data pin switches function upon calling `read` or `write` same as Arduino. +If you wish to use duplex mode call `setDuplex();` this will change the pin setup to use both data lines. If you want to switch back ti simplex call `setSimplex();` and if you need to get current state call `isDuplex();` (detailed function description below). + +## Modes + * I2S_PHILIPS_MODE Most common mode see elsewhere + * I2S_RIGHT_JUSTIFIED_MODE ? + * I2S_LEFT_JUSTIFIED_MODE ? + * ADC_DAC_MODE outputting and inputting raw analog signal - see example ADCPlotter + * PDM_STEREO_MODE Pulse Density Modulation is basically oversampled 1bit signal. not to be confused with PCM. + * PDM_MONO_MODE Same as previous but transmits only 1 channel TODO explain timing diagram and data interpretation + +## Functions +copy paste from .h + // 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); - variable pins: + // 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 @@ -66,4 +86,32 @@ same for read int setDuplex(); int setSimplex(); - int isDuplex(); \ No newline at end of file + 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 data); + 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(); \ No newline at end of file diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index 1cd769109a2..a013ae09aa4 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -117,45 +117,109 @@ namespace esp_i2s { #endif typedef enum { - I2S_PHILIPS_MODE, - I2S_RIGHT_JUSTIFIED_MODE, - I2S_LEFT_JUSTIFIED_MODE, - ADC_DAC_MODE, - PDM_STEREO_MODE, - PDM_MONO_MODE + I2S_PHILIPS_MODE, // Most common I2S mode, WS signal spans across whole channel period + I2S_RIGHT_JUSTIFIED_MODE, // TODO check oscilloscope what it does + I2S_LEFT_JUSTIFIED_MODE, // TODO check oscilloscope what it does + ADC_DAC_MODE, // Receive and transmit raw analog signal + PDM_STEREO_MODE, // Pulse Density Modulation - stereo / 2 channels + PDM_MONO_MODE // Pulse Density Modulation - mono / 1 channel } 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 + /* + * Constructor - initializes object with default values + * + * Parameters: + * uint8_t deviceIndex In case the SoC has more I2S module, specify which one is instantiated. Possible values are "0" (for all ESPs) and "1" (only for ESP32 and ESP32-S3) + * uint8_t clockGenerator Has no meaning for ESP and is kept only for compatibility + * uint8_t sdPin Shared data pin used for simplex mode + * uint8_t sckPin Clock pin + * uint8_t fsPin Frame (word) select pin + * + * Default settings: + * Input data pin (used for duplex mode) is initialized with PIN_I2S_SD_IN + * Out data pin (used for duplex mode) is initialized with PIN_I2S_SD + * Mode = I2S_PHILIPS_MODE + * Buffer size = 128 + */ 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 + /* + * Init in MASTER mode: the SCK and FS pins are driven as outputs using the sample rate + * Parameters: + * int mode Operation mode (Phillips, Left/Right Justified, ADC+DAC,PDM) see i2s_mode_t for exact enumerations + * int sampleRate sampling frequency in Hz. Common values are 8000,11025,16000,22050,32000,44100,64000,88200,128000 + * int bitsPerSample Number of bits per one sample (one channel). Possible values are 8,16,24,32 + * Returns: 1 on success; 0 on error + */ int begin(int mode, int sampleRate, int bitsPerSample); - // Init in SLAVE mode: the SCK and FS pins are inputs, other side controls sample rate + /* Init in SLAVE mode: the SCK and FS pins are inputs and must be controlled(generated) be external source (MASTER device). + * Parameters: + * int mode Operation mode (Phillips, Left/Right Justified, ADC+DAC,PDM) see i2s_mode_t for exact enumerations + * int bitsPerSample Number of bits per one sample (one channel). Possible values are 8,16,24,32 + * Returns: 1 on success; 0 on error + */ 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); - + /* + * Change pin setup for each pin separately. + * Can be called only on initialized object (after begin). + * The change takes effect immediately and does not need driver restart. + * Parameter: int pin number of GPIO which should be used for the requested pin setup + * Returns: 1 on success; 0 on error + */ + int setSckPin(int sckPin); // Set Clock pin + int setFsPin(int fsPin); // Set Frame Sync (Word Select) pin + int setDataPin(int sdPin); // Set shared Data pin for simplex mode + int setDataOutPin(int outSdPin); // Set Data Output pin for duplex mode + int setDataInPin(int inSdPin); // Set Data Input pin for duplex mode + + /* + * Change pin setup for all pins at one call using default values set constants in I2S.h + * Can be called only on initialized object (after begin) + * The change takes effect immediately and does not need driver restart. + * Returns: 1 on success; 0 on error + */ int setAllPins(); - int setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin); - int getSckPin(); - int getFsPin(); - int getDataPin(); - int getDataOutPin(); - int getDataInPin(); + /* + * Change pin setup for all pins at one call. + * Can be called only on initialized object (after begin). + * The change takes effect immediately and does not need driver restart. + * Parameters: + * int sckPin Clock pin + * int fsPin Frame Sync (Word Select) pin + * int sdPin Shared Data pin for simplex mode + * int outSdPin Data Output pin for duplex mode + * int inSdPin Data Input pin for duplex mode + * Returns: 1 on success; 0 on error + */ + int setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin); + /* + * Get current pin GPIO number + * Returns: the GPIO number of requested pin + */ + int getSckPin(); // Get Clock pin + int getFsPin(); // Get Frame Sync (Word Select) pin + int getDataPin(); // Get shared Data pin for simplex mode + int getDataOutPin(); // Get Data Output pin for duplex mode + int getDataInPin(); // Get Data Input pin for duplex mode + + /* + * Change mode (default is Half Duplex) + * Returns: 1 on success; 0 on error + */ int setDuplex(); int setSimplex(); + + /* + * Get current mode + * Returns: 1 if current mode is Duplex; 0 If current mode is not Duplex + */ int isDuplex(); void end(); From adff9e16e7be689d71fae20831e9bda81d9fad2a Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Fri, 30 Sep 2022 16:24:37 +0200 Subject: [PATCH 09/46] Finished documenting header file + README.md --- libraries/I2S/README.md | 317 +++++++++++++++++++++++++++++++++------- libraries/I2S/src/I2S.h | 126 ++++++++++++++-- 2 files changed, 379 insertions(+), 64 deletions(-) diff --git a/libraries/I2S/README.md b/libraries/I2S/README.md index 2bf0c24a676..9ff7fe38aae 100644 --- a/libraries/I2S/README.md +++ b/libraries/I2S/README.md @@ -51,67 +51,286 @@ For backward compatibility with Arduino we are using DataPin for multiplexed dat The default mode is simplex and the shared data pin switches function upon calling `read` or `write` same as Arduino. If you wish to use duplex mode call `setDuplex();` this will change the pin setup to use both data lines. If you want to switch back ti simplex call `setSimplex();` and if you need to get current state call `isDuplex();` (detailed function description below). -## Modes - * I2S_PHILIPS_MODE Most common mode see elsewhere - * I2S_RIGHT_JUSTIFIED_MODE ? - * I2S_LEFT_JUSTIFIED_MODE ? - * ADC_DAC_MODE outputting and inputting raw analog signal - see example ADCPlotter - * PDM_STEREO_MODE Pulse Density Modulation is basically oversampled 1bit signal. not to be confused with PCM. - * PDM_MONO_MODE Same as previous but transmits only 1 channel TODO explain timing diagram and data interpretation +## Modes (i2s_mode_t) + + * **I2S_PHILIPS_MODE** Most common mode, FS signal spans across whole channel period. TODO more info + * **I2S_RIGHT_JUSTIFIED_MODE** ? TODO + * **I2S_LEFT_JUSTIFIED_MODE** ? TODO + * **ADC_DAC_MODE** Outputting and inputting raw analog signal - see example ADCPlotter + * **PDM_STEREO_MODE** Pulse Density Modulation is basically an over-sampled 1-bit signal. Not to be confused with PCM. + * **PDM_MONO_MODE** Same as previous but transmits only 1 channel. TODO explain timing diagram and data interpretation ## Functions -copy paste from .h - // 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); +#### I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, uint8_t sckPin, uint8_t fsPin) + +Constructor - initializes object with default values + +**Parameters:** + +* **uint8_t deviceIndex** In case the SoC has more I2S modules, specify which one is instantiated. Possible values are "0" (for all ESPs) and "1" (only for ESP32 and ESP32-S3) +* **uint8_t clockGenerator** Has no meaning for ESP and is kept only for compatibility +* **uint8_t sdPin** Shared data pin used for simplex mode +* **uint8_t sckPin** Clock pin +* **uint8_t fsPin** Frame Sync (Word Select) pin + +**Default settings:** + +* Input data pin (used for duplex mode) is initialized with `PIN_I2S_SD_IN` +* Out data pin (used for duplex mode) is initialized with `PIN_I2S_SD` +* Mode = `I2S_PHILIPS_MODE` +* Buffer size = `128` + +*** +#### int begin(int mode, int sampleRate, int bitsPerSample) + +*Init in MASTER mode.* + +The SCK and FS pins are driven as outputs using the sample rate. +Initializes IDF I2S driver, creates ring buffers and callback handler task. + +**Parameters:** + +* **int mode** Operation mode (Phillips, Left/Right Justified, ADC+DAC,PDM) see **Modes** for exact enumerations +* **int sampleRate** sampling frequency in Hz. Common values are 8000,11025,16000,22050,32000,44100,64000,88200,128000 +* **int bitsPerSample** Number of bits per one sample (one channel). Possible values are 8,16,24,32 + +**Returns:** 1 on success; 0 on error +*** +#### int begin(int mode, int bitsPerSample) + +*Init in SLAVE mode[.](https://en.wiktionary.org/wiki/slave#Etymology)* + +The SCK and FS pins are inputs and must be controlled(generated) be external source (MASTER device). +Initializes IDF I2S driver, creates ring buffers and callback handler task. + +**Parameters:** + +* **int mode** Operation mode (Phillips, Left/Right Justified, ADC+DAC,PDM) see i2s_mode_t for exact enumerations +* **int bitsPerSample** Number of bits per one sample (one channel). Possible values are 8,16,24,32 + +**Returns:** 1 on success; 0 on error +*** +#### void end() + +*De-initialize IDF I2S driver, frees ring buffers and terminates callback handler task.* +*** +#### int setXPin() + +*Change pin setup for each pin separately.* + +Can be called only on initialized object (after `begin()`). +The change takes effect immediately and does not need driver restart. + +**Parameter:** + +* **int pin** number of GPIO which should be used for the requested pin setup + +**Returns:** 1 on success; 0 on error + +**Function list:** + +* **int setSckPin(int sckPin)** Set Clock pin +* **int setFsPin(int fsPin)** Set Frame Sync (Word Select) pin +* **int setDataPin(int sdPin)** Set shared Data pin for simplex mode +* **int setDataOutPin(int outSdPin)** Set Data Output pin for duplex mode +* **int setDataInPin(int inSdPin)** Set Data Input pin for duplex mode + +*** +#### int setAllPins() + +*Change pin setup for all pins at one call using default values set constants in I2S.h* + +Can be called only on initialized object (after `begin()`). +The change takes effect immediately and does not need driver restart. + +**Returns:** 1 on success; 0 on error +*** +#### int setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin) + +*Change pin setup for all pins at one call.* + +Can be called only on initialized object (after `begin()`). +The change takes effect immediately and does not need driver restart. + +**Parameters:** + +* **int sckPin** Clock pin +* **int fsPin** Frame Sync (Word Select) pin +* **int sdPin** Shared Data pin for simplex mode +* **int outSdPin** Data Output pin for duplex mode +* **int inSdPin** Data Input pin for duplex mode + +**Returns:** 1 on success; 0 on error +*** +#### int getXPin() + +*Get current pin GPIO number* + +**Returns:** the GPIO number of requested pin + +**Function list:** + +* **int getSckPin()** Get Clock pin +* **int getFsPin()** Get Frame Sync (Word Select) pin +* **int getDataPin()** Get shared Data pin for simplex mode +* **int getDataOutPin()** Get Data Output pin for duplex mode +* **int getDataInPin()** Get Data Input pin for duplex mode + +*** +#### int setDuplex() + +*Change mode to duplex* (default is simplex) + +**Returns:** 1 on success; 0 on error +*** +#### int setSimplex() +Change mode to Simplex + +**Returns:** 1 on success; 0 on error +*** +#### int isDuplex() + +*Get current mode* + +**Returns:** 1 if current mode is Duplex; 0 If current mode is not Duplex +*** +#### int available() +**Returns:** number of Bytes available to read from ring buffer. + +.. note:: The ring buffer is filled automatically by handler task from IDF I2S driver. + +*** +#### int peek() + +Reads a single sample from ring buffer and keeps it available for future read (i.e. does not remove the sample from ring buffer) + +**Returns:** First sample from ring buffer + +.. note:: The ring buffer is filled automatically by handler task from IDF I2S driver. + +*** +#### int read() + +*Reads a single sample* from ring buffer and removes it from the ring buffer. + +**Returns:** First sample from ring buffer + +.. note:: The ring buffer is filled automatically by handler task from IDF I2S driver. + +*** +#### int read(void* buffer, size_t size) + +*Reads an array of samples* from ring buffer and removes them from the ring buffer. + +**Parameters:** + +* **[OUT] void* buffer** Buffer into which the samples will be copied. The buffer must allocated before calling this function! +* **[IN] size_t size** Requested number of bytes to be read + +**Returns:** Number of bytes that were actually read. + +.. note:: Always check the returned value! + +*** +#### virtual int availableForWrite() + +**Returns:** number of bytes that can be written into the ring buffer. +*** +#### virtual size_t write(uint8_t data) + +*Write single sample of 8 bit size.* + +This function is **blocking** - if there is not enough space in ring buffer the function will wait until it can write the sample. + +**Parameter:** +* **uint8_t data** The sample to be sent + +**Returns:** 1 on successful write; 0 on error = did not write the sample to ring buffer + +.. note:: This functions is used in many examples for it's simplicity, but it's use is discouraged for performance reasons. + +Please consider sending data in arrays using function `size_t write(const uint8_t *buffer, size_t size)` +*** +#### size_t write(int32_t) + +*Write single sample of up to 32 bit size.* + +This function is **blocking** - if there is not enough space in ring buffer the function will wait until it can write the sample. + +**Parameter:** +* **int32_t data** The sample to be sent + +**Returns:** Number of written bytes, if successful the value will be equal to bitsPerSample/8 + +.. note:: This functions is used in many examples for it's simplicity, but it's use is discouraged for performance reasons. + +Please consider sending data in arrays using function `size_t write(const uint8_t *buffer, size_t size)` +*** +#### size_t write(const uint8_t* buffer, size_t size) + +*Write array of samples.* + +This function is **non-blocking** - the function might write only portion of samples into ring buffer, or potentially none at all. Do check the returned value at all times! + +**Parameters:** + +* **uint8_t* buffer** Array of samples +* **size_t size** Number of bytes in array + +**Returns:** Number of bytes successfully written to ring buffer. + +.. note:: This is the preferred function for writing samples. + +*** +#### size_t write(const void* buffer, size_t size) + +*Write array of samples.* + +This function is **non-blocking** - the function might write only portion of samples into ring buffer, or potentially none at all. Do check the returned value at all times! + +**Parameters:** +* **void* buffer** Array of samples +* **size_t size** Number of bytes in array + +**Returns:** Number of bytes successfully written to ring buffer. + +.. note:: This is the preferred function for writing samples. + +*** +#### void flush() - // 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); +*Force-write data* from ring buffer to IDF I2S driver. - int setAllPins(); - int setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin); +This function is useful when sending low amount of data, however such use will lead to low quality audio. - int getSckPin(); - int getFsPin(); - int getDataPin(); - int getDataOutPin(); - int getDataInPin(); +.. note:: The ring buffer is emptied (sent) automatically by handler task from IDF I2S driver. - int setDuplex(); - int setSimplex(); - int isDuplex(); +*** +#### void onTransmit(void(\*)(void)) - void end(); +*Callback handle* which will be used each time when the IDF I2S driver **transmits** data from buffer. +*** +#### void onReceive(void(\*)(void)) - // from Stream - virtual int available(); - virtual int read(); - virtual int peek(); - virtual void flush(); +*Callback handle* which will be used each time when the IDF I2S driver **receives** data into buffer. +*** +#### int setBufferSize(int bufferSize) - // from Print - virtual size_t write(uint8_t data); - virtual size_t write(const uint8_t *buffer, size_t size); +*Change the size of buffers.* The unit is number of sample frames `(number_of_channels * (bits_per_sample/8))` - virtual int availableForWrite(); +The resulting Bytes size of ring buffers can be calculated: - int read(void* buffer, size_t size); +`ring_buffer_bytes_size = (number_of_channels * (bits_per_sample/8)) * bufferSize * _I2S_DMA_BUFFER_COUNT` - //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); +*Example:* default value of `_I2S_DMA_BUFFER_COUNT` is **2**, default value of `bufferSize` is **128**; for *dual channel*, *16 bps* we will get +``` +ring_buffer_bytes_size = (number_of_channels * (bits_per_sample/8)) * bufferSize * _I2S_DMA_BUFFER_COUNT + 1024 = ( 2 * ( 16 /8)) * 128 * 2 +``` +*** +#### int getBufferSize() - void onTransmit(void(*)(void)); - void onReceive(void(*)(void)); +*Get buffer size.* The unit is number of sample frames `(number_of_channels * (bits_per_sample/8))` - int setBufferSize(int bufferSize); - int getBufferSize(); \ No newline at end of file +For more info see `setBufferSize` diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index a013ae09aa4..99a2f837e16 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -117,7 +117,7 @@ namespace esp_i2s { #endif typedef enum { - I2S_PHILIPS_MODE, // Most common I2S mode, WS signal spans across whole channel period + I2S_PHILIPS_MODE, // Most common I2S mode, FS signal spans across whole channel period I2S_RIGHT_JUSTIFIED_MODE, // TODO check oscilloscope what it does I2S_LEFT_JUSTIFIED_MODE, // TODO check oscilloscope what it does ADC_DAC_MODE, // Receive and transmit raw analog signal @@ -132,11 +132,11 @@ class I2SClass : public Stream * Constructor - initializes object with default values * * Parameters: - * uint8_t deviceIndex In case the SoC has more I2S module, specify which one is instantiated. Possible values are "0" (for all ESPs) and "1" (only for ESP32 and ESP32-S3) + * uint8_t deviceIndex In case the SoC has more I2S modules, specify which one is instantiated. Possible values are "0" (for all ESPs) and "1" (only for ESP32 and ESP32-S3) * uint8_t clockGenerator Has no meaning for ESP and is kept only for compatibility * uint8_t sdPin Shared data pin used for simplex mode * uint8_t sckPin Clock pin - * uint8_t fsPin Frame (word) select pin + * uint8_t fsPin Frame Sync (Word Select) pin * * Default settings: * Input data pin (used for duplex mode) is initialized with PIN_I2S_SD_IN @@ -147,7 +147,8 @@ class I2SClass : public Stream 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 + * Init in MASTER mode: the SCK and FS pins are driven as outputs using the sample rate. + * Initializes IDF I2S driver, creates ring buffers and callback handler task. * Parameters: * int mode Operation mode (Phillips, Left/Right Justified, ADC+DAC,PDM) see i2s_mode_t for exact enumerations * int sampleRate sampling frequency in Hz. Common values are 8000,11025,16000,22050,32000,44100,64000,88200,128000 @@ -157,6 +158,7 @@ class I2SClass : public Stream int begin(int mode, int sampleRate, int bitsPerSample); /* Init in SLAVE mode: the SCK and FS pins are inputs and must be controlled(generated) be external source (MASTER device). + * Initializes IDF I2S driver, creates ring buffers and callback handler task. * Parameters: * int mode Operation mode (Phillips, Left/Right Justified, ADC+DAC,PDM) see i2s_mode_t for exact enumerations * int bitsPerSample Number of bits per one sample (one channel). Possible values are 8,16,24,32 @@ -164,6 +166,11 @@ class I2SClass : public Stream */ int begin(int mode, int bitsPerSample); + /* + De-initialize IDF I2S driver, frees ring buffers and terminates callback handler task. + */ + void end(); + /* * Change pin setup for each pin separately. * Can be called only on initialized object (after begin). @@ -210,7 +217,7 @@ class I2SClass : public Stream int getDataInPin(); // Get Data Input pin for duplex mode /* - * Change mode (default is Half Duplex) + * Change mode (default is Simplex) * Returns: 1 on success; 0 on error */ int setDuplex(); @@ -222,33 +229,122 @@ class I2SClass : public Stream */ int isDuplex(); - void end(); - - // from Stream + /* + * Returns: number of Bytes available to read from ring buffer. + * Note: The ring buffer is filled automatically by handler task from IDF I2S driver. + */ virtual int available(); - virtual int read(); + + /* + * Reads a single sample from ring buffer and keeps it available for future read (i.e. does not remove the sample from ring buffer) + * Returns: First sample from ring buffer + * Note: The ring buffer is filled automatically by handler task from IDF I2S driver. + */ virtual int peek(); - virtual void flush(); - // from Print - virtual size_t write(uint8_t data); - virtual size_t write(const uint8_t *buffer, size_t size); + /* + * Reads a single sample from ring buffer and removes it from the ring buffer. + * Returns: First sample from ring buffer + * Note: The ring buffer is filled automatically by handler task from IDF I2S driver. + */ + virtual int read(); + + /* + * Reads an array of samples from ring buffer and removes them from the ring buffer. + * Parameters: + * [OUT] void* buffer Buffer into which the samples will be copied. The buffer must allocated before calling this function! + * [IN] size_t size Requested number of bytes to be read + * Returns: Number of bytes that were actually read. + * Note: Always check the returned value! + */ + int read(void* buffer, size_t size); + /* + * Returns: number of bytes that can be written into the ring buffer. + */ virtual int availableForWrite(); - int read(void* buffer, size_t size); + /* + * Write single sample of 8 bit size. + * This function is blocking - if there is not enough space in ring buffer the function will wait until it can write the sample. + * Parameter: + * uint8_t data The sample to be sent + * Returns: 1 on successful write; 0 on error = did not write the sample to ring buffer + * Note: This functions is used in many examples for it's simplicity, but it's use is discouraged for performance reasons. + * Please consider sending data in arrays using function `size_t write(const uint8_t *buffer, size_t size)` + */ + virtual size_t write(uint8_t data); - //size_t write(int); + /* + * Write single sample of up to 32 bit size. + * This function is blocking - if there is not enough space in ring buffer the function will wait until it can write the sample. + * Parameter: + * int32_t data The sample to be sent + * Returns: Number of written bytes, if successful the value will be equal to bitsPerSample/8 + * Note: This functions is used in many examples for it's simplicity, but it's use is discouraged for performance reasons. + * Please consider sending data in arrays using function `size_t write(const uint8_t *buffer, size_t size)` + */ size_t write(int32_t); + + /* + * Write array of samples. + * This function is non-blocking - the function might write only portion of samples into ring buffer, or potentially none at all. Do check the returned value at all times! + * Parameters: + * uint8_t* buffer Array of samples + * size_t size Number of bytes in array + * Returns: Number of bytes successfully written to ring buffer. + * Note: This is the preferred function for writing samples. + */ + virtual size_t write(const uint8_t *buffer, size_t size); + + /* + * Write array of samples. + * This function is non-blocking - the function might write only portion of samples into ring buffer, or potentially none at all. Do check the returned value at all times! + * Parameters: + * void* buffer Array of samples + * size_t size Number of bytes in array + * Returns: Number of bytes successfully written to ring buffer. + * Note: This is the preferred function for writing samples. + */ size_t write(const void *buffer, size_t size); + + // Internally used functions size_t write_blocking(const void *buffer, size_t size); size_t write_nonblocking(const void *buffer, size_t size); + /* + * Force-write data from ring buffer to IDF I2S driver + * This function is useful when sending low amount of data, however such use will lead to low quality audio. + * Note: The ring buffer is emptied (sent) automatically by handler task from IDF I2S driver. + */ + virtual void flush(); + + /* + * Callback handle which will be used each time when the IDF I2S driver transmits data from buffer. + */ void onTransmit(void(*)(void)); + + /* + * Callback handle which will be used each time when the IDF I2S driver receives data into buffer. + */ void onReceive(void(*)(void)); + /* + * Change the size of buffers. The unit is number of sample frames (number_of_channels * (bits_per_sample/8)) + * The resulting Bytes size of ring buffers can be calculated: + * ring_buffer_bytes_size = (number_of_channels * (bits_per_sample/8)) * bufferSize * _I2S_DMA_BUFFER_COUNT + * Example: default value of _I2S_DMA_BUFFER_COUNT is 2, default value of bufferSize is 128; for dual channel, 16 bps we will get + * ring_buffer_bytes_size = (number_of_channels * (bits_per_sample/8)) * bufferSize * _I2S_DMA_BUFFER_COUNT + * 1024 = ( 2 * ( 16 /8)) * 128 * 2 + */ int setBufferSize(int bufferSize); + + /* + * Get buffer size. The unit is number of sample frames (number_of_channels * (bits_per_sample/8)) + * For more info see setBufferSize + */ 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); From 26ac979e4128b56e7aa917e30eae9d8883a6c610 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Mon, 3 Oct 2022 10:47:54 +0200 Subject: [PATCH 10/46] Updated pins in examples + removed skip files --- libraries/I2S/README.md | 10 +++++++- .../I2S/examples/ADCPlotter/.skip.esp32c3 | 0 .../I2S/examples/FullDuplex/.skip.esp32c3 | 0 .../I2S/examples/FullDuplex/FullDuplex.ino | 25 ++++++++----------- .../examples/InputSerialPlotter/.skip.esp32c3 | 0 .../InputSerialPlotter/InputSerialPlotter.ino | 12 +++++---- .../I2S/examples/SimpleTone/SimpleTone.ino | 22 ++++++++-------- 7 files changed, 39 insertions(+), 30 deletions(-) delete mode 100644 libraries/I2S/examples/ADCPlotter/.skip.esp32c3 delete mode 100644 libraries/I2S/examples/FullDuplex/.skip.esp32c3 delete mode 100644 libraries/I2S/examples/InputSerialPlotter/.skip.esp32c3 diff --git a/libraries/I2S/README.md b/libraries/I2S/README.md index 9ff7fe38aae..3cb22c695b5 100644 --- a/libraries/I2S/README.md +++ b/libraries/I2S/README.md @@ -26,7 +26,15 @@ From this we can get also size of the ring buffers. As stated their size is equa The function `setBufferSize` allows you to change the size in frames (see detailed description below). On Arduino and most ESPs you can use object `I2S` to use the I2S module. on ESP32 and ESP32-S3 you have the option to use addititonal object `I2S1`. -I2S module functionality on each SoC differs, please refer to [IDF documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html?highlight=dac#overview-of-all-modes) for an overview. +I2S module functionality on each SoC differs, please refer to the following table. More info can be found in [IDF documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html?highlight=dac#overview-of-all-modes). + +### Overview of I2S Modes: +| SoC | Philips | ADC/DAC | PDM TX | PDM RX | +| -------- | ---------- | ------- | ------ | ------ | +| ESP32 | I2S + I2S1 | I2S | I2S | I2S | +| ESP32-S2 | I2S | N/A | N/A | N/A | +| ESP32-C3 | I2S | N/A | I2S | N/A | +| ESP32-S3 | I2S + I2S1 | N/A | I2S | I2S | ## Pins ESP I2S has fully configurable pins. There is a group of setter and getter functions for each pin separately and one setter for all pins at once (detailed description below). Calling the setter will take effect immediately and does not need driver restart. diff --git a/libraries/I2S/examples/ADCPlotter/.skip.esp32c3 b/libraries/I2S/examples/ADCPlotter/.skip.esp32c3 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libraries/I2S/examples/FullDuplex/.skip.esp32c3 b/libraries/I2S/examples/FullDuplex/.skip.esp32c3 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libraries/I2S/examples/FullDuplex/FullDuplex.ino b/libraries/I2S/examples/FullDuplex/FullDuplex.ino index 9b3625fbd60..acb8306d33f 100644 --- a/libraries/I2S/examples/FullDuplex/FullDuplex.ino +++ b/libraries/I2S/examples/FullDuplex/FullDuplex.ino @@ -1,18 +1,17 @@ /* 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 + The application transfers data from input to output. + You will need I2S microphone I2S decoder + headphones / speakerI2S microphone - Circuit: - * ESP32 - * GND connected GND - * VIN connected 5V - * SCK 5 - * FS 25 - * DIN 35 - * DOUT 26 - * I2S microphone - * I2S decoder + headphones / speaker + | Pin | ESP32 | ESP32-S2, ESP32-C3, ESP32-S3 | + | -----|-------|------------------------------| + | GND | GND | GND | + | VIN | 5V | 5V | + | SCK | 19 | 19 | + | FS | 21 | 21 | + | DIN | 23 | 5 | + | DOUT | 22 | 4 | created 8 October 2021 by Tomas Pilny @@ -25,7 +24,7 @@ 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) + //I2S.setAllPins(19, 21, 23, 22); // you can change default pins; order of pins = (CLK, WS, IN, OUT) if(!I2S.setDuplex()){ Serial.println("ERROR - could not set duplex"); while(true){ @@ -54,6 +53,4 @@ void loop() { // 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 e69de29bb2d..00000000000 diff --git a/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino b/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino index db9f7d0d4f8..2d37fe28222 100644 --- a/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino +++ b/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino @@ -7,11 +7,13 @@ 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) + | Pin | Zero | MKR | Nano | ESP32 | ESP32-S2, ESP32-C3, ESP32-S3 | + | -----|-------|-------|-------|-------|------------------------------| + | GND | GND | GND | GND | GND | GND | + | 3.3V | 3.3V | 3.3V | 3.3V | 3.3V | 3.3V | + | SCK | 1 | 2 | A3 | 19 | 19 | + | FS | 0 | 3 | A2 | 21 | 21 | + | DIN | 9 | A6 | 4 | 23 | 5 | created 17 November 2016 by Sandeep Mistry */ diff --git a/libraries/I2S/examples/SimpleTone/SimpleTone.ino b/libraries/I2S/examples/SimpleTone/SimpleTone.ino index bd5a820b1d1..93b701b2c25 100644 --- a/libraries/I2S/examples/SimpleTone/SimpleTone.ino +++ b/libraries/I2S/examples/SimpleTone/SimpleTone.ino @@ -4,20 +4,22 @@ 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) + * Arduino/Genuino Zero, MKR family, Nano 33 IoT or any ESP32 + * MAX08357 or PCM510xA: + | Pin | Zero | MKR | Nano | ESP32 | ESP32-S2, ESP32-C3, ESP32-S3 | + | -----|-------|-------|-------|-------|------------------------------| + | GND | GND | GND | GND | GND | GND | + | 5V | 5V | 5V | 5V | 5V | 5V | + | SCK | 1 | 2 | A3 | 19 | 19 | + | FS | 0 | 3 | A2 | 21 | 21 | + | SD | 9 | A6 | 4 | 22 | 4 | + * note: those chips supports only 16/24/32 bits per sample, i.e. 8 bps will be refused = no audio output DAC Circuit: - * ESP32 or ESP32-S2 + * ESP32 * 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 @@ -31,7 +33,7 @@ const int frequency = 440; // frequency of square wave in Hz const int sampleRate = 8000; // sample rate in Hz const int bps = 16; -const int amplitude = ((1< Date: Mon, 17 Oct 2022 16:12:02 +0200 Subject: [PATCH 11/46] Added C3 PDM support --- .../I2S/examples/SimpleTone/.skip.esp32c3 | 0 libraries/I2S/src/I2S.cpp | 47 +++++++++++++++---- libraries/I2S/src/I2S.h | 13 ++++- 3 files changed, 48 insertions(+), 12 deletions(-) delete mode 100644 libraries/I2S/examples/SimpleTone/.skip.esp32c3 diff --git a/libraries/I2S/examples/SimpleTone/.skip.esp32c3 b/libraries/I2S/examples/SimpleTone/.skip.esp32c3 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index f4ccada5eac..f384697af6f 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -117,6 +117,7 @@ int I2SClass::_installDriver(){ log_e("(I2S#%d) This chip does not support ADC / DAC mode", _deviceIndex); return 0; // ERR #endif + // TODO left/right justified mode is actually stereo, but ignoring the other channel input// left/righ }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 @@ -128,8 +129,11 @@ int I2SClass::_installDriver(){ log_w("(I2S#%d) Original Arduino library does not support 24 bits per sample.\nKeep that in mind if you should switch back to Arduino", _deviceIndex); } }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) + #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); + #ifndef SOC_I2S_SUPPORTS_PDM_RX + i2s_mode = (esp_i2s::i2s_mode_t)(i2s_mode & ~esp_i2s::I2S_MODE_RX); // remove PRM RX which is not supported on ESP32-C3 + #endif #else log_e("(I2S#%d) This chip does not support PDM", _deviceIndex); return 0; // ERR @@ -254,13 +258,13 @@ int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock // There is work in progress on this library. if(_bitsPerSample == 16 && _sampleRate > 16000 && driveClock){ - log_w("(I2S#%d) This sample rate is not officially supported - audio might be noisy.\nTry using sample rate below or equal to 16000", _deviceIndex); + log_w("(I2S#%d) The sample rate value %d is not officially supported - audio might be noisy.\nTry using sample rate below or equal to 16000", _deviceIndex, _sampleRate); } if(_bitsPerSample != 16){ log_w("(I2S#%d) This bit-per-sample is not officially supported - audio quality might suffer.\nTry using 16bps, with sample rate below or equal 16000", _deviceIndex); } if(_mode != I2S_PHILIPS_MODE){ - log_w("(I2S#%d) This mode is not officially supported - audio quality might suffer.\nAt the moment the only supported mode is I2S_PHILIPS_MODE", _deviceIndex); + log_w("(I2S#%d) The mode %s is not officially supported - audio quality might suffer.\nAt the moment the only supported mode is I2S_PHILIPS_MODE", _deviceIndex, i2s_mode_text[_mode]); } if (_state != I2S_STATE_IDLE && _state != I2S_STATE_DUPLEX) { @@ -276,14 +280,24 @@ int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) case ADC_DAC_MODE: + #else + log_e("(I2S#%d) ERROR: ADC/DAC is not supported on this SoC. Change mode or SoC", _deviceIndex); + _give_if_top_call(); + return 0; // ERR #endif +#if defined(SOC_I2S_SUPPORTS_PDM_TX) || defined(SOC_I2S_SUPPORTS_PDM_RX) case PDM_STEREO_MODE: case PDM_MONO_MODE: break; +#else + log_e("(I2S#%d) ERROR: PDM is not supported on this SoC. Change mode or SoC", _deviceIndex); + _give_if_top_call(); + return 0; // ERR +#endif default: // invalid mode - log_e("(I2S#%d) ERROR: unknown mode", _deviceIndex); + log_e("(I2S#%d) ERROR: unknown or unsupported mode", _deviceIndex); _give_if_top_call(); return 0; // ERR } @@ -295,6 +309,7 @@ int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock } _buffer_byte_size = _i2s_dma_buffer_size * (_bitsPerSample / 8) * _I2S_DMA_BUFFER_COUNT * 2; + log_d("(I2S#%d) Creating internal buffers. Requested size = %dB\n", _deviceIndex, _buffer_byte_size); _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){ @@ -361,7 +376,6 @@ int I2SClass::setSckPin(int sckPin){ _take_if_not_holding(); _setSckPin(sckPin); int ret = _applyPinSetting(); - _applyPinSetting(); _give_if_top_call(); return ret; } @@ -454,15 +468,17 @@ int I2SClass::setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inS int I2SClass::setDuplex(){ _take_if_not_holding(); _state = I2S_STATE_DUPLEX; + int ret = _applyPinSetting(); _give_if_top_call(); - return 1; + return ret; } int I2SClass::setSimplex(){ _take_if_not_holding(); _state = I2S_STATE_IDLE; + int ret = _applyPinSetting(); _give_if_top_call(); - return 1; + return ret; } int I2SClass::isDuplex(){ @@ -707,6 +723,8 @@ size_t I2SClass::write_blocking(const void *buffer, size_t size){ // 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 +// The function will return number of successfully written bytes. It is users responsibility +// to take care of the remaining data. size_t I2SClass::write_nonblocking(const void *buffer, size_t size){ _take_if_not_holding(); if(_initialized){ @@ -759,8 +777,8 @@ int I2SClass::peek(){ _give_if_top_call(); return ret; } - -// TODO flush everything instead of one buffer +// Requests data from ring buffer and writes data to I2S module +// note: it is NOT necessary to call the flush after writes. Buffer is flushed automatically after receiveing enough data. void I2SClass::flush(){ _take_if_not_holding(); if(_initialized){ @@ -804,6 +822,11 @@ void I2SClass::onReceive(void(*function)(void)){ _give_if_top_call(); } + +// Change buffer size. The unit is in frames. +// Byte value can be calculated as follows: +// ByteSize = (bits_per_sample / 8) * number_of_channels * bufferSize +// Calling this function will automatically restart the driver, which could cause audio output gap. int I2SClass::setBufferSize(int bufferSize){ _take_if_not_holding(); int ret = 0; @@ -997,7 +1020,11 @@ void I2SClass::_post_read_data_fix(void *input, size_t *size){ ((uint16_t*)input)[dst_ptr++] = tmp; } break; - default: ; // Do nothing + case 24: + // TODO check if need fix and if so, implement it + break; + default: ; + // Do nothing } // switch } diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index 99a2f837e16..08dd9a8c6b2 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -118,13 +118,22 @@ namespace esp_i2s { typedef enum { I2S_PHILIPS_MODE, // Most common I2S mode, FS signal spans across whole channel period - I2S_RIGHT_JUSTIFIED_MODE, // TODO check oscilloscope what it does - I2S_LEFT_JUSTIFIED_MODE, // TODO check oscilloscope what it does + I2S_RIGHT_JUSTIFIED_MODE, // Right channel data are copied to both channels + I2S_LEFT_JUSTIFIED_MODE, // Left channel data are copied to both channels ADC_DAC_MODE, // Receive and transmit raw analog signal PDM_STEREO_MODE, // Pulse Density Modulation - stereo / 2 channels PDM_MONO_MODE // Pulse Density Modulation - mono / 1 channel } i2s_mode_t; +const char i2s_mode_text[][32] = { + "I2S_PHILIPS_MODE", + "I2S_RIGHT_JUSTIFIED_MODE", + "I2S_LEFT_JUSTIFIED_MODE", + "ADC_DAC_MODE", + "PDM_STEREO_MODE", + "PDM_MONO_MODE" +}; + class I2SClass : public Stream { public: From b02596b7a02b339bb206b66a5e67fcb94654e977 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Mon, 17 Oct 2022 17:00:15 +0200 Subject: [PATCH 12/46] Simplified peek() - NOT tested --- .../InputSerialPlotter/InputSerialPlotter.ino | 2 +- libraries/I2S/src/I2S.cpp | 20 +++---------------- libraries/I2S/src/I2S.h | 2 -- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino b/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino index 2d37fe28222..990f7e86d74 100644 --- a/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino +++ b/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino @@ -40,7 +40,7 @@ void loop() { // read a sample int sample = I2S.read(); - if (sample && sample != -1 && sample != 1) { + if (abs(sample) > 1) { Serial.println(sample); } } diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index f384697af6f..6e6e7922055 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -52,8 +52,6 @@ I2SClass::I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, u _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), @@ -621,11 +619,6 @@ int I2SClass::read(void* buffer, size_t size){ 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); @@ -758,24 +751,17 @@ size_t I2SClass::write_nonblocking(const void *buffer, size_t size){ */ int I2SClass::peek(){ _take_if_not_holding(); - int ret = 0; - if(_initialized && _input_ring_buffer != NULL && !_peek_buff_valid){ + if(_initialized && _input_ring_buffer != NULL){ 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; + return *((int*)item); } - } // if(_initialized) - if(_peek_buff_valid){ - ret = _peek_buff; - } _give_if_top_call(); - return ret; + return 0; } // Requests data from ring buffer and writes data to I2S module // note: it is NOT necessary to call the flush after writes. Buffer is flushed automatically after receiveing enough data. diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index 08dd9a8c6b2..85563f26615 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -408,8 +408,6 @@ class I2SClass : public Stream 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(); From 0f8e9bcf689e76755e9c09e757b20ec2c19324f1 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Mon, 17 Oct 2022 17:39:53 +0200 Subject: [PATCH 13/46] Updated README.md - section describing modes --- libraries/I2S/README.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/libraries/I2S/README.md b/libraries/I2S/README.md index 3cb22c695b5..73cd50a0600 100644 --- a/libraries/I2S/README.md +++ b/libraries/I2S/README.md @@ -60,13 +60,19 @@ The default mode is simplex and the shared data pin switches function upon calli If you wish to use duplex mode call `setDuplex();` this will change the pin setup to use both data lines. If you want to switch back ti simplex call `setSimplex();` and if you need to get current state call `isDuplex();` (detailed function description below). ## Modes (i2s_mode_t) +.. note:: (Arduino MKRZero) First transmitted sample is in Right channel (WS=1). If your audio has swapped left and right channel try feed single zero-value sample before the data stream. + + * **I2S_PHILIPS_MODE** Most common mode, FS signal spans across whole channel period. MSB is transmitted first and data starts SCK falling edge 1 SCK period after WS change. WS 0 = Left channel, 1 = Right channel. See more on [Wikipedia](https://en.wikipedia.org/wiki/I%C2%B2S) + * **I2S_RIGHT_JUSTIFIED_MODE** Does not work on MKRZero, opened [issue](https://github.com/arduino/ArduinoCore-samd/issues/682). Should be expected to the ooposite of `I2S_LEFT_JUSTIFIED_MODE`. + * **I2S_LEFT_JUSTIFIED_MODE** Input data belonging to righ channel are ignored and data belonging to left channel are transmitted in both channels. Example: `int16_t sample[] = {0x0001, 0x0002, 0x0003, 0x0004}` samples with value `0x0001` and `0x0003` belong to the right channel and will be ignored. Samples with value `0x0002` and `0x0004` will be transmitted as follows: Right channel (WS=1) `0x0002` followd by Left channel )WS=0) `0x0004`. this pattern repeats. + * **ADC_DAC_MODE** Outputting and inputting raw analog signal - see [example ADCPlotter](https://github.com/espressif/arduino-esp32/blob/master/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino). + * **PDM_STEREO_MODE** Pulse Density Modulation is basically an over-sampled 1-bit signal. Not to be confused with PCM. (ESP32-C3 supports only Transmit mode - read attempts will time-out) + * **PDM_MONO_MODE** Same as previous but transmits only 1 channel. TODO explain timing diagram and data interpretation. + +.. note:: First 2 samples in a stream are zero value, this will not affect audio quality. + +.. note:: Some ESP32 SoCs support PCM, TDM and LCD/Camera modes, however support for those modes is out of scope in this simplified library. If you need to use those mode you will need to use [IDF I2S driver](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html). - * **I2S_PHILIPS_MODE** Most common mode, FS signal spans across whole channel period. TODO more info - * **I2S_RIGHT_JUSTIFIED_MODE** ? TODO - * **I2S_LEFT_JUSTIFIED_MODE** ? TODO - * **ADC_DAC_MODE** Outputting and inputting raw analog signal - see example ADCPlotter - * **PDM_STEREO_MODE** Pulse Density Modulation is basically an over-sampled 1-bit signal. Not to be confused with PCM. - * **PDM_MONO_MODE** Same as previous but transmits only 1 channel. TODO explain timing diagram and data interpretation ## Functions From 2b04bf84fc76990672995772c135434e0f37efdf Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Tue, 18 Oct 2022 16:12:38 +0200 Subject: [PATCH 14/46] Revert "Simplified peek() - NOT tested" This reverts commit b02596b7a02b339bb206b66a5e67fcb94654e977. --- .../InputSerialPlotter/InputSerialPlotter.ino | 2 +- libraries/I2S/src/I2S.cpp | 20 ++++++++++++++++--- libraries/I2S/src/I2S.h | 2 ++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino b/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino index 990f7e86d74..2d37fe28222 100644 --- a/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino +++ b/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino @@ -40,7 +40,7 @@ void loop() { // read a sample int sample = I2S.read(); - if (abs(sample) > 1) { + if (sample && sample != -1 && sample != 1) { Serial.println(sample); } } diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index 6e6e7922055..f384697af6f 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -52,6 +52,8 @@ I2SClass::I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, u _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), @@ -619,6 +621,11 @@ int I2SClass::read(void* buffer, size_t size){ 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); @@ -751,17 +758,24 @@ size_t I2SClass::write_nonblocking(const void *buffer, size_t size){ */ int I2SClass::peek(){ _take_if_not_holding(); - if(_initialized && _input_ring_buffer != NULL){ + 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){ - return *((int*)item); + _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 0; + return ret; } // Requests data from ring buffer and writes data to I2S module // note: it is NOT necessary to call the flush after writes. Buffer is flushed automatically after receiveing enough data. diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index 85563f26615..08dd9a8c6b2 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -408,6 +408,8 @@ class I2SClass : public Stream 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(); From 989817c6b15d4bc45a48136b2679b52e1a21e7ce Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Tue, 18 Oct 2022 16:19:04 +0200 Subject: [PATCH 15/46] Extended mode warning and added mode text array --- libraries/I2S/src/I2S.cpp | 11 +++++++++-- libraries/I2S/src/I2S.h | 3 ++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index f384697af6f..7b4a74aa3d6 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -261,7 +261,7 @@ int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock log_w("(I2S#%d) The sample rate value %d is not officially supported - audio might be noisy.\nTry using sample rate below or equal to 16000", _deviceIndex, _sampleRate); } if(_bitsPerSample != 16){ - log_w("(I2S#%d) This bit-per-sample is not officially supported - audio quality might suffer.\nTry using 16bps, with sample rate below or equal 16000", _deviceIndex); + log_w("(I2S#%d) The %d bit-per-sample is not officially supported - audio quality might suffer.\nTry using 16bps, with sample rate below or equal 16000", _deviceIndex, _bitsPerSample); } if(_mode != I2S_PHILIPS_MODE){ log_w("(I2S#%d) The mode %s is not officially supported - audio quality might suffer.\nAt the moment the only supported mode is I2S_PHILIPS_MODE", _deviceIndex, i2s_mode_text[_mode]); @@ -273,13 +273,16 @@ int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock return 0; // ERR } + delay(250); switch (mode) { case I2S_PHILIPS_MODE: case I2S_RIGHT_JUSTIFIED_MODE: case I2S_LEFT_JUSTIFIED_MODE: + break; #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) case ADC_DAC_MODE: + break; #else log_e("(I2S#%d) ERROR: ADC/DAC is not supported on this SoC. Change mode or SoC", _deviceIndex); _give_if_top_call(); @@ -297,7 +300,11 @@ int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock #endif default: // invalid mode - log_e("(I2S#%d) ERROR: unknown or unsupported mode", _deviceIndex); + if(mode >= 0 && mode < MODE_MAX){ + log_e("(I2S#%d) ERROR: Mode '%s' is not supported on this SoC", _deviceIndex, i2s_mode_text[mode]); + }else{ + log_e("(I2S#%d) ERROR: Mode %d is not recognized", _deviceIndex, mode); + } _give_if_top_call(); return 0; // ERR } diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index 08dd9a8c6b2..9ec730b0d9b 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -122,7 +122,8 @@ typedef enum { I2S_LEFT_JUSTIFIED_MODE, // Left channel data are copied to both channels ADC_DAC_MODE, // Receive and transmit raw analog signal PDM_STEREO_MODE, // Pulse Density Modulation - stereo / 2 channels - PDM_MONO_MODE // Pulse Density Modulation - mono / 1 channel + PDM_MONO_MODE, // Pulse Density Modulation - mono / 1 channel + MODE_MAX // Helper value - not actual mode } i2s_mode_t; const char i2s_mode_text[][32] = { From fe268c82af0da95ad3310c4bb2538e62de195504 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Mon, 24 Oct 2022 14:46:00 +0200 Subject: [PATCH 16/46] Added new example demonstrating usage of both I2S module (not working yet) --- .../I2S/examples/DualModule/DualModule.ino | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 libraries/I2S/examples/DualModule/DualModule.ino diff --git a/libraries/I2S/examples/DualModule/DualModule.ino b/libraries/I2S/examples/DualModule/DualModule.ino new file mode 100644 index 00000000000..2a6113d3112 --- /dev/null +++ b/libraries/I2S/examples/DualModule/DualModule.ino @@ -0,0 +1,116 @@ +/* + This example is only for ESP32 and ESP32-S3 which have two separate I2S modules + This example demonstrates simultaneous usage of both I2S module. + The application generates square wave for both modules and transfers to each other. + You can plot the waves with Arduino plotter + + I2S + | Pin | ESP32 | ESP32-S3 | + | -----|-------|- --------| + | GND | GND | GND | + | VIN | 5V | 5V | + | SCK | 19 | 19 | + | FS | 21 | 21 | + | DIN | 23 | 5 | + | DOUT | 22 | 4 | + + I2S1 + | Pin | ESP32 | ESP32-S3 | + | -----|-------|- --------| + | GND | GND | GND | + | VIN | 5V | 5V | + | SCK | 18 | 36 | + | FS | 22 | 37 | + | DIN | 26 | 40 | + | DOUT | 25 | 39 | + + created 18 October 2022 + by Tomas Pilny + */ + +#include +const long sampleRate = 16000; +const int bps = 32; +uint8_t *buffer; +uint8_t *buffer1; + + +const int signal_frequency = 1000; // frequency of square wave in Hz +const int signal_frequency1 = 2000; // frequency of square wave in Hz +const int amplitude = (1<<(bps-1))-1; // amplitude of square wave +const int halfWavelength = (sampleRate / signal_frequency); // half wavelength of square wave +const int halfWavelength1 = (sampleRate / signal_frequency1); // half wavelength of square wave +int32_t write_sample = amplitude; // current sample value +int32_t write_sample1 = amplitude; // current sample value +int count = 0; + + +void setup() { + Serial.begin(115200); + while(!Serial); + I2S.setAllPins(PIN_I2S1_SCK, PIN_I2S1_FS, PIN_I2S1_SD, PIN_I2S1_SD_OUT, PIN_I2S1_SD_IN); // set all pins to match I2S1 to connect them internally + if(!I2S.setDuplex()){ + Serial.println("ERROR - could not set duplex for I2S"); + while(true){ + vTaskDelay(10); // Cannot continue + } + } + if (!I2S.begin(I2S_PHILIPS_MODE, sampleRate, bps)) { + Serial.println("Failed to initialize I2S!"); + while(true){ + vTaskDelay(10); // Cannot continue + } + } + buffer = (uint8_t*) malloc(I2S.getBufferSize() * (bps / 8)); + if(buffer == NULL){ + Serial.println("Failed to allocate buffer!"); + while(true){ + vTaskDelay(10); // Cannot continue + } + } + + if(!I2S1.setDuplex()){ + Serial.println("ERROR - could not set duplex for I2S1"); + while(true){ + vTaskDelay(10); // Cannot continue + } + } + if (!I2S1.begin(I2S_PHILIPS_MODE, bps)) { // start in slave mode --- CRASHING! + Serial.println("Failed to initialize I2S1!"); + while(true){ + vTaskDelay(10); // Cannot continue + } + } + buffer1 = (uint8_t*) malloc(I2S1.getBufferSize() * (bps / 8)); + if(buffer1 == NULL){ + Serial.println("Failed to allocate buffer1!"); + while(true){ + vTaskDelay(10); // Cannot continue + } + } + Serial.println("Setup done"); +} + +void loop() { + // invert the sample every half wavelength count multiple to generate square wave + if (count % halfWavelength == 0 ) { + write_sample = -1 * write_sample; + } + I2S.write(write_sample); // Right channel + I2S.write(write_sample); // Left channel + if (count % halfWavelength1 == 0 ) { + write_sample1 = -1 * write_sample1; + } + I2S1.write(write_sample1); // Right channel + I2S1.write(write_sample1); // Left channel + + // increment the counter for the next sample + count++; + + + int read_sample = I2S.read(); + int read_sample1 = I2S1.read(); + + Serial.printf("%d %d", read_sample, read_sample1); + Serial.printf("%d", read_sample1); +} \ No newline at end of file From a0ab150b6c2a1e73cab435037aa409789d0ce1ae Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Tue, 25 Oct 2022 11:20:16 +0200 Subject: [PATCH 17/46] Changed mutex to recursive mutex --- libraries/I2S/src/I2S.cpp | 233 +++++++++++++++++++------------------- libraries/I2S/src/I2S.h | 5 +- 2 files changed, 118 insertions(+), 120 deletions(-) diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index 7b4a74aa3d6..818671dbad1 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -59,7 +59,7 @@ I2SClass::I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, u _onTransmit(NULL), _onReceive(NULL) { - _i2s_general_mutex = xSemaphoreCreateMutex(); + _i2s_general_mutex = xSemaphoreCreateRecursiveMutex(); if(_i2s_general_mutex == NULL){ log_e("(I2S#%d) I2S could not create internal mutex!", _deviceIndex); } @@ -225,29 +225,28 @@ int I2SClass::_installDriver(){ // 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(); + if(!_take_mux()){ return 0; /* ERR */ } // master mode (driving clock and frame select pins - output) int ret = begin(mode, sampleRate, bitsPerSample, true); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } 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(); + if(!_take_mux()){ return 0; /* ERR */ } // slave mode (not driving clock and frame select pin - input) int ret = begin(mode, 96000, bitsPerSample, false); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } - // Core function int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } if(_initialized){ log_e("(I2S#%d) ERROR: Object already initialized! Call I2S.end() to disable", _deviceIndex); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR } _driveClock = driveClock; @@ -269,7 +268,7 @@ int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock if (_state != I2S_STATE_IDLE && _state != I2S_STATE_DUPLEX) { log_e("(I2S#%d) Error: unexpected _state (%d)", _deviceIndex, _state); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR } @@ -285,7 +284,7 @@ int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock break; #else log_e("(I2S#%d) ERROR: ADC/DAC is not supported on this SoC. Change mode or SoC", _deviceIndex); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR #endif @@ -295,7 +294,7 @@ int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock break; #else log_e("(I2S#%d) ERROR: PDM is not supported on this SoC. Change mode or SoC", _deviceIndex); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR #endif @@ -305,13 +304,13 @@ int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock }else{ log_e("(I2S#%d) ERROR: Mode %d is not recognized", _deviceIndex, mode); } - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR } if(!_installDriver()){ log_e("(I2S#%d) ERROR: failed to install driver", _deviceIndex); end(); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR } @@ -321,18 +320,18 @@ int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock _output_ring_buffer = xRingbufferCreate(_buffer_byte_size, RINGBUF_TYPE_BYTEBUF); if(_input_ring_buffer == NULL || _output_ring_buffer == NULL){ log_e("(I2S#%d) ERROR: could not create one or both internal buffers. Requested size = %d\n", _deviceIndex, _buffer_byte_size); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR } if(!_createCallbackTask()){ log_e("(I2S#%d) ERROR: failed to create callback task", _deviceIndex); end(); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR } _initialized = true; - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 1; // OK } @@ -370,163 +369,171 @@ int I2SClass::_applyPinSetting(){ } void I2SClass::_setSckPin(int sckPin){ - _take_if_not_holding(); + if(!_take_mux()){ return; /* ERR */ } if(sckPin >= 0){ _sckPin = sckPin; }else{ _sckPin = PIN_I2S_SCK; } - _give_if_top_call(); + if(!_give_mux()){ return; /* ERR */ } } int I2SClass::setSckPin(int sckPin){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } _setSckPin(sckPin); int ret = _applyPinSetting(); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } void I2SClass::_setFsPin(int fsPin){ + if(!_take_mux()){ return; /* ERR */ } if(fsPin >= 0){ _fsPin = fsPin; }else{ _fsPin = PIN_I2S_FS; } + if(!_give_mux()){ return; /* ERR */ } } int I2SClass::setFsPin(int fsPin){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } _setFsPin(fsPin); int ret = _applyPinSetting(); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } // shared data pin for simplex void I2SClass::_setDataPin(int sdPin){ + if(!_take_mux()){ return; /* ERR */ } if(sdPin >= 0){ _sdPin = sdPin; }else{ _sdPin = PIN_I2S_SD; } + if(!_give_mux()){ return; /* ERR */ } } // shared data pin for simplex int I2SClass::setDataPin(int sdPin){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } _setDataPin(sdPin); int ret = _applyPinSetting(); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } void I2SClass::_setDataInPin(int inSdPin){ + if(!_take_mux()){ return; /* ERR */ } if(inSdPin >= 0){ _inSdPin = inSdPin; }else{ _inSdPin = PIN_I2S_SD_IN; } + if(!_give_mux()){ return; /* ERR */ } } int I2SClass::setDataInPin(int inSdPin){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } _setDataInPin(inSdPin); int ret = _applyPinSetting(); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } void I2SClass::_setDataOutPin(int outSdPin){ + if(!_take_mux()){ return; /* ERR */ } if(outSdPin >= 0){ _outSdPin = outSdPin; }else{ _outSdPin = PIN_I2S_SD; } + if(!_give_mux()){ return; /* ERR */ } } int I2SClass::setDataOutPin(int outSdPin){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } _setDataOutPin(outSdPin); int ret = _applyPinSetting(); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } int I2SClass::setAllPins(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } int ret = setAllPins(PIN_I2S_SCK, PIN_I2S_FS, PIN_I2S_SD, PIN_I2S_SD_OUT, PIN_I2S_SD_IN); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } int I2SClass::setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } _setSckPin(sckPin); _setFsPin(fsPin); _setDataPin(sdPin); _setDataOutPin(outSdPin); _setDataInPin(inSdPin); int ret = _applyPinSetting(); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } int I2SClass::setDuplex(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } _state = I2S_STATE_DUPLEX; int ret = _applyPinSetting(); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } int I2SClass::setSimplex(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } _state = I2S_STATE_IDLE; int ret = _applyPinSetting(); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } int I2SClass::isDuplex(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } int ret = (int)(_state == I2S_STATE_DUPLEX); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } int I2SClass::getSckPin(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } int ret = _sckPin; - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } int I2SClass::getFsPin(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } int ret = _fsPin; - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } int I2SClass::getDataPin(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } int ret = _sdPin; - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } int I2SClass::getDataInPin(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } int ret = _inSdPin; - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } int I2SClass::getDataOutPin(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } int ret = _outSdPin; - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } @@ -547,7 +554,7 @@ void I2SClass::_uninstallDriver(){ } void I2SClass::end(){ - _take_if_not_holding(); + if(!_take_mux()){ return; /* ERR */ } if(xTaskGetCurrentTaskHandle() != _callbackTaskHandle){ if(_callbackTaskHandle){ vTaskDelete(_callbackTaskHandle); @@ -568,17 +575,17 @@ void I2SClass::end(){ }else{ log_w("(I2S#%d) WARNING: ending I2SClass from callback task not permitted, but attempted!", _deviceIndex); } - _give_if_top_call(); + if(!_give_mux()){ return; /* ERR */ } } // Bytes available to read int I2SClass::available(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } int ret = 0; if(_input_ring_buffer != NULL){ ret = _buffer_byte_size - (int)xRingbufferGetCurFreeSize(_input_ring_buffer); } - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } @@ -589,39 +596,39 @@ union i2s_sample_t { }; int I2SClass::read(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } i2s_sample_t sample; sample.b32 = 0; if(_initialized){ read(&sample, _bitsPerSample / 8); if (_bitsPerSample == 32) { - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return sample.b32; } else if (_bitsPerSample == 24) { - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return sample.b32; } else if (_bitsPerSample == 16) { - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return sample.b16; } else if (_bitsPerSample == 8) { - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return sample.b8; } else { - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // sample value } } // if(_initialized) - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // sample value } int I2SClass::read(void* buffer, size_t size){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } size_t requested_size = size; if(_initialized){ if(!_enableReceiver()){ - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // There was an error switching to receiver } // _enableReceiver succeeded ? @@ -644,57 +651,57 @@ int I2SClass::read(void* buffer, size_t size){ } // ADC/DAC mode #endif vRingbufferReturnItem(_input_ring_buffer, tmp_buffer); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return item_size; }else{ log_w("(I2S#%d) input buffer is empty - timed out", _deviceIndex); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // 0 Bytes read / ERR } // tmp buffer not NULL ? } // ring buffer not NULL ? } // if(_initialized) - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // 0 Bytes read / ERR } size_t I2SClass::write(uint8_t data){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } size_t ret = 0; if(_initialized){ ret = write_blocking((int32_t*)&data, 1); } - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } size_t I2SClass::write(int32_t sample){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } size_t ret = 0; if(_initialized){ ret = write_blocking(&sample, _bitsPerSample/8); } - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } size_t I2SClass::write(const uint8_t *buffer, size_t size){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } size_t ret = 0; if(_initialized){ ret = write((const void*)buffer, size); } - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } size_t I2SClass::write(const void *buffer, size_t size){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } size_t ret = 0; if(_initialized){ //size_t ret = write_blocking(buffer, size); ret = write_nonblocking(buffer, size); } // if(_initialized) - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } @@ -702,28 +709,28 @@ size_t I2SClass::write(const void *buffer, size_t size){ // 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(!_take_mux()){ return 0; /* ERR */ } if(_initialized){ if(!_enableTransmitter()){ - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } 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(); + if(!_give_mux()){ return 0; /* ERR */ } return size; }else{ log_e("(I2S#%d) xRingbufferSend() with infinite wait returned with error", _deviceIndex); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; } // ring buffer send ok ? } // ring buffer not NULL ? } // if(_initialized) return 0; log_w("(I2S#%d) I2S not initialized", _deviceIndex); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; } @@ -733,11 +740,11 @@ size_t I2SClass::write_blocking(const void *buffer, size_t size){ // The function will return number of successfully written bytes. It is users responsibility // to take care of the remaining data. size_t I2SClass::write_nonblocking(const void *buffer, size_t size){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } if(_initialized){ if(_state != I2S_STATE_TRANSMITTER && _state != I2S_STATE_DUPLEX){ if(!_enableTransmitter()){ - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // There was an error switching to transmitter } } @@ -746,17 +753,17 @@ size_t I2SClass::write_nonblocking(const void *buffer, size_t size){ } if(_output_ring_buffer != NULL){ if(pdTRUE == xRingbufferSend(_output_ring_buffer, buffer, size, 0)){ - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return size; }else{ log_w("(I2S#%d) I2S could not write all data into ring buffer!", _deviceIndex); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; } } } // if(_initialized) return 0; - _give_if_top_call(); // this should not be needed + if(!_give_mux()){ return 0; /* ERR */ } // this should not be needed } /* @@ -764,7 +771,7 @@ size_t I2SClass::write_nonblocking(const void *buffer, size_t size){ Repeated peeks will return the same sample until read is called. */ int I2SClass::peek(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } int ret = 0; if(_initialized && _input_ring_buffer != NULL && !_peek_buff_valid){ size_t item_size = 0; @@ -781,13 +788,13 @@ int I2SClass::peek(){ if(_peek_buff_valid){ ret = _peek_buff; } - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } // Requests data from ring buffer and writes data to I2S module // note: it is NOT necessary to call the flush after writes. Buffer is flushed automatically after receiveing enough data. void I2SClass::flush(){ - _take_if_not_holding(); + if(!_take_mux()){ return; /* ERR */ } if(_initialized){ const size_t single_dma_buf = _i2s_dma_buffer_size*(_bitsPerSample/8)*2; size_t item_size = 0; @@ -801,32 +808,32 @@ void I2SClass::flush(){ } } } // if(_initialized) - _give_if_top_call(); + if(!_give_mux()){ return; /* ERR */ } } // Bytes available to write int I2SClass::availableForWrite(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } int ret = 0; if(_initialized){ if(_output_ring_buffer != NULL){ ret = (int)xRingbufferGetCurFreeSize(_output_ring_buffer); } } // if(_initialized) - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } void I2SClass::onTransmit(void(*function)(void)){ - _take_if_not_holding(); + if(!_take_mux()){ return; /* ERR */ } _onTransmit = function; - _give_if_top_call(); + if(!_give_mux()){ return; /* ERR */ } } void I2SClass::onReceive(void(*function)(void)){ - _take_if_not_holding(); + if(!_take_mux()){ return; /* ERR */ } _onReceive = function; - _give_if_top_call(); + if(!_give_mux()){ return; /* ERR */ } } @@ -835,33 +842,33 @@ void I2SClass::onReceive(void(*function)(void)){ // ByteSize = (bits_per_sample / 8) * number_of_channels * bufferSize // Calling this function will automatically restart the driver, which could cause audio output gap. int I2SClass::setBufferSize(int bufferSize){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } int ret = 0; if(bufferSize >= 8 && bufferSize <= 1024){ _i2s_dma_buffer_size = bufferSize; }else{ log_e("(I2S#%d) setBufferSize: wrong input! Buffer size must be between 8 and 1024. Requested %d", _deviceIndex, bufferSize); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR } // check requested buffer size if(_initialized){ _uninstallDriver(); ret = _installDriver(); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; }else{ // check requested buffer size - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } 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(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR } int I2SClass::getBufferSize(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } int ret = _i2s_dma_buffer_size; - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } @@ -981,28 +988,20 @@ void I2SClass::onDmaTransferComplete(void *deviceIndex){ vTaskDelete(NULL); } -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#%d) I2S internal mutex take returned with error", _deviceIndex); +inline bool I2SClass::_take_mux(){ + if(_i2s_general_mutex == NULL || xSemaphoreTakeRecursive(_i2s_general_mutex, portMAX_DELAY) != pdTRUE){ + log_e("(I2S#%d) Could not take semaphore %p", _deviceIndex, _i2s_general_mutex); + return false; } - //_give_if_top_call(); // call after this function + return true; } -void I2SClass::_give_if_top_call(){ - if(_nesting_counter){ - --_nesting_counter; - }else{ - if(xSemaphoreGive(_i2s_general_mutex) != pdTRUE){ - log_e("(I2S#%d) I2S internal mutex give error", _deviceIndex); - } +inline bool I2SClass::_give_mux(){ + if(_i2s_general_mutex == NULL || xSemaphoreGiveRecursive(_i2s_general_mutex) != pdTRUE){ + log_e("(I2S#%d) Could not give semaphore %p", _deviceIndex, _i2s_general_mutex); + return false; } + return true; } diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index 9ec730b0d9b..5c2d31ac591 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -411,13 +411,12 @@ class I2SClass : public Stream 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(); + inline bool _take_mux(); + inline bool _give_mux(); 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); From d7e122811438e73448a7040044218a13ac8bcfd6 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Thu, 27 Oct 2022 11:00:17 +0200 Subject: [PATCH 18/46] Added missing initializers to satisfy compilier's warnings --- libraries/I2S/src/I2S.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index 818671dbad1..e5628c6ecc7 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -148,7 +148,19 @@ int I2SClass::_installDriver(){ .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2, .dma_buf_count = _I2S_DMA_BUFFER_COUNT, .dma_buf_len = _i2s_dma_buffer_size, - .use_apll = false + .use_apll = false, + .tx_desc_auto_clear = true, + .fixed_mclk = 0, + .mclk_multiple = esp_i2s::I2S_MCLK_MULTIPLE_DEFAULT, + .bits_per_chan = esp_i2s::I2S_BITS_PER_CHAN_DEFAULT, +#if SOC_I2S_SUPPORTS_TDM + .chan_mask = I2S_TDM_ACTIVE_CH0 | I2S_TDM_ACTIVE_CH1, + .total_chan = 2, + .left_align = false, + .big_edin = false, + .bit_order_msb = false, + .skip_msk = false +#endif }; if(_driveClock == false){ @@ -272,7 +284,6 @@ int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock return 0; // ERR } - delay(250); switch (mode) { case I2S_PHILIPS_MODE: case I2S_RIGHT_JUSTIFIED_MODE: @@ -338,6 +349,7 @@ int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock 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, From e96f5a97c0e0a19214266f3ec4f95e9e20fe56bc Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Thu, 27 Oct 2022 11:09:54 +0200 Subject: [PATCH 19/46] Fix for previous commit - I2S_TDM_ACTIVE_CH --- libraries/I2S/src/I2S.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index e5628c6ecc7..284e7162e75 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -154,7 +154,7 @@ int I2SClass::_installDriver(){ .mclk_multiple = esp_i2s::I2S_MCLK_MULTIPLE_DEFAULT, .bits_per_chan = esp_i2s::I2S_BITS_PER_CHAN_DEFAULT, #if SOC_I2S_SUPPORTS_TDM - .chan_mask = I2S_TDM_ACTIVE_CH0 | I2S_TDM_ACTIVE_CH1, + .chan_mask = (esp_i2s::i2s_channel_t)(esp_i2s::I2S_TDM_ACTIVE_CH0 | esp_i2s::I2S_TDM_ACTIVE_CH1), .total_chan = 2, .left_align = false, .big_edin = false, From 8f6613e803bd59b76c0626e410fd3b3600c90304 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Thu, 27 Oct 2022 16:44:10 +0200 Subject: [PATCH 20/46] First I2S tests --- tests/i2s/i2s.ino | 130 ++++++++++++++++++++++++++++++++++++++++++ tests/i2s/test_i2s.py | 2 + 2 files changed, 132 insertions(+) create mode 100644 tests/i2s/i2s.ino create mode 100644 tests/i2s/test_i2s.py diff --git a/tests/i2s/i2s.ino b/tests/i2s/i2s.ino new file mode 100644 index 00000000000..f6b373121fd --- /dev/null +++ b/tests/i2s/i2s.ino @@ -0,0 +1,130 @@ +#include +#include +#include "Arduino.h" + +I2SClass *I2S_obj; +I2SClass *I2S_obj_arr[SOC_I2S_NUM]; + +/* These functions are intended to be called before and after each test. */ +void setUp(void) { + // use default pins + I2S.setAllPins(); + #if SOC_I2S_NUM > 1 + I2S1.setAllPins(); + #endif +} + +void tearDown(void){ + I2S.end(); + #if SOC_I2S_NUM > 1 + I2S1.end(); + #endif +} + +// Test begin - all expected possibilities, then few unexpected option +void test_01(void){ + //Serial.printf("[%lu] test 1: I2S_obj=%p\n", millis(), I2S_obj); + int sample_rate[] = {8000,11025,16000,22050,32000,44100,64000,88200,128000}; + //int sample_rate[] = {8000}; + int srp_max = sizeof(sample_rate)/sizeof(int); + + int bps[] = {8,16,24,32}; + //int bps[] = {8}; + int bpsp_max = sizeof(bps)/sizeof(int); + + int ret = -1; + + //Serial.printf("*********** [%lu] Begin I2S with golden parameters **************\n", millis()); + for(int mode = 0; mode < MODE_MAX; ++mode){ + for(int srp = 0; srp < srp_max; ++srp){ + for(int bpsp = 0; bpsp < bpsp_max; ++bpsp){ + //Serial.printf("[%lu] begin: mode %d \"%s\", sample rate %d, bps %d\n", millis(), mode, i2s_mode_text[mode], sample_rate[srp], bps[bpsp]); + ret = I2S_obj->begin(mode, sample_rate[srp], bps[bpsp]); + + if(mode == ADC_DAC_MODE){ + #if defined(SOC_I2S_SUPPORTS_ADC) && defined(SOC_I2S_SUPPORTS_DAC) + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + #else + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + #endif + }else if(mode == PDM_STEREO_MODE || mode == PDM_MONO_MODE){ + #if defined(SOC_I2S_SUPPORTS_PDM_TX) || defined(SOC_I2S_SUPPORTS_PDM_RX) + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + #else + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + #endif + }else{ + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + } + I2S_obj->end(); + } + } + } + + //Serial.printf("*********** [%lu] Begin I2S with nonexistent mode **************\n", millis()); + // Nonexistent mode - should fail + ret = I2S_obj->begin(MODE_MAX, 8000, 8); + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + I2S_obj->end(); + + //Serial.printf("*********** [%lu] Begin I2S with Unsupported Bits Per Sample **************\n", millis()); + // Unsupported Bits Per Sample - all should fail + int unsupported_bps[] = {-1,0,1,7,9,15,17,23,25,31,33,255,65536}; + int ubpsp_max = sizeof(unsupported_bps)/sizeof(int); + for(int ubpsp = 0; ubpsp < ubpsp_max; ++ubpsp){ + ret = I2S_obj->begin(I2S_PHILIPS_MODE, 8000, unsupported_bps[ubpsp]); + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + I2S_obj->end(); + } + + //Serial.printf("*********** [%lu] Begin I2S with Unusual, yet possible sample rates **************\n", millis()); + // Unusual, yet possible sample rates - all should succeed + int unusual_sample_rate[] = {4000,64000,88200,96000,128000}; + int usrp_max = sizeof(unusual_sample_rate)/sizeof(int); + for(int usrp = 0; usrp < usrp_max; ++usrp){ + ret = I2S_obj->begin(I2S_PHILIPS_MODE, unusual_sample_rate[usrp], 32); + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + I2S_obj->end(); + } + //Serial.printf("*********** [%lu] test_01 complete **************\n", millis()); +} + +// Pin setters and geters +void test_02(void){ + int ret = -1; + + // Test on UNINITIALIZED I2S + + ret = I2S_obj->setAllPins(); // Set default pins + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + // TODO +} + +void setup() { + Serial.begin(115200); + while (!Serial) { + ; + } + + //Serial.printf("Num of I2S module =%d\n", SOC_I2S_NUM); + //Serial.printf("I2S0=%p\n", &I2S); + I2S_obj_arr[0] = &I2S; + #if SOC_I2S_NUM > 1 + I2S_obj_arr[1] = &I2S1; + //Serial.printf("I2S1=%p\n", &I2S1); + #endif + + UNITY_BEGIN(); + for(int obj = 0; obj < SOC_I2S_NUM; ++ obj){ + //Serial.printf("*******************************************************\n"); + //Serial.printf("********************* I2S # %d *************************\n", obj); + //Serial.printf("*******************************************************\n"); + I2S_obj = I2S_obj_arr[obj]; + RUN_TEST(test_01); + RUN_TEST(test_02); + } + UNITY_END(); +} + +void loop() { +} diff --git a/tests/i2s/test_i2s.py b/tests/i2s/test_i2s.py new file mode 100644 index 00000000000..b49bc2b6ed8 --- /dev/null +++ b/tests/i2s/test_i2s.py @@ -0,0 +1,2 @@ +def test_i2s(dut): + dut.expect_unity_test_output(timeout=120) From 5f3542c979119b7eb418894d89df2292bbfb579c Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Thu, 27 Oct 2022 16:47:01 +0200 Subject: [PATCH 21/46] Changed default FS pin which was accidentally set on UART TX for C3 --- libraries/I2S/src/I2S.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index 5c2d31ac591..026534e4172 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -29,14 +29,14 @@ namespace esp_i2s { // Default pins //I2S0 is available for all ESP32 SoCs //SCK WS SD(OUT) SDIN -// 19 21 22 23 ESP32 -// 19 21 4 5 ESP32-x (C3,S2,S3) +// 19 18 22 23 ESP32 +// 19 18 4 5 ESP32-x (C3,S2,S3) #ifndef PIN_I2S_SCK #define PIN_I2S_SCK 19 #endif #ifndef PIN_I2S_FS - #define PIN_I2S_FS 21 + #define PIN_I2S_FS 18 #endif #ifndef PIN_I2S_SD From f67ce84211a7012f0075323535b10f1f22d631f3 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Mon, 31 Oct 2022 09:50:04 +0100 Subject: [PATCH 22/46] Added getI2SNum() --- libraries/I2S/README.md | 10 ++++++++++ libraries/I2S/src/I2S.cpp | 4 ++++ libraries/I2S/src/I2S.h | 9 +++++++++ 3 files changed, 23 insertions(+) diff --git a/libraries/I2S/README.md b/libraries/I2S/README.md index 73cd50a0600..66031561b70 100644 --- a/libraries/I2S/README.md +++ b/libraries/I2S/README.md @@ -348,3 +348,13 @@ ring_buffer_bytes_size = (number_of_channels * (bits_per_sample/8)) * bufferSize *Get buffer size.* The unit is number of sample frames `(number_of_channels * (bits_per_sample/8))` For more info see `setBufferSize` +*** +#### int getI2SNum() + +Get the ID number of I2S module used for particular object. + +Object `I2S` returns value `0` + +Object `I2S1` returns value `1` + +Only ESP32 and ESP32-S3 have two modules, other SoCs have only one I2S module controlled by object `I2S` and the return value will always be `0`, the second object `I2S1` does not exist. diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index 284e7162e75..7f931c57792 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -884,6 +884,10 @@ int I2SClass::getBufferSize(){ return ret; } +int I2SClass::getI2SNum(){ + return _deviceIndex; +} + int I2SClass::_enableTransmitter(){ if(_state != I2S_STATE_DUPLEX && _state != I2S_STATE_TRANSMITTER){ _state = I2S_STATE_TRANSMITTER; diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index 026534e4172..56c1d828c70 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -355,6 +355,15 @@ class I2SClass : public Stream */ int getBufferSize(); + /* + Get the ID number of I2S module used for particular object. + Object I2S returns value 0 + Object I2S1 returns value 1 + Only ESP32 and ESP32-S3 have two modules, other SoCs have only one I2S module + controlled by object I2S and the return value will always be 0, the second object I2S1 does not exist. + */ + int getI2SNum(); + private: #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) int _gpioToAdcUnit(gpio_num_t gpio_num, esp_i2s::adc_unit_t* adc_unit); From 0719bdcbd66d2481a949027f76c5ea0cd7e4d3b8 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Mon, 31 Oct 2022 10:46:46 +0100 Subject: [PATCH 23/46] Added function isInitialized() --- libraries/I2S/README.md | 6 ++++++ libraries/I2S/src/I2S.cpp | 4 ++++ libraries/I2S/src/I2S.h | 6 ++++++ 3 files changed, 16 insertions(+) diff --git a/libraries/I2S/README.md b/libraries/I2S/README.md index 66031561b70..ccce60c81a6 100644 --- a/libraries/I2S/README.md +++ b/libraries/I2S/README.md @@ -358,3 +358,9 @@ Object `I2S` returns value `0` Object `I2S1` returns value `1` Only ESP32 and ESP32-S3 have two modules, other SoCs have only one I2S module controlled by object `I2S` and the return value will always be `0`, the second object `I2S1` does not exist. +*** +#### bool isInitialized() + +Returns `true` if I2S module is correctly initialized and ready for use (function `begin()` was called and returned `1`) + +Returns `false` if I2S module has not yet been initialized (function `begin()` was not called, or returned `0`), or it has been de-initialized (function `end()` was called) \ No newline at end of file diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index 7f931c57792..9dee6fa6b40 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -888,6 +888,10 @@ int I2SClass::getI2SNum(){ return _deviceIndex; } +bool I2SClass::isInitialized(){ + return _initialized; +} + int I2SClass::_enableTransmitter(){ if(_state != I2S_STATE_DUPLEX && _state != I2S_STATE_TRANSMITTER){ _state = I2S_STATE_TRANSMITTER; diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index 56c1d828c70..07ede2fefec 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -364,6 +364,12 @@ class I2SClass : public Stream */ int getI2SNum(); + /* + * Returns true if I2S module is correctly initialized and ready for use (function begin() was called and returned 1) + * Returns false if I2S module has not yet been initialized (function begin() was called returned 1), or it has been de-initialized (function end() was called) + */ + bool isInitialized(); + private: #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) int _gpioToAdcUnit(gpio_num_t gpio_num, esp_i2s::adc_unit_t* adc_unit); From 972114999e2dbf69249be29d657c7a23f7a5b9da Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Mon, 31 Oct 2022 10:48:29 +0100 Subject: [PATCH 24/46] Extended begin test for slave mode + added pin setter & getter test --- tests/i2s/i2s.ino | 201 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 188 insertions(+), 13 deletions(-) diff --git a/tests/i2s/i2s.ino b/tests/i2s/i2s.ino index f6b373121fd..5643ade8521 100644 --- a/tests/i2s/i2s.ino +++ b/tests/i2s/i2s.ino @@ -4,6 +4,13 @@ I2SClass *I2S_obj; I2SClass *I2S_obj_arr[SOC_I2S_NUM]; +int i2s_index; + +#define TEST_SCK 1 +#define TEST_FS 2 +#define TEST_SD 3 +#define TEST_SD_OUT 4 +#define TEST_SD_IN 5 /* These functions are intended to be called before and after each test. */ void setUp(void) { @@ -21,9 +28,9 @@ void tearDown(void){ #endif } -// Test begin - all expected possibilities, then few unexpected option +// Test begin in master mode - all expected possibilities, then few unexpected option void test_01(void){ - //Serial.printf("[%lu] test 1: I2S_obj=%p\n", millis(), I2S_obj); + //Serial.printf("[%lu] test_01: I2S_obj=%p\n", millis(), I2S_obj); int sample_rate[] = {8000,11025,16000,22050,32000,44100,64000,88200,128000}; //int sample_rate[] = {8000}; int srp_max = sizeof(sample_rate)/sizeof(int); @@ -44,17 +51,22 @@ void test_01(void){ if(mode == ADC_DAC_MODE){ #if defined(SOC_I2S_SUPPORTS_ADC) && defined(SOC_I2S_SUPPORTS_DAC) TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + TEST_ASSERT_EQUAL(true, I2S_obj->isInitialized()); // Expecting true == Success #else TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure #endif }else if(mode == PDM_STEREO_MODE || mode == PDM_MONO_MODE){ #if defined(SOC_I2S_SUPPORTS_PDM_TX) || defined(SOC_I2S_SUPPORTS_PDM_RX) TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + TEST_ASSERT_EQUAL(true, I2S_obj->isInitialized()); // Expecting true == Success #else TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure #endif }else{ TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + TEST_ASSERT_EQUAL(true, I2S_obj->isInitialized()); // Expecting true == Success } I2S_obj->end(); } @@ -65,6 +77,7 @@ void test_01(void){ // Nonexistent mode - should fail ret = I2S_obj->begin(MODE_MAX, 8000, 8); TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure I2S_obj->end(); //Serial.printf("*********** [%lu] Begin I2S with Unsupported Bits Per Sample **************\n", millis()); @@ -74,6 +87,7 @@ void test_01(void){ for(int ubpsp = 0; ubpsp < ubpsp_max; ++ubpsp){ ret = I2S_obj->begin(I2S_PHILIPS_MODE, 8000, unsupported_bps[ubpsp]); TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure I2S_obj->end(); } @@ -84,20 +98,180 @@ void test_01(void){ for(int usrp = 0; usrp < usrp_max; ++usrp){ ret = I2S_obj->begin(I2S_PHILIPS_MODE, unusual_sample_rate[usrp], 32); TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + TEST_ASSERT_EQUAL(true, I2S_obj->isInitialized()); // Expecting true == Success I2S_obj->end(); } //Serial.printf("*********** [%lu] test_01 complete **************\n", millis()); } -// Pin setters and geters +// Test begin in slave mode - all expected possibilities, then few unexpected option void test_02(void){ + //Serial.printf("[%lu] test_02: I2S_obj=%p\n", millis(), I2S_obj); + + int bps[] = {8,16,24,32}; + //int bps[] = {8}; + int bpsp_max = sizeof(bps)/sizeof(int); + + int ret = -1; + + //Serial.printf("*********** [%lu] Begin I2S with golden parameters **************\n", millis()); + for(int mode = 0; mode < MODE_MAX; ++mode){ + for(int bpsp = 0; bpsp < bpsp_max; ++bpsp){ + //Serial.printf("[%lu] begin: mode %d \"%s\", bps %d\n", millis(), mode, i2s_mode_text[mode], bps[bpsp]); + ret = I2S_obj->begin(mode, bps[bpsp]); + + if(mode == ADC_DAC_MODE){ + #if defined(SOC_I2S_SUPPORTS_ADC) && defined(SOC_I2S_SUPPORTS_DAC) + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + TEST_ASSERT_EQUAL(true, I2S_obj->isInitialized()); // Expecting true == Success + #else + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure + #endif + }else if(mode == PDM_STEREO_MODE || mode == PDM_MONO_MODE){ + #if defined(SOC_I2S_SUPPORTS_PDM_TX) || defined(SOC_I2S_SUPPORTS_PDM_RX) + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + TEST_ASSERT_EQUAL(true, I2S_obj->isInitialized()); // Expecting true == Success + #else + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure + #endif + }else{ + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + TEST_ASSERT_EQUAL(true, I2S_obj->isInitialized()); // Expecting true == Success + } + I2S_obj->end(); + } + } + + //Serial.printf("*********** [%lu] Begin I2S with nonexistent mode **************\n", millis()); + // Nonexistent mode - should fail + ret = I2S_obj->begin(MODE_MAX, 8000, 8); + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure + I2S_obj->end(); + + //Serial.printf("*********** [%lu] Begin I2S with Unsupported Bits Per Sample **************\n", millis()); + // Unsupported Bits Per Sample - all should fail + int unsupported_bps[] = {-1,0,1,7,9,15,17,23,25,31,33,255,65536}; + int ubpsp_max = sizeof(unsupported_bps)/sizeof(int); + for(int ubpsp = 0; ubpsp < ubpsp_max; ++ubpsp){ + ret = I2S_obj->begin(I2S_PHILIPS_MODE, unsupported_bps[ubpsp]); + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure + I2S_obj->end(); + } + + //Serial.printf("*********** [%lu] test_02 complete **************\n", millis()); +} + +// Pin setters and geters +// set all pins and check if they are set as expected +// +// set all pins to default +// get all pins and check is the are default +// +// set wrong pin numbers - expect fail +void test_03(void){ + //Serial.printf("[%lu] test_03: I2S_obj=%p\n", millis(), I2S_obj); int ret = -1; + bool end = false; + while(!end){ + end = I2S_obj->isInitialized(); // initialize after first set of test and set end flag for next cycle + ////////////////////////////////////////////////////////////////////////////////////////////////////// + // Test on UNINITIALIZED I2S + //////////////////////////////////////////////////////////////////////////////////////////////////////// + // Set pins to test values and check + + ret = I2S_obj->setSckPin(TEST_SCK); // Set Clock pin + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + ret = I2S_obj->getSckPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SCK, ret); + + ret = I2S_obj->setFsPin(TEST_FS); // Set Frame Sync (Word Select) pin + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + ret = I2S_obj->getFsPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_FS, ret); + + ret = I2S_obj->setDataPin(TEST_SD); // Set shared Data pin for simplex mode + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + ret = I2S_obj->getDataPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SD, ret); + + ret = I2S_obj->setDataOutPin(TEST_SD_OUT); // Set Data Output pin for duplex mode + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + ret = I2S_obj->getDataOutPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SD_OUT, ret); + + ret = I2S_obj->setDataInPin(TEST_SD_IN); // Set Data Input pin for duplex mode + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + ret = I2S_obj->getDataInPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SD_IN, ret); + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + // set All pins to default values and check + + ret = I2S_obj->setAllPins(); // Set pins to default + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + + ret = I2S_obj->getSckPin(); // Get Clock pin + TEST_ASSERT_EQUAL(i2s_index ? PIN_I2S1_SCK : PIN_I2S_SCK, ret); + ret = I2S_obj->getFsPin(); // Get Frame Sync (Word Select) pin + TEST_ASSERT_EQUAL(i2s_index ? PIN_I2S1_FS : PIN_I2S_FS, ret); + ret = I2S_obj->getDataPin(); // Get shared Data pin for simplex mode + TEST_ASSERT_EQUAL(i2s_index ? PIN_I2S1_SD : PIN_I2S_SD, ret); + ret = I2S_obj->getDataOutPin(); // Get Data Output pin for duplex mode + TEST_ASSERT_EQUAL(i2s_index ? PIN_I2S1_SD_OUT : PIN_I2S_SD_OUT, ret); + ret = I2S_obj->getDataInPin(); // Get Data Input pin for duplex mode + TEST_ASSERT_EQUAL(i2s_index ? PIN_I2S1_SD_IN : PIN_I2S_SD_IN, ret); + ret = I2S_obj->setAllPins(); // Set default pins to test values + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + // Set All pins to test values and check + + ret = I2S_obj->setSckPin(TEST_SCK); // Set Clock pin + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + ret = I2S_obj->getSckPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SCK, ret); + + ret = I2S_obj->setFsPin(TEST_FS); // Set Frame Sync (Word Select) pin + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + ret = I2S_obj->getFsPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_FS, ret); + + ret = I2S_obj->setDataPin(TEST_SD); // Set shared Data pin for simplex mode + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + ret = I2S_obj->getDataPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SD, ret); + + ret = I2S_obj->setDataOutPin(TEST_SD_OUT); // Set Data Output pin for duplex mode + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + ret = I2S_obj->getDataOutPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SD_OUT, ret); + + ret = I2S_obj->setDataInPin(TEST_SD_IN); // Set Data Input pin for duplex mode + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + ret = I2S_obj->getDataInPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SD_IN, ret); + + ret = I2S_obj->setAllPins(); // Set pins to default + ret = I2S_obj->setAllPins(TEST_SCK, TEST_FS, TEST_SD, TEST_SD_OUT, TEST_SD_IN); // Set pins to test values + ret = I2S_obj->getSckPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SCK, ret); + ret = I2S_obj->getFsPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_FS, ret); + ret = I2S_obj->getDataPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SD, ret); + ret = I2S_obj->getDataOutPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SD_OUT, ret); + ret = I2S_obj->getDataInPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SD_IN, ret); - // Test on UNINITIALIZED I2S + // Test on INITIALIZED I2S + I2S_obj->begin(I2S_PHILIPS_MODE, 32000, 32); + } - ret = I2S_obj->setAllPins(); // Set default pins - TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success - // TODO + //Serial.printf("*********** [%lu] test_03 complete **************\n", millis()); } void setup() { @@ -113,15 +287,16 @@ void setup() { I2S_obj_arr[1] = &I2S1; //Serial.printf("I2S1=%p\n", &I2S1); #endif - + i2s_index = 0; UNITY_BEGIN(); - for(int obj = 0; obj < SOC_I2S_NUM; ++ obj){ + for(; i2s_index < SOC_I2S_NUM; ++ i2s_index){ //Serial.printf("*******************************************************\n"); - //Serial.printf("********************* I2S # %d *************************\n", obj); + //Serial.printf("********************* I2S # %d *************************\n", i2s_index); //Serial.printf("*******************************************************\n"); - I2S_obj = I2S_obj_arr[obj]; - RUN_TEST(test_01); - RUN_TEST(test_02); + I2S_obj = I2S_obj_arr[i2s_index]; + RUN_TEST(test_01); // begin master + RUN_TEST(test_02); // begin slave + RUN_TEST(test_03); // pin setters and geters } UNITY_END(); } From 289901105b6af4a533bd4f1ea90177c140c8b7cb Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Thu, 3 Nov 2022 15:54:35 +0100 Subject: [PATCH 25/46] Added & modified buffer functions; Renamed I2S1 to I2S_1 --- libraries/I2S/README.md | 117 +++++++++++++++++++++++++++++-------- libraries/I2S/src/I2S.cpp | 118 +++++++++++++++++++++++++++----------- libraries/I2S/src/I2S.h | 107 ++++++++++++++++++++++++---------- 3 files changed, 253 insertions(+), 89 deletions(-) diff --git a/libraries/I2S/README.md b/libraries/I2S/README.md index ccce60c81a6..f14a2f8fbe6 100644 --- a/libraries/I2S/README.md +++ b/libraries/I2S/README.md @@ -23,18 +23,18 @@ The IDF I2S driver uses multiple sets of buffer of the same size. The size of ri The size of IDF I2S buffer is by default 128. The unit used for this buffer is a frame. A frame means the data of all channels in a WS cycle. For example dual channel 16 bits per sample and default buffer size = 2 * 2 * 128 = 512 Bytes. From this we can get also size of the ring buffers. As stated their size is equal to the sum of IDF I2S buffers. For the example of dual channel, 16 bps the size of each ring buffer is 1024 B. -The function `setBufferSize` allows you to change the size in frames (see detailed description below). +The function `setDMABufferFrameSize` allows you to change the size in frames (see detailed description below). -On Arduino and most ESPs you can use object `I2S` to use the I2S module. on ESP32 and ESP32-S3 you have the option to use addititonal object `I2S1`. +On Arduino and most ESPs you can use object `I2S` to use the I2S module. on ESP32 and ESP32-S3 you have the option to use addititonal object `I2S_1`. I2S module functionality on each SoC differs, please refer to the following table. More info can be found in [IDF documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html?highlight=dac#overview-of-all-modes). ### Overview of I2S Modes: -| SoC | Philips | ADC/DAC | PDM TX | PDM RX | -| -------- | ---------- | ------- | ------ | ------ | -| ESP32 | I2S + I2S1 | I2S | I2S | I2S | -| ESP32-S2 | I2S | N/A | N/A | N/A | -| ESP32-C3 | I2S | N/A | I2S | N/A | -| ESP32-S3 | I2S + I2S1 | N/A | I2S | I2S | +| SoC | Philips | ADC/DAC | PDM TX | PDM RX | +| -------- | ----------- | ------- | ------ | ------ | +| ESP32 | I2S + I2S_1 | I2S | I2S | I2S | +| ESP32-S2 | I2S | N/A | N/A | N/A | +| ESP32-C3 | I2S | N/A | I2S | N/A | +| ESP32-S3 | I2S + I2S_1 | N/A | I2S | I2S | ## Pins ESP I2S has fully configurable pins. There is a group of setter and getter functions for each pin separately and one setter for all pins at once (detailed description below). Calling the setter will take effect immediately and does not need driver restart. @@ -46,7 +46,7 @@ Default pins are setup as follows: | 19 | 21 | 4 | 5 | ESP32-C3, ESP32-S2, ESP32-S3 | -### I2S1 object: +### I2S_1 object: | SCK | WS | SD(OUT) | SDIN | SoC | | --- | -- | ------- | ---- |:-------- | | 18 | 22 | 25 | 26 | ESP32 | @@ -73,6 +73,25 @@ If you wish to use duplex mode call `setDuplex();` this will change the pin setu .. note:: Some ESP32 SoCs support PCM, TDM and LCD/Camera modes, however support for those modes is out of scope in this simplified library. If you need to use those mode you will need to use [IDF I2S driver](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html). +## Buffers + +### Direct Memory Access buffers +The underlying IDF I2S driver uses a set of Direct Memory Access (DMA) buffers which can read from (in case of transmit line) or written to (in case of receive line) directly by the I2S module without using CPU. This helps to achieve flawless audio experience. However if TX buffers are not filled, or RX buffers emptied fast enough the I2S driver will transmit zeros (silence), or rewrite existing data (missing recorded samples) and this can be perceived as audio artifacts such as digital noise, lags, slowed playback, etc. + +The best way is to fill and read the buffers in chunks and let the CPU be used on other tasks. Once the DMA buffers are transmitted and freed for write, or read and stored elsewhere the CPU can fill the TX DMA buffer again and the I2S driver can fill the RX DMA buffers with new incoming samples. + +The number of DMA buffers can be from 2 - 128 for each data line (TX & RX) this is set by constant `_I2S_DMA_BUFFER_COUNT` in `I2S.cpp`. The default value is `2` and can be changed only in code - requires recompilation. + +The size of each DMA buffer is in sample frames and can be between 8 and 1024. One frame equals to number of channels (in this library always 2) multiplied by Bytes per samples (or `(bits_per_sample/8)`. The default value s `128` and can be changed changed by function `setDMABufferFrameSize()`, this automatically reinstalls the driver. The DMA buffer size can be read by functions `getDMABufferFrameSize()`, `getDMABufferSampleSize()`, and `getDMABufferByteSize()`. More info can be found in section *FUNCTIONS*. + +### Ring buffers buffers +Original Arduino I2S library uses extensively single-sample writes and reads - such usage would result in poor audio quality. Therefore another layer of buffers has been added - ring buffers. +There are two ring buffers - one for transmit line and one for receiving line. + +The size of ring buffer is equal to the sum of DMA buffers: `ring_buffer_bytes_size = (CHANNEL_NUMBER * (bits_per_sample/8)) * DMABufferFrameSize * _I2S_DMA_BUFFER_COUNT`. +Maximum size of those buffers can be read with functions `getRingBufferSampleSize()` and `getRingBufferByteSize()`. + +To get number of free Bytes in the transmitt buffer, that can be actually written is performed with function `availableForWrite()`. To get number of Bytes ready to be read from receiving ring buffer use function `available()`. Changing the size of ring buffer is not possible directly - it is automatically calculated during driver installation based on DMA Buffer size. ## Functions @@ -251,6 +270,10 @@ Reads a single sample from ring buffer and keeps it available for future read (i **Returns:** number of bytes that can be written into the ring buffer. *** +#### virtual int availableSamplesForWrite() + +**Returns:** number of samples that can be written into the ring buffer. +*** #### virtual size_t write(uint8_t data) *Write single sample of 8 bit size.* @@ -273,13 +296,16 @@ Please consider sending data in arrays using function `size_t write(const uint8_ This function is **blocking** - if there is not enough space in ring buffer the function will wait until it can write the sample. **Parameter:** -* **int32_t data** The sample to be sent +**int32_t data** The sample to be sent -**Returns:** Number of written bytes, if successful the value will be equal to bitsPerSample/8 +**Returns:** Number of written bytes, if successful the value will be equal to `bitsPerSample/8` .. note:: This functions is used in many examples for it's simplicity, but it's use is discouraged for performance reasons. Please consider sending data in arrays using function `size_t write(const uint8_t *buffer, size_t size)` + +.. note:: If used with `bits_per_sample == 24` then the Most Significant Byte will be ignored. For example `int32_t sample = 0xFFEEDDCC` will be transferred as an array: `sample[0]=0xCC, sample[1]=0xDD, sample[2]=0xEE` + *** #### size_t write(const uint8_t* buffer, size_t size) @@ -329,38 +355,79 @@ This function is useful when sending low amount of data, however such use will l *Callback handle* which will be used each time when the IDF I2S driver **receives** data into buffer. *** -#### int setBufferSize(int bufferSize) +#### int setDMABufferFrameSize(int DMABufferFrameSize) -*Change the size of buffers.* The unit is number of sample frames `(number_of_channels * (bits_per_sample/8))` +*Change the size of DMA buffers.* The unit is number of sample frames `(CHANNEL_NUMBER * (bits_per_sample/8))` The resulting Bytes size of ring buffers can be calculated: -`ring_buffer_bytes_size = (number_of_channels * (bits_per_sample/8)) * bufferSize * _I2S_DMA_BUFFER_COUNT` +`ring_buffer_bytes_size = (CHANNEL_NUMBER * (bits_per_sample/8)) * bufferSize * _I2S_DMA_BUFFER_COUNT` -*Example:* default value of `_I2S_DMA_BUFFER_COUNT` is **2**, default value of `bufferSize` is **128**; for *dual channel*, *16 bps* we will get +**Example:** + + * This library statically set to *dual channel*, therefore `CHANNEL_NUMBER` is always **2** + * For this example let's have `bits_per_sample` set to **16** + * Default value of `DMABufferFrameSize` is **128** + * Default value of `_I2S_DMA_BUFFER_COUNT` is **2** ``` -ring_buffer_bytes_size = (number_of_channels * (bits_per_sample/8)) * bufferSize * _I2S_DMA_BUFFER_COUNT - 1024 = ( 2 * ( 16 /8)) * 128 * 2 +ring_buffer_bytes_size = (CHANNEL_NUMBER * (bits_per_sample / 8)) * DMABufferFrameSize * _I2S_DMA_BUFFER_COUNT + 1024 = ( 2 * ( 16 / 8)) * 128 * 2 ``` *** -#### int getBufferSize() +#### int getDMABufferFrameSize() +*Get size of single DMA buffer.* -*Get buffer size.* The unit is number of sample frames `(number_of_channels * (bits_per_sample/8))` +The unit is number of sample frames: Bytes size of 1 frame = `(CHANNEL_NUMBER * (bits_per_sample / 8))` -For more info see `setBufferSize` +For more info see `setDMABufferFrameSize` +*** +#### int getDMABufferSampleSize() +*Get size of single DMA buffer.* + +The unit is number of samples: 1 sample = `(bits_per_sample / 8)` + +For more info see setDMABufferFrameSize +*** +#### int getDMABufferByteSize() +*Get size of single DMA buffer in Bytes.* + +For more info see setDMABufferFrameSize +*** +#### int getRingBufferSampleSize(); +*Get ring buffer size.* + +The unit is number of samples: 1 sample = `(bits_per_sample / 8)` + +For more info see setDMABufferFrameSize +*** +#### int getRingBufferByteSize(); +Get ring buffer size in Bytes. + +For more info see setDMABufferFrameSize *** #### int getI2SNum() Get the ID number of I2S module used for particular object. -Object `I2S` returns value `0` +Object `I2S` **returns** value `0` -Object `I2S1` returns value `1` +Object `I2S_1` **returns** value `1` -Only ESP32 and ESP32-S3 have two modules, other SoCs have only one I2S module controlled by object `I2S` and the return value will always be `0`, the second object `I2S1` does not exist. +Only ESP32 and ESP32-S3 have two modules, other SoCs have only one I2S module controlled by object `I2S` and the return value will always be `0`, the second object `I2S_1` does not exist. *** #### bool isInitialized() -Returns `true` if I2S module is correctly initialized and ready for use (function `begin()` was called and returned `1`) +**Returns** `true` if I2S module is correctly initialized and ready for use (function `begin()` was called and returned `1`) + +**Returns** `false` if I2S module has not yet been initialized (function `begin()` was not called, or returned `0`), or it has been de-initialized (function `end()` was called) +*** +#### int getSampleRate() +**Returns:** 0 on un-initialized object, or if the object is initialized as slave. + +On initialized master object **returns** sample rate in Hz (same value which was passed as argument with begin() function) +*** +#### int getBitsPerSample() + +**Returns:** 0 on un-initialized object. -Returns `false` if I2S module has not yet been initialized (function `begin()` was not called, or returned `0`), or it has been de-initialized (function `end()` was called) \ No newline at end of file +On initialized object **returns** bits per sample (same value which was passed as argument with begin() function) \ No newline at end of file diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index 9dee6fa6b40..a2900502445 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -50,7 +50,7 @@ I2SClass::I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, u _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 + _i2s_dma_buffer_frame_size(128), // Number of frames in each DMA buffer. Must be between 8 and 1024. Frame size = number of channels (always 2) * Bytes per sample _driveClock(true), _peek_buff(0), _peek_buff_valid(false), @@ -117,7 +117,6 @@ int I2SClass::_installDriver(){ log_e("(I2S#%d) This chip does not support ADC / DAC mode", _deviceIndex); return 0; // ERR #endif - // TODO left/right justified mode is actually stereo, but ignoring the other channel input// left/righ }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 @@ -147,7 +146,7 @@ int I2SClass::_installDriver(){ .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, + .dma_buf_len = _i2s_dma_buffer_frame_size, .use_apll = false, .tx_desc_auto_clear = true, .fixed_mclk = 0, @@ -170,23 +169,23 @@ int I2SClass::_installDriver(){ // 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("(I2S#%d) WARNING i2s driver install failed.\nTrying to increase I2S DMA buffer size from %d to %d\n", _deviceIndex, _i2s_dma_buffer_size, 2*_i2s_dma_buffer_size); - setBufferSize(2*_i2s_dma_buffer_size); - }else if(_i2s_dma_buffer_size < 1024){ - log_w("(I2S#%d) WARNING i2s driver install failed.\nTrying to decrease I2S DMA buffer size from %d to 1024\n", _deviceIndex, _i2s_dma_buffer_size); - setBufferSize(1024); + // Double the DMA buffer size + if(2*_i2s_dma_buffer_frame_size <= 1024){ + log_w("(I2S#%d) WARNING i2s driver install failed.\nTrying to double the I2S DMA buffer size from %d to %d", _deviceIndex, _i2s_dma_buffer_frame_size, 2*_i2s_dma_buffer_frame_size); + setDMABufferFrameSize(2*_i2s_dma_buffer_frame_size); // Double the buffer size + }else if(_i2s_dma_buffer_frame_size < 1024){ + log_w("(I2S#%d) WARNING i2s driver install failed.\nTrying to decrease I2S DMA buffer size from %d to maximum of 1024", _deviceIndex, _i2s_dma_buffer_frame_size); + setDMABufferFrameSize(1024); }else{ // install failed with max buffer size log_e("(I2S#%d) ERROR i2s driver install failed", _deviceIndex); 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 + if(_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("(I2S#%d) Setting the I2S Clock has failed!\n", _deviceIndex); + log_e("(I2S#%d) Setting the I2S Clock has failed!", _deviceIndex); return 0; // ERR } } // mono channel mode @@ -325,12 +324,11 @@ int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock return 0; // ERR } - _buffer_byte_size = _i2s_dma_buffer_size * (_bitsPerSample / 8) * _I2S_DMA_BUFFER_COUNT * 2; - log_d("(I2S#%d) Creating internal buffers. Requested size = %dB\n", _deviceIndex, _buffer_byte_size); + _buffer_byte_size = _i2s_dma_buffer_frame_size * (_bitsPerSample / 8) * _I2S_DMA_BUFFER_COUNT * CHANNEL_NUMBER; // The magic "*k2" stands for number of channels which are statically set to stereo _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("(I2S#%d) ERROR: could not create one or both internal buffers. Requested size = %d\n", _deviceIndex, _buffer_byte_size); + log_e("(I2S#%d) ERROR: could not create one or both internal buffers. Requested size = %d", _deviceIndex, _buffer_byte_size); if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR } @@ -808,12 +806,12 @@ int I2SClass::peek(){ void I2SClass::flush(){ if(!_take_mux()){ return; /* ERR */ } if(_initialized){ - const size_t single_dma_buf = _i2s_dma_buffer_size*(_bitsPerSample/8)*2; + const size_t single_dma_buf_byte_size = _i2s_dma_buffer_frame_size*(_bitsPerSample/8)*2; size_t item_size = 0; void *item = NULL; if(_output_ring_buffer != NULL){ // TODO while available data to fill entire DMA buff - keep flushing - item = xRingbufferReceiveUpTo(_output_ring_buffer, &item_size, 0, single_dma_buf); + item = xRingbufferReceiveUpTo(_output_ring_buffer, &item_size, 0, single_dma_buf_byte_size); if (item != NULL){ _fix_and_write(item, item_size); vRingbufferReturnItem(_output_ring_buffer, item); @@ -825,8 +823,8 @@ void I2SClass::flush(){ // Bytes available to write int I2SClass::availableForWrite(){ - if(!_take_mux()){ return 0; /* ERR */ } int ret = 0; + if(!_take_mux()){ return 0; /* ERR */ } if(_initialized){ if(_output_ring_buffer != NULL){ ret = (int)xRingbufferGetCurFreeSize(_output_ring_buffer); @@ -836,6 +834,14 @@ int I2SClass::availableForWrite(){ return ret; } +int I2SClass::availableSamplesForWrite(){ + int ret = 0; + if(!_take_mux()){ return 0; /* ERR */ } + ret = availableForWrite() / (_bitsPerSample/8); + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + void I2SClass::onTransmit(void(*function)(void)){ if(!_take_mux()){ return; /* ERR */ } _onTransmit = function; @@ -851,15 +857,15 @@ void I2SClass::onReceive(void(*function)(void)){ // Change buffer size. The unit is in frames. // Byte value can be calculated as follows: -// ByteSize = (bits_per_sample / 8) * number_of_channels * bufferSize +// ByteSize = (bits_per_sample / 8) * number_of_channels * DMABufferFrameSize // Calling this function will automatically restart the driver, which could cause audio output gap. -int I2SClass::setBufferSize(int bufferSize){ +int I2SClass::setDMABufferFrameSize(int DMABufferFrameSize){ if(!_take_mux()){ return 0; /* ERR */ } int ret = 0; - if(bufferSize >= 8 && bufferSize <= 1024){ - _i2s_dma_buffer_size = bufferSize; + if(DMABufferFrameSize >= 8 && DMABufferFrameSize <= 1024){ + _i2s_dma_buffer_frame_size = DMABufferFrameSize; }else{ - log_e("(I2S#%d) setBufferSize: wrong input! Buffer size must be between 8 and 1024. Requested %d", _deviceIndex, bufferSize); + log_e("(I2S#%d) setDMABufferFrameSize: wrong input! Buffer size must be between 8 and 1024. Requested %d", _deviceIndex, DMABufferFrameSize); if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR } // check requested buffer size @@ -877,9 +883,37 @@ int I2SClass::setBufferSize(int bufferSize){ return 0; // ERR } -int I2SClass::getBufferSize(){ +int I2SClass::getDMABufferFrameSize(){ + if(!_take_mux()){ return 0; /* ERR */ } + int ret = _i2s_dma_buffer_frame_size; + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +int I2SClass::getDMABufferSampleSize(){ + if(!_take_mux()){ return 0; /* ERR */ } + int ret = _i2s_dma_buffer_frame_size * CHANNEL_NUMBER; + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +int I2SClass::getDMABufferByteSize(){ if(!_take_mux()){ return 0; /* ERR */ } - int ret = _i2s_dma_buffer_size; + int ret = _i2s_dma_buffer_frame_size * CHANNEL_NUMBER * (_bitsPerSample/8); + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +int I2SClass::getRingBufferSampleSize(){ + if(!_take_mux()){ return 0; /* ERR */ } + int ret = _i2s_dma_buffer_frame_size * CHANNEL_NUMBER * _I2S_DMA_BUFFER_COUNT; + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +int I2SClass::getRingBufferByteSize(){ + if(!_take_mux()){ return 0; /* ERR */ } + int ret = _i2s_dma_buffer_frame_size * CHANNEL_NUMBER * (_bitsPerSample/8) * _I2S_DMA_BUFFER_COUNT; if(!_give_mux()){ return 0; /* ERR */ } return ret; } @@ -910,7 +944,7 @@ int I2SClass::_enableReceiver(){ 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 + const size_t single_dma_buf_byte_size = _i2s_dma_buffer_frame_size*(_bitsPerSample/8)* CHANNEL_NUMBER; static size_t item_size = 0; static size_t prev_item_size = 0; static void *item = NULL; @@ -926,11 +960,11 @@ void I2SClass::_tx_done_routine(uint8_t* prev_item){ 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 + if(_output_ring_buffer != NULL && (_buffer_byte_size - xRingbufferGetCurFreeSize(_output_ring_buffer) >= single_dma_buf_byte_size)){ // 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(_buffer_byte_size - xRingbufferGetCurFreeSize(_output_ring_buffer) >= _i2s_dma_buffer_frame_size*(_bitsPerSample/8)){ // don't read from almost empty buffer + item = xRingbufferReceiveUpTo(_output_ring_buffer, &item_size, pdMS_TO_TICKS(0), single_dma_buf_byte_size); 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 @@ -950,13 +984,13 @@ void I2SClass::_tx_done_routine(uint8_t* prev_item){ void I2SClass::_rx_done_routine(){ size_t bytes_read = 0; - const size_t single_dma_buf = _i2s_dma_buffer_size*(_bitsPerSample/8); + const size_t single_dma_buf_byte_size = _i2s_dma_buffer_frame_size*(_bitsPerSample/8)*CHANNEL_NUMBER; if(_input_ring_buffer != NULL){ - uint8_t *_inputBuffer = (uint8_t*)malloc(_i2s_dma_buffer_size*4); + uint8_t *_inputBuffer = (uint8_t*)malloc(single_dma_buf_byte_size); 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); + esp_err_t ret = esp_i2s::i2s_read((esp_i2s::i2s_port_t) _deviceIndex, _inputBuffer, avail <= single_dma_buf_byte_size ? avail : single_dma_buf_byte_size, (size_t*) &bytes_read, 0); if(ret != ESP_OK){ log_w("(I2S#%d) i2s_read returned with error %d", _deviceIndex, ret); } @@ -976,7 +1010,7 @@ void I2SClass::_rx_done_routine(){ } void I2SClass::_onTransferComplete(){ - uint8_t prev_item[_i2s_dma_buffer_size*4]; + uint8_t prev_item[_i2s_dma_buffer_frame_size*4]; esp_i2s::i2s_event_t i2s_event; if(_i2sEventQueue == NULL){ @@ -1001,7 +1035,7 @@ void I2SClass::onDmaTransferComplete(void *deviceIndex){ } #if SOC_I2S_NUM > 1 if(*index == 1){ - I2S1._onTransferComplete(); + I2S_1._onTransferComplete(); } #endif log_w("(I2S#%d) Deleting callback task from inside!", index); @@ -1109,6 +1143,17 @@ void I2SClass::_fix_and_write(void *output, size_t size, size_t *bytes_written, break; // Do nothing } // switch + if(_mode == I2S_RIGHT_JUSTIFIED_MODE){ + for(int i = 0; i < buff_size; i+=2){ + buff[i] = buff[i+1]; + } + } + if(_mode == I2S_LEFT_JUSTIFIED_MODE){ + for(int i = 0; i < buff_size; i+=2){ + buff[i+1] = buff[i]; + } + } + 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){ @@ -1149,6 +1194,9 @@ int I2SClass::_gpioToAdcUnit(gpio_num_t gpio_num, esp_i2s::adc_unit_t* adc_unit) // ADC 2 case GPIO_NUM_0: log_w("(I2S#%d) GPIO 0 for ADC should not be used for dev boards due to external auto program circuits.", _deviceIndex); + // Only to suppress Warnings "may fall through" which exactly what is intended + *adc_unit = esp_i2s::ADC_UNIT_2; + return 1; // OK case GPIO_NUM_4: case GPIO_NUM_2: case GPIO_NUM_15: @@ -1286,5 +1334,5 @@ int I2SClass::_gpioToAdcChannel(gpio_num_t gpio_num, esp_i2s::adc_channel_t* adc #endif #if SOC_I2S_NUM > 1 - I2SClass I2S1(1, I2S_CLOCK_GENERATOR, PIN_I2S1_SD, PIN_I2S1_SCK, PIN_I2S1_FS); // default - half duplex + I2SClass I2S_1(1, I2S_CLOCK_GENERATOR, PIN_I2S1_SD, PIN_I2S1_SCK, PIN_I2S1_FS); // default - half duplex #endif diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index 07ede2fefec..56d0373e893 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -32,34 +32,34 @@ namespace esp_i2s { // 19 18 22 23 ESP32 // 19 18 4 5 ESP32-x (C3,S2,S3) #ifndef PIN_I2S_SCK - #define PIN_I2S_SCK 19 + #define PIN_I2S_SCK GPIO_NUM_19 #endif #ifndef PIN_I2S_FS - #define PIN_I2S_FS 18 + #define PIN_I2S_FS GPIO_NUM_18 #endif #ifndef PIN_I2S_SD #if CONFIG_IDF_TARGET_ESP32 - #define PIN_I2S_SD 22 + #define PIN_I2S_SD GPIO_NUM_22 #else - #define PIN_I2S_SD 4 + #define PIN_I2S_SD GPIO_NUM_4 #endif #endif #ifndef PIN_I2S_SD_OUT #if CONFIG_IDF_TARGET_ESP32 - #define PIN_I2S_SD_OUT 22 + #define PIN_I2S_SD_OUT GPIO_NUM_22 #else - #define PIN_I2S_SD_OUT 4 + #define PIN_I2S_SD_OUT GPIO_NUM_4 #endif #endif #ifndef PIN_I2S_SD_IN #if CONFIG_IDF_TARGET_ESP32 - #define PIN_I2S_SD_IN 23 + #define PIN_I2S_SD_IN GPIO_NUM_23 #else - #define PIN_I2S_SD_IN 5 + #define PIN_I2S_SD_IN GPIO_NUM_5 #endif #endif @@ -70,23 +70,23 @@ namespace esp_i2s { //SCK WS SD(OUT) SDIN // 18 22 25 26 #ifndef PIN_I2S1_SCK - #define PIN_I2S1_SCK 18 + #define PIN_I2S1_SCK GPIO_NUM_18 #endif #ifndef PIN_I2S1_FS - #define PIN_I2S1_FS 22 + #define PIN_I2S1_FS GPIO_NUM_22 #endif #ifndef PIN_I2S1_SD - #define PIN_I2S1_SD 25 + #define PIN_I2S1_SD GPIO_NUM_25 #endif #ifndef PIN_I2S1_SD_OUT - #define PIN_I2S1_SD_OUT 25 + #define PIN_I2S1_SD_OUT GPIO_NUM_25 #endif #ifndef PIN_I2S1_SD_IN - #define PIN_I2S1_SD_IN 26 + #define PIN_I2S1_SD_IN GPIO_NUM_26 #endif #endif @@ -95,27 +95,29 @@ namespace esp_i2s { //SCK WS SD(OUT) SDIN // 36 37 39 40 #ifndef PIN_I2S1_SCK - #define PIN_I2S1_SCK 36 + #define PIN_I2S1_SCK GPIO_NUM_36 #endif #ifndef PIN_I2S1_FS - #define PIN_I2S1_FS 37 + #define PIN_I2S1_FS GPIO_NUM_37 #endif #ifndef PIN_I2S1_SD - #define PIN_I2S1_SD 39 + #define PIN_I2S1_SD GPIO_NUM_39 #endif #ifndef PIN_I2S1_SD_OUT - #define PIN_I2S1_SD_OUT 39 + #define PIN_I2S1_SD_OUT GPIO_NUM_39 #endif #ifndef PIN_I2S1_SD_IN - #define PIN_I2S1_SD_IN 40 + #define PIN_I2S1_SD_IN GPIO_NUM_40 #endif #endif #endif +#define CHANNEL_NUMBER 2 + typedef enum { I2S_PHILIPS_MODE, // Most common I2S mode, FS signal spans across whole channel period I2S_RIGHT_JUSTIFIED_MODE, // Right channel data are copied to both channels @@ -274,6 +276,11 @@ class I2SClass : public Stream */ virtual int availableForWrite(); + /* + * Returns: number of samples that can be written into the ring buffer. + */ + int availableSamplesForWrite(); + /* * Write single sample of 8 bit size. * This function is blocking - if there is not enough space in ring buffer the function will wait until it can write the sample. @@ -340,20 +347,49 @@ class I2SClass : public Stream void onReceive(void(*)(void)); /* - * Change the size of buffers. The unit is number of sample frames (number_of_channels * (bits_per_sample/8)) + * Change the size of DMA buffers. The unit is number of sample frames (CHANNEL_NUMBER * (bits_per_sample/8)) * The resulting Bytes size of ring buffers can be calculated: - * ring_buffer_bytes_size = (number_of_channels * (bits_per_sample/8)) * bufferSize * _I2S_DMA_BUFFER_COUNT - * Example: default value of _I2S_DMA_BUFFER_COUNT is 2, default value of bufferSize is 128; for dual channel, 16 bps we will get - * ring_buffer_bytes_size = (number_of_channels * (bits_per_sample/8)) * bufferSize * _I2S_DMA_BUFFER_COUNT - * 1024 = ( 2 * ( 16 /8)) * 128 * 2 + * ring_buffer_bytes_size = (CHANNEL_NUMBER * (bits_per_sample/8)) * DMABufferFrameSize * _I2S_DMA_BUFFER_COUNT + * Example: + * - This library statically set to dual channel, therefore CHANNEL_NUMBER is always 2 + * - For this example let's have bits_per_sample set to 16 + * - Default value of bufferSize is 128 + * - Default value of _I2S_DMA_BUFFER_COUNT is 2 + * ring_buffer_bytes_size = (CHANNEL_NUMBER * (bits_per_sample / 8)) * DMABufferFrameSize * _I2S_DMA_BUFFER_COUNT + * 1024 = ( 2 * ( 16 / 8)) * 128 * 2 + */ + int setDMABufferFrameSize(int DMABufferFrameSize); + + /* + * Get size of single DMA buffer. + * The unit is number of sample frames: Bytes size of 1 frame = (CHANNEL_NUMBER * (bits_per_sample / 8)) + * For more info see setDMABufferFrameSize + */ + int getDMABufferFrameSize(); + + /* + * Get size of single DMA buffer. The unit is number of samples: Byte size of 1 sample = (bits_per_sample / 8) + * For more info see setDMABufferFrameSize + */ + int getDMABufferSampleSize(); + + /* + * Get size of single DMA buffer in Bytes. + * For more info see setDMABufferFrameSize + */ + int getDMABufferByteSize(); + + /* + * Get ring buffer size. The unit is number of samples: 1 sample = (bits_per_sample / 8) + * For more info see setDMABufferFrameSize */ - int setBufferSize(int bufferSize); + int getRingBufferSampleSize(); /* - * Get buffer size. The unit is number of sample frames (number_of_channels * (bits_per_sample/8)) - * For more info see setBufferSize + * Get ring buffer size in Bytes. + * For more info see setDMABufferFrameSize */ - int getBufferSize(); + int getRingBufferByteSize(); /* Get the ID number of I2S module used for particular object. @@ -370,6 +406,18 @@ class I2SClass : public Stream */ bool isInitialized(); + /* + * Returns 0 on un-initialized object, or if the object is initialized as slave. + * On initialized master object returns sample rate in Hz (same value which was passed as argument with begin() function) + */ + int getSampleRate(); + + /* + * Returns 0 on un-initialized object. + * On initialized master object returns bits per sample (same value which was passed as argument with begin() function) + */ + int getBitsPerSample(); + private: #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) int _gpioToAdcUnit(gpio_num_t gpio_num, esp_i2s::adc_unit_t* adc_unit); @@ -422,7 +470,7 @@ class I2SClass : public Stream SemaphoreHandle_t _i2s_general_mutex; RingbufHandle_t _input_ring_buffer; RingbufHandle_t _output_ring_buffer; - int _i2s_dma_buffer_size; + int _i2s_dma_buffer_frame_size; bool _driveClock; uint32_t _peek_buff; bool _peek_buff_valid; @@ -440,8 +488,9 @@ class I2SClass : public Stream }; extern I2SClass I2S; + #if SOC_I2S_NUM > 1 - extern I2SClass I2S1; + extern I2SClass I2S_1; #endif #endif From d746aa244826804b45f4e7cb262aeb576b106865 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Mon, 7 Nov 2022 11:48:53 +0100 Subject: [PATCH 26/46] Updated DualModule example --- .../I2S/examples/DualModule/DualModule.ino | 83 +++++++++++++++---- 1 file changed, 66 insertions(+), 17 deletions(-) diff --git a/libraries/I2S/examples/DualModule/DualModule.ino b/libraries/I2S/examples/DualModule/DualModule.ino index 2a6113d3112..23d76117bb6 100644 --- a/libraries/I2S/examples/DualModule/DualModule.ino +++ b/libraries/I2S/examples/DualModule/DualModule.ino @@ -14,7 +14,7 @@ | DIN | 23 | 5 | | DOUT | 22 | 4 | - I2S1 + I2S_1 | Pin | ESP32 | ESP32-S3 | | -----|-------|- --------| | GND | GND | GND | @@ -24,17 +24,24 @@ | DIN | 26 | 40 | | DOUT | 25 | 39 | - created 18 October 2022 + created 7 Nov 2022 by Tomas Pilny */ +#if SOC_I2S_NUM > 1 + #include +#include "hal/gpio_hal.h" +#include "esp_rom_gpio.h" + +#include "soc/i2s_periph.h" +#include "soc/gpio_sig_map.h" + const long sampleRate = 16000; const int bps = 32; uint8_t *buffer; uint8_t *buffer1; - const int signal_frequency = 1000; // frequency of square wave in Hz const int signal_frequency1 = 2000; // frequency of square wave in Hz const int amplitude = (1<<(bps-1))-1; // amplitude of square wave @@ -44,24 +51,39 @@ int32_t write_sample = amplitude; // current sample value int32_t write_sample1 = amplitude; // current sample value int count = 0; +#define DATA_MASTER_TO_SLAVE PIN_I2S_SD_OUT +#define DATA_SLAVE_TO_MASTER PIN_I2S_SD_IN + +#if CONFIG_IDF_TARGET_ESP32 + #define I2S0_DATA_OUT_IDX I2S0O_DATA_OUT23_IDX + #define I2S0_DATA_IN_IDX I2S0I_DATA_IN15_IDX + #define I2S1_DATA_OUT_IDX I2S1O_DATA_OUT23_IDX + #define I2S1_DATA_IN_IDX I2S1I_DATA_IN15_IDX +#elif CONFIG_IDF_TARGET_ESP32S3 + #define I2S0_DATA_OUT_IDX I2S0O_SD_OUT_IDX + #define I2S0_DATA_IN_IDX I2S0I_SD_IN_IDX + #define I2S1_DATA_OUT_IDX I2S1O_SD_OUT_IDX + #define I2S1_DATA_IN_IDX I2S1I_SD_IN_IDX +#endif void setup() { Serial.begin(115200); while(!Serial); - I2S.setAllPins(PIN_I2S1_SCK, PIN_I2S1_FS, PIN_I2S1_SD, PIN_I2S1_SD_OUT, PIN_I2S1_SD_IN); // set all pins to match I2S1 to connect them internally + if(!I2S.setDuplex()){ Serial.println("ERROR - could not set duplex for I2S"); while(true){ vTaskDelay(10); // Cannot continue } } + if (!I2S.begin(I2S_PHILIPS_MODE, sampleRate, bps)) { Serial.println("Failed to initialize I2S!"); while(true){ vTaskDelay(10); // Cannot continue } } - buffer = (uint8_t*) malloc(I2S.getBufferSize() * (bps / 8)); + buffer = (uint8_t*) malloc(I2S.getDMABufferByteSize()); if(buffer == NULL){ Serial.println("Failed to allocate buffer!"); while(true){ @@ -69,25 +91,50 @@ void setup() { } } - if(!I2S1.setDuplex()){ - Serial.println("ERROR - could not set duplex for I2S1"); + I2S.setDataInPin(DATA_MASTER_TO_SLAVE); + I2S.setDataOutPin(DATA_SLAVE_TO_MASTER); + if(!I2S_1.setDuplex()){ + Serial.println("ERROR - could not set duplex for I2S_1"); while(true){ vTaskDelay(10); // Cannot continue } } - if (!I2S1.begin(I2S_PHILIPS_MODE, bps)) { // start in slave mode --- CRASHING! - Serial.println("Failed to initialize I2S1!"); + if (!I2S_1.begin(I2S_PHILIPS_MODE, bps)) { // start in slave mode --- CRASHING! (i2s_driver_install) + Serial.println("Failed to initialize I2S_1!"); while(true){ vTaskDelay(10); // Cannot continue } } - buffer1 = (uint8_t*) malloc(I2S1.getBufferSize() * (bps / 8)); + buffer1 = (uint8_t*) malloc(I2S_1.getDMABufferByteSize()); if(buffer1 == NULL){ Serial.println("Failed to allocate buffer1!"); while(true){ vTaskDelay(10); // Cannot continue } } + + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_SCK], PIN_FUNC_GPIO); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_FS], PIN_FUNC_GPIO); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_SD_OUT], PIN_FUNC_GPIO); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_SD_IN], PIN_FUNC_GPIO); + + gpio_set_direction(PIN_I2S_SCK, GPIO_MODE_INPUT_OUTPUT); + gpio_set_direction(PIN_I2S_FS, GPIO_MODE_INPUT_OUTPUT); + gpio_set_direction(PIN_I2S_SD_OUT, GPIO_MODE_INPUT_OUTPUT); + gpio_set_direction(PIN_I2S_SD_IN, GPIO_MODE_INPUT_OUTPUT); + + esp_rom_gpio_connect_out_signal(PIN_I2S_SCK, I2S0I_BCK_OUT_IDX, 0, 0); + esp_rom_gpio_connect_in_signal(PIN_I2S1_SCK, I2S1O_BCK_IN_IDX, 0); + + esp_rom_gpio_connect_out_signal(PIN_I2S_FS, I2S0I_WS_OUT_IDX, 0, 0); + esp_rom_gpio_connect_in_signal(PIN_I2S1_FS, I2S1O_WS_IN_IDX, 0); + + esp_rom_gpio_connect_out_signal(DATA_MASTER_TO_SLAVE, I2S0_DATA_OUT_IDX, 0, 0); + esp_rom_gpio_connect_in_signal(DATA_MASTER_TO_SLAVE, I2S1_DATA_IN_IDX, 0); + + esp_rom_gpio_connect_out_signal(DATA_SLAVE_TO_MASTER, I2S1_DATA_OUT_IDX, 0, 0); + esp_rom_gpio_connect_in_signal(DATA_SLAVE_TO_MASTER, I2S0_DATA_IN_IDX, 0); + Serial.println("Setup done"); } @@ -101,16 +148,18 @@ void loop() { if (count % halfWavelength1 == 0 ) { write_sample1 = -1 * write_sample1; } - I2S1.write(write_sample1); // Right channel - I2S1.write(write_sample1); // Left channel + I2S_1.write(write_sample1); // Right channel + I2S_1.write(write_sample1); // Left channel // increment the counter for the next sample count++; - int read_sample = I2S.read(); - int read_sample1 = I2S1.read(); + int read_sample1 = I2S_1.read(); + + Serial.printf("%d %d\n", read_sample, read_sample1); +} - Serial.printf("%d %d", read_sample, read_sample1); - Serial.printf("%d", read_sample1); -} \ No newline at end of file +#else + #error "This example cannot run on current SoC - the example requires two I2S modules found in ESP32 and ESP32-S3" +#endif From de30999f498f7ec2766effb0ed646035267f1b8c Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Fri, 11 Nov 2022 13:26:19 +0100 Subject: [PATCH 27/46] Added test --- tests/i2s/i2s.ino | 312 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 269 insertions(+), 43 deletions(-) diff --git a/tests/i2s/i2s.ino b/tests/i2s/i2s.ino index 5643ade8521..c489de4a806 100644 --- a/tests/i2s/i2s.ino +++ b/tests/i2s/i2s.ino @@ -2,6 +2,10 @@ #include #include "Arduino.h" +// Internal connections +#include "hal/gpio_hal.h" +#include "esp_rom_gpio.h" + I2SClass *I2S_obj; I2SClass *I2S_obj_arr[SOC_I2S_NUM]; int i2s_index; @@ -12,37 +16,50 @@ int i2s_index; #define TEST_SD_OUT 4 #define TEST_SD_IN 5 +// Supported sample rates (do not change) +int sample_rate[] = {8000,11025,16000,22050,32000,44100,64000,88200,128000}; +int srp_max = sizeof(sample_rate)/sizeof(int); + +// Supported bits per sample (do not change) +int bps[] = {8,16,24,32}; +int bpsp_max = sizeof(bps)/sizeof(int); + +// Frame 1 | | Frame 2 | | Frame 3 +// R channel | L channel | | R channel | L channel | | R channel | L channel +uint8_t data_8bit[] = { 0xFF, 0xAA, 0x55, 0x00, 0xCC, 0x33}; +uint16_t data_16bit[] = { 0xFFFF, 0xAAAA, 0x5555, 0x0000, 0xCCCC, 0x3333}; +uint8_t data_24bit[] = {0xFF, 0xFF, 0xFF, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33}; +uint32_t data_32bit[] = { 0xFFFFFFFF, 0xAAAAAAAA, 0x55555555, 0x00000000, 0xCCCCCCCC, 0x33333333}; + /* These functions are intended to be called before and after each test. */ void setUp(void) { // use default pins I2S.setAllPins(); #if SOC_I2S_NUM > 1 - I2S1.setAllPins(); + I2S_1.setAllPins(); #endif } void tearDown(void){ I2S.end(); #if SOC_I2S_NUM > 1 - I2S1.end(); + I2S_1.end(); #endif } +// test which will always pass - this is used for chips wich do not have I2S module +void test_pass(void){ + TEST_ASSERT_EQUAL(1, 1); // always pass +} + // Test begin in master mode - all expected possibilities, then few unexpected option void test_01(void){ - //Serial.printf("[%lu] test_01: I2S_obj=%p\n", millis(), I2S_obj); - int sample_rate[] = {8000,11025,16000,22050,32000,44100,64000,88200,128000}; - //int sample_rate[] = {8000}; - int srp_max = sizeof(sample_rate)/sizeof(int); - - int bps[] = {8,16,24,32}; - //int bps[] = {8}; - int bpsp_max = sizeof(bps)/sizeof(int); - + Serial.printf("[%lu] test_01: I2S_obj=%p\n", millis(), I2S_obj); int ret = -1; - //Serial.printf("*********** [%lu] Begin I2S with golden parameters **************\n", millis()); + Serial.printf("*********** [%lu] Begin I2S with golden parameters **************\n", millis()); for(int mode = 0; mode < MODE_MAX; ++mode){ + Serial.printf("[%lu] begin: mode %d \"%s\"\n", millis(), mode, i2s_mode_text[mode]); for(int srp = 0; srp < srp_max; ++srp){ for(int bpsp = 0; bpsp < bpsp_max; ++bpsp){ //Serial.printf("[%lu] begin: mode %d \"%s\", sample rate %d, bps %d\n", millis(), mode, i2s_mode_text[mode], sample_rate[srp], bps[bpsp]); @@ -73,14 +90,14 @@ void test_01(void){ } } - //Serial.printf("*********** [%lu] Begin I2S with nonexistent mode **************\n", millis()); + Serial.printf("*********** [%lu] Begin I2S with nonexistent mode **************\n", millis()); // Nonexistent mode - should fail ret = I2S_obj->begin(MODE_MAX, 8000, 8); TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure I2S_obj->end(); - //Serial.printf("*********** [%lu] Begin I2S with Unsupported Bits Per Sample **************\n", millis()); + Serial.printf("*********** [%lu] Begin I2S with Unsupported Bits Per Sample **************\n", millis()); // Unsupported Bits Per Sample - all should fail int unsupported_bps[] = {-1,0,1,7,9,15,17,23,25,31,33,255,65536}; int ubpsp_max = sizeof(unsupported_bps)/sizeof(int); @@ -91,7 +108,7 @@ void test_01(void){ I2S_obj->end(); } - //Serial.printf("*********** [%lu] Begin I2S with Unusual, yet possible sample rates **************\n", millis()); + Serial.printf("*********** [%lu] Begin I2S with Unusual, yet possible sample rates **************\n", millis()); // Unusual, yet possible sample rates - all should succeed int unusual_sample_rate[] = {4000,64000,88200,96000,128000}; int usrp_max = sizeof(unusual_sample_rate)/sizeof(int); @@ -101,20 +118,15 @@ void test_01(void){ TEST_ASSERT_EQUAL(true, I2S_obj->isInitialized()); // Expecting true == Success I2S_obj->end(); } - //Serial.printf("*********** [%lu] test_01 complete **************\n", millis()); + Serial.printf("*********** [%lu] test_01 complete **************\n", millis()); } // Test begin in slave mode - all expected possibilities, then few unexpected option void test_02(void){ - //Serial.printf("[%lu] test_02: I2S_obj=%p\n", millis(), I2S_obj); - - int bps[] = {8,16,24,32}; - //int bps[] = {8}; - int bpsp_max = sizeof(bps)/sizeof(int); - + Serial.printf("[%lu] test_02: I2S_obj=%p\n", millis(), I2S_obj); int ret = -1; - //Serial.printf("*********** [%lu] Begin I2S with golden parameters **************\n", millis()); + Serial.printf("*********** [%lu] Begin I2S with golden parameters **************\n", millis()); for(int mode = 0; mode < MODE_MAX; ++mode){ for(int bpsp = 0; bpsp < bpsp_max; ++bpsp){ //Serial.printf("[%lu] begin: mode %d \"%s\", bps %d\n", millis(), mode, i2s_mode_text[mode], bps[bpsp]); @@ -144,14 +156,14 @@ void test_02(void){ } } - //Serial.printf("*********** [%lu] Begin I2S with nonexistent mode **************\n", millis()); + Serial.printf("*********** [%lu] Begin I2S with nonexistent mode **************\n", millis()); // Nonexistent mode - should fail ret = I2S_obj->begin(MODE_MAX, 8000, 8); TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure I2S_obj->end(); - //Serial.printf("*********** [%lu] Begin I2S with Unsupported Bits Per Sample **************\n", millis()); + Serial.printf("*********** [%lu] Begin I2S with Unsupported Bits Per Sample **************\n", millis()); // Unsupported Bits Per Sample - all should fail int unsupported_bps[] = {-1,0,1,7,9,15,17,23,25,31,33,255,65536}; int ubpsp_max = sizeof(unsupported_bps)/sizeof(int); @@ -162,7 +174,7 @@ void test_02(void){ I2S_obj->end(); } - //Serial.printf("*********** [%lu] test_02 complete **************\n", millis()); + Serial.printf("*********** [%lu] test_02 complete **************\n", millis()); } // Pin setters and geters @@ -173,11 +185,12 @@ void test_02(void){ // // set wrong pin numbers - expect fail void test_03(void){ - //Serial.printf("[%lu] test_03: I2S_obj=%p\n", millis(), I2S_obj); + Serial.printf("[%lu] test_03: I2S_obj=%p\n", millis(), I2S_obj); int ret = -1; bool end = false; while(!end){ end = I2S_obj->isInitialized(); // initialize after first set of test and set end flag for next cycle + Serial.printf("[%lu] test_03: intialized = %d\n", millis(), end); ////////////////////////////////////////////////////////////////////////////////////////////////////// // Test on UNINITIALIZED I2S //////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -271,7 +284,206 @@ void test_03(void){ I2S_obj->begin(I2S_PHILIPS_MODE, 32000, 32); } - //Serial.printf("*********** [%lu] test_03 complete **************\n", millis()); + Serial.printf("*********** [%lu] test_03 complete **************\n", millis()); +} + + +// test simplex / duplex switch functions (not actual data transfer) +void test_04(void){ + Serial.printf("*********** [%lu] test_04 starting **************\n", millis()); + int ret = -1; + bool end = false; + while(!end){ + end = I2S_obj->isInitialized(); // initialize after first set of test and set end flag for next cycle + + ret = I2S_obj->isDuplex(); + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == simplex (default) + + ret = I2S_obj->setDuplex(); + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + + ret = I2S_obj->isDuplex(); + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == duplex + + ret = I2S_obj->setSimplex(); + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + + ret = I2S_obj->isDuplex(); + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == simplex (default) + + // Test on INITIALIZED I2S + I2S_obj->begin(I2S_PHILIPS_MODE, 32000, 32); + } + Serial.printf("*********** [%lu] test_04 complete **************\n", millis()); +} + +#if CONFIG_IDF_TARGET_ESP32 +#define I2S0_DATA_OUT_IDX I2S0O_DATA_OUT23_IDX +#define I2S0_DATA_IN_IDX I2S0I_DATA_IN15_IDX +#elif CONFIG_IDF_TARGET_ESP32S2 +#define I2S0_DATA_OUT_IDX I2S0O_DATA_OUT23_IDX +#define I2S0_DATA_IN_IDX I2S0I_DATA_IN15_IDX +#elif CONFIG_IDF_TARGET_ESP32C3 +#define I2S0_DATA_OUT_IDX I2SO_SD_OUT_IDX +#define I2S0_DATA_IN_IDX I2SI_SD_IN_IDX +#elif CONFIG_IDF_TARGET_ESP32S3 +#define I2S0_DATA_OUT_IDX I2S0O_SD_OUT_IDX +#define I2S0_DATA_IN_IDX I2S0I_SD_IN_IDX +#endif + +// Simple data transmit, returned written bytes check and buffer check. +void test_05(void){ + Serial.printf("*********** [%lu] test_05 starting **************\n", millis()); + I2S_obj->setAllPins(); + // set input and output on same pin and connect internally + I2S_obj->setDataOutPin(PIN_I2S_SD); + I2S_obj->setDataInPin(PIN_I2S_SD); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_SD], PIN_FUNC_GPIO); + gpio_set_direction(PIN_I2S_SD, GPIO_MODE_INPUT_OUTPUT); + esp_rom_gpio_connect_out_signal(PIN_I2S_SD, I2S0_DATA_OUT_IDX, 0, 0); + esp_rom_gpio_connect_in_signal(PIN_I2S_SD, I2S0_DATA_IN_IDX, 0); + + int ret = -1; + + for(int mode = 0; mode < MODE_MAX; ++mode){ + Serial.printf("[%lu] data transfer begin: mode %d \"%s\"\n", millis(), mode, i2s_mode_text[mode]); + for(int srp = 0; srp < srp_max; ++srp){ + for(int bpsp = 0; bpsp < bpsp_max; ++bpsp){ + //Serial.printf("[%lu] begin: mode %d \"%s\", sample rate %d, bps %d\n", millis(), mode, i2s_mode_text[mode], sample_rate[srp], bps[bpsp]); + ret = I2S_obj->begin(mode, sample_rate[srp], bps[bpsp]); + + // Output buffer after init (before any write) should be empty and available for write should equal to the size of buffer + int DMA_buffer_frame_size = I2S_obj->getDMABufferFrameSize(); + int DMA_buffer_sample_size = I2S_obj->getDMABufferSampleSize(); + int buffer_byte_size = I2S_obj->getRingBufferByteSize(); + int available_for_write = I2S_obj->availableForWrite(); + int available_samples_for_write = I2S_obj->availableSamplesForWrite(); + TEST_ASSERT_EQUAL(DMA_buffer_frame_size * 2, DMA_buffer_sample_size); + TEST_ASSERT_EQUAL(DMA_buffer_frame_size * 2 * (bps[bpsp]/8), buffer_byte_size); + TEST_ASSERT_EQUAL(available_for_write, buffer_byte_size); + TEST_ASSERT_EQUAL(available_samples_for_write, DMA_buffer_sample_size); + + switch(bps[bpsp]){ + case 8: + ret = I2S_obj->write((int8_t)data_8bit[0]); + break; + case 16: + ret = I2S_obj->write((int16_t)data_16bit[0]); + break; + case 24: + // 24 bits per sample mode will ignore the MSB from the 32-bit sample and return 3 on success, + // therefore a 32-bit function inside the following case can be used. + case 32: + ret = I2S_obj->write((int32_t)data_32bit[0]); + break; + } // switch + TEST_ASSERT_EQUAL(ret, bps[bpsp]/8); + TEST_ASSERT_EQUAL(available_for_write-ret, I2S_obj->availableForWrite()); + available_for_write -= ret; // update for next tests + + // write rest of the + size_t expected_bytes_written = 0; + switch(bps[bpsp]){ + case 8: + expected_bytes_written = sizeof(data_8bit) - sizeof(data_8bit[0]); + ret = I2S_obj->write((uint8_t*)(&data_8bit[1]), expected_bytes_written); + break; + case 16: + expected_bytes_written = sizeof(data_16bit) - sizeof(data_16bit[0]); + ret = I2S_obj->write((uint8_t*)(&data_16bit[1]), expected_bytes_written); + break; + case 24: + expected_bytes_written = sizeof(data_24bit) - 3; + ret = I2S_obj->write((uint8_t*)(&data_24bit[3]), expected_bytes_written); + break; + case 32: + expected_bytes_written = sizeof(data_32bit) - sizeof(data_32bit[0]); + ret = I2S_obj->write((uint8_t*)(&data_32bit[1]), expected_bytes_written); + break; + } // switch + TEST_ASSERT_EQUAL(ret, expected_bytes_written); + TEST_ASSERT_EQUAL(available_for_write-ret, I2S_obj->availableForWrite()); + } // for all bits per sample + } // for all sample rates + } // for all modes + + Serial.printf("*********** [%lu] test_05 complete **************\n", millis()); +} + +// Loop-back data transfer +void test_06(void){ + Serial.printf("*********** [%lu] test_06 starting **************\n", millis()); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_SD], PIN_FUNC_GPIO); + gpio_set_direction(PIN_I2S_SD, GPIO_MODE_INPUT_OUTPUT); + esp_rom_gpio_connect_out_signal(PIN_I2S_SD, I2S0_DATA_OUT_IDX, 0, 0); + esp_rom_gpio_connect_in_signal(PIN_I2S_SD, I2S0_DATA_IN_IDX, 0); + + int ret = -1; + + for(int mode = 0; mode < MODE_MAX; ++mode){ + Serial.printf("[%lu] data transfer begin: mode %d \"%s\"\n", millis(), mode, i2s_mode_text[mode]); + for(int srp = 0; srp < srp_max; ++srp){ + for(int bpsp = 0; bpsp < bpsp_max; ++bpsp){ + //Serial.printf("[%lu] begin: mode %d \"%s\", sample rate %d, bps %d\n", millis(), mode, i2s_mode_text[mode], sample_rate[srp], bps[bpsp]); + ret = I2S_obj->begin(mode, sample_rate[srp], bps[bpsp]); + size_t expected_bytes_written = 0; + int available_for_write = I2S_obj->getRingBufferByteSize(); + int max_i = 0; + switch(bps[bpsp]){ + case 8: + expected_bytes_written = sizeof(data_8bit); + ret = I2S_obj->write(data_8bit, expected_bytes_written); + max_i = sizeof(data_8bit) / sizeof(data_8bit[0]); + break; + case 16: + expected_bytes_written = sizeof(data_16bit); + ret = I2S_obj->write(data_16bit, expected_bytes_written); + max_i = sizeof(data_16bit) / sizeof(data_16bit[0]); + break; + case 24: + expected_bytes_written = sizeof(data_24bit); + ret = I2S_obj->write(data_24bit, expected_bytes_written); + max_i = sizeof(data_24bit) / sizeof(data_24bit[0]); + break; + case 32: + expected_bytes_written = sizeof(data_32bit); + ret = I2S_obj->write(data_32bit, expected_bytes_written); + max_i = sizeof(data_32bit) / sizeof(data_32bit[0]); + break; + } // switch + TEST_ASSERT_EQUAL(ret, expected_bytes_written); + TEST_ASSERT_EQUAL(available_for_write-ret, I2S_obj->availableForWrite()); + while(!I2S_obj->available()){ delay(1); } + uint8_t buffer[sizeof(data_32bit)]; // Create buffer to accommodate the largest possible array + ret = I2S_obj->read(buffer, expected_bytes_written); + TEST_ASSERT_EQUAL(ret, expected_bytes_written); + TEST_ASSERT_EQUAL(0, I2S_obj->available()); // Input buffer should be empty + + // Check data + for(int i = 0; i < max_i; ++i){ + switch(bps[bpsp]){ + case 8: + TEST_ASSERT_EQUAL(((uint8_t*)buffer)[i], data_8bit[i]); + break; + case 16: + TEST_ASSERT_EQUAL(((uint16_t*)buffer)[i], data_16bit[i]); + case 24: + break; + TEST_ASSERT_EQUAL(((uint8_t*)buffer)[i], data_24bit[i]); + break; + case 32: + TEST_ASSERT_EQUAL(((uint32_t*)buffer)[i], data_32bit[i]); + break; + } // switch + } // Check data for loop + } // for all bps + } // for all sample rates + } // for all modes +} + +// Dual module data transfer test (only for ESP32 and ESP32-S3) +void test_0x(void){ + } void setup() { @@ -282,22 +494,36 @@ void setup() { //Serial.printf("Num of I2S module =%d\n", SOC_I2S_NUM); //Serial.printf("I2S0=%p\n", &I2S); - I2S_obj_arr[0] = &I2S; - #if SOC_I2S_NUM > 1 - I2S_obj_arr[1] = &I2S1; - //Serial.printf("I2S1=%p\n", &I2S1); - #endif - i2s_index = 0; + UNITY_BEGIN(); - for(; i2s_index < SOC_I2S_NUM; ++ i2s_index){ - //Serial.printf("*******************************************************\n"); - //Serial.printf("********************* I2S # %d *************************\n", i2s_index); - //Serial.printf("*******************************************************\n"); - I2S_obj = I2S_obj_arr[i2s_index]; - RUN_TEST(test_01); // begin master - RUN_TEST(test_02); // begin slave - RUN_TEST(test_03); // pin setters and geters - } + #if SOC_I2S_NUM == 0 + RUN_TEST(test_pass); // This SoC does not have I2S - pass the test without running the tests + #endif + + #if SOC_I2S_NUM > 0 + I2S_obj_arr[0] = &I2S; + #if SOC_I2S_NUM > 1 + I2S_obj_arr[1] = &I2S_1; + //Serial.printf("I2S_1=%p\n", &I2S_1); + #endif + i2s_index = 0; + for(; i2s_index < SOC_I2S_NUM; ++ i2s_index){ + //Serial.printf("*******************************************************\n"); + //Serial.printf("********************* I2S # %d *************************\n", i2s_index); + //Serial.printf("*******************************************************\n"); + I2S_obj = I2S_obj_arr[i2s_index]; + RUN_TEST(test_01); // begin master + RUN_TEST(test_02); // begin slave + RUN_TEST(test_03); // pin setters and geters + RUN_TEST(test_04); // duplex / simplex + + RUN_TEST(test_05); // Simple data transmit, returned written bytes check and buffer check. + RUN_TEST(test_06); // Loop-back data transfer + } + #if SOC_I2S_NUM > 1 + RUN_TEST(test_0x); // Dual module data transfer test (only for ESP32 and ESP32-S3) + #endif + #endif // SOC_I2S_NUM > 0 UNITY_END(); } From 53d8f237f4f79b34c7098009474fa2ed7cd1f193 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Fri, 11 Nov 2022 13:52:56 +0100 Subject: [PATCH 28/46] Updated default pins --- .../I2S/examples/DualModule/DualModule.ino | 40 +++++++++---------- .../I2S/examples/FullDuplex/FullDuplex.ino | 8 ++-- .../InputSerialPlotter/InputSerialPlotter.ino | 7 ++-- .../I2S/examples/SimpleTone/SimpleTone.ino | 8 ++-- libraries/I2S/src/I2S.h | 26 ++++++------ 5 files changed, 45 insertions(+), 44 deletions(-) diff --git a/libraries/I2S/examples/DualModule/DualModule.ino b/libraries/I2S/examples/DualModule/DualModule.ino index 23d76117bb6..4676cf603da 100644 --- a/libraries/I2S/examples/DualModule/DualModule.ino +++ b/libraries/I2S/examples/DualModule/DualModule.ino @@ -4,30 +4,31 @@ The application generates square wave for both modules and transfers to each other. You can plot the waves with Arduino plotter - I2S - | Pin | ESP32 | ESP32-S3 | + To prepare the example you will need to connect the output pins of the modules as if they were standalone devices. + The pin-out differ for the SoCs - refer to the table of used SoC. + + ESP32 + | Pin | I2S | I2S_1 | | -----|-------|- --------| - | GND | GND | GND | - | VIN | 5V | 5V | - | SCK | 19 | 19 | - | FS | 21 | 21 | - | DIN | 23 | 5 | - | DOUT | 22 | 4 | - - I2S_1 - | Pin | ESP32 | ESP32-S3 | + | SCK | 18 | 23 | + | FS | 19 | 25 | + | SD 1 | 21 | 27 | I2S DOUT -> I2S_1 DIN + | SD 2 | 22 | 26 | I2S DIN <- I2S_1 DOUT + + ESP32-S3 + | Pin | I2S | I2S_1 | | -----|-------|- --------| - | GND | GND | GND | - | VIN | 5V | 5V | | SCK | 18 | 36 | - | FS | 22 | 37 | - | DIN | 26 | 40 | - | DOUT | 25 | 39 | + | FS | 19 | 37 | + | SD 1 | 4 | 40 | I2S DOUT -> I2S_1 DIN + | SD 2 | 5 | 39 | I2S DIN <- I2S_1 DOUT created 7 Nov 2022 by Tomas Pilny */ +//#define INTERNAL_CONNECTIONS // uncomment to use without external connections + #if SOC_I2S_NUM > 1 #include @@ -91,15 +92,13 @@ void setup() { } } - I2S.setDataInPin(DATA_MASTER_TO_SLAVE); - I2S.setDataOutPin(DATA_SLAVE_TO_MASTER); if(!I2S_1.setDuplex()){ Serial.println("ERROR - could not set duplex for I2S_1"); while(true){ vTaskDelay(10); // Cannot continue } } - if (!I2S_1.begin(I2S_PHILIPS_MODE, bps)) { // start in slave mode --- CRASHING! (i2s_driver_install) + if (!I2S_1.begin(I2S_PHILIPS_MODE, bps)) { // start in slave mode Serial.println("Failed to initialize I2S_1!"); while(true){ vTaskDelay(10); // Cannot continue @@ -113,6 +112,7 @@ void setup() { } } +#ifdef INTERNAL_CONNECTIONS gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_SCK], PIN_FUNC_GPIO); gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_FS], PIN_FUNC_GPIO); gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_SD_OUT], PIN_FUNC_GPIO); @@ -134,7 +134,7 @@ void setup() { esp_rom_gpio_connect_out_signal(DATA_SLAVE_TO_MASTER, I2S1_DATA_OUT_IDX, 0, 0); esp_rom_gpio_connect_in_signal(DATA_SLAVE_TO_MASTER, I2S0_DATA_IN_IDX, 0); - +#endif Serial.println("Setup done"); } diff --git a/libraries/I2S/examples/FullDuplex/FullDuplex.ino b/libraries/I2S/examples/FullDuplex/FullDuplex.ino index acb8306d33f..8f57e185824 100644 --- a/libraries/I2S/examples/FullDuplex/FullDuplex.ino +++ b/libraries/I2S/examples/FullDuplex/FullDuplex.ino @@ -8,10 +8,10 @@ | -----|-------|------------------------------| | GND | GND | GND | | VIN | 5V | 5V | - | SCK | 19 | 19 | - | FS | 21 | 21 | - | DIN | 23 | 5 | - | DOUT | 22 | 4 | + | SCK | 18 | 18 | + | FS | 19 | 19 | + | DOUT | 21 | 4 | + | DIN | 22 | 5 | created 8 October 2021 by Tomas Pilny diff --git a/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino b/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino index 2d37fe28222..28819fa3f73 100644 --- a/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino +++ b/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino @@ -11,9 +11,10 @@ | -----|-------|-------|-------|-------|------------------------------| | GND | GND | GND | GND | GND | GND | | 3.3V | 3.3V | 3.3V | 3.3V | 3.3V | 3.3V | - | SCK | 1 | 2 | A3 | 19 | 19 | - | FS | 0 | 3 | A2 | 21 | 21 | - | DIN | 9 | A6 | 4 | 23 | 5 | + | SCK | 1 | 2 | A3 | 18 | 18 | + | FS | 0 | 3 | A2 | 19 | 19 | + | SD | 9 | A6 | 4 | 22 | 5 | + created 17 November 2016 by Sandeep Mistry */ diff --git a/libraries/I2S/examples/SimpleTone/SimpleTone.ino b/libraries/I2S/examples/SimpleTone/SimpleTone.ino index 93b701b2c25..e35018bae32 100644 --- a/libraries/I2S/examples/SimpleTone/SimpleTone.ino +++ b/libraries/I2S/examples/SimpleTone/SimpleTone.ino @@ -10,9 +10,9 @@ | -----|-------|-------|-------|-------|------------------------------| | GND | GND | GND | GND | GND | GND | | 5V | 5V | 5V | 5V | 5V | 5V | - | SCK | 1 | 2 | A3 | 19 | 19 | - | FS | 0 | 3 | A2 | 21 | 21 | - | SD | 9 | A6 | 4 | 22 | 4 | + | SCK | 1 | 2 | A3 | 18 | 18 | + | FS | 0 | 3 | A2 | 19 | 19 | + | SD | 9 | A6 | 4 | 21 | 4 | * note: those chips supports only 16/24/32 bits per sample, i.e. 8 bps will be refused = no audio output DAC Circuit: @@ -41,7 +41,7 @@ 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 +//i2s_mode_t mode = ADC_DAC_MODE; // Audio amplifier is needed // Mono channel input // This is ESP specific implementation - diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index 56d0373e893..c19b37e3da1 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -29,19 +29,19 @@ namespace esp_i2s { // Default pins //I2S0 is available for all ESP32 SoCs //SCK WS SD(OUT) SDIN -// 19 18 22 23 ESP32 -// 19 18 4 5 ESP32-x (C3,S2,S3) +// 18 19 21 22 ESP32 +// 18 19 4 5 ESP32-x (C3,S2,S3) #ifndef PIN_I2S_SCK - #define PIN_I2S_SCK GPIO_NUM_19 + #define PIN_I2S_SCK GPIO_NUM_18 #endif #ifndef PIN_I2S_FS - #define PIN_I2S_FS GPIO_NUM_18 + #define PIN_I2S_FS GPIO_NUM_19 #endif #ifndef PIN_I2S_SD #if CONFIG_IDF_TARGET_ESP32 - #define PIN_I2S_SD GPIO_NUM_22 + #define PIN_I2S_SD GPIO_NUM_21 #else #define PIN_I2S_SD GPIO_NUM_4 #endif @@ -49,7 +49,7 @@ namespace esp_i2s { #ifndef PIN_I2S_SD_OUT #if CONFIG_IDF_TARGET_ESP32 - #define PIN_I2S_SD_OUT GPIO_NUM_22 + #define PIN_I2S_SD_OUT GPIO_NUM_21 #else #define PIN_I2S_SD_OUT GPIO_NUM_4 #endif @@ -57,7 +57,7 @@ namespace esp_i2s { #ifndef PIN_I2S_SD_IN #if CONFIG_IDF_TARGET_ESP32 - #define PIN_I2S_SD_IN GPIO_NUM_23 + #define PIN_I2S_SD_IN GPIO_NUM_22 #else #define PIN_I2S_SD_IN GPIO_NUM_5 #endif @@ -68,25 +68,25 @@ namespace esp_i2s { #if CONFIG_IDF_TARGET_ESP32 // ESP32 pins //SCK WS SD(OUT) SDIN - // 18 22 25 26 + // 23 25 26 27 #ifndef PIN_I2S1_SCK - #define PIN_I2S1_SCK GPIO_NUM_18 + #define PIN_I2S1_SCK GPIO_NUM_23 #endif #ifndef PIN_I2S1_FS - #define PIN_I2S1_FS GPIO_NUM_22 + #define PIN_I2S1_FS GPIO_NUM_25 #endif #ifndef PIN_I2S1_SD - #define PIN_I2S1_SD GPIO_NUM_25 + #define PIN_I2S1_SD GPIO_NUM_26 #endif #ifndef PIN_I2S1_SD_OUT - #define PIN_I2S1_SD_OUT GPIO_NUM_25 + #define PIN_I2S1_SD_OUT GPIO_NUM_26 #endif #ifndef PIN_I2S1_SD_IN - #define PIN_I2S1_SD_IN GPIO_NUM_26 + #define PIN_I2S1_SD_IN GPIO_NUM_27 #endif #endif From a3898e68c8731d231c2271c5fb9f93d9f5627245 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Fri, 11 Nov 2022 14:08:55 +0100 Subject: [PATCH 29/46] Fixed buffer calls in example --- libraries/I2S/examples/FullDuplex/FullDuplex.ino | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/I2S/examples/FullDuplex/FullDuplex.ino b/libraries/I2S/examples/FullDuplex/FullDuplex.ino index 8f57e185824..e24b7fbaff6 100644 --- a/libraries/I2S/examples/FullDuplex/FullDuplex.ino +++ b/libraries/I2S/examples/FullDuplex/FullDuplex.ino @@ -37,7 +37,7 @@ void setup() { vTaskDelay(10); // Cannot continue } } - buffer = (uint8_t*) malloc(I2S.getBufferSize() * (bitsPerSample / 8)); + buffer = (uint8_t*) malloc(I2S.getDMABufferByteSize()); if(buffer == NULL){ Serial.println("Failed to allocate buffer!"); while(true){ @@ -51,6 +51,6 @@ 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)); + I2S.read(buffer, I2S.getDMABufferByteSize()); + I2S.write(buffer, I2S.getDMABufferByteSize()); } From 6ac821e3eeee18e6c63b2fe5a1cabcca290b39f0 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Fri, 11 Nov 2022 14:39:19 +0100 Subject: [PATCH 30/46] Added skip files for DualModule example --- libraries/I2S/examples/DualModule/.skip.esp32c3 | 0 libraries/I2S/examples/DualModule/.skip.esp32s2 | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 libraries/I2S/examples/DualModule/.skip.esp32c3 create mode 100644 libraries/I2S/examples/DualModule/.skip.esp32s2 diff --git a/libraries/I2S/examples/DualModule/.skip.esp32c3 b/libraries/I2S/examples/DualModule/.skip.esp32c3 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/I2S/examples/DualModule/.skip.esp32s2 b/libraries/I2S/examples/DualModule/.skip.esp32s2 new file mode 100644 index 00000000000..e69de29bb2d From 85b0ea473d6f95a96aa01114ab9da3553b14219a Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Mon, 28 Nov 2022 16:02:50 +0100 Subject: [PATCH 31/46] Renamed data parameter to sample; removed _buffer_byte_size - now using getRingBufferByteSize; reduced i2s_config.fixed_mclk --- libraries/I2S/README.md | 4 +- libraries/I2S/src/I2S.cpp | 78 +++++++++++++++++++++------------------ libraries/I2S/src/I2S.h | 10 ++--- 3 files changed, 48 insertions(+), 44 deletions(-) diff --git a/libraries/I2S/README.md b/libraries/I2S/README.md index f14a2f8fbe6..5c42d175eb3 100644 --- a/libraries/I2S/README.md +++ b/libraries/I2S/README.md @@ -281,7 +281,7 @@ Reads a single sample from ring buffer and keeps it available for future read (i This function is **blocking** - if there is not enough space in ring buffer the function will wait until it can write the sample. **Parameter:** -* **uint8_t data** The sample to be sent +* **uint8_t sample** The sample to be sent **Returns:** 1 on successful write; 0 on error = did not write the sample to ring buffer @@ -296,7 +296,7 @@ Please consider sending data in arrays using function `size_t write(const uint8_ This function is **blocking** - if there is not enough space in ring buffer the function will wait until it can write the sample. **Parameter:** -**int32_t data** The sample to be sent +**int32_t sample** The sample to be sent **Returns:** Number of written bytes, if successful the value will be equal to `bitsPerSample/8` diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index a2900502445..ef90ec0bf13 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -31,8 +31,8 @@ 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 + _inSdPin(deviceIndex == 0 ? PIN_I2S_SD_IN : PIN_I2S1_SD_IN), // input data pin + _outSdPin(deviceIndex == 0 ? PIN_I2S_SD : PIN_I2S1_SD), // output data pin _sckPin(sckPin), // clock pin _fsPin(fsPin), // frame (word) select pin @@ -41,8 +41,6 @@ I2SClass::I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, u _sampleRate(0), _mode(I2S_PHILIPS_MODE), - _buffer_byte_size(0), - _driverInstalled(false), _initialized(false), _callbackTaskHandle(NULL), @@ -76,8 +74,8 @@ int I2SClass::_createCallbackTask(){ onDmaTransferComplete, // Function to implement the task "onDmaTransferComplete", // Name of the task stack_size, // Stack size in words - (void *)&_deviceIndex, // Task input parameter - 2, // Priority of the task + (void *)&_deviceIndex, // Task input parameter + 4, // Priority of the task &_callbackTaskHandle // Task handle. ); if(_callbackTaskHandle == NULL){ @@ -164,7 +162,7 @@ int I2SClass::_installDriver(){ if(_driveClock == false){ i2s_config.use_apll = true; - i2s_config.fixed_mclk = 512*_sampleRate; + i2s_config.fixed_mclk = 8*_sampleRate; } // Install and start i2s driver @@ -324,11 +322,10 @@ int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock return 0; // ERR } - _buffer_byte_size = _i2s_dma_buffer_frame_size * (_bitsPerSample / 8) * _I2S_DMA_BUFFER_COUNT * CHANNEL_NUMBER; // The magic "*k2" stands for number of channels which are statically set to stereo - _input_ring_buffer = xRingbufferCreate(_buffer_byte_size, RINGBUF_TYPE_BYTEBUF); - _output_ring_buffer = xRingbufferCreate(_buffer_byte_size, RINGBUF_TYPE_BYTEBUF); + _input_ring_buffer = xRingbufferCreate(getRingBufferByteSize(), RINGBUF_TYPE_BYTEBUF); + _output_ring_buffer = xRingbufferCreate(getRingBufferByteSize(), RINGBUF_TYPE_BYTEBUF); if(_input_ring_buffer == NULL || _output_ring_buffer == NULL){ - log_e("(I2S#%d) ERROR: could not create one or both internal buffers. Requested size = %d", _deviceIndex, _buffer_byte_size); + log_e("(I2S#%d) ERROR: could not create one or both internal buffers. Requested size = %d", _deviceIndex, getRingBufferByteSize()); if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR } @@ -593,7 +590,7 @@ int I2SClass::available(){ if(!_take_mux()){ return 0; /* ERR */ } int ret = 0; if(_input_ring_buffer != NULL){ - ret = _buffer_byte_size - (int)xRingbufferGetCurFreeSize(_input_ring_buffer); + ret = getRingBufferByteSize() - (int)xRingbufferGetCurFreeSize(_input_ring_buffer); } if(!_give_mux()){ return 0; /* ERR */ } return ret; @@ -766,14 +763,14 @@ size_t I2SClass::write_nonblocking(const void *buffer, size_t size){ if(!_give_mux()){ return 0; /* ERR */ } return size; }else{ - log_w("(I2S#%d) I2S could not write all data into ring buffer!", _deviceIndex); + log_w("(I2S#%d) I2S could not write all data (%d B) into ring buffer!", _deviceIndex, size); if(!_give_mux()){ return 0; /* ERR */ } return 0; } } } // if(_initialized) - return 0; if(!_give_mux()){ return 0; /* ERR */ } // this should not be needed + return 0; } /* @@ -944,7 +941,6 @@ int I2SClass::_enableReceiver(){ void I2SClass::_tx_done_routine(uint8_t* prev_item){ static bool prev_item_valid = false; - const size_t single_dma_buf_byte_size = _i2s_dma_buffer_frame_size*(_bitsPerSample/8)* CHANNEL_NUMBER; static size_t item_size = 0; static size_t prev_item_size = 0; static void *item = NULL; @@ -960,22 +956,20 @@ void I2SClass::_tx_done_routine(uint8_t* prev_item){ prev_item_size -= bytes_written; } // prev_item_valid - if(_output_ring_buffer != NULL && (_buffer_byte_size - xRingbufferGetCurFreeSize(_output_ring_buffer) >= single_dma_buf_byte_size)){ // fill up the I2S DMA buffer + if(_output_ring_buffer != NULL && (getRingBufferByteSize() - xRingbufferGetCurFreeSize(_output_ring_buffer) >= getDMABufferByteSize())){ // fill up the I2S DMA buffer bytes_written = 0; item_size = 0; - if(_buffer_byte_size - xRingbufferGetCurFreeSize(_output_ring_buffer) >= _i2s_dma_buffer_frame_size*(_bitsPerSample/8)){ // don't read from almost empty buffer - item = xRingbufferReceiveUpTo(_output_ring_buffer, &item_size, pdMS_TO_TICKS(0), single_dma_buf_byte_size); - 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 + item = xRingbufferReceiveUpTo(_output_ring_buffer, &item_size, pdMS_TO_TICKS(0), getDMABufferByteSize()); + 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 } // fill up the I2S DMA buffer if(_onTransmit){ _onTransmit(); @@ -984,16 +978,25 @@ void I2SClass::_tx_done_routine(uint8_t* prev_item){ void I2SClass::_rx_done_routine(){ size_t bytes_read = 0; - const size_t single_dma_buf_byte_size = _i2s_dma_buffer_frame_size*(_bitsPerSample/8)*CHANNEL_NUMBER; + size_t request_read = 0; if(_input_ring_buffer != NULL){ - uint8_t *_inputBuffer = (uint8_t*)malloc(single_dma_buf_byte_size); + uint8_t *_inputBuffer = (uint8_t*)malloc(getDMABufferByteSize()); 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_byte_size ? avail : single_dma_buf_byte_size, (size_t*) &bytes_read, 0); + if(_bitsPerSample == 8){ // 8-bps is received with padding as 16bits - read twice than actually needed + request_read = avail*2 <= getDMABufferByteSize() ? avail*2 : getDMABufferByteSize(); + }else{ + request_read = avail <= getDMABufferByteSize() ? avail : getDMABufferByteSize(); + } + + esp_err_t ret = esp_i2s::i2s_read((esp_i2s::i2s_port_t) _deviceIndex, _inputBuffer, request_read, &bytes_read, 0); if(ret != ESP_OK){ log_w("(I2S#%d) i2s_read returned with error %d", _deviceIndex, ret); } + if(request_read != bytes_read){ + log_w("(I2S#%d) i2s_read read only %d bytes instead of %d requested", _deviceIndex, bytes_read, request_read); + } _post_read_data_fix(_inputBuffer, &bytes_read); } @@ -1003,7 +1006,7 @@ void I2SClass::_rx_done_routine(){ } // 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 + if (_onReceive && avail < getRingBufferByteSize()){ // when user callback is registered && and there is some data in ring buffer to read _onReceive(); } // user callback } @@ -1038,7 +1041,7 @@ void I2SClass::onDmaTransferComplete(void *deviceIndex){ I2S_1._onTransferComplete(); } #endif - log_w("(I2S#%d) Deleting callback task from inside!", index); + log_w("(I2S#%d) Deleting callback task from inside!", *index); vTaskDelete(NULL); } @@ -1058,10 +1061,14 @@ inline bool I2SClass::_give_mux(){ return true; } - // 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) +// In 8 bit mode data are received as 16 bit number with LSB padded with 0s and actual data on MSB, +// this function removes the padding (shrinking the size to 1/2) +// In 16 bit mode data are received with wrong endianity, this function swaps the bytes +// In 24 bit mode - not tested if needs fixing. +// In 32 bit mode data are ok - no action is performed. void I2SClass::_post_read_data_fix(void *input, size_t *size){ ulong dst_ptr = 0; switch(_bitsPerSample){ @@ -1174,7 +1181,6 @@ void I2SClass::_fix_and_write(void *output, size_t size, size_t *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){ diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index c19b37e3da1..d213bc35d2a 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -285,23 +285,23 @@ class I2SClass : public Stream * Write single sample of 8 bit size. * This function is blocking - if there is not enough space in ring buffer the function will wait until it can write the sample. * Parameter: - * uint8_t data The sample to be sent + * uint8_t sample The sample to be sent * Returns: 1 on successful write; 0 on error = did not write the sample to ring buffer * Note: This functions is used in many examples for it's simplicity, but it's use is discouraged for performance reasons. * Please consider sending data in arrays using function `size_t write(const uint8_t *buffer, size_t size)` */ - virtual size_t write(uint8_t data); + virtual size_t write(uint8_t sample); /* * Write single sample of up to 32 bit size. * This function is blocking - if there is not enough space in ring buffer the function will wait until it can write the sample. * Parameter: - * int32_t data The sample to be sent + * int32_t sample The sample to be sent * Returns: Number of written bytes, if successful the value will be equal to bitsPerSample/8 * Note: This functions is used in many examples for it's simplicity, but it's use is discouraged for performance reasons. * Please consider sending data in arrays using function `size_t write(const uint8_t *buffer, size_t size)` */ - size_t write(int32_t); + size_t write(int32_t sample); /* * Write array of samples. @@ -461,8 +461,6 @@ class I2SClass : public Stream 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; From 9fa388c74ca5abca468f3595307fd6a067c98b73 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Wed, 30 Nov 2022 13:59:39 +0100 Subject: [PATCH 32/46] Added MCLK --- libraries/I2S/README.md | 29 ++++++--- libraries/I2S/src/I2S.cpp | 132 ++++++++++++++++++++++++++------------ libraries/I2S/src/I2S.h | 41 +++++++++--- 3 files changed, 142 insertions(+), 60 deletions(-) diff --git a/libraries/I2S/README.md b/libraries/I2S/README.md index 5c42d175eb3..9becd2fb925 100644 --- a/libraries/I2S/README.md +++ b/libraries/I2S/README.md @@ -40,18 +40,25 @@ I2S module functionality on each SoC differs, please refer to the following tabl ESP I2S has fully configurable pins. There is a group of setter and getter functions for each pin separately and one setter for all pins at once (detailed description below). Calling the setter will take effect immediately and does not need driver restart. Default pins are setup as follows: ### Common I2S object: -| SCK | WS | SD(OUT) | SDIN | SoC | -| --- | -- | ------- | ---- |:---------------------------- | -| 19 | 21 | 22 | 23 | ESP32 | -| 19 | 21 | 4 | 5 | ESP32-C3, ESP32-S2, ESP32-S3 | +| MCLK | SCK | WS | SD(OUT) | SDIN | SoC | +| ---- | --- | -- | ------- | ---- |:---------------------------- | +| 32 | 19 | 21 | 22 | 23 | ESP32 | +| 1 | 19 | 21 | 4 | 5 | ESP32-C3, ESP32-S2, ESP32-S3 | ### I2S_1 object: -| SCK | WS | SD(OUT) | SDIN | SoC | -| --- | -- | ------- | ---- |:-------- | -| 18 | 22 | 25 | 26 | ESP32 | -| 36 | 37 | 39 | 40 | ESP32-S3 | +| MCLK | SCK | WS | SD(OUT) | SDIN | SoC | +| ---- | --- | -- | ------- | ---- |:-------- | +| 33 | 18 | 22 | 25 | 26 | ESP32 | +| 35 | 36 | 37 | 39 | 40 | ESP32-S3 | +## Master / Slave[ ](https://en.wiktionary.org/wiki/slave#Etymology) modes +There are two versions of initializer function `begin` - one initializes in master mode and the other one in slave mode (see functions below for details). +The usual use-case is operation in master mode which means that the MCU generates the clock signals which are transmitted to slave devices - usually microphones, speakers or I2S decoders. + +In master mode it is required to connect the device with clock (SCK), word-select (WS) and data (SD) signals. In duplex mode (see below) it is necessary to connect two data lines: input data (SDIN) and output data (SDOUT). The shared data (SD) signal acts as output in duplex mode. + +In slave mode it is necessary to connect the devices with the same signals as in master mode and also master clock (MCLK) signal. If you should try to operat the MCU in slave mode without MCLK, the MCU would not be able properly deduce the timing only from the SCK and there would be no data output from the slave and also the slave would not be able to read any data. ## Duplex / Simplex Arduino has only one data pin and the driver is switching between receiving and transmitting upon calling `read` or `write`. On all ESP32s each I2S module has 2 independent data lines, one for transmitting and one for receiving. @@ -162,7 +169,7 @@ The change takes effect immediately and does not need driver restart. **Returns:** 1 on success; 0 on error **Function list:** - +* **int setMclkPin(int sckPin)** Set Master Clock pin * **int setSckPin(int sckPin)** Set Clock pin * **int setFsPin(int fsPin)** Set Frame Sync (Word Select) pin * **int setDataPin(int sdPin)** Set shared Data pin for simplex mode @@ -179,7 +186,7 @@ The change takes effect immediately and does not need driver restart. **Returns:** 1 on success; 0 on error *** -#### int setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin) +#### int setAllPins(int mclkPin, int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin) *Change pin setup for all pins at one call.* @@ -188,6 +195,7 @@ The change takes effect immediately and does not need driver restart. **Parameters:** +* **int mclkPin** Clock pin * **int sckPin** Clock pin * **int fsPin** Frame Sync (Word Select) pin * **int sdPin** Shared Data pin for simplex mode @@ -204,6 +212,7 @@ The change takes effect immediately and does not need driver restart. **Function list:** +* **int getMclkPin()** Get Master Clock pin * **int getSckPin()** Get Clock pin * **int getFsPin()** Get Frame Sync (Word Select) pin * **int getDataPin()** Get shared Data pin for simplex mode diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index ef90ec0bf13..ccb24b05ee5 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -30,11 +30,12 @@ I2SClass::I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, uint8_t sckPin, uint8_t fsPin) : _deviceIndex(deviceIndex), + _mclkPin(deviceIndex == 0 ? PIN_I2S_MCLK : PIN_I2S1_MCLK), // input data pin + _sckPin(sckPin), // clock pin + _fsPin(fsPin), // frame (word) select pin _sdPin(sdPin), // shared data pin _inSdPin(deviceIndex == 0 ? PIN_I2S_SD_IN : PIN_I2S1_SD_IN), // input data pin _outSdPin(deviceIndex == 0 ? PIN_I2S_SD : PIN_I2S1_SD), // output data pin - _sckPin(sckPin), // clock pin - _fsPin(fsPin), // frame (word) select pin _state(I2S_STATE_IDLE), _bitsPerSample(0), @@ -344,7 +345,7 @@ int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock int I2SClass::_applyPinSetting(){ if(_driverInstalled){ esp_i2s::i2s_pin_config_t pin_config = { - .mck_io_num = I2S_PIN_NO_CHANGE, + .mck_io_num = _mclkPin, .bck_io_num = _sckPin, .ws_io_num = _fsPin, .data_out_num = I2S_PIN_NO_CHANGE, @@ -375,42 +376,45 @@ int I2SClass::_applyPinSetting(){ return 1; // OK } +void I2SClass::_setMclkPin(int mclkPin){ + if(!_take_mux()){ return; /* ERR */ } + if(mclkPin >= 0){ + _mclkPin = mclkPin; + }else{ + _mclkPin = PIN_I2S_MCLK; + #if SOC_I2S_NUM > 1 + if(_deviceIndex==1){_mclkPin = PIN_I2S1_MCLK;} + #endif + } + if(!_give_mux()){ return; /* ERR */ } +} + void I2SClass::_setSckPin(int sckPin){ if(!_take_mux()){ return; /* ERR */ } if(sckPin >= 0){ _sckPin = sckPin; }else{ _sckPin = PIN_I2S_SCK; + #if SOC_I2S_NUM > 1 + if(_deviceIndex==1){_sckPin = PIN_I2S1_SCK;} + #endif } if(!_give_mux()){ return; /* ERR */ } } -int I2SClass::setSckPin(int sckPin){ - if(!_take_mux()){ return 0; /* ERR */ } - _setSckPin(sckPin); - int ret = _applyPinSetting(); - if(!_give_mux()){ return 0; /* ERR */ } - return ret; -} - void I2SClass::_setFsPin(int fsPin){ if(!_take_mux()){ return; /* ERR */ } if(fsPin >= 0){ _fsPin = fsPin; }else{ _fsPin = PIN_I2S_FS; + #if SOC_I2S_NUM > 1 + if(_deviceIndex==1){_sckPin = PIN_I2S1_FS;} + #endif } if(!_give_mux()){ return; /* ERR */ } } -int I2SClass::setFsPin(int fsPin){ - if(!_take_mux()){ return 0; /* ERR */ } - _setFsPin(fsPin); - int ret = _applyPinSetting(); - if(!_give_mux()){ return 0; /* ERR */ } - return ret; -} - // shared data pin for simplex void I2SClass::_setDataPin(int sdPin){ if(!_take_mux()){ return; /* ERR */ } @@ -418,47 +422,80 @@ void I2SClass::_setDataPin(int sdPin){ _sdPin = sdPin; }else{ _sdPin = PIN_I2S_SD; + #if SOC_I2S_NUM > 1 + if(_deviceIndex==1){_sckPin = PIN_I2S1_SD;} + #endif } if(!_give_mux()){ return; /* ERR */ } } -// shared data pin for simplex -int I2SClass::setDataPin(int sdPin){ - if(!_take_mux()){ return 0; /* ERR */ } - _setDataPin(sdPin); - int ret = _applyPinSetting(); - if(!_give_mux()){ return 0; /* ERR */ } - return ret; -} - void I2SClass::_setDataInPin(int inSdPin){ if(!_take_mux()){ return; /* ERR */ } if(inSdPin >= 0){ _inSdPin = inSdPin; }else{ _inSdPin = PIN_I2S_SD_IN; + #if SOC_I2S_NUM > 1 + if(_deviceIndex==1){_sckPin = PIN_I2S1_SD_IN;} + #endif } if(!_give_mux()){ return; /* ERR */ } } -int I2SClass::setDataInPin(int inSdPin){ - if(!_take_mux()){ return 0; /* ERR */ } - _setDataInPin(inSdPin); - int ret = _applyPinSetting(); - if(!_give_mux()){ return 0; /* ERR */ } - return ret; -} - void I2SClass::_setDataOutPin(int outSdPin){ if(!_take_mux()){ return; /* ERR */ } if(outSdPin >= 0){ _outSdPin = outSdPin; }else{ - _outSdPin = PIN_I2S_SD; + _outSdPin = PIN_I2S_SD_OUT; + #if SOC_I2S_NUM > 1 + if(_deviceIndex==1){_sckPin = PIN_I2S1_SD_OUT;} + #endif } if(!_give_mux()){ return; /* ERR */ } } +int I2SClass::setMclkPin(int mclkPin){ + if(!_take_mux()){ return 0; /* ERR */ } + _setMclkPin(mclkPin); + int ret = _applyPinSetting(); + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +int I2SClass::setSckPin(int sckPin){ + if(!_take_mux()){ return 0; /* ERR */ } + _setSckPin(sckPin); + int ret = _applyPinSetting(); + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +int I2SClass::setFsPin(int fsPin){ + if(!_take_mux()){ return 0; /* ERR */ } + _setFsPin(fsPin); + int ret = _applyPinSetting(); + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +// shared data pin for simplex +int I2SClass::setDataPin(int sdPin){ + if(!_take_mux()){ return 0; /* ERR */ } + _setDataPin(sdPin); + int ret = _applyPinSetting(); + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +int I2SClass::setDataInPin(int inSdPin){ + if(!_take_mux()){ return 0; /* ERR */ } + _setDataInPin(inSdPin); + int ret = _applyPinSetting(); + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + int I2SClass::setDataOutPin(int outSdPin){ if(!_take_mux()){ return 0; /* ERR */ } _setDataOutPin(outSdPin); @@ -469,14 +506,22 @@ int I2SClass::setDataOutPin(int outSdPin){ int I2SClass::setAllPins(){ if(!_take_mux()){ return 0; /* ERR */ } - int ret = setAllPins(PIN_I2S_SCK, PIN_I2S_FS, PIN_I2S_SD, PIN_I2S_SD_OUT, PIN_I2S_SD_IN); + int ret = 0; + if(_deviceIndex==0){ + ret = setAllPins(PIN_I2S_MCLK, PIN_I2S_SCK, PIN_I2S_FS, PIN_I2S_SD, PIN_I2S_SD_OUT, PIN_I2S_SD_IN); + } + #if SOC_I2S_NUM > 1 + if(_deviceIndex==1){ + ret = setAllPins(PIN_I2S1_MCLK, PIN_I2S1_SCK, PIN_I2S1_FS, PIN_I2S1_SD, PIN_I2S1_SD_OUT, PIN_I2S1_SD_IN); + } + #endif if(!_give_mux()){ return 0; /* ERR */ } return ret; } -int I2SClass::setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin){ +int I2SClass::setAllPins(int mclkPin, int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin){ if(!_take_mux()){ return 0; /* ERR */ } - _setSckPin(sckPin); + _setMclkPin(mclkPin); _setFsPin(fsPin); _setDataPin(sdPin); _setDataOutPin(outSdPin); @@ -509,6 +554,13 @@ int I2SClass::isDuplex(){ return ret; } +int I2SClass::getMclkPin(){ + if(!_take_mux()){ return 0; /* ERR */ } + int ret = _mclkPin; + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + int I2SClass::getSckPin(){ if(!_take_mux()){ return 0; /* ERR */ } int ret = _sckPin; diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index d213bc35d2a..d2bb4b7e55b 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -28,9 +28,17 @@ namespace esp_i2s { // Default pins //I2S0 is available for all ESP32 SoCs -//SCK WS SD(OUT) SDIN -// 18 19 21 22 ESP32 -// 18 19 4 5 ESP32-x (C3,S2,S3) +//MCLK SCK WS SD(OUT) SDIN +// 32 18 19 21 22 ESP32 +// 1 18 19 4 5 ESP32-x (C3,S2,S3) +#ifndef PIN_I2S_MCLK + #if CONFIG_IDF_TARGET_ESP32 + #define PIN_I2S_MCLK GPIO_NUM_32 + #else + #define PIN_I2S_MCLK GPIO_NUM_1 + #endif +#endif + #ifndef PIN_I2S_SCK #define PIN_I2S_SCK GPIO_NUM_18 #endif @@ -67,8 +75,12 @@ namespace esp_i2s { // I2S1 is available only for ESP32 and ESP32-S3 #if CONFIG_IDF_TARGET_ESP32 // ESP32 pins - //SCK WS SD(OUT) SDIN - // 23 25 26 27 + // MCLK SCK WS SD(OUT) SDIN + // 33 23 25 26 27 + #ifndef PIN_I2S1_MCLK + #define PIN_I2S1_MCLK GPIO_NUM_33 + #endif + #ifndef PIN_I2S1_SCK #define PIN_I2S1_SCK GPIO_NUM_23 #endif @@ -92,8 +104,12 @@ namespace esp_i2s { #if CONFIG_IDF_TARGET_ESP32S3 // ESP32-S3 pins - //SCK WS SD(OUT) SDIN - // 36 37 39 40 + // MCLK SCK WS SD(OUT) SDIN + // 35 36 37 39 40 + #ifndef PIN_I2S1_MCLK + #define PIN_I2S1_MCLK GPIO_NUM_35 + #endif + #ifndef PIN_I2S1_SCK #define PIN_I2S1_SCK GPIO_NUM_36 #endif @@ -190,6 +206,7 @@ class I2SClass : public Stream * Parameter: int pin number of GPIO which should be used for the requested pin setup * Returns: 1 on success; 0 on error */ + int setMclkPin(int sckPin); // Set Master Clock pin int setSckPin(int sckPin); // Set Clock pin int setFsPin(int fsPin); // Set Frame Sync (Word Select) pin int setDataPin(int sdPin); // Set shared Data pin for simplex mode @@ -209,6 +226,7 @@ class I2SClass : public Stream * Can be called only on initialized object (after begin). * The change takes effect immediately and does not need driver restart. * Parameters: + * int mclkPin Master Clock pin * int sckPin Clock pin * int fsPin Frame Sync (Word Select) pin * int sdPin Shared Data pin for simplex mode @@ -216,12 +234,13 @@ class I2SClass : public Stream * int inSdPin Data Input pin for duplex mode * Returns: 1 on success; 0 on error */ - int setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin); + int setAllPins(int mclkPin, int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin); /* * Get current pin GPIO number * Returns: the GPIO number of requested pin */ + int getMclkPin(); // Get Master Clock pin int getSckPin(); // Get Clock pin int getFsPin(); // Get Frame Sync (Word Select) pin int getDataPin(); // Get shared Data pin for simplex mode @@ -434,6 +453,7 @@ class I2SClass : public Stream static void onDmaTransferComplete(void *device_index); int _installDriver(); void _uninstallDriver(); + void _setMclkPin(int sckPin); void _setSckPin(int sckPin); void _setFsPin(int fsPin); void _setDataPin(int sdPin); @@ -450,11 +470,12 @@ class I2SClass : public Stream } i2s_state_t; int _deviceIndex; + int _mclkPin; + int _sckPin; + int _fsPin; int _sdPin; int _inSdPin; int _outSdPin; - int _sckPin; - int _fsPin; i2s_state_t _state; int _bitsPerSample; From 5f779df1cc7a9a8594570396f0c5a5a6c4869419 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Wed, 30 Nov 2022 14:39:02 +0100 Subject: [PATCH 33/46] Fixed constructor --- libraries/I2S/src/I2S.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index ccb24b05ee5..9d26d51a383 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -30,12 +30,18 @@ I2SClass::I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, uint8_t sckPin, uint8_t fsPin) : _deviceIndex(deviceIndex), +#if SOC_I2S_NUM > 1 _mclkPin(deviceIndex == 0 ? PIN_I2S_MCLK : PIN_I2S1_MCLK), // input data pin + _inSdPin(deviceIndex == 0 ? PIN_I2S_SD_IN : PIN_I2S1_SD_IN), // input data pin + _outSdPin(deviceIndex == 0 ? PIN_I2S_SD : PIN_I2S1_SD), // output data pin +#else + _mclkPin(PIN_I2S_MCLK), // input data pin + _inSdPin(PIN_I2S_SD_IN), // input data pin + _outSdPin(PIN_I2S_SD), // output data pin +#endif _sckPin(sckPin), // clock pin _fsPin(fsPin), // frame (word) select pin _sdPin(sdPin), // shared data pin - _inSdPin(deviceIndex == 0 ? PIN_I2S_SD_IN : PIN_I2S1_SD_IN), // input data pin - _outSdPin(deviceIndex == 0 ? PIN_I2S_SD : PIN_I2S1_SD), // output data pin _state(I2S_STATE_IDLE), _bitsPerSample(0), From b8dcf69e4d31bb22d61599f75c185de92359b7ee Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Wed, 30 Nov 2022 15:58:37 +0100 Subject: [PATCH 34/46] fixed variable order --- libraries/I2S/src/I2S.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index d2bb4b7e55b..2c5e9c66fa3 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -471,11 +471,11 @@ class I2SClass : public Stream int _deviceIndex; int _mclkPin; + int _inSdPin; + int _outSdPin; int _sckPin; int _fsPin; int _sdPin; - int _inSdPin; - int _outSdPin; i2s_state_t _state; int _bitsPerSample; From 70c9e2642585f62bb6788841647bba73a6cda4d0 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Wed, 7 Dec 2022 16:19:40 +0100 Subject: [PATCH 35/46] Fixed freezing output --- libraries/I2S/src/I2S.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index 9d26d51a383..183e1a35ab8 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -153,7 +153,7 @@ int I2SClass::_installDriver(){ .dma_buf_count = _I2S_DMA_BUFFER_COUNT, .dma_buf_len = _i2s_dma_buffer_frame_size, .use_apll = false, - .tx_desc_auto_clear = true, + .tx_desc_auto_clear = false, .fixed_mclk = 0, .mclk_multiple = esp_i2s::I2S_MCLK_MULTIPLE_DEFAULT, .bits_per_chan = esp_i2s::I2S_BITS_PER_CHAN_DEFAULT, From 6aed27f43e214dff668fb183ea0fd5a8ca8f1073 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Wed, 7 Dec 2022 16:21:43 +0100 Subject: [PATCH 36/46] Separated info message with doc link for each SoC --- libraries/I2S/src/I2S.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index 183e1a35ab8..04d6ad6d5d9 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -1018,7 +1018,7 @@ void I2SClass::_tx_done_routine(uint8_t* prev_item){ bytes_written = 0; item_size = 0; item = xRingbufferReceiveUpTo(_output_ring_buffer, &item_size, pdMS_TO_TICKS(0), getDMABufferByteSize()); - if (item != NULL){ + 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); @@ -1318,7 +1318,21 @@ int I2SClass::_gpioToAdcUnit(gpio_num_t gpio_num, esp_i2s::adc_unit_t* adc_unit) #endif default: log_e("(I2S#%d) GPIO %d not usable for ADC!", _deviceIndex, gpio_num); - log_i("Please refer to documentation https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/gpio.html"); + #if CONFIG_IDF_TARGET_ESP32 + log_i("Please refer to documentation https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/gpio.html"); + #elif + CONFIG_IDF_TARGET_ESP32S2 + log_i("Please refer to documentation https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/gpio.html"); + #elif + CONFIG_IDF_TARGET_ESP32S3 + log_i("Please refer to documentation https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/gpio.html"); + #elif + CONFIG_IDF_TARGET_ESP32C2 + log_i("Please refer to documentation https://docs.espressif.com/projects/esp-idf/en/latest/esp32c2/api-reference/peripherals/gpio.html"); + #elif + CONFIG_IDF_TARGET_ESP32C3 + log_i("Please refer to documentation https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/gpio.html"); + #endif return 0; // ERR } } From d831f1a08ec7e1bec6d17815bc5bf8943434ae72 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Wed, 7 Dec 2022 16:22:30 +0100 Subject: [PATCH 37/46] Changed default MCLK pins --- libraries/I2S/src/I2S.h | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index 2c5e9c66fa3..d61ea56d5c9 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -29,13 +29,14 @@ namespace esp_i2s { // Default pins //I2S0 is available for all ESP32 SoCs //MCLK SCK WS SD(OUT) SDIN -// 32 18 19 21 22 ESP32 -// 1 18 19 4 5 ESP32-x (C3,S2,S3) +// 0 18 19 21 22 ESP32 +// 0 18 19 4 5 ESP32-x (C3,S2,S3) + #ifndef PIN_I2S_MCLK #if CONFIG_IDF_TARGET_ESP32 - #define PIN_I2S_MCLK GPIO_NUM_32 + #define PIN_I2S_MCLK GPIO_NUM_0 #else - #define PIN_I2S_MCLK GPIO_NUM_1 + #define PIN_I2S_MCLK GPIO_NUM_0 #endif #endif @@ -76,9 +77,10 @@ namespace esp_i2s { #if CONFIG_IDF_TARGET_ESP32 // ESP32 pins // MCLK SCK WS SD(OUT) SDIN - // 33 23 25 26 27 + // 1 23 25 26 27 + #ifndef PIN_I2S1_MCLK - #define PIN_I2S1_MCLK GPIO_NUM_33 + #define PIN_I2S1_MCLK GPIO_NUM_1 #endif #ifndef PIN_I2S1_SCK @@ -204,6 +206,7 @@ class I2SClass : public Stream * Can be called only on initialized object (after begin). * The change takes effect immediately and does not need driver restart. * Parameter: int pin number of GPIO which should be used for the requested pin setup + * any negative value will set default pin number defined in PIN_I2S_X or PIN_I2S1_X * Returns: 1 on success; 0 on error */ int setMclkPin(int sckPin); // Set Master Clock pin From 9761860f6cebd29eb4155d4f154d5336bca34171 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Thu, 8 Dec 2022 11:18:11 +0100 Subject: [PATCH 38/46] Modified MCLK code and doc --- libraries/I2S/README.md | 115 +++++++++++++++++++++++++------------- libraries/I2S/src/I2S.cpp | 79 ++++++++++++++------------ libraries/I2S/src/I2S.h | 34 +++++++---- 3 files changed, 142 insertions(+), 86 deletions(-) diff --git a/libraries/I2S/README.md b/libraries/I2S/README.md index 9becd2fb925..32f901eaa5a 100644 --- a/libraries/I2S/README.md +++ b/libraries/I2S/README.md @@ -1,15 +1,29 @@ # I2S / IIS / Inter-IC Sound +Inter-IC Sound bus is designed for digital transmission of audio data. + +The bus consists of the following signals: + + - SCK - Serial Clock signal. + - WS - Word Select - switching between Left and Right channel. + - SD - Serial Data - In Simplex mode this is multiplexed data - depending on function calls this is either input or output. + - SD OUT - In duplex mode this line is only for outgouing data (TX). + - SD IN - In duplex mode this line is only for incoming data (RX). + - MCLK - Master Clock signal - runs on higher frequency than SCK and for most cases is not needed. + This library is based on Arduino I2S library (see doc [here](https://docs.arduino.cc/learn/built-in-libraries/i2s)) + This library mimics the behavior and extends possibilities with ESP-specific functionalities. -The main differences are: -Property Arduino ESP32 extends -bits per sample 8,16,32 24 -data channels 1 (simplex) 2(duplex) -modes Philips PDM, ADC/DAC -I2S modules 1 2 (only for ESP32, other SoC have also only 1) -pins fixed configurable +**The main differences are:** + +| Property | Arduino | ESP32 extends | +| ---------------- | ----------- | ---------------------------------------------- | +| Bits per sample | 8,16,32 | 24 | +| data channels | 1 (simplex) | 2(duplex) | +| modes | Philips | PDM, ADC/DAC | +| I2S modules | 1 | 2 (only for ESP32, other SoC have also only 1) | +| Pins | Fixed | configurable | In this document you will find overview for this version of library and description of class functions. @@ -28,6 +42,21 @@ The function `setDMABufferFrameSize` allows you to change the size in frames (se On Arduino and most ESPs you can use object `I2S` to use the I2S module. on ESP32 and ESP32-S3 you have the option to use addititonal object `I2S_1`. I2S module functionality on each SoC differs, please refer to the following table. More info can be found in [IDF documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html?highlight=dac#overview-of-all-modes). +## Modes (i2s_mode_t) +.. note:: (Arduino MKRZero) First transmitted sample is in Right channel (WS=1). If your audio has swapped left and right channel try feed single zero-value sample before the data stream. + + * **I2S_PHILIPS_MODE** Most common mode, FS signal spans across whole channel period. MSB is transmitted first and data starts SCK falling edge 1 SCK period after WS change. WS 0 = Left channel, 1 = Right channel. See more on [Wikipedia](https://en.wikipedia.org/wiki/I%C2%B2S) + * **I2S_RIGHT_JUSTIFIED_MODE** Does not work on MKRZero, opened [issue](https://github.com/arduino/ArduinoCore-samd/issues/682). Should be expected to the ooposite of `I2S_LEFT_JUSTIFIED_MODE`. + * **I2S_LEFT_JUSTIFIED_MODE** Input data belonging to righ channel are ignored and data belonging to left channel are transmitted in both channels. Example: `int16_t sample[] = {0x0001, 0x0002, 0x0003, 0x0004}` samples with value `0x0001` and `0x0003` belong to the right channel and will be ignored. Samples with value `0x0002` and `0x0004` will be transmitted as follows: Right channel (WS=1) `0x0002` followd by Left channel )WS=0) `0x0004`. this pattern repeats. + * **ADC_DAC_MODE** Outputting and inputting raw analog signal - see [example ADCPlotter](https://github.com/espressif/arduino-esp32/blob/master/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino). Can only be initialized with data pins set to 25 a 26. + * **PDM_STEREO_MODE** Pulse Density Modulation is basically an over-sampled 1-bit signal. Not to be confused with PCM. (ESP32-C3 supports only Transmit mode - read attempts will time-out) + * **PDM_MONO_MODE** Same as previous but transmits only 1 channel. TODO explain timing diagram and data interpretation. + +.. note:: First 2 samples in a stream are zero value, this will not affect audio quality. + +.. note:: Some ESP32 SoCs support PCM, TDM and LCD/Camera modes, however support for those modes is out of scope in this simplified library. If you need to use those mode you will need to use [IDF I2S driver](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html). + + ### Overview of I2S Modes: | SoC | Philips | ADC/DAC | PDM TX | PDM RX | | -------- | ----------- | ------- | ------ | ------ | @@ -37,20 +66,29 @@ I2S module functionality on each SoC differs, please refer to the following tabl | ESP32-S3 | I2S + I2S_1 | N/A | I2S | I2S | ## Pins -ESP I2S has fully configurable pins. There is a group of setter and getter functions for each pin separately and one setter for all pins at once (detailed description below). Calling the setter will take effect immediately and does not need driver restart. -Default pins are setup as follows: -### Common I2S object: -| MCLK | SCK | WS | SD(OUT) | SDIN | SoC | -| ---- | --- | -- | ------- | ---- |:---------------------------- | -| 32 | 19 | 21 | 22 | 23 | ESP32 | -| 1 | 19 | 21 | 4 | 5 | ESP32-C3, ESP32-S2, ESP32-S3 | +ESP I2S has fully configurable pins. There is a group of setter and getter functions for each pin separately, two setters for all pins at once (detailed description below) and one un-setter for MCLK. Calling the setter (or un-setter) will take effect immediately and does not need driver restart. + +The MCLK pin is usually not needed and for most cases can be ignored. By default the MCLK pin is not attached to any GPIO. To use the MCLK pin it is has to be explicitly configured using `setMclkPin()`, or by providing last parameter to function `setAllPins()`. The MCLK pin can be attached only to few specific pins, the suggested pin is specified by `PIN_I2S_MCLK` constant (each SoC may have different GPIO number). + +### Default pins for SoCs and I2S modules: +**I2S object:** + +| SoC | SCK | WS | SD(OUT) | SDIN | MCLK | +|:---------------------------- |:---:|:--:|:-------:|:----:|:----:| +| ESP32 | 19 | 21 | 22 | 23 | 0 | +| ESP32-C3, ESP32-S2, ESP32-S3 | 19 | 21 | 4 | 5 | 0 | + + +**I2S_1 object:** +| SoC | SCK | WS | SD(OUT) | SDIN | MCLK | +|:-------- |:---:|:--:|:-------:|:----:|:----:| +| ESP32 | 18 | 22 | 25 | 26 | 1 | +| ESP32-S3 | 36 | 37 | 39 | 40 | 35 | -### I2S_1 object: -| MCLK | SCK | WS | SD(OUT) | SDIN | SoC | -| ---- | --- | -- | ------- | ---- |:-------- | -| 33 | 18 | 22 | 25 | 26 | ESP32 | -| 35 | 36 | 37 | 39 | 40 | ESP32-S3 | +**ADC / DAC pin limitation** + +Unlike other modes, the ADC can only be used on 2 specific pins: 25 and 26. Attempt to initialize I2S in `ADC_DAC_MODE` with any than allowed data pins will result in failure. ## Master / Slave[ ](https://en.wiktionary.org/wiki/slave#Etymology) modes There are two versions of initializer function `begin` - one initializes in master mode and the other one in slave mode (see functions below for details). @@ -66,19 +104,6 @@ For backward compatibility with Arduino we are using DataPin for multiplexed dat The default mode is simplex and the shared data pin switches function upon calling `read` or `write` same as Arduino. If you wish to use duplex mode call `setDuplex();` this will change the pin setup to use both data lines. If you want to switch back ti simplex call `setSimplex();` and if you need to get current state call `isDuplex();` (detailed function description below). -## Modes (i2s_mode_t) -.. note:: (Arduino MKRZero) First transmitted sample is in Right channel (WS=1). If your audio has swapped left and right channel try feed single zero-value sample before the data stream. - - * **I2S_PHILIPS_MODE** Most common mode, FS signal spans across whole channel period. MSB is transmitted first and data starts SCK falling edge 1 SCK period after WS change. WS 0 = Left channel, 1 = Right channel. See more on [Wikipedia](https://en.wikipedia.org/wiki/I%C2%B2S) - * **I2S_RIGHT_JUSTIFIED_MODE** Does not work on MKRZero, opened [issue](https://github.com/arduino/ArduinoCore-samd/issues/682). Should be expected to the ooposite of `I2S_LEFT_JUSTIFIED_MODE`. - * **I2S_LEFT_JUSTIFIED_MODE** Input data belonging to righ channel are ignored and data belonging to left channel are transmitted in both channels. Example: `int16_t sample[] = {0x0001, 0x0002, 0x0003, 0x0004}` samples with value `0x0001` and `0x0003` belong to the right channel and will be ignored. Samples with value `0x0002` and `0x0004` will be transmitted as follows: Right channel (WS=1) `0x0002` followd by Left channel )WS=0) `0x0004`. this pattern repeats. - * **ADC_DAC_MODE** Outputting and inputting raw analog signal - see [example ADCPlotter](https://github.com/espressif/arduino-esp32/blob/master/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino). - * **PDM_STEREO_MODE** Pulse Density Modulation is basically an over-sampled 1-bit signal. Not to be confused with PCM. (ESP32-C3 supports only Transmit mode - read attempts will time-out) - * **PDM_MONO_MODE** Same as previous but transmits only 1 channel. TODO explain timing diagram and data interpretation. - -.. note:: First 2 samples in a stream are zero value, this will not affect audio quality. - -.. note:: Some ESP32 SoCs support PCM, TDM and LCD/Camera modes, however support for those modes is out of scope in this simplified library. If you need to use those mode you will need to use [IDF I2S driver](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html). ## Buffers @@ -159,22 +184,25 @@ Initializes IDF I2S driver, creates ring buffers and callback handler task. *Change pin setup for each pin separately.* -Can be called only on initialized object (after `begin()`). +Can be called on both uninitialized and initialized object (before and after `begin`). The change takes effect immediately and does not need driver restart. +Note: You can use value `-1` for default value. (Default for `MCLK` is detached state). + **Parameter:** -* **int pin** number of GPIO which should be used for the requested pin setup +* **int pin** number of GPIO which should be used for the requested pin setup. Any negative value will set default pin number defined in PIN_I2S_X or PIN_I2S1_X. **Returns:** 1 on success; 0 on error **Function list:** -* **int setMclkPin(int sckPin)** Set Master Clock pin + * **int setSckPin(int sckPin)** Set Clock pin * **int setFsPin(int fsPin)** Set Frame Sync (Word Select) pin * **int setDataPin(int sdPin)** Set shared Data pin for simplex mode * **int setDataOutPin(int outSdPin)** Set Data Output pin for duplex mode * **int setDataInPin(int inSdPin)** Set Data Input pin for duplex mode +* **int setMclkPin(int sckPin)** Set Master Clock pin (by default not used-no pin attached) *** #### int setAllPins() @@ -183,24 +211,35 @@ The change takes effect immediately and does not need driver restart. Can be called only on initialized object (after `begin()`). The change takes effect immediately and does not need driver restart. +The `MCLK` pin will be un-set - i.e. detached from any GPIO. **Returns:** 1 on success; 0 on error *** -#### int setAllPins(int mclkPin, int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin) +#### int setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin, int mclkPin=-1) *Change pin setup for all pins at one call.* Can be called only on initialized object (after `begin()`). The change takes effect immediately and does not need driver restart. +Note: You can use value `-1` for default value. (Default for `MCLK` is detached state). **Parameters:** -* **int mclkPin** Clock pin * **int sckPin** Clock pin * **int fsPin** Frame Sync (Word Select) pin * **int sdPin** Shared Data pin for simplex mode * **int outSdPin** Data Output pin for duplex mode * **int inSdPin** Data Input pin for duplex mode +* **int mclkPin** Master Clock pin - this parameter has default value `-1` i.e. detached state (this parameter can be omitted in function call) + +**Returns:** 1 on success; 0 on error + +*** +#### int unSetMclkPin() +*Unset MCLK pin making it available for other use* + +Can be called on both uninitialized and initialized object (before and after begin). +The change takes effect immediately and does not need driver restart. **Returns:** 1 on success; 0 on error *** @@ -212,12 +251,12 @@ The change takes effect immediately and does not need driver restart. **Function list:** -* **int getMclkPin()** Get Master Clock pin * **int getSckPin()** Get Clock pin * **int getFsPin()** Get Frame Sync (Word Select) pin * **int getDataPin()** Get shared Data pin for simplex mode * **int getDataOutPin()** Get Data Output pin for duplex mode * **int getDataInPin()** Get Data Input pin for duplex mode +* **int getMclkPin()** Get Master Clock pin *** #### int setDuplex() diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index 04d6ad6d5d9..c7462d780be 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -31,11 +31,11 @@ I2SClass::I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, uint8_t sckPin, uint8_t fsPin) : _deviceIndex(deviceIndex), #if SOC_I2S_NUM > 1 - _mclkPin(deviceIndex == 0 ? PIN_I2S_MCLK : PIN_I2S1_MCLK), // input data pin + _mclkPin(I2S_PIN_NO_CHANGE), // By default the MCLK pin is not assigned _inSdPin(deviceIndex == 0 ? PIN_I2S_SD_IN : PIN_I2S1_SD_IN), // input data pin _outSdPin(deviceIndex == 0 ? PIN_I2S_SD : PIN_I2S1_SD), // output data pin #else - _mclkPin(PIN_I2S_MCLK), // input data pin + _mclkPin(I2S_PIN_NO_CHANGE), // By default the MCLK pin is not assigned _inSdPin(PIN_I2S_SD_IN), // input data pin _outSdPin(PIN_I2S_SD), // output data pin #endif @@ -387,10 +387,7 @@ void I2SClass::_setMclkPin(int mclkPin){ if(mclkPin >= 0){ _mclkPin = mclkPin; }else{ - _mclkPin = PIN_I2S_MCLK; - #if SOC_I2S_NUM > 1 - if(_deviceIndex==1){_mclkPin = PIN_I2S1_MCLK;} - #endif + _mclkPin = I2S_PIN_NO_CHANGE; } if(!_give_mux()){ return; /* ERR */ } } @@ -461,14 +458,6 @@ void I2SClass::_setDataOutPin(int outSdPin){ if(!_give_mux()){ return; /* ERR */ } } -int I2SClass::setMclkPin(int mclkPin){ - if(!_take_mux()){ return 0; /* ERR */ } - _setMclkPin(mclkPin); - int ret = _applyPinSetting(); - if(!_give_mux()){ return 0; /* ERR */ } - return ret; -} - int I2SClass::setSckPin(int sckPin){ if(!_take_mux()){ return 0; /* ERR */ } _setSckPin(sckPin); @@ -510,94 +499,110 @@ int I2SClass::setDataOutPin(int outSdPin){ return ret; } +int I2SClass::setMclkPin(int mclkPin){ + if(!_take_mux()){ return 0; /* ERR */ } + _setMclkPin(mclkPin); + int ret = _applyPinSetting(); + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + int I2SClass::setAllPins(){ if(!_take_mux()){ return 0; /* ERR */ } int ret = 0; if(_deviceIndex==0){ - ret = setAllPins(PIN_I2S_MCLK, PIN_I2S_SCK, PIN_I2S_FS, PIN_I2S_SD, PIN_I2S_SD_OUT, PIN_I2S_SD_IN); + ret = setAllPins(PIN_I2S_SCK, PIN_I2S_FS, PIN_I2S_SD, PIN_I2S_SD_OUT, PIN_I2S_SD_IN, I2S_PIN_NO_CHANGE); } #if SOC_I2S_NUM > 1 if(_deviceIndex==1){ - ret = setAllPins(PIN_I2S1_MCLK, PIN_I2S1_SCK, PIN_I2S1_FS, PIN_I2S1_SD, PIN_I2S1_SD_OUT, PIN_I2S1_SD_IN); + ret = setAllPins(PIN_I2S1_SCK, PIN_I2S1_FS, PIN_I2S1_SD, PIN_I2S1_SD_OUT, PIN_I2S1_SD_IN, I2S_PIN_NO_CHANGE); } #endif if(!_give_mux()){ return 0; /* ERR */ } return ret; } -int I2SClass::setAllPins(int mclkPin, int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin){ +int I2SClass::setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin, int mclkPin /*=-1*/ ){ if(!_take_mux()){ return 0; /* ERR */ } - _setMclkPin(mclkPin); _setFsPin(fsPin); _setDataPin(sdPin); _setDataOutPin(outSdPin); _setDataInPin(inSdPin); + _setMclkPin(mclkPin); int ret = _applyPinSetting(); if(!_give_mux()){ return 0; /* ERR */ } return ret; } -int I2SClass::setDuplex(){ +int I2SClass::unSetMclkPin(){ if(!_take_mux()){ return 0; /* ERR */ } - _state = I2S_STATE_DUPLEX; + _setMclkPin(I2S_PIN_NO_CHANGE); int ret = _applyPinSetting(); if(!_give_mux()){ return 0; /* ERR */ } return ret; } -int I2SClass::setSimplex(){ +int I2SClass::getSckPin(){ if(!_take_mux()){ return 0; /* ERR */ } - _state = I2S_STATE_IDLE; - int ret = _applyPinSetting(); + int ret = _sckPin; if(!_give_mux()){ return 0; /* ERR */ } return ret; } -int I2SClass::isDuplex(){ +int I2SClass::getFsPin(){ if(!_take_mux()){ return 0; /* ERR */ } - int ret = (int)(_state == I2S_STATE_DUPLEX); + int ret = _fsPin; if(!_give_mux()){ return 0; /* ERR */ } return ret; } -int I2SClass::getMclkPin(){ +int I2SClass::getDataPin(){ if(!_take_mux()){ return 0; /* ERR */ } - int ret = _mclkPin; + int ret = _sdPin; if(!_give_mux()){ return 0; /* ERR */ } return ret; } -int I2SClass::getSckPin(){ +int I2SClass::getDataInPin(){ if(!_take_mux()){ return 0; /* ERR */ } - int ret = _sckPin; + int ret = _inSdPin; if(!_give_mux()){ return 0; /* ERR */ } return ret; } -int I2SClass::getFsPin(){ +int I2SClass::getDataOutPin(){ if(!_take_mux()){ return 0; /* ERR */ } - int ret = _fsPin; + int ret = _outSdPin; if(!_give_mux()){ return 0; /* ERR */ } return ret; } -int I2SClass::getDataPin(){ +int I2SClass::getMclkPin(){ if(!_take_mux()){ return 0; /* ERR */ } - int ret = _sdPin; + int ret = _mclkPin; if(!_give_mux()){ return 0; /* ERR */ } return ret; } -int I2SClass::getDataInPin(){ +int I2SClass::setDuplex(){ if(!_take_mux()){ return 0; /* ERR */ } - int ret = _inSdPin; + _state = I2S_STATE_DUPLEX; + int ret = _applyPinSetting(); if(!_give_mux()){ return 0; /* ERR */ } return ret; } -int I2SClass::getDataOutPin(){ +int I2SClass::setSimplex(){ if(!_take_mux()){ return 0; /* ERR */ } - int ret = _outSdPin; + _state = I2S_STATE_IDLE; + int ret = _applyPinSetting(); + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +int I2SClass::isDuplex(){ + if(!_take_mux()){ return 0; /* ERR */ } + int ret = (int)(_state == I2S_STATE_DUPLEX); if(!_give_mux()){ return 0; /* ERR */ } return ret; } diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index d61ea56d5c9..b1493c563d3 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -192,6 +192,7 @@ class I2SClass : public Stream * Parameters: * int mode Operation mode (Phillips, Left/Right Justified, ADC+DAC,PDM) see i2s_mode_t for exact enumerations * int bitsPerSample Number of bits per one sample (one channel). Possible values are 8,16,24,32 + * Note: You can use value -1 for default value. (Default for MCLK is detached state) * Returns: 1 on success; 0 on error */ int begin(int mode, int bitsPerSample); @@ -203,41 +204,52 @@ class I2SClass : public Stream /* * Change pin setup for each pin separately. - * Can be called only on initialized object (after begin). + * Can be called on both uninitialized and initialized object (before and after begin). * The change takes effect immediately and does not need driver restart. * Parameter: int pin number of GPIO which should be used for the requested pin setup * any negative value will set default pin number defined in PIN_I2S_X or PIN_I2S1_X * Returns: 1 on success; 0 on error + * Note: You can use value -1 for default value. (Default for MCLK is detached state) */ - int setMclkPin(int sckPin); // Set Master Clock pin int setSckPin(int sckPin); // Set Clock pin int setFsPin(int fsPin); // Set Frame Sync (Word Select) pin int setDataPin(int sdPin); // Set shared Data pin for simplex mode int setDataOutPin(int outSdPin); // Set Data Output pin for duplex mode int setDataInPin(int inSdPin); // Set Data Input pin for duplex mode + int setMclkPin(int sckPin); // Set Master Clock pin /* - * Change pin setup for all pins at one call using default values set constants in I2S.h - * Can be called only on initialized object (after begin) + * Change pin setup for all pins (except MCLK) at one call using default values set constants in I2S.h + * Can be called on both uninitialized and initialized object (before and after begin). * The change takes effect immediately and does not need driver restart. + * The MCLK pin will be un-set - i.e. detached from any GPIO. * Returns: 1 on success; 0 on error */ int setAllPins(); /* * Change pin setup for all pins at one call. - * Can be called only on initialized object (after begin). + * Can be called on both uninitialized and initialized object (before and after begin). * The change takes effect immediately and does not need driver restart. * Parameters: - * int mclkPin Master Clock pin - * int sckPin Clock pin - * int fsPin Frame Sync (Word Select) pin - * int sdPin Shared Data pin for simplex mode + * int sckPin Clock pin + * int fsPin Frame Sync (Word Select) pin + * int sdPin Shared Data pin for simplex mode * int outSdPin Data Output pin for duplex mode - * int inSdPin Data Input pin for duplex mode + * int inSdPin Data Input pin for duplex mode + * int mclkPin Master Clock pin + * Returns: 1 on success; 0 on error + * Note: You can use value -1 for default value. (Default for MCLK is detached state) + */ + int setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin, int mclkPin=-1); + + /* + * Unset MCLK pin making it available for other use + * Can be called on both uninitialized and initialized object (before and after begin). + * The change takes effect immediately and does not need driver restart. * Returns: 1 on success; 0 on error */ - int setAllPins(int mclkPin, int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin); + int unSetMclkPin(); /* * Get current pin GPIO number From 77a876e5cb3eb23493077a533d1799998b8e02b4 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Thu, 8 Dec 2022 13:08:08 +0100 Subject: [PATCH 39/46] Added note to legacy example --- libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino b/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino index e3b40d2c8d5..47cc732f3da 100644 --- a/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino +++ b/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino @@ -1,4 +1,7 @@ /* + NOTE: This is a legacy example using IDF I2S driver. There is an I2S library using Arduino API. + You can find it on relative address `../../../../I2S/` or absolute address `arduino-esp32/libraries/I2S/` + 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. From 653c995734a8c8e6e26739d2240f5dbc191e1233 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Thu, 8 Dec 2022 15:26:41 +0100 Subject: [PATCH 40/46] Fixed input --- libraries/I2S/README.md | 5 ++++- libraries/I2S/src/I2S.cpp | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/libraries/I2S/README.md b/libraries/I2S/README.md index 32f901eaa5a..3de2ac68457 100644 --- a/libraries/I2S/README.md +++ b/libraries/I2S/README.md @@ -123,7 +123,10 @@ There are two ring buffers - one for transmit line and one for receiving line. The size of ring buffer is equal to the sum of DMA buffers: `ring_buffer_bytes_size = (CHANNEL_NUMBER * (bits_per_sample/8)) * DMABufferFrameSize * _I2S_DMA_BUFFER_COUNT`. Maximum size of those buffers can be read with functions `getRingBufferSampleSize()` and `getRingBufferByteSize()`. -To get number of free Bytes in the transmitt buffer, that can be actually written is performed with function `availableForWrite()`. To get number of Bytes ready to be read from receiving ring buffer use function `available()`. Changing the size of ring buffer is not possible directly - it is automatically calculated during driver installation based on DMA Buffer size. +To get number of free Bytes in the transmit buffer, that can be actually written is performed with function `availableForWrite()`. To get number of Bytes ready to be read from receiving ring buffer use function `available()`. Changing the size of ring buffer is not possible directly - it is automatically calculated during driver installation based on DMA Buffer size. + +### Known issues +InputSerialPlotter often freezes for a short while - this is caused by the fact that the serial output and the incoming data are not synchronized - portion of the input buffer is written to serial followed by few milliseconds until input data are buffered and ready to be read by the loop. ## Functions diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index c7462d780be..975ac51ea8e 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -660,7 +660,7 @@ int I2SClass::available(){ } union i2s_sample_t { - uint8_t b8; + int8_t b8; int16_t b16; int32_t b32; }; @@ -710,7 +710,7 @@ int I2SClass::read(void* buffer, size_t size){ _peek_buff_valid = false; requested_size -= _bitsPerSample/8; } - tmp_buffer = xRingbufferReceiveUpTo(_input_ring_buffer, &item_size, pdMS_TO_TICKS(1000), requested_size); + tmp_buffer = xRingbufferReceiveUpTo(_input_ring_buffer, &item_size, pdMS_TO_TICKS(0), requested_size); if(tmp_buffer != NULL){ memcpy(buffer, tmp_buffer, item_size); #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) From 83f53728b67f3ed1baaadec692b0968f495a1fec Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Fri, 9 Dec 2022 22:07:16 +0100 Subject: [PATCH 41/46] changed default input pin to match with ADC support --- libraries/I2S/examples/ADCPlotter/ADCPlotter.ino | 6 +++--- .../InputSerialPlotter/InputSerialPlotter.ino | 8 ++++---- libraries/I2S/examples/SimpleTone/SimpleTone.ino | 9 ++------- libraries/I2S/src/I2S.h | 16 ++++++++-------- 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino b/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino index 5f3bd93ca9d..0131ff80efd 100644 --- a/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino +++ b/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino @@ -49,10 +49,10 @@ Second option to measure voltage on trimmer / potentiometer has following connec 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. + When you see in console line like this: "Connecting........_____.....__" press "Boot" button on your board. 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. + If this fails, try the pressing the "Boot" button on board right after pressing upload button in IDE. + You can also try reconnect the USB cable. Reconnecting may change the serial port on your computer. 3. Open plotter Tools -> Serial Plotter Enjoy diff --git a/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino b/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino index 28819fa3f73..4fd3a946a14 100644 --- a/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino +++ b/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino @@ -13,7 +13,7 @@ | 3.3V | 3.3V | 3.3V | 3.3V | 3.3V | 3.3V | | SCK | 1 | 2 | A3 | 18 | 18 | | FS | 0 | 3 | A2 | 19 | 19 | - | SD | 9 | A6 | 4 | 22 | 5 | + | SD | 9 | A6 | 4 | 21 | 4 | created 17 November 2016 by Sandeep Mistry @@ -38,10 +38,10 @@ void setup() { } void loop() { - // read a sample - int sample = I2S.read(); + int sample; + sample = I2S.read(); // read a sample - if (sample && sample != -1 && sample != 1) { + if(sample){ Serial.println(sample); } } diff --git a/libraries/I2S/examples/SimpleTone/SimpleTone.ino b/libraries/I2S/examples/SimpleTone/SimpleTone.ino index e35018bae32..c98d75d4468 100644 --- a/libraries/I2S/examples/SimpleTone/SimpleTone.ino +++ b/libraries/I2S/examples/SimpleTone/SimpleTone.ino @@ -68,13 +68,8 @@ void loop() { 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); - } + I2S.write(sample); // Right channel + I2S.write(sample); // Left channel // increment the counter for the next sample count++; diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index b1493c563d3..0c3a045fd36 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -29,7 +29,7 @@ namespace esp_i2s { // Default pins //I2S0 is available for all ESP32 SoCs //MCLK SCK WS SD(OUT) SDIN -// 0 18 19 21 22 ESP32 +// 0 18 19 21 34 ESP32 // 0 18 19 4 5 ESP32-x (C3,S2,S3) #ifndef PIN_I2S_MCLK @@ -66,7 +66,7 @@ namespace esp_i2s { #ifndef PIN_I2S_SD_IN #if CONFIG_IDF_TARGET_ESP32 - #define PIN_I2S_SD_IN GPIO_NUM_22 + #define PIN_I2S_SD_IN GPIO_NUM_34 #else #define PIN_I2S_SD_IN GPIO_NUM_5 #endif @@ -77,30 +77,30 @@ namespace esp_i2s { #if CONFIG_IDF_TARGET_ESP32 // ESP32 pins // MCLK SCK WS SD(OUT) SDIN - // 1 23 25 26 27 + // 1 22 23 27 35 #ifndef PIN_I2S1_MCLK #define PIN_I2S1_MCLK GPIO_NUM_1 #endif #ifndef PIN_I2S1_SCK - #define PIN_I2S1_SCK GPIO_NUM_23 + #define PIN_I2S1_SCK GPIO_NUM_22 #endif #ifndef PIN_I2S1_FS - #define PIN_I2S1_FS GPIO_NUM_25 + #define PIN_I2S1_FS GPIO_NUM_23 #endif #ifndef PIN_I2S1_SD - #define PIN_I2S1_SD GPIO_NUM_26 + #define PIN_I2S1_SD GPIO_NUM_27 #endif #ifndef PIN_I2S1_SD_OUT - #define PIN_I2S1_SD_OUT GPIO_NUM_26 + #define PIN_I2S1_SD_OUT GPIO_NUM_27 #endif #ifndef PIN_I2S1_SD_IN - #define PIN_I2S1_SD_IN GPIO_NUM_27 + #define PIN_I2S1_SD_IN GPIO_NUM_35 #endif #endif From 4af7fa322ca175b183bfefad7bc77fba623be562 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Thu, 15 Dec 2022 17:26:45 +0100 Subject: [PATCH 42/46] Fixed DAC --- libraries/I2S/src/I2S.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index 975ac51ea8e..d2c2a9e8d2e 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -172,6 +172,18 @@ int I2SClass::_installDriver(){ i2s_config.fixed_mclk = 8*_sampleRate; } + if(_mode == ADC_DAC_MODE){ + i2s_config.communication_format = (esp_i2s::i2s_comm_format_t)(esp_i2s::I2S_COMM_FORMAT_STAND_MSB); + } + + if(_mode == I2S_RIGHT_JUSTIFIED_MODE){ + i2s_config.communication_format = (esp_i2s::i2s_comm_format_t)(esp_i2s::I2S_CHANNEL_FMT_ALL_RIGHT); // Load right channel data in both two channels + } + + if(_mode == I2S_LEFT_JUSTIFIED_MODE){ + i2s_config.communication_format = (esp_i2s::i2s_comm_format_t)(esp_i2s::I2S_CHANNEL_FMT_ALL_LEFT); // Load left channel data in both two channels + } + // 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)){ // Double the DMA buffer size From 4d174af8cfb00f0c2326eb8c352aabdd5f5d929f Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Fri, 16 Dec 2022 10:13:01 +0100 Subject: [PATCH 43/46] Added setDMABufferSampleSize function and made few changes in README --- libraries/I2S/README.md | 113 +++++++++++++++++++++++++++----------- libraries/I2S/src/I2S.cpp | 18 +++++- libraries/I2S/src/I2S.h | 9 ++- 3 files changed, 106 insertions(+), 34 deletions(-) diff --git a/libraries/I2S/README.md b/libraries/I2S/README.md index 3de2ac68457..92cf5a4cca7 100644 --- a/libraries/I2S/README.md +++ b/libraries/I2S/README.md @@ -2,14 +2,6 @@ Inter-IC Sound bus is designed for digital transmission of audio data. -The bus consists of the following signals: - - - SCK - Serial Clock signal. - - WS - Word Select - switching between Left and Right channel. - - SD - Serial Data - In Simplex mode this is multiplexed data - depending on function calls this is either input or output. - - SD OUT - In duplex mode this line is only for outgouing data (TX). - - SD IN - In duplex mode this line is only for incoming data (RX). - - MCLK - Master Clock signal - runs on higher frequency than SCK and for most cases is not needed. This library is based on Arduino I2S library (see doc [here](https://docs.arduino.cc/learn/built-in-libraries/i2s)) @@ -17,27 +9,47 @@ This library mimics the behavior and extends possibilities with ESP-specific fun **The main differences are:** -| Property | Arduino | ESP32 extends | -| ---------------- | ----------- | ---------------------------------------------- | -| Bits per sample | 8,16,32 | 24 | -| data channels | 1 (simplex) | 2(duplex) | -| modes | Philips | PDM, ADC/DAC | -| I2S modules | 1 | 2 (only for ESP32, other SoC have also only 1) | -| Pins | Fixed | configurable | +| Property | Arduino | ESP32 extends | +| ---------------- | ----------- | -------------------------- | +| Bits per sample | 8,16,32 | 24 | +| data channels | 1 (simplex) | 2 (duplex) | +| modes | Philips | PDM, ADC/DAC | +| I2S modules | 1 | ESP32 (only) has 2 modules | +| Pins | Fixed | configurable | + +**In this document you will find overview for this version of library and description of class functions.** + +**The bus uses following signals:** + + - **SCK** - Serial Clock signal. + - **WS** - Word Select - switching between Left and Right channel. + - **SD** - Serial Data - In Simplex mode this is multiplexed data - depending on function calls this is either input or output. + - **SD OUT** - In duplex mode this line is only for outgouing data (TX). + - **SD IN** - In duplex mode this line is only for incoming data (RX). + - **MCLK** - Master Clock signal - runs on higher frequency than SCK and for most cases is not needed. -In this document you will find overview for this version of library and description of class functions. +**The library uses following data structures:** -Since all ESP32 code runs on FreeRTOS (operating system handling execution time on CPUs and allowing pseudo-parallel execution) this library uses 1 task which monitors messages from underlying IDF I2S driver which sends them whenever it sends sample from buffer to data line, or when it fills buffer with samples received from data line. -Since Arduino utilizes single sample writes and reads heavily, which would case unbearable lags there had to be implemented another layer of ring buffers (one for transmitted data and one for received data). The user of this library writes, or reads from this buffer via functions `write` and `read` (exact variants are described below). -The task, upon receiving the message from IDF I2S driver performs either flush data from transmit ring buffer to IDF I2S driver or received data from IDF I2S driver to receive ring buffer. + - **Bits per sample (bps)** - determines how many bits is used for each sample (see line below). More bits means greater audio quality, however it is more demanding on memory and timing. The allowed values are `8`, `16`, `24` and `32` i.e. `1`, `2`, `3` or `4` Bytes per sample. + - **Sample** - single channel data. The size of sample depends on `bits per sample` the size of single sample in bytes is simply calculated by `bits per sample / 8`. + - **Frame** - sum of samples across all channels. This library is currently hard-coded to dual channel / stereo, i.e. the number of channels is constantly `2`. The size of frame in Bytes can be calculated as `sample size * number of channels = (bits per sample / 8) * number of channels = (bits per sample / 8) * 2`. For example when `bps=16` the frame has `(16 / 8) * 2 = 4 B`. + - **DMA buffer** - is used in underlying IDF driver and its size is in frames. The Byte size of single DMA buffer is `DMA buf len * frame size = DMA buf len * sample size * number of channels = DMA buf len * (bits per sample / 8) * number of channels = DMA buf len * (bits per sample / 8) * 2`. By default the `DMA buf len = 128` therefore the Byte size is `128 * (bits per sample / 8) * 2`. For example when set `bps=16` then the Byte size is `128 * (16 / 8) * 2 = 512 B`. The functions `setDMABufferFrameSize` and `setDMABufferSampleSize` can be used to change the size (see function section for more details). + - **DMA buffer array** - There is more than one DMA buffer. The number of DMA buffers is defined in constant `_I2S_DMA_BUFFER_COUNT` by default to value `2`. If you want to change it modify file `I2S.cpp`. The Byte size of all DMA buffer can be calculated as `_I2S_DMA_BUFFER_COUNT * DMA buf len * (bits per sample / 8) * 2`. Extending the previous example (`bps=16`, default `dma buf len=128`): `2* 128 *(16 / 8) * 2 = 1024 B` + - **Ring buffer** - is used as an abstraction layer above the DMA buffers. There is one ring buffer for transmitting size and second one for receiving side. The size of a ring buffer will be always automatically set to the sum of all DMA buffers. The size of a single ring buffer in Bytes can be calculated as `DMA buffer len * _I2S_DMA_BUFFER_COUNT * (bits per sample / 8) * number of channels`. + +**Usage of underlying IDF driver** + +Since all ESP32 code runs on FreeRTOS (operating system handling execution time on CPUs and allowing pseudo-parallel execution) this library uses 1 task which monitors messages from underlying IDF I2S driver which sends them whenever it sends contents of single DMA buffer to output (TX) data line, or when it fills a DMA buffer with samples received on input (RX) data line. +Since Arduino utilizes single sample writes and reads heavily, which would cause unbearable lags, there had to be implemented another layer of ring buffers (one for transmitted data and one for received data). The user of this library writes, or reads from this buffer via functions `write` and `read` (exact variants are described in functions section). +The task, upon receiving the message from IDF I2S driver performs either write data from transmit ring buffer into IDFs I2S driver (into TX DMA buffer) or reads received data from IDF I2S driver (from RX DMA buffer) to the receive ring buffer. Usage of the ring buffers also enabled implementation of functions `available`, `availableForWrite`, `peak` and single sample write/read. -The IDF I2S driver uses multiple sets of buffer of the same size. The size of ring buffer equals to sum of all those buffers. By default the number of IDF I2S buffers is 2. Usually there should be no need to change this, however if you choose to change it, change the constant `_I2S_DMA_BUFFER_COUNT` in the library, in `src/I2S.cpp`: +The IDF I2S driver uses multiple sets of buffers of the same size. The size of ring buffer equals to sum of all those buffers. By default the number of IDF I2S buffers is 2. Usually there should be no need to change this, however if you choose to change it, change the constant `_I2S_DMA_BUFFER_COUNT` in the library, in `src/I2S.cpp`: The size of IDF I2S buffer is by default 128. The unit used for this buffer is a frame. A frame means the data of all channels in a WS cycle. For example dual channel 16 bits per sample and default buffer size = 2 * 2 * 128 = 512 Bytes. From this we can get also size of the ring buffers. As stated their size is equal to the sum of IDF I2S buffers. For the example of dual channel, 16 bps the size of each ring buffer is 1024 B. -The function `setDMABufferFrameSize` allows you to change the size in frames (see detailed description below). +The functions `setDMABufferFrameSize` and `setDMABufferSampleSize` allows you to change the size in frames and samples respectively (see detailed description below). On Arduino and most ESPs you can use object `I2S` to use the I2S module. on ESP32 and ESP32-S3 you have the option to use addititonal object `I2S_1`. I2S module functionality on each SoC differs, please refer to the following table. More info can be found in [IDF documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html?highlight=dac#overview-of-all-modes). @@ -75,20 +87,30 @@ The MCLK pin is usually not needed and for most cases can be ignored. By default | SoC | SCK | WS | SD(OUT) | SDIN | MCLK | |:---------------------------- |:---:|:--:|:-------:|:----:|:----:| -| ESP32 | 19 | 21 | 22 | 23 | 0 | -| ESP32-C3, ESP32-S2, ESP32-S3 | 19 | 21 | 4 | 5 | 0 | - +| ESP32 | 18 | 19 | 21 | 34 | 0 | +| ESP32-C3, ESP32-S2, ESP32-S3 | 18 | 19 | 4 | 5 | 0 | **I2S_1 object:** | SoC | SCK | WS | SD(OUT) | SDIN | MCLK | |:-------- |:---:|:--:|:-------:|:----:|:----:| -| ESP32 | 18 | 22 | 25 | 26 | 1 | +| ESP32 | 22 | 23 | 27 | 35 | 1 | | ESP32-S3 | 36 | 37 | 39 | 40 | 35 | -**ADC / DAC pin limitation** +**DAC pin limitation** + +Unlike other modes, the ADC output is hard-wired to GPIO 25 (R) and 26 (L). DAC output is slightly limited too - please refer to the following table. Attempt to initialize I2S in `ADC_DAC_MODE` with SD IN pin set to anything other than allowed GPIO will result in failure. -Unlike other modes, the ADC can only be used on 2 specific pins: 25 and 26. Attempt to initialize I2S in `ADC_DAC_MODE` with any than allowed data pins will result in failure. +**ADC pin limitation:** + +ADC input uses internally ADC modules - usage of I2S in `ADC_DAC_MODE` may interfere with other usage of these ADC modules. Please refer to the [documentation]() for more info. + +| SoC | GPIOs supporting ADC | +|:-------- |:----------------------------:| +| ESP32 | 0, 2, 4, 12-15, 25-27, 32-39 | +| ESP32-C3 | 0-5 | +| ESP32-S2 | 1-20 | +| ESP32-S3 | 1-20 | ## Master / Slave[ ](https://en.wiktionary.org/wiki/slave#Etymology) modes There are two versions of initializer function `begin` - one initializes in master mode and the other one in slave mode (see functions below for details). @@ -114,7 +136,7 @@ The best way is to fill and read the buffers in chunks and let the CPU be used o The number of DMA buffers can be from 2 - 128 for each data line (TX & RX) this is set by constant `_I2S_DMA_BUFFER_COUNT` in `I2S.cpp`. The default value is `2` and can be changed only in code - requires recompilation. -The size of each DMA buffer is in sample frames and can be between 8 and 1024. One frame equals to number of channels (in this library always 2) multiplied by Bytes per samples (or `(bits_per_sample/8)`. The default value s `128` and can be changed changed by function `setDMABufferFrameSize()`, this automatically reinstalls the driver. The DMA buffer size can be read by functions `getDMABufferFrameSize()`, `getDMABufferSampleSize()`, and `getDMABufferByteSize()`. More info can be found in section *FUNCTIONS*. +The size of each DMA buffer is in sample frames and can be between 8 and 1024. One frame equals to number of channels (in this library always 2) multiplied by Bytes per samples (or `(bits_per_sample/8)`. The default value s `128` and can be changed changed by functions `setDMABufferFrameSize()` and `setDMABufferSampleSize()`, this automatically reinstalls the driver. The DMA buffer size can be read by functions `getDMABufferFrameSize()`, `getDMABufferSampleSize()`, and `getDMABufferByteSize()`. More info can be found in section *FUNCTIONS*. ### Ring buffers buffers Original Arduino I2S library uses extensively single-sample writes and reads - such usage would result in poor audio quality. Therefore another layer of buffers has been added - ring buffers. @@ -410,9 +432,15 @@ This function is useful when sending low amount of data, however such use will l *Change the size of DMA buffers.* The unit is number of sample frames `(CHANNEL_NUMBER * (bits_per_sample/8))` +**Parameter:** + +* **int DMABufferFrameSize** The number of frames in one DMA buffer. The value must be between 8 and 1024 (including). + +**Returns:** 1 on successful setup; 0 on error = could not install driver with new settings or could not take mutex. + The resulting Bytes size of ring buffers can be calculated: -`ring_buffer_bytes_size = (CHANNEL_NUMBER * (bits_per_sample/8)) * bufferSize * _I2S_DMA_BUFFER_COUNT` +`ring_buffer_bytes_size = (CHANNEL_NUMBER * (bits_per_sample/8)) * DMABufferFrameSize * _I2S_DMA_BUFFER_COUNT` **Example:** @@ -421,8 +449,31 @@ The resulting Bytes size of ring buffers can be calculated: * Default value of `DMABufferFrameSize` is **128** * Default value of `_I2S_DMA_BUFFER_COUNT` is **2** ``` -ring_buffer_bytes_size = (CHANNEL_NUMBER * (bits_per_sample / 8)) * DMABufferFrameSize * _I2S_DMA_BUFFER_COUNT - 1024 = ( 2 * ( 16 / 8)) * 128 * 2 +ring_buffer_bytes_size = (bits_per_sample / 8) * DMABufferFrameSize * CHANNEL_NUMBER * _I2S_DMA_BUFFER_COUNT + 1024 = ( 16 / 8) * 128 * 2 * 2 +``` +*** +#### int setDMABufferSampleSize(int DMABufferSampleSize) + +*Change the size of DMA buffers.* The unit is number of samples `bits_per_sample/8` + +This function simply calls `setDMABufferFrameSize(DMABufferSampleSize / CHANNEL_NUMBER);` + +**Parameter:** + +* **int DMABufferFrameSize** The number of samples in one DMA buffer. The value must be always even (multiple of 2) and between 16 and 2048 (including). + +**Returns:** 1 on successful setup; 0 on error = could not install driver with new settings or could not take mutex. + +**Example:** + + * This library statically set to *dual channel*, therefore `CHANNEL_NUMBER` is always **2** + * For this example let's have `bits_per_sample` set to **16** + * Same value as default can be achieved with parameter value `DMABufferSampleSize` set to **256** + * Default value of `_I2S_DMA_BUFFER_COUNT` is **2** +``` +ring_buffer_bytes_size = (bits_per_sample / 8) * DMABufferSampleSize * _I2S_DMA_BUFFER_COUNT + 1024 = ( 16 / 8) * 256 * 2 ``` *** #### int getDMABufferFrameSize() diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index d2c2a9e8d2e..65a85bb8632 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -115,6 +115,7 @@ int I2SClass::_installDriver(){ #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) if(_bitsPerSample != 16){ // ADC/DAC can only work in 16-bit sample mode log_e("(I2S#%d) ERROR invalid bps for ADC/DAC. Allowed only 16, requested %d", _deviceIndex, _bitsPerSample); + // TODO handle data transfer and allow users to use any bps 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); @@ -929,7 +930,8 @@ void I2SClass::onReceive(void(*function)(void)){ // Change buffer size. The unit is in frames. // Byte value can be calculated as follows: -// ByteSize = (bits_per_sample / 8) * number_of_channels * DMABufferFrameSize +// ByteSize = (bits_per_sample / 8) * CHANNEL_NUMBER * DMABufferFrameSize +// Note: CHANNEL_NUMBER is hard-coded to value 2 // Calling this function will automatically restart the driver, which could cause audio output gap. int I2SClass::setDMABufferFrameSize(int DMABufferFrameSize){ if(!_take_mux()){ return 0; /* ERR */ } @@ -955,6 +957,15 @@ int I2SClass::setDMABufferFrameSize(int DMABufferFrameSize){ return 0; // ERR } +// Change buffer size. The unit is in samples. +// Byte value can be calculated as follows: +// ByteSize = CHANNEL_NUMBER * DMABufferSampleSize +// Note: CHANNEL_NUMBER is hard-coded to value 2 +// Calling this function will automatically restart the driver, which could cause audio output gap. +int I2SClass::setDMABufferSampleSize(int DMABufferSampleSize){ + return setDMABufferFrameSize(DMABufferSampleSize / CHANNEL_NUMBER); +} + int I2SClass::getDMABufferFrameSize(){ if(!_take_mux()){ return 0; /* ERR */ } int ret = _i2s_dma_buffer_frame_size; @@ -1225,6 +1236,8 @@ void I2SClass::_fix_and_write(void *output, size_t size, size_t *bytes_written, break; // Do nothing } // switch +/* +// Should be taken care of by comm format: I2S_CHANNEL_FMT_ALL_RIGHT / I2S_CHANNEL_FMT_ALL_LEFT if(_mode == I2S_RIGHT_JUSTIFIED_MODE){ for(int i = 0; i < buff_size; i+=2){ buff[i] = buff[i+1]; @@ -1235,9 +1248,10 @@ void I2SClass::_fix_and_write(void *output, size_t size, size_t *bytes_written, buff[i+1] = buff[i]; } } +*/ 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 + esp_err_t ret = esp_i2s::i2s_write((esp_i2s::i2s_port_t) _deviceIndex, buff, buff_size, &_bytes_written, 0); if(ret != ESP_OK){ log_e("(I2S#%d) Error: writing data to i2s - function returned with err code %d", _deviceIndex, ret); } diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index 0c3a045fd36..61bca1d3daa 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -134,7 +134,7 @@ namespace esp_i2s { #endif #endif -#define CHANNEL_NUMBER 2 +#define CHANNEL_NUMBER (2) typedef enum { I2S_PHILIPS_MODE, // Most common I2S mode, FS signal spans across whole channel period @@ -394,6 +394,13 @@ class I2SClass : public Stream */ int setDMABufferFrameSize(int DMABufferFrameSize); + /* + * Change the size of DMA buffers. The unit is number of samples (bits_per_sample/8)) + * The resulting Bytes size of ring buffers can be calculated: + * ring_buffer_bytes_size = (CHANNEL_NUMBER * DMABufferSampleSize * _I2S_DMA_BUFFER_COUNT + */ + int setDMABufferSampleSize(int DMABufferSampleSize); + /* * Get size of single DMA buffer. * The unit is number of sample frames: Bytes size of 1 frame = (CHANNEL_NUMBER * (bits_per_sample / 8)) From 17caf842f83f2e795d4a01a4ede1408fd17a751d Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Sun, 18 Dec 2022 16:26:46 +0100 Subject: [PATCH 44/46] Lowered I2S task priority --- libraries/I2S/src/I2S.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index 65a85bb8632..5bf384ca91b 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -82,7 +82,7 @@ int I2SClass::_createCallbackTask(){ "onDmaTransferComplete", // Name of the task stack_size, // Stack size in words (void *)&_deviceIndex, // Task input parameter - 4, // Priority of the task + 1, // Priority of the task &_callbackTaskHandle // Task handle. ); if(_callbackTaskHandle == NULL){ From 3d8585385d705fc4411f1eddf38b6e2276646ca5 Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Mon, 17 Jul 2023 12:48:11 +0200 Subject: [PATCH 45/46] backup --- cores/esp32/esp32-hal-gpio.c | 2 + cores/esp32/esp32-hal-rgb-led.c | 1 + libraries/BLE/src/BLEAddress.h | 2 +- libraries/BLE/src/BLEAdvertisedDevice.h | 180 ++++++++-------- libraries/BLE/src/BLEAdvertising.cpp | 1 + libraries/BLE/src/BLEBeacon.cpp | 1 + libraries/BLE/src/BLEEddystoneURL.cpp | 38 +++- libraries/BLE/src/BLEUUID.cpp | 2 +- libraries/BLE/src/BLEValue.h | 11 +- .../I2S/examples/ADCPlotter/ADCPlotter.ino | 3 +- .../I2S/examples/DualModule/DualModule.ino | 202 ++++++++++++++---- .../I2S/examples/FullDuplex/FullDuplex.ino | 24 +-- .../InputSerialPlotter/InputSerialPlotter.ino | 12 +- libraries/I2S/src/I2S.cpp | 10 + libraries/Ticker/src/Ticker.cpp | 4 +- tests/i2s/i2s.ino | 65 ++++-- .../esp32/include/driver/include/driver/i2s.h | 10 +- 17 files changed, 380 insertions(+), 188 deletions(-) diff --git a/cores/esp32/esp32-hal-gpio.c b/cores/esp32/esp32-hal-gpio.c index f0f99db9abf..b5e17eb2d09 100644 --- a/cores/esp32/esp32-hal-gpio.c +++ b/cores/esp32/esp32-hal-gpio.c @@ -135,7 +135,9 @@ extern void ARDUINO_ISR_ATTR __pinMode(uint8_t pin, uint8_t mode) extern void ARDUINO_ISR_ATTR __digitalWrite(uint8_t pin, uint8_t val) { #ifdef RGB_BUILTIN + log_d("RGB_BUILTIN is defined"); if(pin == RGB_BUILTIN){ + log_d("and its the target"); //use RMT to set all channels on/off const uint8_t comm_val = val != 0 ? RGB_BRIGHTNESS : 0; neopixelWrite(RGB_BUILTIN, comm_val, comm_val, comm_val); diff --git a/cores/esp32/esp32-hal-rgb-led.c b/cores/esp32/esp32-hal-rgb-led.c index 61558b86ee6..c8b9b76bedb 100644 --- a/cores/esp32/esp32-hal-rgb-led.c +++ b/cores/esp32/esp32-hal-rgb-led.c @@ -2,6 +2,7 @@ void neopixelWrite(uint8_t pin, uint8_t red_val, uint8_t green_val, uint8_t blue_val){ + log_d("light it up to %d", red_val); rmt_data_t led_data[24]; static rmt_obj_t* rmt_send = NULL; static bool initialized = false; diff --git a/libraries/BLE/src/BLEAddress.h b/libraries/BLE/src/BLEAddress.h index e8fa326a226..a01fe295633 100644 --- a/libraries/BLE/src/BLEAddress.h +++ b/libraries/BLE/src/BLEAddress.h @@ -30,7 +30,7 @@ class BLEAddress { bool operator>(const BLEAddress& otherAddress) const; bool operator>=(const BLEAddress& otherAddress) const; esp_bd_addr_t* getNative(); - std::string toString(); + String toString(); private: esp_bd_addr_t m_address; diff --git a/libraries/BLE/src/BLEAdvertisedDevice.h b/libraries/BLE/src/BLEAdvertisedDevice.h index b785838cb76..e9a5a8c9a5e 100644 --- a/libraries/BLE/src/BLEAdvertisedDevice.h +++ b/libraries/BLE/src/BLEAdvertisedDevice.h @@ -28,82 +28,82 @@ class BLEScan; */ class BLEAdvertisedDevice { public: - BLEAdvertisedDevice(); - - BLEAddress getAddress(); - uint16_t getAppearance(); - std::string getManufacturerData(); - std::string getName(); - int getRSSI(); - BLEScan* getScan(); - std::string getServiceData(); - std::string getServiceData(int i); - BLEUUID getServiceDataUUID(); - BLEUUID getServiceDataUUID(int i); - BLEUUID getServiceUUID(); - BLEUUID getServiceUUID(int i); - int getServiceDataCount(); - int getServiceDataUUIDCount(); - int getServiceUUIDCount(); - int8_t getTXPower(); - uint8_t* getPayload(); - size_t getPayloadLength(); - esp_ble_addr_type_t getAddressType(); - void setAddressType(esp_ble_addr_type_t type); - - - bool isAdvertisingService(BLEUUID uuid); - bool haveAppearance(); - bool haveManufacturerData(); - bool haveName(); - bool haveRSSI(); - bool haveServiceData(); - bool haveServiceUUID(); - bool haveTXPower(); - - std::string toString(); + BLEAdvertisedDevice(); + + BLEAddress getAddress(); + uint16_t getAppearance(); + std::string getManufacturerData(); + std::string getName(); + int getRSSI(); + BLEScan* getScan(); + std::string getServiceData(); + std::string getServiceData(int i); + BLEUUID getServiceDataUUID(); + BLEUUID getServiceDataUUID(int i); + BLEUUID getServiceUUID(); + BLEUUID getServiceUUID(int i); + int getServiceDataCount(); + int getServiceDataUUIDCount(); + int getServiceUUIDCount(); + int8_t getTXPower(); + uint8_t* getPayload(); + size_t getPayloadLength(); + esp_ble_addr_type_t getAddressType(); + void setAddressType(esp_ble_addr_type_t type); + + + bool isAdvertisingService(BLEUUID uuid); + bool haveAppearance(); + bool haveManufacturerData(); + bool haveName(); + bool haveRSSI(); + bool haveServiceData(); + bool haveServiceUUID(); + bool haveTXPower(); + + std::string toString(); private: - friend class BLEScan; - - void parseAdvertisement(uint8_t* payload, size_t total_len=62); - void setPayload(uint8_t* payload, size_t total_len=62); - void setAddress(BLEAddress address); - void setAdFlag(uint8_t adFlag); - void setAdvertizementResult(uint8_t* payload); - void setAppearance(uint16_t appearance); - void setManufacturerData(std::string manufacturerData); - void setName(std::string name); - void setRSSI(int rssi); - void setScan(BLEScan* pScan); - void setServiceData(std::string data); - void setServiceDataUUID(BLEUUID uuid); - void setServiceUUID(const char* serviceUUID); - void setServiceUUID(BLEUUID serviceUUID); - void setTXPower(int8_t txPower); - - bool m_haveAppearance; - bool m_haveManufacturerData; - bool m_haveName; - bool m_haveRSSI; - bool m_haveTXPower; - - - BLEAddress m_address = BLEAddress((uint8_t*)"\0\0\0\0\0\0"); - uint8_t m_adFlag; - uint16_t m_appearance; - int m_deviceType; - std::string m_manufacturerData; - std::string m_name; - BLEScan* m_pScan; - int m_rssi; - std::vector m_serviceUUIDs; - int8_t m_txPower; - std::vector m_serviceData; - std::vector m_serviceDataUUIDs; - uint8_t* m_payload; - size_t m_payloadLength = 0; - esp_ble_addr_type_t m_addressType; + friend class BLEScan; + + void parseAdvertisement(uint8_t* payload, size_t total_len=62); + void setPayload(uint8_t* payload, size_t total_len=62); + void setAddress(BLEAddress address); + void setAdFlag(uint8_t adFlag); + void setAdvertizementResult(uint8_t* payload); + void setAppearance(uint16_t appearance); + void setManufacturerData(std::string manufacturerData); + void setName(std::string name); + void setRSSI(int rssi); + void setScan(BLEScan* pScan); + void setServiceData(std::string data); + void setServiceDataUUID(BLEUUID uuid); + void setServiceUUID(const char* serviceUUID); + void setServiceUUID(BLEUUID serviceUUID); + void setTXPower(int8_t txPower); + + bool m_haveAppearance; + bool m_haveManufacturerData; + bool m_haveName; + bool m_haveRSSI; + bool m_haveTXPower; + + + BLEAddress m_address = BLEAddress((uint8_t*)"\0\0\0\0\0\0"); + uint8_t m_adFlag; + uint16_t m_appearance; + int m_deviceType; + std::string m_manufacturerData; + std::string m_name; + BLEScan* m_pScan; + int m_rssi; + std::vector m_serviceUUIDs; + int8_t m_txPower; + std::vector m_serviceData; + std::vector m_serviceDataUUIDs; + uint8_t* m_payload; + size_t m_payloadLength = 0; + esp_ble_addr_type_t m_addressType; }; /** @@ -115,27 +115,27 @@ class BLEAdvertisedDevice { */ class BLEAdvertisedDeviceCallbacks { public: - virtual ~BLEAdvertisedDeviceCallbacks() {} - /** - * @brief Called when a new scan result is detected. - * - * As we are scanning, we will find new devices. When found, this call back is invoked with a reference to the - * device that was found. During any individual scan, a device will only be detected one time. - */ - virtual void onResult(BLEAdvertisedDevice advertisedDevice) = 0; + virtual ~BLEAdvertisedDeviceCallbacks() {} + /** + * @brief Called when a new scan result is detected. + * + * As we are scanning, we will find new devices. When found, this call back is invoked with a reference to the + * device that was found. During any individual scan, a device will only be detected one time. + */ + virtual void onResult(BLEAdvertisedDevice advertisedDevice) = 0; }; #ifdef CONFIG_BT_BLE_50_FEATURES_SUPPORTED class BLEExtAdvertisingCallbacks { public: - virtual ~BLEExtAdvertisingCallbacks() {} - /** - * @brief Called when a new scan result is detected. - * - * As we are scanning, we will find new devices. When found, this call back is invoked with a reference to the - * device that was found. During any individual scan, a device will only be detected one time. - */ - virtual void onResult(esp_ble_gap_ext_adv_reprot_t report) = 0; + virtual ~BLEExtAdvertisingCallbacks() {} + /** + * @brief Called when a new scan result is detected. + * + * As we are scanning, we will find new devices. When found, this call back is invoked with a reference to the + * device that was found. During any individual scan, a device will only be detected one time. + */ + virtual void onResult(esp_ble_gap_ext_adv_reprot_t report) = 0; }; #endif // CONFIG_BT_BLE_50_FEATURES_SUPPORTED diff --git a/libraries/BLE/src/BLEAdvertising.cpp b/libraries/BLE/src/BLEAdvertising.cpp index 3dcd99b77c3..a3cd7189b2f 100644 --- a/libraries/BLE/src/BLEAdvertising.cpp +++ b/libraries/BLE/src/BLEAdvertising.cpp @@ -297,6 +297,7 @@ void BLEAdvertisementData::addData(std::string data) { if ((m_payload.length() + data.length()) > ESP_BLE_ADV_DATA_LEN_MAX) { return; } + log_d("Appending 0x%X", data.c_str()); m_payload.append(data); } // addData diff --git a/libraries/BLE/src/BLEBeacon.cpp b/libraries/BLE/src/BLEBeacon.cpp index 9f3519b8a19..3bffcd503a1 100644 --- a/libraries/BLE/src/BLEBeacon.cpp +++ b/libraries/BLE/src/BLEBeacon.cpp @@ -51,6 +51,7 @@ int8_t BLEBeacon::getSignalPower() { * Set the raw data for the beacon record. */ void BLEBeacon::setData(std::string data) { + log_d("getting data of length %d", data.length()); if (data.length() != sizeof(m_beaconData)) { log_e("Unable to set the data ... length passed in was %d and expected %d", data.length(), sizeof(m_beaconData)); return; diff --git a/libraries/BLE/src/BLEEddystoneURL.cpp b/libraries/BLE/src/BLEEddystoneURL.cpp index 1c23e52bf44..85040bc7ab7 100644 --- a/libraries/BLE/src/BLEEddystoneURL.cpp +++ b/libraries/BLE/src/BLEEddystoneURL.cpp @@ -20,8 +20,26 @@ BLEEddystoneURL::BLEEddystoneURL() { memset(m_eddystoneData.url, 0, sizeof(m_eddystoneData.url)); } // BLEEddystoneURL -std::string BLEEddystoneURL::getData() { - return std::string((char*) &m_eddystoneData, sizeof(m_eddystoneData)); + +BLEEddystoneURL(BLEAdvertisedDevice *advertisedDevice){ + uint8_t *payLoad = advertisedDevice->getPayload(); + beaconUUID = 0xFEAA; + m_eddystoneData.frameType = EDDYSTONE_URL_FRAME_TYPE; + if(payload[11] != 0x10){ // Not Eddystone URL! + log_e("Failed to interpret Advertised Device as Eddystone URL!"); + lengthURL = 0; + m_eddystoneData.advertisedTxPower = 0; + memset(m_eddystoneData.url, 0, sizeof(m_eddystoneData.url)); + } + lengthURL = payload[7] - 6; // Subtracting 6 Bytes containing header and other data which are not actual URL data + //setData(String((char*)payLoad+11, lengthURL)); + m_eddystoneData.advertisedTxPower = payload[12]; + log_d("using data from [14]=0x%02X=%c up to [%d]=0x%02X=%c", payload[14], payload[14], lengthURL, payload[14+lengthURL], payload[14+lengthURL]); + memcpy(m_eddystoneData.url, payload[14], lengthURL); +} + +String BLEEddystoneURL::getData() { + return String((char*) &m_eddystoneData, sizeof(m_eddystoneData)); } // getData BLEUUID BLEEddystoneURL::getUUID() { @@ -32,13 +50,14 @@ int8_t BLEEddystoneURL::getPower() { return m_eddystoneData.advertisedTxPower; } // getPower -std::string BLEEddystoneURL::getURL() { - return std::string((char*) &m_eddystoneData.url, sizeof(m_eddystoneData.url)); +String BLEEddystoneURL::getURL() { + return String((char*) &m_eddystoneData.url, sizeof(m_eddystoneData.url)); } // getURL -std::string BLEEddystoneURL::getDecodedURL() { - std::string decodedURL = ""; - +String BLEEddystoneURL::getDecodedURL() { + String decodedURL = ""; + log_d("prefix = m_eddystoneData.url[0] 0x%02X",m_eddystoneData.url[0]); + log_e("prefix type m_eddystoneData.url[0]=%d", m_eddystoneData.url[0]); // this is actually debug switch (m_eddystoneData.url[0]) { case 0x00: decodedURL += "http://www."; @@ -60,6 +79,7 @@ std::string BLEEddystoneURL::getDecodedURL() { if (m_eddystoneData.url[i] > 33 && m_eddystoneData.url[i] < 127) { decodedURL += m_eddystoneData.url[i]; } else { + log_d("suffix = m_eddystoneData.url[%d] 0x%02X", i, m_eddystoneData.url[i]); switch (m_eddystoneData.url[i]) { case 0x00: decodedURL += ".com/"; @@ -116,7 +136,7 @@ std::string BLEEddystoneURL::getDecodedURL() { /** * Set the raw data for the beacon record. */ -void BLEEddystoneURL::setData(std::string data) { +void BLEEddystoneURL::setData(String data) { if (data.length() > sizeof(m_eddystoneData)) { log_e("Unable to set the data ... length passed in was %d and max expected %d", data.length(), sizeof(m_eddystoneData)); return; @@ -134,7 +154,7 @@ void BLEEddystoneURL::setPower(int8_t advertisedTxPower) { m_eddystoneData.advertisedTxPower = advertisedTxPower; } // setPower -void BLEEddystoneURL::setURL(std::string url) { +void BLEEddystoneURL::setURL(String url) { if (url.length() > sizeof(m_eddystoneData.url)) { log_e("Unable to set the url ... length passed in was %d and max expected %d", url.length(), sizeof(m_eddystoneData.url)); return; diff --git a/libraries/BLE/src/BLEUUID.cpp b/libraries/BLE/src/BLEUUID.cpp index 45f698afb3a..d6f4d38de66 100644 --- a/libraries/BLE/src/BLEUUID.cpp +++ b/libraries/BLE/src/BLEUUID.cpp @@ -64,6 +64,7 @@ static void memrcpy(uint8_t* target, uint8_t* source, uint32_t size) { * @param [in] value The string to build a UUID from. */ BLEUUID::BLEUUID(std::string value) { + Serial.printf("BLEUUID string constructor- input=\"%s\"\n", value.c_str()); m_valueSet = true; if (value.length() == 4) { m_uuid.len = ESP_UUID_LEN_16; @@ -118,7 +119,6 @@ BLEUUID::BLEUUID(std::string value) { } } //BLEUUID(std::string) - /** * @brief Create a UUID from 16 bytes of memory. * diff --git a/libraries/BLE/src/BLEValue.h b/libraries/BLE/src/BLEValue.h index 31734e489cc..5d2d90702ee 100644 --- a/libraries/BLE/src/BLEValue.h +++ b/libraries/BLE/src/BLEValue.h @@ -7,6 +7,7 @@ #ifndef COMPONENTS_CPP_UTILS_BLEVALUE_H_ #define COMPONENTS_CPP_UTILS_BLEVALUE_H_ +#include #include "sdkconfig.h" #if defined(CONFIG_BLUEDROID_ENABLED) #include @@ -17,22 +18,22 @@ class BLEValue { public: BLEValue(); - void addPart(std::string part); + void addPart(String part); void addPart(uint8_t* pData, size_t length); void cancel(); void commit(); uint8_t* getData(); size_t getLength(); uint16_t getReadOffset(); - std::string getValue(); + String getValue(); void setReadOffset(uint16_t readOffset); - void setValue(std::string value); + void setValue(String value); void setValue(uint8_t* pData, size_t length); private: - std::string m_accumulation; + String m_accumulation; uint16_t m_readOffset; - std::string m_value; + String m_value; }; #endif // CONFIG_BLUEDROID_ENABLED diff --git a/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino b/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino index 0131ff80efd..83839295da7 100644 --- a/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino +++ b/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino @@ -72,8 +72,7 @@ void setup() { ; // 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)) { + if (!I2S.begin(ADC_DAC_MODE, 44100, 16)) { Serial.println("Failed to initialize I2S!"); while (1); // do nothing } diff --git a/libraries/I2S/examples/DualModule/DualModule.ino b/libraries/I2S/examples/DualModule/DualModule.ino index 4676cf603da..2641763c84e 100644 --- a/libraries/I2S/examples/DualModule/DualModule.ino +++ b/libraries/I2S/examples/DualModule/DualModule.ino @@ -10,10 +10,10 @@ ESP32 | Pin | I2S | I2S_1 | | -----|-------|- --------| - | SCK | 18 | 23 | - | FS | 19 | 25 | - | SD 1 | 21 | 27 | I2S DOUT -> I2S_1 DIN - | SD 2 | 22 | 26 | I2S DIN <- I2S_1 DOUT + | SCK | 18 | 22 | + | FS | 19 | 23 | + | SD 1 | 21 | 35 | I2S DOUT -> I2S_1 DIN + | SD 2 | 34 | 27 | I2S DIN <- I2S_1 DOUT ESP32-S3 | Pin | I2S | I2S_1 | @@ -29,7 +29,7 @@ //#define INTERNAL_CONNECTIONS // uncomment to use without external connections -#if SOC_I2S_NUM > 1 +//#if SOC_I2S_NUM > 1 #include #include "hal/gpio_hal.h" @@ -38,8 +38,8 @@ #include "soc/i2s_periph.h" #include "soc/gpio_sig_map.h" -const long sampleRate = 16000; -const int bps = 32; +const long sampleRate = 8000; +const int bps = 8; uint8_t *buffer; uint8_t *buffer1; @@ -49,28 +49,60 @@ const int amplitude = (1<<(bps-1))-1; // amplitude of square wave const int halfWavelength = (sampleRate / signal_frequency); // half wavelength of square wave const int halfWavelength1 = (sampleRate / signal_frequency1); // half wavelength of square wave int32_t write_sample = amplitude; // current sample value +//int32_t write_sample = 165; // current sample value int32_t write_sample1 = amplitude; // current sample value int count = 0; +int count1 = 0; -#define DATA_MASTER_TO_SLAVE PIN_I2S_SD_OUT -#define DATA_SLAVE_TO_MASTER PIN_I2S_SD_IN - -#if CONFIG_IDF_TARGET_ESP32 - #define I2S0_DATA_OUT_IDX I2S0O_DATA_OUT23_IDX - #define I2S0_DATA_IN_IDX I2S0I_DATA_IN15_IDX - #define I2S1_DATA_OUT_IDX I2S1O_DATA_OUT23_IDX - #define I2S1_DATA_IN_IDX I2S1I_DATA_IN15_IDX -#elif CONFIG_IDF_TARGET_ESP32S3 - #define I2S0_DATA_OUT_IDX I2S0O_SD_OUT_IDX - #define I2S0_DATA_IN_IDX I2S0I_SD_IN_IDX - #define I2S1_DATA_OUT_IDX I2S1O_SD_OUT_IDX - #define I2S1_DATA_IN_IDX I2S1I_SD_IN_IDX -#endif +void *write_buffer0; +size_t wr_buf_size0 = (bps/8)*halfWavelength*2; + +void *write_buffer1; +size_t wr_buf_size1 = (bps/8)*halfWavelength1*2; + +//#define TASK_PRIORITY 0 // only reads +//#define TASK_PRIORITY 1 // sometimes working ok; sometimes only writes +#define TASK_PRIORITY 2 // only writes + +/* +static void I2S_write_task(void *args){ + while(true){ + if(I2S.availableForWrite() >= wr_buf_size0){ + Serial.printf("ok write because: I2S.availableForWrite() = %d\n", I2S.availableForWrite()); + I2S.write(write_buffer0, wr_buf_size0); + }else{ + //Serial.printf("no write because: I2S.availableForWrite() = %d\n", I2S.availableForWrite()); + } + delay(1); + } +} + +static void I2S_1_write_task(void *args){ + while(true){ + if(I2S_1.availableForWrite() >= wr_buf_size1){ + Serial.printf("ok write because: I2S_1.availableForWrite() = %d\n", I2S_1.availableForWrite()); + I2S_1.write(write_buffer1, wr_buf_size1); + }else{ + Serial.printf("no write because: I2S_1.availableForWrite() = %d\n", I2S_1.availableForWrite()); + } + delay(1); + } +} +*/ void setup() { - Serial.begin(115200); + Serial.begin(115200, SERIAL_8N1, 16, 17); while(!Serial); + Serial.printf("Try to init\nI2S: MCLK=%d, SCK=%d, FS=%d, SD=%d, OUT=%d, IN=%d\nI2S1: MCLK=%d, SCK=%d, FS=%d, SD=%d, OUT=%d, IN=%d\n", + I2S.getMclkPin(), I2S.getSckPin(), I2S.getFsPin(), I2S.getDataPin(), I2S.getDataOutPin(), I2S.getDataInPin(), + I2S_1.getMclkPin(), I2S_1.getSckPin(), I2S_1.getFsPin(), I2S_1.getDataPin(), I2S_1.getDataOutPin(), I2S_1.getDataInPin()); + + I2S.setDMABufferFrameSize(32); + //I2S.setDMABufferFrameSize(halfWavelength > 8 ? halfWavelength : 8); + I2S_1.setDMABufferFrameSize(32); + //I2S_1.setDMABufferFrameSize(halfWavelength1 > 8 ? halfWavelength1 : 8); + if(!I2S.setDuplex()){ Serial.println("ERROR - could not set duplex for I2S"); while(true){ @@ -104,6 +136,7 @@ void setup() { vTaskDelay(10); // Cannot continue } } + buffer1 = (uint8_t*) malloc(I2S_1.getDMABufferByteSize()); if(buffer1 == NULL){ Serial.println("Failed to allocate buffer1!"); @@ -112,12 +145,82 @@ void setup() { } } + Serial.printf("Initialized successfully\nI2S: MCLK=%d, SCK=%d, FS=%d, SD=%d, OUT=%d, IN=%d\nI2S1: MCLK=%d, SCK=%d, FS=%d, SD=%d, OUT=%d, IN=%d\n", + I2S.getMclkPin(), I2S.getSckPin(), I2S.getFsPin(), I2S.getDataPin(), I2S.getDataOutPin(), I2S.getDataInPin(), + I2S_1.getMclkPin(), I2S_1.getSckPin(), I2S_1.getFsPin(), I2S_1.getDataPin(), I2S_1.getDataOutPin(), I2S_1.getDataInPin()); + /* + Serial.printf("Setup done; create tasks with priority %d\n", (int) TASK_PRIORITY); + delay(1000); + + xTaskCreate(I2S_write_task, "I2S_write_task", 4096, NULL, (UBaseType_t) TASK_PRIORITY, NULL); + xTaskCreate(I2S_1_write_task, "I2S_1_write_task", 4096, NULL, (UBaseType_t) TASK_PRIORITY, NULL); + xTaskCreate(read_task, "read_task", 4096, NULL, (UBaseType_t) TASK_PRIORITY, NULL); + Serial.println("Tasks created"); + */ + Serial.printf("write value (amplitude) = %d\n", write_sample); + + // create buffers + write_buffer0 = malloc(wr_buf_size0); + if(write_buffer0 == NULL){ + Serial.printf("ERROR creating write_buffer0; halt."); + while(true); + } + write_buffer1 = malloc(wr_buf_size1); + if(write_buffer1 == NULL){ + Serial.printf("ERROR creating write_buffer0; halt."); + while(true); + } + + for(int i = 0; i < halfWavelength*2; ++i){ + if(i % halfWavelength == 0 ) { + write_sample = -1 * write_sample; + } + switch(bps){ + case 8: + ((int8_t*)write_buffer0)[i] = write_sample; + break; + case 16: + ((int16_t*)write_buffer0)[i] = write_sample; + break; + case 24: + case 32: + ((int32_t*)write_buffer0)[i] = write_sample; + break; + } + } + + for(int i = 0; i < halfWavelength1*2; ++i){ + if(i % halfWavelength1 == 0 ) { + write_sample1 = -1 * write_sample1; + } + switch(bps){ + case 8: + ((int8_t*)write_buffer1)[i] = write_sample1; + break; + case 16: + ((int16_t*)write_buffer1)[i] = write_sample1; + break; + case 24: + case 32: + ((int32_t*)write_buffer1)[i] = write_sample1; + break; + } + } + +/* + xTaskCreate(I2S_write_task, "I2S_write_task", 4096, NULL, (UBaseType_t) TASK_PRIORITY, NULL); + xTaskCreate(I2S_1_write_task, "I2S_1_write_task", 4096, NULL, (UBaseType_t) TASK_PRIORITY, NULL); + Serial.println("Tasks created"); +*/ + #ifdef INTERNAL_CONNECTIONS + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_MCLK], PIN_FUNC_GPIO); gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_SCK], PIN_FUNC_GPIO); gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_FS], PIN_FUNC_GPIO); gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_SD_OUT], PIN_FUNC_GPIO); gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_SD_IN], PIN_FUNC_GPIO); + gpio_set_direction(PIN_I2S_MCLK, GPIO_MODE_INPUT_OUTPUT); gpio_set_direction(PIN_I2S_SCK, GPIO_MODE_INPUT_OUTPUT); gpio_set_direction(PIN_I2S_FS, GPIO_MODE_INPUT_OUTPUT); gpio_set_direction(PIN_I2S_SD_OUT, GPIO_MODE_INPUT_OUTPUT); @@ -135,31 +238,50 @@ void setup() { esp_rom_gpio_connect_out_signal(DATA_SLAVE_TO_MASTER, I2S1_DATA_OUT_IDX, 0, 0); esp_rom_gpio_connect_in_signal(DATA_SLAVE_TO_MASTER, I2S0_DATA_IN_IDX, 0); #endif - Serial.println("Setup done"); } +int read_sample; +int read_sample1; + void loop() { - // invert the sample every half wavelength count multiple to generate square wave - if (count % halfWavelength == 0 ) { - write_sample = -1 * write_sample; + if(I2S.availableForWrite() >= wr_buf_size0){ + //Serial.printf("ok write because: I2S.availableForWrite() = %d and the buffer is %d Bytes (wr_buf_size0)\n", I2S.availableForWrite(), wr_buf_size0); + I2S.write(write_buffer0, wr_buf_size0); } - I2S.write(write_sample); // Right channel - I2S.write(write_sample); // Left channel - if (count % halfWavelength1 == 0 ) { - write_sample1 = -1 * write_sample1; + if(I2S_1.availableForWrite() >= wr_buf_size1){ + //Serial.printf("ok write because: I2S_1.availableForWrite() = %d and the buffer is %d Bytes (wr_buf_size1)\n", I2S_1.availableForWrite(), wr_buf_size1); + //Serial.printf("ok write because: I2S_1.availableForWrite() = %d\n", I2S_1.availableForWrite()); + I2S_1.write(write_buffer1, wr_buf_size1); } - I2S_1.write(write_sample1); // Right channel - I2S_1.write(write_sample1); // Left channel - // increment the counter for the next sample - count++; + if(I2S.available()){ + read_sample = I2S.read(); + Serial.printf("Slave to master: 0x%X=%hhd\n", read_sample, (char)read_sample); + //Serial.printf("1->0: 0x%X=%hhd\n", read_sample, (char)read_sample); + } - int read_sample = I2S.read(); - int read_sample1 = I2S_1.read(); + if(I2S_1.available()){ + read_sample1 = I2S_1.read(); + //Serial.printf("0->1: 0x%X=%hhd\n", read_sample1, (char)read_sample1); + Serial.printf("Master to slave: 0x%X=%hhd\n", read_sample1, (char)read_sample1); + } - Serial.printf("%d %d\n", read_sample, read_sample1); +// if(read_sample && read_sample1){ +/* + if(read_sample || read_sample1){ + Serial.printf("1->0: 0x%X=%hhd; 0->1: 0x%X=%hhd\n", read_sample, (char)read_sample, read_sample1, (char)read_sample1); + } +*/ } +/* +extern "C" void app_main(){ + setup(); + while(true){loop();} +} +*/ +//#else +// #error "This example cannot run on current SoC - the example requires two I2S modules found in ESP32 and ESP32-S3" +//#endif + + -#else - #error "This example cannot run on current SoC - the example requires two I2S modules found in ESP32 and ESP32-S3" -#endif diff --git a/libraries/I2S/examples/FullDuplex/FullDuplex.ino b/libraries/I2S/examples/FullDuplex/FullDuplex.ino index e24b7fbaff6..e9fb22f679c 100644 --- a/libraries/I2S/examples/FullDuplex/FullDuplex.ino +++ b/libraries/I2S/examples/FullDuplex/FullDuplex.ino @@ -2,16 +2,16 @@ 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. - You will need I2S microphone I2S decoder + headphones / speakerI2S microphone + You will need I2S microphone I2S decoder + headphones or speaker to be plugged into the I2S decoder. - | Pin | ESP32 | ESP32-S2, ESP32-C3, ESP32-S3 | - | -----|-------|------------------------------| - | GND | GND | GND | - | VIN | 5V | 5V | - | SCK | 18 | 18 | - | FS | 19 | 19 | - | DOUT | 21 | 4 | - | DIN | 22 | 5 | + | Pin | ESP32 | ESP32-S2, ESP32-C3, ESP32-S3 | + | -------- |-------|------------------------------| + | GND | GND | GND | + | VIN | 5V | 5V | + | SCK | 18 | 18 | + | FS | 19 | 19 | + | SD(DOUT) | 21 | 4 | + | DIN | 34 | 5 | created 8 October 2021 by Tomas Pilny @@ -24,7 +24,7 @@ uint8_t *buffer; void setup() { Serial.begin(115200); - //I2S.setAllPins(19, 21, 23, 22); // you can change default pins; order of pins = (CLK, WS, IN, OUT) + //I2S.setAllPins(18, 19, 21, 21, 34); // you can change default pins; order of pins = (CLK, WS, SD, SDIN, SDOUT) if(!I2S.setDuplex()){ Serial.println("ERROR - could not set duplex"); while(true){ @@ -48,9 +48,9 @@ void setup() { } void loop() { - //I2S.write(I2S.read()); // primitive implementation sample-by-sample + //I2S.write(I2S.read()); // Primitive implementation sample-by-sample is discouraged - // Buffer based implementation + // Buffer based implementation which is strongly advised I2S.read(buffer, I2S.getDMABufferByteSize()); I2S.write(buffer, I2S.getDMABufferByteSize()); } diff --git a/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino b/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino index 4fd3a946a14..99dc2041152 100644 --- a/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino +++ b/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino @@ -31,7 +31,7 @@ void setup() { } // start I2S at 8 kHz with 32-bits per sample - if (!I2S.begin(I2S_PHILIPS_MODE, 8000, 32)) { + if (!I2S.begin(I2S_PHILIPS_MODE, 32000, 32)) { Serial.println("Failed to initialize I2S!"); while (1); // do nothing } @@ -39,9 +39,13 @@ void setup() { void loop() { int sample; - sample = I2S.read(); // read a sample + int available = I2S.available(); + //Serial.printf("avail %d\n", available); + if(available){ + sample = I2S.read(); // read a sample - if(sample){ - Serial.println(sample); + //if(abs(sample) > 2){ + Serial.println(sample); + //} } } diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index 5bf384ca91b..4465d8f1f17 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -97,6 +97,7 @@ int I2SClass::_installDriver(){ log_e("(I2S#%d) I2S driver is already installed", _deviceIndex); return 0; // ERR } + log_d("(I2S#%d) I2S driver install...", _deviceIndex); if(_deviceIndex >= SOC_I2S_NUM){ log_e("Max allowed I2S device number is %d but requested %d", SOC_I2S_NUM-1, _deviceIndex); @@ -792,6 +793,7 @@ size_t I2SClass::write(const void *buffer, size_t size){ // 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){ + //log_d("(I2S#%d) ...", _deviceIndex); if(!_take_mux()){ return 0; /* ERR */ } if(_initialized){ if(!_enableTransmitter()){ @@ -1252,6 +1254,7 @@ void I2SClass::_fix_and_write(void *output, size_t size, size_t *bytes_written, size_t _bytes_written; esp_err_t ret = esp_i2s::i2s_write((esp_i2s::i2s_port_t) _deviceIndex, buff, buff_size, &_bytes_written, 0); + log_d("written %d/%d", _bytes_written,buff_size); if(ret != ESP_OK){ log_e("(I2S#%d) Error: writing data to i2s - function returned with err code %d", _deviceIndex, ret); } @@ -1270,6 +1273,13 @@ void I2SClass::_fix_and_write(void *output, size_t size, size_t *bytes_written, } } +// TODO change to usage of +/* +int8_t digitalPinToTouchChannel(uint8_t pin); +int8_t digitalPinToAnalogChannel(uint8_t pin); +int8_t analogChannelToDigitalPin(uint8_t channel); +*/ + #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){ diff --git a/libraries/Ticker/src/Ticker.cpp b/libraries/Ticker/src/Ticker.cpp index 629361b2dd0..28b209f1d72 100644 --- a/libraries/Ticker/src/Ticker.cpp +++ b/libraries/Ticker/src/Ticker.cpp @@ -31,9 +31,9 @@ Ticker::~Ticker() { detach(); } -void Ticker::_attach_ms(uint32_t milliseconds, bool repeat, callback_with_arg_t callback, uint32_t arg) { +void Ticker::_attach_ms(uint32_t milliseconds, bool repeat, callback_with_arg_t callback) { esp_timer_create_args_t _timerConfig; - _timerConfig.arg = reinterpret_cast(arg); + _timerConfig.arg = NULL; _timerConfig.callback = callback; _timerConfig.dispatch_method = ESP_TIMER_TASK; _timerConfig.name = "Ticker"; diff --git a/tests/i2s/i2s.ino b/tests/i2s/i2s.ino index c489de4a806..661849a3a9f 100644 --- a/tests/i2s/i2s.ino +++ b/tests/i2s/i2s.ino @@ -58,17 +58,33 @@ void test_01(void){ int ret = -1; Serial.printf("*********** [%lu] Begin I2S with golden parameters **************\n", millis()); - for(int mode = 0; mode < MODE_MAX; ++mode){ + for(int mode = 3; mode < MODE_MAX; ++mode){ + //for(int mode = 0; mode < MODE_MAX; ++mode){ Serial.printf("[%lu] begin: mode %d \"%s\"\n", millis(), mode, i2s_mode_text[mode]); for(int srp = 0; srp < srp_max; ++srp){ for(int bpsp = 0; bpsp < bpsp_max; ++bpsp){ - //Serial.printf("[%lu] begin: mode %d \"%s\", sample rate %d, bps %d\n", millis(), mode, i2s_mode_text[mode], sample_rate[srp], bps[bpsp]); + Serial.printf("[%lu] begin: mode %d \"%s\", sample rate %d, bps %d\n", millis(), mode, i2s_mode_text[mode], sample_rate[srp], bps[bpsp]); + #if defined(SOC_I2S_SUPPORTS_ADC) && defined(SOC_I2S_SUPPORTS_DAC) + if(mode == ADC_DAC_MODE && bps[bpsp] == 16){ + #if CONFIG_IDF_TARGET_ESP32 + I2S_obj->setDataInPin(32); // Default data pin does not support DAC + #else + I2S_obj->setDataInPin(4); // Default data pin does not support DAC + #endif + } + #endif ret = I2S_obj->begin(mode, sample_rate[srp], bps[bpsp]); if(mode == ADC_DAC_MODE){ #if defined(SOC_I2S_SUPPORTS_ADC) && defined(SOC_I2S_SUPPORTS_DAC) - TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success - TEST_ASSERT_EQUAL(true, I2S_obj->isInitialized()); // Expecting true == Success + if(bps[bpsp] == 16){ + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + TEST_ASSERT_EQUAL(true, I2S_obj->isInitialized()); // Expecting true == Success + I2S_obj->setDataInPin(-1); // Set to default data pin + }else{ + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure + } #else TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure @@ -129,14 +145,29 @@ void test_02(void){ Serial.printf("*********** [%lu] Begin I2S with golden parameters **************\n", millis()); for(int mode = 0; mode < MODE_MAX; ++mode){ for(int bpsp = 0; bpsp < bpsp_max; ++bpsp){ - //Serial.printf("[%lu] begin: mode %d \"%s\", bps %d\n", millis(), mode, i2s_mode_text[mode], bps[bpsp]); + Serial.printf("[%lu] begin: mode %d \"%s\", bps %d\n", millis(), mode, i2s_mode_text[mode], bps[bpsp]); + #if defined(SOC_I2S_SUPPORTS_ADC) && defined(SOC_I2S_SUPPORTS_DAC) + if(mode == ADC_DAC_MODE && bps[bpsp] == 16){ + #if CONFIG_IDF_TARGET_ESP32 + I2S_obj->setDataInPin(32); // Default data pin does not support DAC + #else + I2S_obj->setDataInPin(4); // Default data pin does not support DAC + #endif + } + #endif ret = I2S_obj->begin(mode, bps[bpsp]); if(mode == ADC_DAC_MODE){ #if defined(SOC_I2S_SUPPORTS_ADC) && defined(SOC_I2S_SUPPORTS_DAC) - TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success - TEST_ASSERT_EQUAL(true, I2S_obj->isInitialized()); // Expecting true == Success - #else + if(bps[bpsp] == 16){ + I2S_obj->setDataInPin(4); // Default data pin does not support DAC + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + TEST_ASSERT_EQUAL(true, I2S_obj->isInitialized()); // Expecting true == Success + I2S_obj->setDataInPin(-1); // Set to default data pin + }else{ + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure + } TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure #endif @@ -310,7 +341,7 @@ void test_04(void){ ret = I2S_obj->isDuplex(); TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == simplex (default) - + Serial.printf("Tests done, now begin\n"); // Test on INITIALIZED I2S I2S_obj->begin(I2S_PHILIPS_MODE, 32000, 32); } @@ -483,7 +514,7 @@ void test_06(void){ // Dual module data transfer test (only for ESP32 and ESP32-S3) void test_0x(void){ - + // TODO } void setup() { @@ -491,7 +522,7 @@ void setup() { while (!Serial) { ; } - + delay(500); //Serial.printf("Num of I2S module =%d\n", SOC_I2S_NUM); //Serial.printf("I2S0=%p\n", &I2S); @@ -508,13 +539,13 @@ void setup() { #endif i2s_index = 0; for(; i2s_index < SOC_I2S_NUM; ++ i2s_index){ - //Serial.printf("*******************************************************\n"); - //Serial.printf("********************* I2S # %d *************************\n", i2s_index); - //Serial.printf("*******************************************************\n"); + Serial.printf("*******************************************************\n"); + Serial.printf("********************* I2S # %d *************************\n", i2s_index); + Serial.printf("*******************************************************\n"); I2S_obj = I2S_obj_arr[i2s_index]; - RUN_TEST(test_01); // begin master - RUN_TEST(test_02); // begin slave - RUN_TEST(test_03); // pin setters and geters + //RUN_TEST(test_01); // begin master + //RUN_TEST(test_02); // begin slave + //RUN_TEST(test_03); // pin setters and geters RUN_TEST(test_04); // duplex / simplex RUN_TEST(test_05); // Simple data transmit, returned written bytes check and buffer check. diff --git a/tools/sdk/esp32/include/driver/include/driver/i2s.h b/tools/sdk/esp32/include/driver/include/driver/i2s.h index d231ad396bb..3807a8854ee 100644 --- a/tools/sdk/esp32/include/driver/include/driver/i2s.h +++ b/tools/sdk/esp32/include/driver/include/driver/i2s.h @@ -140,11 +140,11 @@ typedef intr_handle_t i2s_isr_handle_t; // for backward compatible */ typedef enum { I2S_EVENT_DMA_ERROR, - I2S_EVENT_TX_DONE, /*!< I2S DMA finish sent 1 buffer*/ - I2S_EVENT_RX_DONE, /*!< I2S DMA finish received 1 buffer*/ - I2S_EVENT_TX_Q_OVF, /*!< I2S DMA sent queue overflow*/ - I2S_EVENT_RX_Q_OVF, /*!< I2S DMA receive queue overflow*/ - I2S_EVENT_MAX, /*!< I2S event max index*/ + I2S_EVENT_TX_DONE, /*!< 1 I2S DMA finish sent 1 buffer*/ + I2S_EVENT_RX_DONE, /*!< 2 I2S DMA finish received 1 buffer*/ + I2S_EVENT_TX_Q_OVF, /*!< 3 I2S DMA sent queue overflow*/ + I2S_EVENT_RX_Q_OVF, /*!< 4 I2S DMA receive queue overflow*/ + I2S_EVENT_MAX, /*!< 5 I2S event max index*/ } i2s_event_type_t; /** From 1d85571e210898ac870f6b3f5943816c85b2391c Mon Sep 17 00:00:00 2001 From: Tomas Pilny Date: Mon, 17 Jul 2023 12:51:49 +0200 Subject: [PATCH 46/46] work in progress --- .../examples/BufferedTone/BufferedTone.ino | 162 ++++++++++++++++++ .../I2S/examples/DualModule/.skip.esp32c2 | 0 libraries/I2S/examples/PinMap/PinMap.ino | 88 ++++++++++ 3 files changed, 250 insertions(+) create mode 100644 libraries/I2S/examples/BufferedTone/BufferedTone.ino create mode 100644 libraries/I2S/examples/DualModule/.skip.esp32c2 create mode 100644 libraries/I2S/examples/PinMap/PinMap.ino diff --git a/libraries/I2S/examples/BufferedTone/BufferedTone.ino b/libraries/I2S/examples/BufferedTone/BufferedTone.ino new file mode 100644 index 00000000000..2202547f333 --- /dev/null +++ b/libraries/I2S/examples/BufferedTone/BufferedTone.ino @@ -0,0 +1,162 @@ +/* + 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, Nano 33 IoT or any ESP32 + * MAX08357 or PCM510xA: + | Pin | Zero | MKR | Nano | ESP32 | ESP32-S2, ESP32-C3, ESP32-S3 | + | -----|-------|-------|-------|-------|------------------------------| + | GND | GND | GND | GND | GND | GND | + | 5V | 5V | 5V | 5V | 5V | 5V | + | SCK | 1 | 2 | A3 | 18 | 18 | + | FS | 0 | 3 | A2 | 19 | 19 | + | SD | 9 | A6 | 4 | 21 | 4 | + * note: those chips supports only 16/24/32 bits per sample, i.e. 8 bps will be refused = no audio output + + DAC Circuit: + * ESP32 + * Audio amplifier + - Note: + - ESP32 has DAC on GPIO pins 25 and 26. + - Connect speaker(s) or headphones. + + created 17 November 2016 + by Sandeep Mistry + For ESP extended + Tomas Pilny + 2nd September 2021 + */ + +#include +const int frequency = 1000; // frequency of square wave in Hz +const int sampleRate = 8000; // sample rate in Hz +//const int sampleRate = 44100; // sample rate in Hz +const int bps = 16; +const int amplitude = (1<<(bps-1))-1; // amplitude of square wave + +const int halfWavelength = (sampleRate / frequency); // half wavelength of square wave + +int32_t sample = amplitude; // current sample value +int count = 0; + +void *data; +const size_t data_elements = halfWavelength * 2 * CHANNEL_NUMBER; +const size_t data_len = (bps / 8) * data_elements; + +i2s_mode_t mode = I2S_PHILIPS_MODE; // I2S decoder is needed +//i2s_mode_t mode = ADC_DAC_MODE; // bps must be 16; 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"); + + // Generate data for buffer write + data = malloc(data_len); + if(data == NULL){ + Serial.println("Failed to create data buffer!"); + while (1); // do nothing + } + + int high; + int low; + switch(bps){ + case 8: + high = (mode == I2S_PHILIPS_MODE) ? 0x7F : 0xFF; + low = (mode == I2S_PHILIPS_MODE) ? 0x80 : 0x00; + break; + case 16: + high = (mode == I2S_PHILIPS_MODE) ? 0x7FFF : 0xFFFF; + low = (mode == I2S_PHILIPS_MODE) ? 0x8000 : 0x00; + break; + case 24: + // 24 bps is stored in 32 bit data type on lower 3 Bytes (MSB is ignored) + high = (mode == I2S_PHILIPS_MODE) ? 0x007FFFFF : 0x00FFFFFF; + low = (mode == I2S_PHILIPS_MODE) ? 0x00800000 : 0x00; + break; + case 32: + high = (mode == I2S_PHILIPS_MODE) ? 0x7FFFFFFF : 0xFFFFFFFF; + low = (mode == I2S_PHILIPS_MODE) ? 0x80000000 : 0x00; + break; + } + + Serial.printf("Create data buffer for bps %d with values %d=0x%d for high and %d=0x%x for low\n", bps, high, high, low, low); + for(int i = 0; i < data_elements; ++i){ + switch(bps){ + case 8: + //((int8_t*)data)[i] = i <= data_elements/2 ? 0x7F : 0x80; + //((int8_t*)data)[i] = i <= data_elements/2 ? 0xFF : 0x0; + ((int8_t*)data)[i] = (int8_t)(i <= data_elements/2 ? high : low); + break; + case 16: + //((int16_t*)data)[i] = i <= data_elements/2 ? 0x7FFF : 0x8000; + //((int16_t*)data)[i] = i <= data_elements/2 ? 0xFFFF : 0x0; + ((int16_t*)data)[i] = (int16_t)(i <= data_elements/2 ? high : low); + break; + case 24: + // 24 bps is stored in 32 bit data type on lower 3 Bytes (MSB is ignored) + //((int32_t*)data)[i] = i <= data_elements/2 ? 0x007FFFFF : 0x00800000; + //((int32_t*)data)[i] = i <= data_elements/2 ? 0x00FFFFFF : 0x0; + ((int32_t*)data)[i] = (int32_t)(i <= data_elements/2 ? high : low); + break; + case 32: + //((int32_t*)data)[i] = i <= data_elements/2 ? 0x7FFFFFFF : 0x80000000; + //((int32_t*)data)[i] = i <= data_elements/2 ? 0xFFFFFFFF : 0x0; + ((int32_t*)data)[i] = (int32_t)(i <= data_elements/2 ? high : low); + break; + } + } + + I2S.setDMABufferSampleSize(data_elements); + Serial.printf("Set buffer sample size to %d\n", data_elements); + + // 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 + } + //I2S.write(data, data_len); + //I2S.write(data, data_len); +} + +void loop() { +//size_t bytes_written; +//i2s_write(I2S_NUM_0, data, data_len, &bytes_written, portMAX_DELAY); + + int avail = I2S.availableForWrite(); + if(avail >= data_len){ + Serial.printf("avail for write = %d; data_len=%d\n", avail, data_len); + I2S.write(data, data_len); + //int written = I2S.write(data, data_len); + //Serial.printf("written = %d\n", written); + }else{ + //Serial.printf("data_len=%d cannot fit inside buffer of size%d => flush\n",data_len, avail); + //I2S.flush(); + //TODO + delay(1); + } + + + /* + if (count % halfWavelength == 0 ) { + // invert the sample every half wavelength count multiple to generate square wave + sample = -1 * sample; + Serial.printf("sample %d\n", sample); + } + + I2S.write(sample); // Right channel + I2S.write(sample); // Left channel + + // increment the counter for the next sample + count++; +*/ +} diff --git a/libraries/I2S/examples/DualModule/.skip.esp32c2 b/libraries/I2S/examples/DualModule/.skip.esp32c2 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/I2S/examples/PinMap/PinMap.ino b/libraries/I2S/examples/PinMap/PinMap.ino new file mode 100644 index 00000000000..afbd4685fbb --- /dev/null +++ b/libraries/I2S/examples/PinMap/PinMap.ino @@ -0,0 +1,88 @@ +// This is actually some personal stuff, not actually example ... +// probably to search usable pins - hence the name + + +/* + This example is only for ESP32 and ESP32-S3 which have two separate I2S modules + This example demonstrates simultaneous usage of both I2S module. + The application generates square wave for both modules and transfers to each other. + You can plot the waves with Arduino plotter + + To prepare the example you will need to connect the output pins of the modules as if they were standalone devices. + The pin-out differ for the SoCs - refer to the table of used SoC. + + ESP32 + | Pin | I2S | I2S_1 | + | -----|-------|- --------| + | SCK | 18 | 22 | + | FS | 19 | 23 | + | SD 1 | 21 | 35 | I2S DOUT -> I2S_1 DIN + | SD 2 | 34 | 27 | I2S DIN <- I2S_1 DOUT + + ESP32-S3 + | Pin | I2S | I2S_1 | + | -----|-------|- --------| + | SCK | 18 | 36 | + | FS | 19 | 37 | + | SD 1 | 4 | 40 | I2S DOUT -> I2S_1 DIN + | SD 2 | 5 | 39 | I2S DIN <- I2S_1 DOUT + + created 7 Nov 2022 + by Tomas Pilny + */ + +#include +#include "hal/gpio_hal.h" +#include "esp_rom_gpio.h" + +#include "soc/i2s_periph.h" +#include "soc/gpio_sig_map.h" + +//uint8_t ban_pins[] = {1,3,6,7,8,9,10,11,24,28,29,30,31}; // ESP32 WROVER-B +uint8_t ban_pins[] = {1,3,6,7,8,9,10,11,20,24,28,29,30,31}; // ESP32 WROVER-B +uint8_t data[] = {0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55}; + +void setup() { + Serial.begin(115200); + while(!Serial); + + if (!I2S.begin(I2S_PHILIPS_MODE, 8000, 8)) { + Serial.println("Failed to initialize I2S!"); + while(true){ + vTaskDelay(10); // Cannot continue + } + } + int ret; + bool skip; + I2S.setDataPin(21); + int ban_size = sizeof(ban_pins) / sizeof(ban_pins[0]); + Serial.println("Initial setup done"); + for(int pin = 22; pin < 34; ++pin){ + skip = false; + Serial.printf("* * * * * * * * Try to set SCK pin to %d * * * * * * * *\n", pin); + for(int i = 0; i < ban_size; ++i){ + if(pin == ban_pins[i]){ + Serial.printf("pin %d is on ban list - skip\n", pin); + skip = true; + break; // out of inner loop + } + } + if(skip){ continue; } + if(pin == I2S.getFsPin()) I2S.setFsPin(pin-1); + if(pin == I2S.getDataPin()) I2S.setDataPin(pin-1); + ret = I2S.setSckPin(pin); + Serial.printf("Set pin returned %s\n", ret ? "OK" : "FAILED"); + Serial.printf("I2S: SCK=%d, FS=%d, SD=%d\n", I2S.getSckPin(), I2S.getFsPin(), I2S.getDataPin()); + uint8_t byte; + while(!Serial.available()){ + I2S.write_blocking(data, sizeof(data) / sizeof(data[0])); + //delay(10); + } + byte = Serial.read(); + Serial.printf("Serial received byte %d = char %c\n", byte, byte); + } +} + +void loop() { + delay(1000); +}