/* SinglePinI2SDriver ESP8266Audio I2S Minimal driver Most of this code is taken and reworked from ESP8266 Arduino core, which itself is reworked from Espessif's I2S examples. Original code is licensed under LGPL 2.1 or above Reasons for rewrite: - Non-configurable size of DMA buffers - We only need GPIO3 connected to I2SO_DATA - No API to queue data from ISR. Callbacks are unusable as i2s_write_* functions are not in IRAM and may re-enable SLC interrupt before returning - ISR overhead is not needed Copyright (C) 2020 Ivan Kostoski This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #if defined(ESP8266) #include "SinglePinI2SDriver.h" SinglePinI2SDriver::~SinglePinI2SDriver() { stopI2S(); freeBuffers(bufCount); } bool SinglePinI2SDriver::begin(uint8_t bufCount, uint16_t bufSize) { if (bufCount < 4) return false; if ((bufSize < 32) || (bufSize > 1020) || ((bufSize % 4) != 0)) return false; stopSLC(); if ((bufCount != this->bufCount) || (bufSize != this->bufSize)) freeBuffers(this->bufCount); this->bufCount = bufCount; this->bufSize = bufSize; if (!allocateBuffers()) return false; zeroBuffers(); configureSLC(); startI2S(); // Send out at least one buffer, so we have valid address of // finished descriptor in SLCRXEDA head = &descriptors[0]; head->owner = 1; startSLC(); while (!lastSentDescriptor()) delayMicroseconds(1); // By here, SLC should be stalled (next descriptor owner is 0) head->owner = 0; head = head->next_link_ptr; headPos = 0; underflowCount = -1; return true; }; void SinglePinI2SDriver::stop() { stopI2S(); } bool SinglePinI2SDriver::allocateBuffers() { // Allocate output (RXLINK) descriptors and bufferss if (descriptors) return true; descriptors = (SLCDecriptor*) calloc(bufCount, sizeof(SLCDecriptor)); if (!descriptors) return false; int allocated; for (allocated = 0; allocated < bufCount; allocated++) { uint32_t* buffer = (uint32_t*)malloc(bufSize * sizeof(uint32_t)); if (!buffer) break; auto descriptor = &descriptors[allocated]; descriptor->eof = 1; // Needed for SLC to update SLCRXEDA descriptor->datalen = bufSize * sizeof(uint32_t); descriptor->blocksize = bufSize * sizeof(uint32_t); descriptor->buf_ptr = buffer; descriptor->next_link_ptr = &descriptors[(allocated+1) % bufCount]; } // Release memory if not all buffers were allocated if (allocated < bufCount) { freeBuffers(allocated); return false; } zeroBuffers(); return true; } void SinglePinI2SDriver::freeBuffers(int allocated) { if (!descriptors) return; while (--allocated >= 0) { if (descriptors[allocated].buf_ptr) { free(descriptors[allocated].buf_ptr); descriptors[allocated].buf_ptr = NULL; } } free(descriptors); descriptors = NULL; } void SinglePinI2SDriver::zeroBuffers() { for(int i = 0; i < bufCount; i++) { auto descriptor = &descriptors[i]; ets_memset((void *)descriptor->buf_ptr, 0, bufSize * sizeof(uint32_t)); } } inline void SinglePinI2SDriver::advanceHead(const SLCDecriptor *lastSent) { if (headPos >= bufSize) { head->owner = 1; // Owned by SLC head = head->next_link_ptr; head->owner = 0; // Owned by CPU headPos = 0; // If SLC stopped, fill-up the buffers before (re)starting if (isSLCStopped() && (head->next_link_ptr == lastSent)) { underflowCount++; startSLC(); } } } bool SinglePinI2SDriver::write(uint32_t sample) { auto lastSent = lastSentDescriptor(); if (isSLCRunning() && (lastSent->next_link_ptr == head)) return false; head->buf_ptr[headPos++] = sample; advanceHead(lastSent); return true; }; bool SinglePinI2SDriver::writeInterleaved(uint32_t samples[4]) { auto lastSent = lastSentDescriptor(); if (isSLCRunning() && (lastSent->next_link_ptr == head)) return false; uint32_t* buf = head->buf_ptr; buf[headPos++] = samples[1]; buf[headPos++] = samples[0]; buf[headPos++] = samples[3]; buf[headPos++] = samples[2]; advanceHead(lastSent); return true; }; void SinglePinI2SDriver::configureSLC() { ETS_SLC_INTR_DISABLE(); // Reset SLC, clear interrupts SLCC0 |= SLCRXLR | SLCTXLR; SLCC0 &= ~(SLCRXLR | SLCTXLR); SLCIC = 0xFFFFFFFF; // Configure SLC DMA in mode 1 SLCC0 &= ~(SLCMM << SLCM); SLCC0 |= (1 << SLCM); SLCRXDC |= SLCBINR | SLCBTNR; SLCRXDC &= ~(SLCBRXFE | SLCBRXEM | SLCBRXFM); // RXLINK is output DMA, but TXLINK must be configured with valid descriptr SLCTXL &= ~(SLCTXLAM << SLCTXLA); SLCRXL &= ~(SLCRXLAM << SLCRXLA); SLCTXL |= (uint32)&descriptors[0] << SLCTXLA; SLCRXL |= (uint32)&descriptors[0] << SLCRXLA; // Not setting up interrupts. Not starting DMA here } void SinglePinI2SDriver::stopSLC() { // Stop SLC, reset descriptors and clear interrupts ETS_SLC_INTR_DISABLE(); SLCTXL |= SLCTXLE; SLCRXL |= SLCRXLE; SLCTXL &= ~(SLCTXLAM << SLCTXLA); SLCRXL &= ~(SLCRXLAM << SLCRXLA); SLCIC = 0xFFFFFFFF; } void SinglePinI2SDriver::setDividers(uint8_t div1, uint8_t div2) { // Ensure dividers fit in bit fields div1 &= I2SBDM; div2 &= I2SCDM; // trans master(active low), recv master(active_low), !bits mod(==16 bits/channel), clear clock dividers I2SC &= ~(I2STSM | I2SRSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD)); // I2SRF = Send/recv right channel first // I2SMR = MSB recv/xmit first // I2SRMS, I2STMS = We don't really care about WS delay here // div1, div2 = Set I2S WS clock frequency. BCLK seems to be generated from 32x this I2SC |= I2SRF | I2SMR | I2SRMS | I2STMS | (div1 << I2SBD) | (div2 << I2SCD); } void SinglePinI2SDriver::setRate(const uint32_t rateHz) { if (rateHz == currentRate) return; currentRate = rateHz; uint32_t scaledBaseFreq = I2SBASEFREQ / 32; float bestDelta = scaledBaseFreq; uint8_t sbdDivBest=1; uint8_t scdDivBest=1; for (uint8_t i = 1; i < 64; i++) { for (uint8_t j = i; j < 64; j++) { float delta = fabs(((float)scaledBaseFreq/i/j) - rateHz); if (delta < bestDelta){ bestDelta = delta; sbdDivBest = i; scdDivBest = j; } } } setDividers(sbdDivBest, scdDivBest); } float SinglePinI2SDriver::getActualRate() { return (float)I2SBASEFREQ/32/((I2SC>>I2SBD) & I2SBDM)/((I2SC >> I2SCD) & I2SCDM); } void SinglePinI2SDriver::startI2S() { // Setup only I2S SD pin for output pinMode(I2SO_DATA, FUNCTION_1); I2S_CLK_ENABLE(); // Reset I2S I2SIC = 0x3F; I2SIE = 0; I2SC &= ~(I2SRST); I2SC |= I2SRST; I2SC &= ~(I2SRST); // I2STXFMM, I2SRXFMM=0 => 16-bit, dual channel data I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM)); I2SFC |= I2SDE; // Enable DMA // I2STXCMM, I2SRXCMM=0 => Dual channel mode, RX/TX CHAN_MOD=0 I2SCC &= ~((I2STXCMM << I2STXCM) | (I2SRXCMM << I2SRXCM)); // Set dividers to something reasonable currentRate = 0; setRate(44100); // Start I2S peripheral I2SC |= I2STXS; } void SinglePinI2SDriver::stopI2S() { // Disable any I2S send or receive I2SC &= ~(I2STXS | I2SRXS); // Reset I2S I2SC &= ~(I2SRST); I2SC |= I2SRST; I2SC &= ~(I2SRST); stopSLC(); // Reconnect RX0 ? //pinMode(I2SO_DATA, SPECIAL); } int SinglePinI2SDriver::getUnderflowCount() { int count = underflowCount; underflowCount = 0; return count; } // Global instance SinglePinI2SDriver I2SDriver; #endif // defined(ESP8266)