Files
ESP8266Audio/src/driver/SinglePinI2SDriver.cpp
2023-01-04 14:01:20 +01:00

284 lines
7.9 KiB
C++

/*
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)