import esp audio lib
This commit is contained in:
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
SinglePinI2SDriver
|
||||
ESP8266Audio I2S Minimal driver
|
||||
|
||||
Most of this code is taken and reworked from ESP8266 Arduino core,
|
||||
which itsef 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/chanel), 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 resonable
|
||||
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)
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
SinglePinI2SDriver
|
||||
ESP8266Audio I2S Minimal driver
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#if defined(ESP8266)
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "osapi.h"
|
||||
#include "ets_sys.h"
|
||||
#include "i2s_reg.h"
|
||||
|
||||
struct SLCDecriptor {
|
||||
uint32_t blocksize : 12;
|
||||
uint32_t datalen : 12;
|
||||
uint32_t unused : 5;
|
||||
uint32_t sub_sof : 1;
|
||||
uint32_t eof : 1;
|
||||
volatile uint32_t owner : 1;
|
||||
uint32_t* buf_ptr;
|
||||
SLCDecriptor* next_link_ptr;
|
||||
};
|
||||
|
||||
class SinglePinI2SDriver
|
||||
{
|
||||
public:
|
||||
enum : uint8_t { I2SO_DATA = 3 }; // GPIO3/RX0
|
||||
|
||||
SinglePinI2SDriver(): descriptors(NULL), bufCount(0), bufSize(0) {};
|
||||
~SinglePinI2SDriver();
|
||||
|
||||
bool begin(uint8_t bufCount, uint16_t bufSize);
|
||||
void stop();
|
||||
void setRate(const uint32_t rateHz);
|
||||
float getActualRate();
|
||||
bool write(uint32_t sample);
|
||||
bool writeInterleaved(uint32_t samples[4]);
|
||||
int getUnderflowCount();
|
||||
|
||||
protected:
|
||||
bool allocateBuffers();
|
||||
void freeBuffers(int allocated);
|
||||
void zeroBuffers();
|
||||
void advanceHead(const SLCDecriptor *finished);
|
||||
void configureSLC();
|
||||
void stopSLC();
|
||||
void setDividers(uint8_t div1, uint8_t div2);
|
||||
void startI2S();
|
||||
void stopI2S();
|
||||
SLCDecriptor *descriptors;
|
||||
uint16_t bufCount;
|
||||
uint16_t bufSize;
|
||||
SLCDecriptor *head;
|
||||
uint32_t headPos;
|
||||
uint32_t currentRate;
|
||||
int underflowCount;
|
||||
|
||||
// (Re)Start transmission ("TX" DMA always needed to be enabled)
|
||||
static inline void startSLC() { SLCTXL |= SLCTXLS; SLCRXL |= SLCRXLS; }
|
||||
static inline uint32_t isSLCRunning() { return (SLCRXS & SLCRXF); };
|
||||
static inline uint32_t isSLCStopped() { return (SLCRXS & SLCRXE); };
|
||||
static inline SLCDecriptor *lastSentDescriptor() { return (SLCDecriptor*)SLCRXEDA; }
|
||||
};
|
||||
|
||||
// Global instance
|
||||
extern SinglePinI2SDriver I2SDriver;
|
||||
|
||||
#endif // defined(ESP8266)
|
||||
Reference in New Issue
Block a user