From 9b95e963e6d00a242d104cb8308bbda80042cff3 Mon Sep 17 00:00:00 2001 From: Hema2-official <49586027+Hema2-official@users.noreply.github.com> Date: Thu, 4 Apr 2024 22:26:22 +0200 Subject: [PATCH] Add support for ESP32-S3 --- src/AudioOutputI2SNoDACS3.cpp | 184 ++++++++++++++++++++++++++++++++++ src/AudioOutputI2SNoDACS3.h | 62 ++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 src/AudioOutputI2SNoDACS3.cpp create mode 100644 src/AudioOutputI2SNoDACS3.h diff --git a/src/AudioOutputI2SNoDACS3.cpp b/src/AudioOutputI2SNoDACS3.cpp new file mode 100644 index 0000000..a4b9052 --- /dev/null +++ b/src/AudioOutputI2SNoDACS3.cpp @@ -0,0 +1,184 @@ +/* + AudioOutputI2SNoDACS3 + Base class for I2S interface port + + Copyright (C) 2017 Earle F. Philhower, III + + 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 . +*/ + +#include +#include "AudioOutputI2SNoDACS3.h" + +#if CONFIG_IDF_TARGET_ESP32S3 +AudioOutputI2SNoDACS3::AudioOutputI2SNoDACS3(int doutPin, int dummyPin, int port, int dma_buf_count) +{ + this->doutPin = (gpio_num_t)doutPin; + this->dummyPin = (gpio_num_t)dummyPin; + + this->portNo = port; + this->dma_buf_count = dma_buf_count; + + // Set initial I2S state + i2sOn = false; + + // Set defaults + mono = false; + bps = 16; + channels = 2; + hertz = 44100; + SetGain(1.0); +} + +AudioOutputI2SNoDACS3::~AudioOutputI2SNoDACS3() +{ + stop(); +} + +bool AudioOutputI2SNoDACS3::SetRate(int hz) +{ + // TODO - have a list of allowable rates from constructor, check them + this->hertz = hz; + if (i2sOn) + { + i2s_set_sample_rates((i2s_port_t)portNo, AdjustI2SRate(hz)); + } + return true; +} + +bool AudioOutputI2SNoDACS3::SetBitsPerSample(int bits) +{ + if ( (bits != 16) && (bits != 8) ) return false; + this->bps = bits; + return true; +} + +bool AudioOutputI2SNoDACS3::SetChannels(int channels) +{ + if ( (channels < 1) || (channels > 2) ) return false; + this->channels = channels; + return true; +} + +bool AudioOutputI2SNoDACS3::SetOutputModeMono(bool mono) +{ + this->mono = mono; + return true; +} + +bool AudioOutputI2SNoDACS3::begin() +{ + if (!i2sOn) + { + i2s_config_t i2s_config = { + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_PDM), + .sample_rate = 30000, // initial value ????????? + .bits_per_sample = i2s_bits_per_sample_t(bps), + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, + .communication_format = I2S_COMM_FORMAT_STAND_I2S, + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // lowest interrupt priority + .dma_buf_count = dma_buf_count, + .dma_buf_len = 128, + .use_apll = true, + .tx_desc_auto_clear = true, + .fixed_mclk = 0, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT, + }; + + audioLogger->printf("+%d %p\n", portNo, &i2s_config); + if (i2s_driver_install((i2s_port_t)portNo, &i2s_config, 0, NULL) != ESP_OK) + { + audioLogger->println(F("ERROR: Unable to install I2S drives\n")); + return false; + } + + // Set pinout + i2s_pin_config_t i2s_pinout = { + .bck_io_num = I2S_PIN_NO_CHANGE, // no bck + .ws_io_num = dummyPin, // dummy pin for LR clock + .data_out_num = doutPin, // PDM data out + .data_in_num = I2S_PIN_NO_CHANGE // no input + }; + + if (i2s_set_pin((i2s_port_t)portNo, &i2s_pinout) != ESP_OK) + { + audioLogger->println(F("ERROR: Unable to set I2S pins\n")); + return false; + } + + i2s_zero_dma_buffer((i2s_port_t)portNo); + i2s_start((i2s_port_t)portNo); + } + + i2sOn = true; + SetRate(hertz); + return true; +} + +bool AudioOutputI2SNoDACS3::ConsumeSample(int16_t sample[2]) +{ + + //return if we haven't called ::begin yet + if (!i2sOn) + return false; + + int16_t ms[2]; + + ms[0] = sample[0]; + ms[1] = sample[1]; + MakeSampleStereo16( ms ); + + if (this->mono) { + // Average the two samples and overwrite + int32_t ttl = ms[LEFTCHANNEL] + ms[RIGHTCHANNEL]; + ms[LEFTCHANNEL] = ms[RIGHTCHANNEL] = (ttl>>1) & 0xffff; + } + + uint32_t s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff); + + size_t i2s_bytes_written; + i2s_write((i2s_port_t)portNo, (const char*)&s32, sizeof(uint32_t), &i2s_bytes_written, 0); + return i2s_bytes_written; +} + +void AudioOutputI2SNoDACS3::flush() +{ + // makes sure that all stored DMA samples are consumed / played + int buffersize = 128 * this->dma_buf_count; + int16_t samples[2] = {0x0, 0x0}; + for (int i = 0; i < buffersize; i++) + { + while (!ConsumeSample(samples)) + { + delay(10); + } + } +} + +bool AudioOutputI2SNoDACS3::stop() +{ + if (!i2sOn) + return false; + + i2s_zero_dma_buffer((i2s_port_t)portNo); + + audioLogger->printf("UNINSTALL I2S\n"); + i2s_driver_uninstall((i2s_port_t)portNo); //stop & destroy i2s driver + + i2sOn = false; + return true; +} + +#endif // CONFIG_IDF_TARGET_ESP32S3 diff --git a/src/AudioOutputI2SNoDACS3.h b/src/AudioOutputI2SNoDACS3.h new file mode 100644 index 0000000..f1ca8ac --- /dev/null +++ b/src/AudioOutputI2SNoDACS3.h @@ -0,0 +1,62 @@ +/* + AudioOutputI2S + Base class for an I2S output port + + Copyright (C) 2017 Earle F. Philhower, III + + 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 . +*/ + +#pragma once + +#include "AudioOutput.h" +#include "driver/i2s.h" + +#if CONFIG_IDF_TARGET_ESP32S3 +class AudioOutputI2SNoDACS3 : public AudioOutput +{ + public: + /// @brief No-DAC Audio Output for ESP32-S3 + /// @param doutPin Pin to modulate audio on (PDM data) + /// @param dummyPin Dummy pin to use for WS/BCLK (PDM clock) + /// @param port + /// @param dma_buf_count + AudioOutputI2SNoDACS3(int doutPin, int dummyPin = GPIO_NUM_3, int port=0, int dma_buf_count = 8); + + virtual ~AudioOutputI2SNoDACS3() override; + virtual bool SetRate(int hz) override; + virtual bool SetBitsPerSample(int bits) override; + virtual bool SetChannels(int channels) override; + virtual bool begin() override; + virtual bool ConsumeSample(int16_t sample[2]) override; + virtual void flush() override; + virtual bool stop() override; + + bool SetOutputModeMono(bool mono); // Force mono output no matter the input + + protected: + virtual int AdjustI2SRate(int hz) { return (int)((float)hz / 1.0f); } + uint8_t portNo; + bool mono; + bool i2sOn; + int dma_buf_count; + + gpio_num_t doutPin; + gpio_num_t dummyPin; + +#if defined(ARDUINO_ARCH_RP2040) + I2S i2s; +#endif +}; +#endif \ No newline at end of file