From 4b240545bda2397a379b73aaddc65a5e185a8830 Mon Sep 17 00:00:00 2001 From: gemu2015 Date: Mon, 15 Aug 2022 13:54:03 +0200 Subject: [PATCH 1/2] i2s bridge --- .../xdrv_42_0_i2s_audio.ino | 66 ++++- .../xdrv_42_1_i2s_mp3mic.ino | 23 +- .../xdrv_42_4_i2s_codecs.ino | 2 +- .../xdrv_42_5_i2s_bridge.ino | 270 ++++++++++++++++++ 4 files changed, 337 insertions(+), 24 deletions(-) create mode 100644 tasmota/tasmota_xdrv_driver/xdrv_42_5_i2s_bridge.ino diff --git a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio.ino b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio.ino index 8c1259d0f..ead3d5a98 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio.ino @@ -67,14 +67,25 @@ #define i2s_port_t uint8_t #endif -#define MODE_MIC 0 -#define MODE_SPK 1 +#define MODE_MIC 1 +#define MODE_SPK 2 #ifndef MICSRATE #define MICSRATE 32000 #endif +typedef union { + uint8_t data; + struct { + uint8_t master : 1; + uint8_t enabled : 1; + uint8_t swap_mic : 1; + uint8_t mode : 2; + }; +} BRIDGE_MODE; + + struct AUDIO_I2S_t { uint8_t is2_volume; // should be in settings i2s_port_t i2s_port; @@ -134,6 +145,16 @@ struct AUDIO_I2S_t { uint8_t mode; +#ifdef I2S_BRIDGE + BRIDGE_MODE bridge_mode; + WiFiUDP i2s_bridge_udp; + WiFiUDP i2s_bridgec_udp; + IPAddress i2s_bridge_ip; + TaskHandle_t i2s_bridge_h; + int8_t ptt_pin = -1; +#endif + + } audio_i2s; #ifndef MIC_CHANNELS @@ -308,11 +329,6 @@ int32_t I2S_Init_0(void) { audio_i2s.mode = MODE_SPK; -#ifdef USE_I2S_COMMON_IO - audio_i2s.out->SetRate(MICSRATE); -#endif - - return 0; } @@ -327,11 +343,12 @@ void I2S_Init(void) { #endif #ifdef USE_W8960 - W8960_Init(); + W8960_Init1(); #endif audio_i2s.is2_volume = 10; audio_i2s.out->SetGain(((float)audio_i2s.is2_volume / 100.0) * 4.0); + audio_i2s.out->begin(); audio_i2s.out->stop(); audio_i2s.mp3ram = nullptr; @@ -356,7 +373,12 @@ void I2S_Init(void) { audio_i2s.mic_channels = MIC_CHANNELS; audio_i2s.mic_rate = MICSRATE; +#ifdef USE_I2S_COMMON_IO + i2s_set_clk(audio_i2s.mic_port, audio_i2s.mic_rate, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_STEREO); +#endif + #endif // ESP32 + } #ifdef ESP32 @@ -589,12 +611,15 @@ const char kI2SAudio_Commands[] PROGMEM = "I2S|" #ifdef USE_I2S_WEBRADIO "|WR" #endif // USE_I2S_WEBRADIO -#if defined(USE_SHINE) && ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) ) +#if ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) ) "|REC" "|MGain" -#ifdef MP3_MIC_STREAM +#if defined(USE_SHINE) && defined(MP3_MIC_STREAM) "|STREAM" #endif // MP3_MIC_STREAM +#ifdef I2S_BRIDGE + "|BRIDGE" +#endif // I2S_BRIDGE #endif // USE_SHINE #endif // ESP32 ; @@ -609,12 +634,15 @@ void (* const I2SAudio_Command[])(void) PROGMEM = { #ifdef USE_I2S_WEBRADIO ,&Cmd_WebRadio #endif // USE_I2S_WEBRADIO -#if defined(USE_SHINE) && ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) ) +#if ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) ) ,&Cmd_MicRec ,&Cmd_MicGain -#ifdef MP3_MIC_STREAM +#if defined(USE_SHINE) && defined(MP3_MIC_STREAM) ,&Cmd_MP3Stream #endif // MP3_MIC_STREAM +#ifdef I2S_BRIDGE + ,&Cmd_I2SBridge +#endif // I2S_BRIDGE #endif // USE_SHINE #endif // ESP32 }; @@ -661,19 +689,27 @@ bool Xdrv42(uint8_t function) { case FUNC_INIT: I2S_Init(); break; -#if defined(MP3_MIC_STREAM) -//#if defined(USE_SHINE) && defined(MP3_MIC_STREAM) case FUNC_WEB_ADD_MAIN_BUTTON: //MP3ShowStream(); break; case FUNC_LOOP: +#if defined(USE_SHINE) && defined(MP3_MIC_STREAM) i2s_mp3_loop(); +#endif +#if defined(I2S_BRIDGE) && ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) ) + i2s_bridge_loop(); +#endif break; case FUNC_WEB_ADD_HANDLER: +#if defined(USE_SHINE) && defined(MP3_MIC_STREAM) audio_i2s.stream_enable = 1; i2s_mp3_init(1); - break; #endif +#if defined(I2S_BRIDGE) && ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) ) + I2SBridgeInit(); +#endif + break; + #ifdef USE_WEBSERVER #ifdef USE_I2S_WEBRADIO case FUNC_WEB_SENSOR: diff --git a/tasmota/tasmota_xdrv_driver/xdrv_42_1_i2s_mp3mic.ino b/tasmota/tasmota_xdrv_driver/xdrv_42_1_i2s_mp3mic.ino index d9ba85259..01a41f959 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_42_1_i2s_mp3mic.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_42_1_i2s_mp3mic.ino @@ -19,7 +19,7 @@ #ifdef ESP32 -#if defined(USE_SHINE) && ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) ) +#if ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) ) uint32_t SpeakerMic(uint8_t spkr) { @@ -27,7 +27,11 @@ esp_err_t err = ESP_OK; #ifndef USE_I2S_COMMON_IO + if (audio_i2s.mode == spkr) { + return 0; + } if (spkr == MODE_SPK) { + if (audio_i2s.i2s_port == audio_i2s.mic_port) { if (audio_i2s.mode != MODE_SPK) { i2s_driver_uninstall(audio_i2s.mic_port); @@ -61,24 +65,26 @@ esp_err_t err = ESP_OK; .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, .communication_format = I2S_COMM_FORMAT_I2S, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, + //.dma_buf_count = 8, .dma_buf_count = 2, //.dma_buf_len = 128, .dma_buf_len = 1024, .use_apll = 0, // Use audio PLL .tx_desc_auto_clear = true, - .fixed_mclk = 0, + .fixed_mclk = 12000000, .mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT, // I2S_MCLK_MULTIPLE_128 .bits_per_chan = I2S_BITS_PER_CHAN_16BIT }; -#ifdef ESP32S3_BOX + +#ifdef USE_I2S_MIC + // mic select to GND i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX); i2s_config.communication_format = I2S_COMM_FORMAT_STAND_I2S; #endif -#ifdef USE_I2S_MIC - // mic select to GND - i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX); +#ifdef ESP32S3_BOX + i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX); i2s_config.communication_format = I2S_COMM_FORMAT_STAND_I2S; #endif @@ -117,8 +123,8 @@ esp_err_t err = ESP_OK; return err; } - #ifdef USE_SHINE + #include #include @@ -306,6 +312,8 @@ void Cmd_MicRec(void) { } } +#endif // USE_SHINE + // mic gain in factor not percent void Cmd_MicGain(void) { @@ -315,6 +323,5 @@ void Cmd_MicGain(void) { ResponseCmndNumber(audio_i2s.mic_gain); } -#endif // USE_SHINE #endif // USE_I2S_AUDIO #endif // ESP32 diff --git a/tasmota/tasmota_xdrv_driver/xdrv_42_4_i2s_codecs.ino b/tasmota/tasmota_xdrv_driver/xdrv_42_4_i2s_codecs.ino index 1b66edbf9..c7092475b 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_42_4_i2s_codecs.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_42_4_i2s_codecs.ino @@ -142,7 +142,7 @@ void S3boxInit(void) { #include -void W8960_Init(void) { +void W8960_Init1(void) { if (TasmotaGlobal.i2c_enabled_2) { if (I2cSetDevice(W8960_ADDR, 1)) { I2cSetActiveFound(W8960_ADDR, "W8960-I2C", 1); diff --git a/tasmota/tasmota_xdrv_driver/xdrv_42_5_i2s_bridge.ino b/tasmota/tasmota_xdrv_driver/xdrv_42_5_i2s_bridge.ino new file mode 100644 index 000000000..d0a74f99d --- /dev/null +++ b/tasmota/tasmota_xdrv_driver/xdrv_42_5_i2s_bridge.ino @@ -0,0 +1,270 @@ + +/* + audio is2 support pcm bridge + + Copyright (C) 2022 Gerhard Mutz and Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#ifdef ESP32 +#if (defined(USE_I2S_AUDIO) || defined(USE_TTGO_WATCH) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) || defined(USE_I2S_MIC)) +#ifdef I2S_BRIDGE + +#ifndef I2S_BRIDGE_PORT +#define I2S_BRIDGE_PORT 6970 +#endif + +#define I2S_BRIDGE_BUFFER_SIZE 512 + +#define I2S_BRIDGE_MODE_OFF 0 +#define I2S_BRIDGE_MODE_READ 1 +#define I2S_BRIDGE_MODE_WRITE 2 + +void i2s_bridge_init(uint8_t mode) { + + audio_i2s.bridge_mode.mode = mode; + + if (I2S_BRIDGE_MODE_OFF == mode) { + audio_i2s.i2s_bridge_udp.flush(); + audio_i2s.i2s_bridge_udp.stop(); + //SpeakerMic(MODE_SPK); + AUDIO_PWR_OFF + } else { + i2s_set_clk(audio_i2s.mic_port, audio_i2s.mic_rate, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_STEREO); + + if ((mode & 3) == I2S_BRIDGE_MODE_WRITE) { + //SpeakerMic(MODE_MIC); + //REG_SET_BIT(I2S_TIMING_REG(audio_i2s.mic_port), BIT(9)); + //REG_SET_BIT(I2S_CONF_REG(audio_i2s.mic_port), I2S_RX_MSB_SHIFT); + } else { + //SpeakerMic(MODE_SPK); + } + + audio_i2s.i2s_bridge_udp.begin(I2S_BRIDGE_PORT); + xTaskCreatePinnedToCore(i2s_bridge_task, "BRIDGE", 8192, NULL, 3, &audio_i2s.i2s_bridge_h, 1); + if (!audio_i2s.bridge_mode.master) { + AddLog(LOG_LEVEL_INFO, PSTR("I2S_bridge: slave started")); + } else { + char buffer[32]; + sprintf_P(buffer, PSTR("%u.%u.%u.%u"), audio_i2s.i2s_bridge_ip[0], audio_i2s.i2s_bridge_ip[1], audio_i2s.i2s_bridge_ip[2], audio_i2s.i2s_bridge_ip[3]); + AddLog(LOG_LEVEL_INFO, PSTR("I2S_bridge: master started sending to ip: %s"), buffer); + } + AUDIO_PWR_ON + } +} + +// make mono +void make_mono(int16_t *packet_buffer, uint32_t size) { + int16_t *wp = (int16_t*)packet_buffer; + for (uint32_t cnt = 0; cnt < size / 2; cnt += 2) { + int16_t val; + if (audio_i2s.bridge_mode.swap_mic) { + val = wp[cnt + 1] * audio_i2s.mic_gain; + } else { + val = wp[cnt] * audio_i2s.mic_gain; + } + wp[cnt] = val; + wp[cnt + 1] = val; + } +} + +void i2s_bridge_task(void *arg) { +int16_t packet_buffer[I2S_BRIDGE_BUFFER_SIZE/2]; +uint16_t bytesize; + + while (I2S_BRIDGE_MODE_OFF != audio_i2s.bridge_mode.mode) { + if ((audio_i2s.bridge_mode.mode & 3) == 3) { + // loopback test mode + uint32_t bytes_read; + bytesize = I2S_BRIDGE_BUFFER_SIZE; + i2s_read(audio_i2s.mic_port, (char *)packet_buffer, bytesize, &bytes_read, (100 / portTICK_RATE_MS)); + make_mono(packet_buffer, bytes_read); + uint32_t bytes_written; + i2s_write(audio_i2s.i2s_port, (const uint8_t*)packet_buffer, bytes_read, &bytes_written, 0); + } else { + if (audio_i2s.bridge_mode.mode & I2S_BRIDGE_MODE_READ) { + if (audio_i2s.i2s_bridge_udp.parsePacket()) { + uint32_t bytes_written; + uint32_t len = audio_i2s.i2s_bridge_udp.available(); + if (len > I2S_BRIDGE_BUFFER_SIZE) { + len = I2S_BRIDGE_BUFFER_SIZE; + } + len = audio_i2s.i2s_bridge_udp.read((uint8_t *)packet_buffer, len); + audio_i2s.i2s_bridge_udp.flush(); + i2s_write(audio_i2s.i2s_port, (const uint8_t*)packet_buffer, len, &bytes_written, 0); + } else { + delay(1); + } + } + + if (audio_i2s.bridge_mode.mode & I2S_BRIDGE_MODE_WRITE) { + uint32_t bytes_read; + bytesize = I2S_BRIDGE_BUFFER_SIZE; + i2s_read(audio_i2s.mic_port, (char *)packet_buffer, bytesize, &bytes_read, (100 / portTICK_RATE_MS)); + make_mono(packet_buffer, bytes_read); + audio_i2s.i2s_bridge_udp.beginPacket(audio_i2s.i2s_bridge_ip, I2S_BRIDGE_PORT); + audio_i2s.i2s_bridge_udp.write((const uint8_t*)packet_buffer, bytes_read); + audio_i2s.i2s_bridge_udp.endPacket(); + } + } + } + AddLog(LOG_LEVEL_INFO, PSTR("I2S_bridge: stopped")); + vTaskDelete(NULL); +} + +void Cmd_I2SBridge(void) { + if (XdrvMailbox.data_len > 0) { + char *cp = XdrvMailbox.data; + if (strchr(cp, '.')) { + // enter destination ip + audio_i2s.i2s_bridge_ip.fromString(cp); + Response_P(PSTR("{\"I2S_bridge\":{\"IP\":\"%s\"}}"), cp); + } else if (cp = strchr(cp, 'p')) { + // enter push to talk pin + cp++; + audio_i2s.ptt_pin = atoi(cp); + pinMode(audio_i2s.ptt_pin, INPUT_PULLUP); + Response_P(PSTR("{\"I2S_bridge\":{\"PTT-PIN\":%d}}"), audio_i2s.ptt_pin); + } else { + I2SBridgeCmd(XdrvMailbox.payload, 1); + } + } +} + +void SendBridgeCmd(uint8_t mode) { +char slavecmd[16]; + if (audio_i2s.bridge_mode.master) { + sprintf(slavecmd,"cmd:%d", mode); + audio_i2s.i2s_bridgec_udp.beginPacket(audio_i2s.i2s_bridge_ip, I2S_BRIDGE_PORT + 1); + audio_i2s.i2s_bridgec_udp.write((const uint8_t*)slavecmd,strlen(slavecmd)); + audio_i2s.i2s_bridgec_udp.endPacket(); + } +} + +void I2SBridgeCmd(uint8_t val, uint8_t flg) { + if ((val >= 0) && (val <= 11)) { + if (val > 3) { + switch (val) { + case 4: + audio_i2s.bridge_mode.master = 1; + break; + case 5: + audio_i2s.bridge_mode.master = 0; + break; + case 6: + audio_i2s.bridge_mode.swap_mic = 1; + break; + case 7: + audio_i2s.bridge_mode.swap_mic = 0; + break; + } + Response_P(PSTR("{\"I2S_bridge\":{\"SWAP_MIC\":%d}}"), audio_i2s.bridge_mode.swap_mic); + } else { + if (audio_i2s.bridge_mode.mode != val) { + if ((val == I2S_BRIDGE_MODE_OFF) && (audio_i2s.bridge_mode.mode != I2S_BRIDGE_MODE_OFF)) { + if (flg && (audio_i2s.bridge_mode.master)) { + // shutdown slave + SendBridgeCmd(I2S_BRIDGE_MODE_OFF); + } + i2s_bridge_init(I2S_BRIDGE_MODE_OFF); + } else { + if (audio_i2s.bridge_mode.mode == I2S_BRIDGE_MODE_OFF) { + // initial on + i2s_bridge_init(val); + } else { + // change mode + if (val & I2S_BRIDGE_MODE_READ) { + //SpeakerMic(MODE_SPK); + } + if (val & I2S_BRIDGE_MODE_WRITE) { + //SpeakerMic(MODE_MIC); + } + } + } + + audio_i2s.bridge_mode.mode = val; + + if (flg) { + if (audio_i2s.bridge_mode.master) { + // set slave to complementary mode + if (audio_i2s.bridge_mode.mode && ((audio_i2s.bridge_mode.mode & 3) != 3)) { + uint8_t slavemode = I2S_BRIDGE_MODE_READ; + if (audio_i2s.bridge_mode.mode & I2S_BRIDGE_MODE_READ) { + slavemode = I2S_BRIDGE_MODE_WRITE; + } + SendBridgeCmd(slavemode); + } + } + } + } + ResponseCmndNumber(audio_i2s.bridge_mode.mode); + } + } +} + +void i2s_bridge_loop(void) { + uint8_t packet_buffer[64]; + + if (TasmotaGlobal.global_state.wifi_down) { + return; + } + + if (audio_i2s.ptt_pin >= 0) { + if (audio_i2s.bridge_mode.mode != I2S_BRIDGE_MODE_OFF) { + if (digitalRead(audio_i2s.ptt_pin) == 0) { + // push to talk + if (audio_i2s.bridge_mode.mode != I2S_BRIDGE_MODE_WRITE) { + I2SBridgeCmd(I2S_BRIDGE_MODE_WRITE, 1); + } + + } else { + if (audio_i2s.bridge_mode.mode != I2S_BRIDGE_MODE_READ) { + I2SBridgeCmd(I2S_BRIDGE_MODE_READ, 1); + } + } + } + } + + if (audio_i2s.i2s_bridgec_udp.parsePacket()) { + // received control command + memset(packet_buffer,0,sizeof(packet_buffer)); + audio_i2s.i2s_bridgec_udp.read(packet_buffer, 63); + char *cp = (char*)packet_buffer; + if (!strncmp(cp, "cmd:", 4)) { + cp += 4; + I2SBridgeCmd(atoi(cp), 0); + audio_i2s.i2s_bridge_ip = audio_i2s.i2s_bridgec_udp.remoteIP(); + AddLog(LOG_LEVEL_INFO, PSTR("I2S_bridge: remote cmd %d"), audio_i2s.bridge_mode.mode); + } + } + + + +} + + +void I2SBridgeInit(void) { + // start udp control channel + audio_i2s.i2s_bridgec_udp.begin(I2S_BRIDGE_PORT + 1); + //audio_i2s.i2s_bridgec_udp.flush(); + //audio_i2s.i2s_bridgec_udp.stop(); + //I2SBridgeCmd(audio_i2s.bridge_mode.mode, 1); + AddLog(LOG_LEVEL_INFO, PSTR("I2S_bridge: command server created on port: %d "), I2S_BRIDGE_PORT + 1); +} + +#endif // I2S_BRIDGE +#endif // USE_I2S_AUDIO +#endif // ESP32 From cae151be2bb69b39203ece58948e360e8f5bb066 Mon Sep 17 00:00:00 2001 From: gemu2015 Date: Mon, 15 Aug 2022 13:54:42 +0200 Subject: [PATCH 2/2] Update wm8960.cpp --- lib/libesp32_audio/wm8960/src/wm8960.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/libesp32_audio/wm8960/src/wm8960.cpp b/lib/libesp32_audio/wm8960/src/wm8960.cpp index d8211831c..076b28475 100755 --- a/lib/libesp32_audio/wm8960/src/wm8960.cpp +++ b/lib/libesp32_audio/wm8960/src/wm8960.cpp @@ -109,6 +109,10 @@ void W8960_SetGain(uint8_t sel, uint16_t value) { W8960_Write(0x15, value); W8960_Write(0x16, value); break; + case 3: + // audio interface + W8960_Write(0x07, value); + break; } } #endif // ESP32