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