diff --git a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_lib_idf51.ino b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_lib_idf51.ino index 5747851ed..40fa3bba6 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_lib_idf51.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_lib_idf51.ino @@ -67,9 +67,27 @@ public: } ~TasmotaI2S() { - this->stop(); + this->stopTx(); } + /*********************************************************************************************\ + * Global state for this class + * + * _exclusive: true if Tx and Rx are sharing a common GPIO, so only Tx or Rx can be active at the same time + * _is_dupleix: (virtual) true if both Rx and Tx are configured, i.e. the same instance handles both Rx/Tx + * Note: there are some restrictions on protocols, for ex. PDM is not supported in duplex, + * and will trigger 'exclusive' + * + * _t/rx_configured: true if Tx/Rx is configured, hence should be possible to start + * _r/tx_handle: handle for I2S driver, or nullptr if driver not stared + * _t/rx_running: true if Tx/Rx is actively running and sending/receiving data + * + \*********************************************************************************************/ + + // ------------------------------------------------------------------------------------------ + // Setters for configuration parameters + // + // ------------------------------------------------------------------------------------------ // Settings void setPinout(int32_t bclk, int32_t ws, int32_t dout, int32_t mclk, int32_t din, bool mclk_inv = false, bool bclk_inv = false, bool ws_inv = false, bool apll = false); @@ -82,20 +100,16 @@ public: } void setRxFreq(uint16_t freq) { _rx_freq = freq; } - // ------------------------------------------------------------------------------------------ - // Setters for configuration parameters - // - // TODO: not sure we still need them since all this should be set at initialiation - // ------------------------------------------------------------------------------------------ + // Settings from superclass virtual bool SetBitsPerSample(int bits) { - AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetBitsPerSample: %i"), bits); + AddLog(LOG_LEVEL_DEBUG,"I2S: SetBitsPerSample: %i", bits); if ( (bits != 16) && (bits != 8) ) { return false; } this->bps = bits; return true; } virtual bool SetChannels(int channels) { - AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetChannels: %i"), channels); + AddLog(LOG_LEVEL_DEBUG,"I2S: SetChannels: %i", channels); if ((channels < 1) || (channels > 2)) { return false; } if (channels == (int)this->channels) { return true; } this->channels = channels; @@ -103,17 +117,14 @@ public: } virtual bool SetRate(int hz) { - AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetRate: %i was %i on=%i"), hz, this->hertz, _i2s_on); + AddLog(LOG_LEVEL_DEBUG,"I2S: SetRate: %i was %i tx_running=%i", hz, this->hertz, _tx_running); if (hz == (int) this->hertz) { return true; } this->hertz = hz; - if (_i2s_on) { - int result = updateClockConfig(); - } - return true; + return updateClockConfig(); } virtual bool SetGain(float f) { - AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetGain: %_f"), &f); + AddLog(LOG_LEVEL_DEBUG, "I2S: SetGain: %_f", &f); return AudioOutput::SetGain(f); } @@ -163,11 +174,14 @@ public: inline void setChannels(int chan) { SetChannels(chan); } inline void setGain(float f) { SetGain(f); } - bool begin(void); - bool stop(void); + // Tx + virtual bool begin(void) { return beginTx(); }; // the name `begin()`is inherited from superclass, prefer `beginTx()` which is more explicit + virtual bool stop(void) { return stopTx(); }; // the name `stop()`is inherited from superclass, prefer `stopTx()` which is more explicit + bool beginTx(void); + bool stopTx(void); bool ConsumeSample(int16_t sample[2]); bool startI2SChannel(bool tx, bool rx); - int updateClockConfig(void); + bool updateClockConfig(void); int32_t readMic(uint8_t *buffer, uint32_t size, bool dc_block, bool apply_gain, bool lowpass, uint32_t *peak_ptr); @@ -183,8 +197,8 @@ public: // ------------------------------------------------------------------------------------------ // Microphone related methods - uint32_t micInit(void); - void micDeinit(void); + uint32_t startRx(void); + bool stopRx(void); protected: int16_t dcFilter(int16_t pcm_in); @@ -192,7 +206,6 @@ protected: protected: - bool _i2s_on = false; // is I2S audio active bool _exclusive = false; // in exclusive mode, stopping this instance needs to uninstall driver, and reinstall for next use i2s_port_t _i2s_port = I2S_NUM_AUTO; // I2S port, I2S_NUM_0/I2S_NUM_1/I2S_NUM_AUTO @@ -266,32 +279,63 @@ void TasmotaI2S::setPinout(int32_t bclk, int32_t ws, int32_t dout, int32_t mclk, _tx_configured, _rx_configured); } -bool TasmotaI2S::begin() { - AddLog(LOG_LEVEL_DEBUG, "I2S: begin _tx_running:%i _i2s_on:%i", _tx_running, _i2s_on); +bool TasmotaI2S::beginTx(void) { + AddLog(LOG_LEVEL_DEBUG, "I2S: Calling beginTX (tx_handle:%i already_running:%i)", _tx_handle, _tx_running); if (_tx_running) { return true; } - // if (!_i2s_on) { - // if ((!_rx_configured || !_tx_configured) && _rx_configured) { // not duplex -- TODO ? - // this->startI2SChannel(); - // } - // } - int result = i2s_channel_enable(_tx_handle); - if (result != 0){ - AddLog(LOG_LEVEL_INFO, "I2S: Could not enable i2s_channel: %i", result); + + if (!_tx_handle){ + if (!startI2SChannel(true, false)) { return false; + } + } + + esp_err_t err = i2s_channel_enable(_tx_handle); + AddLog(LOG_LEVEL_INFO, "I2S: Tx i2s_channel_enable err=0x%04X", err); + if (err != ERR_OK){ + return false; } _tx_running = true; AddLog(LOG_LEVEL_DEBUG, "I2S: begin _tx_running succeeded"); return true; } -bool TasmotaI2S::stop() { - i2s_channel_disable(_tx_handle); - if ((!_rx_configured || !_tx_configured) && _rx_configured) { // not duplex -- TODO ? - i2s_del_channel(_tx_handle); - _i2s_on = false; +bool TasmotaI2S::stopTx() { + AddLog(LOG_LEVEL_DEBUG, "I2S: calling stopTx() tx_running:%i tx_handle:%p", _tx_running, _tx_handle); + if (!_tx_configured) { return false; } // invalid action + if (!_tx_handle) { return true; } // nothing to do + if (_tx_running) { + esp_err_t err = i2s_channel_disable(_tx_handle); + AddLog(LOG_LEVEL_DEBUG, "I2S: stopTx i2s_channel_disable err=0x%04X", err); + _tx_running = false; + } + if (_exclusive) { // exclusive mode, deregister channel + if (_tx_handle) { + esp_err_t err = i2s_del_channel(_tx_handle); + AddLog(LOG_LEVEL_DEBUG, "I2S: stopTx i2s_del_channel err=0x%04X", err); + _tx_handle = nullptr; + } AddLog(LOG_LEVEL_INFO, "I2S: stop: I2S channel disabled"); } - _tx_running = false; + return true; +} + +bool TasmotaI2S::stopRx(void) { + AddLog(LOG_LEVEL_DEBUG, "I2S: calling stopRx() rx_running:%i rx_handle:%p", _rx_running, _rx_handle); + if (!_rx_configured) { return false; } // nothing configured + if (!_rx_handle) { return true; } // bothing to do + + if (_rx_running) { + esp_err_t err = i2s_channel_disable(_rx_handle); + AddLog(LOG_LEVEL_DEBUG, "I2S: stopRx i2s_channel_disable err=0x%04X", err); + _rx_running = false; + } + if (_exclusive) { + if (_rx_handle) { + esp_err_t err = i2s_del_channel(_rx_handle); + AddLog(LOG_LEVEL_DEBUG, "I2S: stopRx i2s_del_channel err=0x%04X", err); + _rx_handle = nullptr; + } + } return true; } @@ -346,28 +390,42 @@ int32_t TasmotaI2S::consumeSamples(int16_t *samples, size_t count) { // Initialize I2S channel // return `true` if successful, `false` otherwise bool TasmotaI2S::startI2SChannel(bool tx, bool rx) { - if (!tx) { _tx_configured = false; } - if (!rx) { _rx_configured = false; } - if (!_tx_configured && !_rx_configured) { return false; } // nothing configured + AddLog(LOG_LEVEL_DEBUG, "I2S: startI2SChannel: tx:%i rx:%i tx_configured:%i rx_configured:%i", tx, rx, _tx_configured, _rx_configured); + if (tx && !_tx_configured) { return false; } + if (rx && !_rx_configured) { return false; } + if (rx && tx && _exclusive) { return false; } // cannot initialize full-duplex with exclusive + if (!tx && !rx) { return false; } // nothing to do + + // check if already configured + if (tx && rx && _tx_handle && _rx_handle) { return true; } + if (tx && _tx_handle) { return true; } + if (rx && _rx_handle) { return true; } + + if (_exclusive) { + // in exclusive mode, we may need to remove exisiting driver + if (tx && _rx_handle) { + AddLog(LOG_LEVEL_DEBUG, "I2S: (exclusive mode) forcing stopRx"); + stopRx(); + } + if (rx && _tx_handle) { + AddLog(LOG_LEVEL_DEBUG, "I2S: (exclusive mode) forcing stopTx"); + stopTx(); + } + } + esp_err_t err = ESP_OK; - gpio_num_t _DIN = I2S_GPIO_UNUSED; // no input pin by default + gpio_num_t _DIN = rx ? (gpio_num_t)_gpio_din : I2S_GPIO_UNUSED; // no input pin if no Rx - if (_tx_configured) { + if (tx) { // default dma_desc_num = 6 (DMA buffers), dma_frame_num = 240 (frames per buffer) i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_1, I2S_ROLE_MASTER); AddLog(LOG_LEVEL_DEBUG, "I2S: tx_chan_cfg id:%i role:%i dma_desc_num:%i dma_frame_num:%i auto_clear:%i", tx_chan_cfg.id, tx_chan_cfg.role, tx_chan_cfg.dma_desc_num, tx_chan_cfg.dma_frame_num, tx_chan_cfg.auto_clear); - if (_tx_configured && _rx_configured) { - _DIN = (gpio_num_t)_gpio_din; - err = i2s_new_channel(&tx_chan_cfg, &_tx_handle, &audio_i2s.out->_rx_handle); - } else{ - err = i2s_new_channel(&tx_chan_cfg, &_tx_handle, NULL); - } - - AddLog(LOG_LEVEL_DEBUG, "I2S: i2s_new_channel err:%i", err); + err = i2s_new_channel(&tx_chan_cfg, &_tx_handle, rx ? &_rx_handle : NULL); // configure Rx only in duplex non-exclusive + AddLog(LOG_LEVEL_DEBUG, "I2S: i2s_new_channel Tx err:0x%04X", err); // by default we configure for MSB 2 slots `I2S_STD_MSB_SLOT_DEFAULT_CONFIG` i2s_std_config_t tx_std_cfg = { @@ -395,36 +453,27 @@ bool TasmotaI2S::startI2SChannel(bool tx, bool rx) { } if (_tx_slot_mask != I2S_SLOT_NOCHANGE) { tx_std_cfg.slot_cfg.slot_mask = (i2s_std_slot_mask_t)_tx_slot_mask; } - // AddLog(LOG_LEVEL_INFO, ">>>: I2S tx_chan_cfg=%*_H", sizeof(tx_chan_cfg), &tx_chan_cfg); - // AddLog(LOG_LEVEL_INFO, ">>>: I2S tx_std_cfg=%*_H", sizeof(tx_std_cfg), &tx_std_cfg); - err = i2s_channel_init_std_mode(_tx_handle, &tx_std_cfg); - AddLog(LOG_LEVEL_DEBUG, "I2S: TX channel bits:%i channels:%i hertz:%i initialized err=0x%04X", bps, channels, hertz, err); + AddLog(LOG_LEVEL_DEBUG, "I2S: i2s_channel_init_std_mode TX channel bits:%i channels:%i hertz:%i err=0x%04X", bps, channels, hertz, err); if (err != ERR_OK) { - _i2s_on = false; + _tx_handle = nullptr; return false; } - _i2s_on = true; - if (_rx_configured) { // full duplex mode - err = i2s_channel_init_std_mode(audio_i2s.out->_rx_handle, &tx_std_cfg); + if (rx) { // full duplex mode + err = i2s_channel_init_std_mode(_rx_handle, &tx_std_cfg); AddLog(LOG_LEVEL_DEBUG, "I2S: i2s_channel_init_std_mode err:%i", err); AddLog(LOG_LEVEL_DEBUG, "I2S: RX channel added in full duplex mode"); } } // if (tx) // configure Rx Microphone - if (_rx_configured && !_tx_configured) { // if Microphone and not duplex + if (rx) { gpio_num_t clk_gpio; i2s_slot_mode_t slot_mode = (_rx_channels == 1) ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO; AddLog(LOG_LEVEL_DEBUG, "I2S: mic init rx_channels:%i rx_running:%i rx_handle:%p", slot_mode, _rx_running, _rx_handle); - if (_tx_configured && _rx_running) { // duplex mode, mic was already initialized - AddLog(LOG_LEVEL_DEBUG, "I2S: mic init exit Rx already enabled"); - return 0; // no need to en- or disable when in full duplex mode and already initialized - } - i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); // change to 3 buffers of 512 samples rx_chan_cfg.dma_desc_num = 3; @@ -434,7 +483,7 @@ bool TasmotaI2S::startI2SChannel(bool tx, bool rx) { rx_chan_cfg.id, rx_chan_cfg.role, rx_chan_cfg.dma_desc_num, rx_chan_cfg.dma_frame_num, rx_chan_cfg.auto_clear); err = i2s_new_channel(&rx_chan_cfg, NULL, &_rx_handle); - AddLog(LOG_LEVEL_DEBUG, "I2S: mic init i2s_new_channel err=%i", err); + AddLog(LOG_LEVEL_DEBUG, "I2S: i2s_new_channel Rx err:%i", err); switch (_rx_mode){ case I2S_MODE_PDM: { @@ -462,9 +511,12 @@ bool TasmotaI2S::startI2SChannel(bool tx, bool rx) { // rx_pdm_cfg.gpio_cfg.clk, rx_pdm_cfg.gpio_cfg.din, rx_pdm_cfg.gpio_cfg.invert_flags.clk_inv); err = i2s_channel_init_pdm_rx_mode(_rx_handle, &rx_pdm_cfg); - AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: RX channel in PDM mode, CLK: %i, DIN: %i, 16 bit width, %i channel(s), err code: 0x%04X"), + AddLog(LOG_LEVEL_DEBUG, "I2S: RX channel in PDM mode, CLK: %i, DIN: %i, 16 bit width, %i channel(s), err code: 0x%04X", _gpio_ws, _gpio_din, _rx_channels, err); - _i2s_on = true; + if (err) { + _rx_handle = nullptr; + return false; + } } break; case I2S_MODE_STD: @@ -487,10 +539,13 @@ bool TasmotaI2S::startI2SChannel(bool tx, bool rx) { }; if (_rx_slot_mask != I2S_SLOT_NOCHANGE) { rx_std_cfg.slot_cfg.slot_mask = (i2s_std_slot_mask_t)_rx_slot_mask; } - err = i2s_channel_init_std_mode(audio_i2s.rx_handle, &rx_std_cfg); + err = i2s_channel_init_std_mode(_rx_handle, &rx_std_cfg); AddLog(LOG_LEVEL_DEBUG, "I2S: RX i2s_channel_init_std_mode err:%i", err); AddLog(LOG_LEVEL_DEBUG, "I2S: RX channel in standard mode with 16 bit width on %i channel(s) initialized", slot_mode); - _i2s_on = true; + if (err) { + _rx_handle = nullptr; + return false; + } } break; default: @@ -500,27 +555,40 @@ bool TasmotaI2S::startI2SChannel(bool tx, bool rx) { return true; } -int TasmotaI2S::updateClockConfig(void) { - i2s_channel_disable(_tx_handle); +bool TasmotaI2S::updateClockConfig(void) { + if (!_tx_handle) { return true; } + if (_tx_running) { + esp_err_t err = i2s_channel_disable(_tx_handle); + AddLog(LOG_LEVEL_INFO, "I2S: updateClockConfig i2s_channel_disable err=0x%04X", err); + } i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(hertz); #ifdef SOC_I2S_SUPPORTS_APLL if (_apll) { clk_cfg.clk_src = I2S_CLK_SRC_APLL; } #endif - int result = i2s_channel_reconfig_std_clock(_tx_handle, &clk_cfg ); - if (_tx_running) { i2s_channel_enable(_tx_handle); } + esp_err_t result = i2s_channel_reconfig_std_clock(_tx_handle, &clk_cfg ); + AddLog(LOG_LEVEL_INFO, "I2S: updateClockConfig i2s_channel_reconfig_std_clock err=0x%04X", result); + if (_tx_running) { + esp_err_t err = i2s_channel_enable(_tx_handle); + AddLog(LOG_LEVEL_INFO, "I2S: updateClockConfig i2s_channel_enable err=0x%04X", err); + } AddLog(LOG_LEVEL_DEBUG, "I2S: Updating clock config"); - return result; + return result == ESP_OK; } /*********************************************************************************************\ * microphone related functions \*********************************************************************************************/ -uint32_t TasmotaI2S::micInit(void) { +uint32_t TasmotaI2S::startRx(void) { + AddLog(LOG_LEVEL_DEBUG, "I2S: startRx called"); if (!_rx_configured) { return 0; } // nothing configured + if (!_rx_handle){ + startI2SChannel(false, true); + } + esp_err_t err = ESP_OK; gpio_num_t clk_gpio; @@ -529,29 +597,12 @@ uint32_t TasmotaI2S::micInit(void) { if (!_rx_running) { err = i2s_channel_enable(_rx_handle); - AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: RX channel enable err:0x%04X"),err); + AddLog(LOG_LEVEL_DEBUG, "I2S: RX channel enable err:0x%04X", err); _rx_running = true; } return err; } - -void TasmotaI2S::micDeinit(void) { - esp_err_t err = ESP_OK; - gpio_num_t clk_gpio; - - AddLog(LOG_LEVEL_DEBUG, "I2S: mic deinit rx_running:%i rx_handle:%p", _rx_running, _rx_handle); - if (!_rx_handle) { return; } - - if (!_tx_configured || !_rx_configured) { // if duplex mode, there is no mic channel - TODO check this - int err = i2s_channel_disable(_rx_handle); - i2s_del_channel(_rx_handle); - _rx_handle = nullptr; - _rx_running = false; - AddLog(LOG_LEVEL_DEBUG, "I2S: RX channel disable: %i", err); - } -} - // Read data into buffer of uint16_t[] // // Returns: @@ -563,7 +614,7 @@ int32_t TasmotaI2S::readMic(uint8_t *buffer, uint32_t size, bool dc_block, bool uint32_t peak = 0; if (peak_ptr) { *peak_ptr = peak; } - if (!audio_i2s.in->getRxRunning()) { return -1; } + if (!getRxRunning()) { return -1; } size_t btr = 0; esp_err_t err = i2s_channel_read(_rx_handle, buffer, size, &btr, 0 /* do not wait */); diff --git a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio_idf51.ino b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio_idf51.ino index fefbd5d36..c9e4f4cd3 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio_idf51.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio_idf51.ino @@ -366,7 +366,7 @@ exit: audio_i2s.client.stop(); } - audio_i2s.out->micDeinit(); + audio_i2s.out->stopRx(); audio_i2s.mic_stop = 0; audio_i2s.mic_error = error; AddLog(LOG_LEVEL_INFO, PSTR("mp3task result code: %d"), error); @@ -390,7 +390,7 @@ int32_t I2sRecordShine(char *path) { if (audio_i2s.use_stream) { stack = 8000; } - audio_i2s.out->micInit(); + audio_i2s.out->startRx(); err = xTaskCreatePinnedToCore(I2sMicTask, "MIC", stack, NULL, 3, &audio_i2s.mic_task_handle, 1); @@ -407,6 +407,7 @@ enum { I2S_ERR_INPUT_NOT_CONFIGURED, I2S_ERR_DECODER_IN_USE, I2S_ERR_FILE_NOT_FOUND, + I2S_ERR_TX_FAILED, }; // signal to an external Berry driver that we turn audio power on or off @@ -489,18 +490,8 @@ void I2sInit(void) { I2SSettingsLoad(AUDIO_CONFIG_FILENAME, false); // load configuration (no-erase) if (!audio_i2s.Settings) { return; } // fatal error, could not allocate memory for configuration - // detect if we need full-duplex on port 0 - bool duplex = false; - if ((gpio_din_0 >= 0) && (gpio_dout_0 >= 0)) { - // conditions are potentially favorable for duplex - if (audio_i2s.Settings->rx.mode == I2S_MODE_PDM || audio_i2s.Settings->tx.mode == I2S_MODE_PDM ){ - AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: PDM forbids full duplex mode, ignoring 'I2S DIN 1'")); - gpio_din_0 = -1; // hence deconfigure DIN_0 which can't be used - } else { - AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: will try to use full duplex mode")); - duplex = true; - } - } + bool duplex = false; // the same ports are used for input and output + bool exclusive = false; // signals that in/out have a shared GPIO and need to un/install driver before use // AddLog(LOG_LEVEL_INFO, PSTR("I2S: init pins bclk=%d, ws=%d, dout=%d, mclk=%d, din=%d"), // Pin(GPIO_I2S_BCLK, 0) , Pin(GPIO_I2S_WS, 0), Pin(GPIO_I2S_DOUT, 0), Pin(GPIO_I2S_MCLK, 0), Pin(GPIO_I2S_DIN, 0)); @@ -509,7 +500,6 @@ void I2sInit(void) { audio_i2s.Settings->sys.tx = false; audio_i2s.Settings->sys.rx = false; audio_i2s.Settings->sys.exclusive = false; - bool exclusive = false; // signals that in/out have a shared GPIO and need to un/install driver before use for (uint32_t port = 0; port < SOC_I2S_NUM; port++) { int32_t bclk = Pin(GPIO_I2S_BCLK, port); @@ -525,8 +515,15 @@ void I2sInit(void) { // if neither input, nor output, nor DAC/ADC skip (WS could is only needed for DAC but supports only port 0) if (din < 0 && dout < 0 && (ws < 0 || port !=0)) { continue; } + duplex = (din >= 0) && (dout >= 0); + if (duplex) { + if (audio_i2s.Settings->rx.mode == I2S_MODE_PDM || audio_i2s.Settings->tx.mode == I2S_MODE_PDM ){ + exclusive = true; + } + AddLog(LOG_LEVEL_DEBUG, "I2S: enabling duplex mode, exclusive;%i", exclusive); + } + const char *err_msg = nullptr; // to save code, we indicate an error with a message configured - bool duplex = (din >= 0) && (dout >= 0); bool dac_mode = false; if (din >= 0 || dout >= 0) { // we have regular I2S configuration @@ -568,12 +565,6 @@ void I2sInit(void) { else if (port != 0 && dout >= 0 && audio_i2s.Settings->tx.mode == I2S_MODE_PDM) { err_msg = "PDM Tx is not supported"; } - // 8. check that we don't try full-duplex with PDM in either direction - else if (duplex && (audio_i2s.Settings->rx.mode == I2S_MODE_PDM || audio_i2s.Settings->tx.mode == I2S_MODE_PDM )) { - err_msg = "PDM forbids full duplex mode"; - duplex = false; - din = -1; // deconfigure DIN_0 which can't be used - } } else { dac_mode = true; // no DIN/DOUT, try DAC mode @@ -592,8 +583,13 @@ void I2sInit(void) { tx = (dout >= 0) || dac_mode; rx = (din >= 0); - if (duplex) { - AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: will try to use full duplex mode")); + if (tx && audio_i2s.out) { + AddLog(LOG_LEVEL_DEBUG, "I2S: Warning: Tx already configured, skipping superfluous Tx configuration"); + tx = false; + } + if (rx && audio_i2s.in) { + AddLog(LOG_LEVEL_DEBUG, "I2S: Warning: Rx already configured, skipping superfluous Rx configuration"); + rx = false; } TasmotaI2S * i2s = new TasmotaI2S; @@ -611,35 +607,44 @@ void I2sInit(void) { i2s->setRxMode(audio_i2s.Settings->rx.mode); i2s->setRxFreq(audio_i2s.Settings->rx.sample_rate); i2s->setRxChannels(audio_i2s.Settings->rx.channels); - i2s->setRate(audio_i2s.Settings->rx.sample_rate); i2s->setRxGain(audio_i2s.Settings->rx.gain); } - if (i2s->startI2SChannel(tx, rx)) { - // succesful, register handlers - if (tx) { - audio_i2s.out = i2s; - audio_i2s.Settings->sys.tx = true; - } - if (rx) { - audio_i2s.in = i2s; - audio_i2s.Settings->sys.rx = true; - } - if (duplex) { - audio_i2s.Settings->sys.duplex = true; - } + bool init_tx_ok = false; + bool init_rx_ok = false; + if (tx && rx && exclusive) { + i2s->setExclusive(true); + audio_i2s.Settings->sys.exclusive = exclusive; + // in exclusive mode, we need to intialize in sequence Tx and Rx + init_tx_ok = i2s->startI2SChannel(true, false); + init_rx_ok = i2s->startI2SChannel(false, true); + } else if (tx && rx) { + init_tx_ok = init_rx_ok = i2s->startI2SChannel(true, true); + } else { + if (tx) { init_tx_ok = i2s->startI2SChannel(true, false); } + if (rx) { init_rx_ok = i2s->startI2SChannel(false, true); } } + if (init_tx_ok) { audio_i2s.out = i2s; } + if (init_rx_ok) { audio_i2s.in = i2s; } + audio_i2s.Settings->sys.tx = init_tx_ok; + audio_i2s.Settings->sys.rx = init_rx_ok; + if (init_tx_ok && init_rx_ok) { audio_i2s.Settings->sys.duplex = true; } + + // if intput and output are configured, don't proceed with other IS2 ports + if (audio_i2s.out && audio_i2s.in) { + break; + } + } // do we have exclusive mode? - audio_i2s.Settings->sys.exclusive = exclusive; if (audio_i2s.out) { audio_i2s.out->setExclusive(exclusive); } if (audio_i2s.in) { audio_i2s.in->setExclusive(exclusive); } if(audio_i2s.out != nullptr){ audio_i2s.out->SetGain(((float)audio_i2s.Settings->tx.gain / 100.0) * 4.0); - audio_i2s.out->begin(); - audio_i2s.out->stop(); + audio_i2s.out->beginTx(); // TODO - useful? + audio_i2s.out->stopTx(); } audio_i2s.mp3ram = nullptr; @@ -647,10 +652,6 @@ void I2sInit(void) { // if (UsePSRAM()) { AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: will allocate buffer for mp3 encoder")); audio_i2s.mp3ram = special_malloc(preallocateCodecSize); - // } - // else{ - // audio_i2s.Settings->sys.mp3_preallocate = 0; // no PS-RAM -> no MP3 encoding - // } } AddLog(LOG_LEVEL_DEBUG, "I2S: I2sInit done"); } @@ -669,9 +670,7 @@ int32_t I2SPrepareTx(void) { AddLog(LOG_LEVEL_DEBUG, "I2S: I2SPrepareTx out=%p", audio_i2s.out); if (!audio_i2s.out) { return I2S_ERR_OUTPUT_NOT_CONFIGURED; } - if (audio_i2s.Settings->sys.exclusive) { - // TODO - deconfigure input driver - } + if (!audio_i2s.out->beginTx()) { return I2S_ERR_TX_FAILED; } return I2S_OK; } @@ -804,7 +803,7 @@ void Say(char *text) { sam->Say(audio_i2s.out, text); delete sam; - audio_i2s.out->stop(); + audio_i2s.out->stopTx(); I2SAudioPower(false); } @@ -821,7 +820,7 @@ void CmndI2SMic(void) { esp_err_t err = ESP_OK; if (audio_i2s.decoder || audio_i2s.mp3) return; - audio_i2s.in->micInit(); + audio_i2s.in->startRx(); if (audio_i2s.in->getRxRunning()) { uint8_t buf[128]; @@ -873,6 +872,9 @@ void CmndI2SPlay(void) { case I2S_ERR_FILE_NOT_FOUND: ResponseCmndChar("File not found"); break; + case I2S_ERR_TX_FAILED: + ResponseCmndChar("Unable to open sound output"); + break; default: ResponseCmndChar("Unknown error"); break; diff --git a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_audio.ino b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_audio.ino index 58aac0419..9d2b44af7 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_audio.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_audio.ino @@ -323,13 +323,13 @@ extern "C" { // AudioInputI2S.begin() -> bool int be_audio_input_i2s_begin(bvm *vm, TasmotaI2S* in) { if (I2SPrepareRx()) { be_raisef(vm, "internal_error", "I2SPrepareRx() failed"); be_return_nil(vm); } - in->micInit(); + in->startRx(); return in->getRxRunning(); } // AudioInputI2S.stop() -> bool int be_audio_input_i2s_stop(TasmotaI2S* in) { - in->micDeinit(); + in->stopRx(); return true; }