/* AudioOutputI2S 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 #ifdef ESP32 #include #elif defined(ARDUINO_ARCH_RP2040) || ARDUINO_ESP8266_MAJOR >= 3 #include #elif ARDUINO_ESP8266_MAJOR < 3 #include #endif #include "AudioOutputI2S.h" AudioOutputI2S::AudioOutputI2S() { i2sOn = false; mono = false; lsb_justified = false; channels = 2; hertz = 44100; // Needs to be application-set #ifdef ESP32 bclkPin = 26; wclkPin = 25; doutPin = 22; #else bclkPin = 26; wclkPin = 27; doutPin = 28; #endif mclkPin = -1; SetGain(1.0); SetBuffers(); // Default DMA sizes } bool AudioOutputI2S::SetBuffers(int dmaBufferCount, int dmaBufferBytes) { if (i2sOn || (dmaBufferCount < 3) || (dmaBufferBytes & 3)) { return false; } _buffers = dmaBufferCount; _bufferWords = dmaBufferBytes / 4; return true; } #if defined(ESP32) || defined(ESP8266) AudioOutputI2S::AudioOutputI2S(int port, int output_mode, int dma_buf_count, int use_apll) : AudioOutputI2S() { (void) port; (void) output_mode; (void) use_apll; SetBuffers(dma_buf_count, 128 * 4); #ifdef ESP32 _useAPLL = use_apll; #endif } #if SOC_CLK_APLL_SUPPORTED bool AudioOutputI2S::SetUseAPLL() { if (i2sOn) { return false; } _useAPLL = true; return true; } #endif #elif defined(ARDUINO_ARCH_RP2040) AudioOutputI2S::AudioOutputI2S(long sampleRate, pin_size_t sck, pin_size_t data) : AudioOutputI2S() { (void) sampleRate; bclkPin = sck; wclkPin = sck + 1; doutPin = data; } #endif AudioOutputI2S::~AudioOutputI2S() { stop(); } bool AudioOutputI2S::SwapClocks(bool swap) { if (i2sOn) { return false; } if (swap) { auto t = bclkPin; bclkPin = wclkPin; wclkPin = t; } return true; } bool AudioOutputI2S::SetPinout(int bclk, int wclk, int dout, int mclk) { if (i2sOn) { return false; } bclkPin = bclk; wclkPin = wclk; doutPin = dout; mclkPin = mclk; return true; } bool AudioOutputI2S::SetRate(int hz) { if (hertz == hz) { return true; } hertz = hz; if (i2sOn) { auto adj = AdjustI2SRate(hz); // Possibly scale for NoDAC #ifdef ESP32 i2s_std_clk_config_t clk_cfg; clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG((uint32_t)adj); #if SOC_CLK_APLL_SUPPORTED clk_cfg.clk_src = _useAPLL ? i2s_clock_src_t::I2S_CLK_SRC_APLL : i2s_clock_src_t::I2S_CLK_SRC_DEFAULT; #endif i2s_channel_disable(_tx_handle); i2s_channel_reconfig_std_clock(_tx_handle, &clk_cfg); i2s_channel_enable(_tx_handle); #elif defined(ESP8266) i2s_set_rate(adj); #elif defined(ARDUINO_ARCH_RP2040) i2s.setFrequency(adj); #endif } return true; } bool AudioOutputI2S::SetChannels(int channels) { if ((channels < 1) || (channels > 2)) { return false; } this->channels = channels; return true; } bool AudioOutputI2S::SetOutputModeMono(bool mono) { this->mono = mono; return true; } bool AudioOutputI2S::SetLsbJustified(bool lsbJustified) { if (i2sOn) { return false; } this->lsb_justified = lsbJustified; return true; } bool AudioOutputI2S::begin() { #ifdef ESP32 i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); chan_cfg.dma_desc_num = _buffers; chan_cfg.dma_frame_num = _bufferWords; assert(ESP_OK == i2s_new_channel(&chan_cfg, &_tx_handle, nullptr)); i2s_std_config_t std_cfg = { .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(hertz), .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), .gpio_cfg = { .mclk = mclkPin < 0 ? I2S_GPIO_UNUSED : (gpio_num_t)mclkPin, .bclk = bclkPin < 0 ? I2S_GPIO_UNUSED : (gpio_num_t)bclkPin, .ws = wclkPin < 0 ? I2S_GPIO_UNUSED : (gpio_num_t)wclkPin, .dout = (gpio_num_t)doutPin, .din = I2S_GPIO_UNUSED, .invert_flags = { .mclk_inv = false, .bclk_inv = false, .ws_inv = false, }, }, }; #if SOC_CLK_APLL_SUPPORTED std_cfg.clk_cfg.clk_src = _useAPLL ? i2s_clock_src_t::I2S_CLK_SRC_APLL : i2s_clock_src_t::I2S_CLK_SRC_DEFAULT; #endif std_cfg.slot_cfg.bit_shift = !lsb_justified; // I2S = shift, LSBJ = no shift assert(ESP_OK == i2s_channel_init_std_mode(_tx_handle, &std_cfg)); // Fill w/0s to start off int16_t a[2] = {0, 0}; size_t written = 0; do { i2s_channel_preload_data(_tx_handle, (void*)a, sizeof(a), &written); } while (written); i2sOn = (ESP_OK == i2s_channel_enable(_tx_handle)); #elif defined(ESP8266) if (!i2sOn) { orig_bck = READ_PERI_REG(PERIPHS_IO_MUX_MTDO_U); orig_ws = READ_PERI_REG(PERIPHS_IO_MUX_GPIO2_U); #ifdef I2S_HAS_BEGIN_RXTX_DRIVE_CLOCKS if (!i2s_rxtxdrive_begin(false, true, false, true)) { return false; } #else if (!i2s_rxtx_begin(false, true)) { return false; } #endif i2sOn = true; } #elif defined(ARDUINO_ARCH_RP2040) if (!i2sOn) { i2s.setSysClk(hertz); if (wclkPin == bclkPin + 1) { i2s.setBCLK(bclkPin); } else if (wclkPin == bclkPin - 1) { // Swapped! i2s.setBCLK(bclkPin - 1); i2s.swapClocks(); } else { audioLogger->printf_P(PSTR("I2S: BCLK and WCLK must be adjacent\n")); return false; } i2s.setDATA(doutPin); if (mclkPin >= 0) { i2s.setMCLK(mclkPin); i2s.setMCLKmult(256); } i2s.setBitsPerSample(16); i2sOn = i2s.begin(hertz); } #endif SetRate(hertz ? hertz : 44100); // Default return true; } bool AudioOutputI2S::ConsumeSample(int16_t sample[2]) { 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; } #ifdef ESP32 uint32_t s32; s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff); size_t i2s_bytes_written = sizeof(uint32_t); i2s_channel_write(_tx_handle, (const char*)&s32, sizeof(uint32_t), &i2s_bytes_written, 0); return i2s_bytes_written; #elif defined(ESP8266) uint32_t s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff); return i2s_write_sample_nb(s32); // If we can't store it, return false. OTW true #elif defined(ARDUINO_ARCH_RP2040) uint32_t s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff); return !!i2s.write((int32_t)s32, false); #endif } #ifdef ARDUINO_ARCH_RP2040 uint16_t AudioOutputI2S::ConsumeSamples(int16_t *samples, uint16_t count) { // We special case the normal stereo, no gain case. OTW just use the regular full-fat path if (this->mono || (gainF2P6 != 1 << 6)) { return AudioOutput::ConsumeSamples(samples, count); } auto ret = i2s.write((const uint8_t *)samples, count * 4); ret /= 4; return ret; } #endif void AudioOutputI2S::flush() { #ifdef ESP32 // makes sure that all stored DMA samples are consumed / played int buffersize = _buffers * _bufferWords; int16_t samples[2] = {0x0, 0x0}; for (int i = 0; i < buffersize; i++) { while (!ConsumeSample(samples)) { delay(10); } } #elif defined(ARDUINO_ARCH_RP2040) i2s.flush(); #endif } bool AudioOutputI2S::stop() { if (!i2sOn) { return false; } #ifdef ESP32 i2s_channel_disable(_tx_handle); i2s_del_channel(_tx_handle); #elif defined(ESP8266) i2s_end(); #elif defined(ARDUINO_ARCH_RP2040) i2s.end(); #endif i2sOn = false; return true; }