initial commit
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
/*!
|
||||
* @file Adafruit_ZeroPDMSPI.cpp
|
||||
*/
|
||||
#include "Adafruit_ZeroPDMSPI.h"
|
||||
|
||||
/// @cond DISABLE
|
||||
#if defined(__SAMD51__)
|
||||
/// @endcond DISABLE
|
||||
|
||||
static uint16_t const sincfilter[64] = {
|
||||
0, 2, 9, 21, 39, 63, 94, 132, 179, 236, 302,
|
||||
379, 467, 565, 674, 792, 920, 1055, 1196, 1341, 1487, 1633,
|
||||
1776, 1913, 2042, 2159, 2263, 2352, 2422, 2474, 2506, 2516, 2506,
|
||||
2474, 2422, 2352, 2263, 2159, 2042, 1913, 1776, 1633, 1487, 1341,
|
||||
1196, 1055, 920, 792, 674, 565, 467, 379, 302, 236, 179,
|
||||
132, 94, 63, 39, 21, 9, 2, 0, 0};
|
||||
|
||||
#define DC_PERIOD 4096 //!< Recalculate DC offset this many samplings
|
||||
// DC_PERIOD does NOT need to be a power of 2, but might save a few cycles.
|
||||
// PDM rate is 46875, so 4096 = 11.44 times/sec
|
||||
|
||||
static Sercom* const sercomList[] = {
|
||||
SERCOM0, SERCOM1, SERCOM2, SERCOM3,
|
||||
#if defined(SERCOM4)
|
||||
SERCOM4,
|
||||
#endif
|
||||
#if defined(SERCOM5)
|
||||
SERCOM5,
|
||||
#endif
|
||||
#if defined(SERCOM6)
|
||||
SERCOM6,
|
||||
#endif
|
||||
#if defined(SERCOM7)
|
||||
SERCOM7,
|
||||
#endif
|
||||
};
|
||||
|
||||
static IRQn_Type const sercomIRQList[]{
|
||||
SERCOM0_0_IRQn, SERCOM1_0_IRQn, SERCOM2_0_IRQn, SERCOM3_0_IRQn,
|
||||
#if defined(SERCOM4)
|
||||
SERCOM4_0_IRQn,
|
||||
#endif
|
||||
#if defined(SERCOM5)
|
||||
SERCOM5_0_IRQn,
|
||||
#endif
|
||||
#if defined(SERCOM6)
|
||||
SERCOM6_0_IRQn,
|
||||
#endif
|
||||
#if defined(SERCOM7)
|
||||
SERCOM7_0_IRQn,
|
||||
#endif
|
||||
};
|
||||
|
||||
Adafruit_ZeroPDMSPI::Adafruit_ZeroPDMSPI(SPIClass* theSPI) {
|
||||
_spi = theSPI;
|
||||
}
|
||||
|
||||
bool Adafruit_ZeroPDMSPI::begin(uint32_t freq) {
|
||||
freq *= 64;
|
||||
SPISettings settings(freq, LSBFIRST, SPI_MODE0);
|
||||
|
||||
_spi->begin();
|
||||
_spi->beginTransaction(settings); // this SPI transaction is left open
|
||||
_sercom = sercomList[_spi->getSercomIndex()];
|
||||
_irq = sercomIRQList[_spi->getSercomIndex()];
|
||||
_dataReg = _spi->getDataRegister();
|
||||
|
||||
// Enabling 32-bit SPI must be done AFTER SPI.begin() which
|
||||
// resets registers. But SPI.CTRLC (where 32-bit mode is set) is
|
||||
// enable-protected, so peripheral must be disabled temporarily...
|
||||
_sercom->SPI.CTRLA.bit.ENABLE = 0; // Disable SPI
|
||||
while (_sercom->SPI.SYNCBUSY.bit.ENABLE)
|
||||
; // Wait for disable
|
||||
_sercom->SPI.CTRLC.bit.DATA32B = 1; // Enable 32-bit mode
|
||||
_sercom->SPI.CTRLA.bit.ENABLE = 1; // Re-enable SPI
|
||||
while (_sercom->SPI.SYNCBUSY.bit.ENABLE)
|
||||
; // Wait for enable
|
||||
// 4-byte word length is implicit in 32-bit mode,
|
||||
// no need to set up LENGTH register.
|
||||
|
||||
_sercom->SPI.INTENSET.bit.DRE = 1; // Data-register-empty interrupt
|
||||
NVIC_DisableIRQ(_irq);
|
||||
NVIC_ClearPendingIRQ(_irq);
|
||||
NVIC_SetPriority(_irq, 0); // Top priority
|
||||
NVIC_EnableIRQ(_irq);
|
||||
|
||||
_sercom->SPI.DATA.bit.DATA = 0; // Kick off SPI free-run
|
||||
|
||||
// sampleRate is float in case factors change to make it not divide evenly.
|
||||
// It DOES NOT CHANGE over time, only playbackRate does.
|
||||
sampleRate = (48000000.0 / 2.0) / ((_sercom->SPI.BAUD.reg + 1) * 64.0);
|
||||
|
||||
return true; // Success
|
||||
}
|
||||
|
||||
bool Adafruit_ZeroPDMSPI::decimateFilterWord(uint16_t* value, bool removeDC) {
|
||||
static bool evenWord = true; // Alternates 0/1 with each interrupt call
|
||||
static uint32_t sumTemp = 0; // Temp. value used across 2 interrupt calls
|
||||
// Shenanigans: SPI data read/write are shadowed...even though it appears
|
||||
// the same register here, it's legit to write new MOSI value before
|
||||
// reading the received MISO value from the same location. This helps
|
||||
// avoid a gap between words...provides a steady stream of bits.
|
||||
*_dataReg = 0; // Write clears DRE flag, starts next xfer
|
||||
uint32_t sample = *_dataReg; // Read last-received word
|
||||
|
||||
uint32_t sum = 0; // local var = register = faster than sumTemp
|
||||
if (evenWord) { // Even-numbered 32-bit word...
|
||||
// At default speed and optimization settings (120 MHz -Os), the PDM-
|
||||
// servicing interrupt consumes about 12.5% of CPU time. Though this
|
||||
// code looks bulky, it's actually reasonably efficient (sincfilter[] is
|
||||
// const, so these compile down to constants, there is no array lookup,
|
||||
// any any zero-value element refs will be removed by the compiler).
|
||||
// Tested MANY methods and this was hard to beat. One managed just under
|
||||
// 10% load, but required 4KB of tables...not worth it for small boost.
|
||||
// Can get an easy boost with overclock and optimizer tweaks.
|
||||
if (sample & 0x00000001)
|
||||
sum += sincfilter[0];
|
||||
if (sample & 0x00000002)
|
||||
sum += sincfilter[1];
|
||||
if (sample & 0x00000004)
|
||||
sum += sincfilter[2];
|
||||
if (sample & 0x00000008)
|
||||
sum += sincfilter[3];
|
||||
if (sample & 0x00000010)
|
||||
sum += sincfilter[4];
|
||||
if (sample & 0x00000020)
|
||||
sum += sincfilter[5];
|
||||
if (sample & 0x00000040)
|
||||
sum += sincfilter[6];
|
||||
if (sample & 0x00000080)
|
||||
sum += sincfilter[7];
|
||||
if (sample & 0x00000100)
|
||||
sum += sincfilter[8];
|
||||
if (sample & 0x00000200)
|
||||
sum += sincfilter[9];
|
||||
if (sample & 0x00000400)
|
||||
sum += sincfilter[10];
|
||||
if (sample & 0x00000800)
|
||||
sum += sincfilter[11];
|
||||
if (sample & 0x00001000)
|
||||
sum += sincfilter[12];
|
||||
if (sample & 0x00002000)
|
||||
sum += sincfilter[13];
|
||||
if (sample & 0x00004000)
|
||||
sum += sincfilter[14];
|
||||
if (sample & 0x00008000)
|
||||
sum += sincfilter[15];
|
||||
if (sample & 0x00010000)
|
||||
sum += sincfilter[16];
|
||||
if (sample & 0x00020000)
|
||||
sum += sincfilter[17];
|
||||
if (sample & 0x00040000)
|
||||
sum += sincfilter[18];
|
||||
if (sample & 0x00080000)
|
||||
sum += sincfilter[19];
|
||||
if (sample & 0x00100000)
|
||||
sum += sincfilter[20];
|
||||
if (sample & 0x00200000)
|
||||
sum += sincfilter[21];
|
||||
if (sample & 0x00400000)
|
||||
sum += sincfilter[22];
|
||||
if (sample & 0x00800000)
|
||||
sum += sincfilter[23];
|
||||
if (sample & 0x01000000)
|
||||
sum += sincfilter[24];
|
||||
if (sample & 0x02000000)
|
||||
sum += sincfilter[25];
|
||||
if (sample & 0x04000000)
|
||||
sum += sincfilter[26];
|
||||
if (sample & 0x08000000)
|
||||
sum += sincfilter[27];
|
||||
if (sample & 0x10000000)
|
||||
sum += sincfilter[28];
|
||||
if (sample & 0x20000000)
|
||||
sum += sincfilter[29];
|
||||
if (sample & 0x40000000)
|
||||
sum += sincfilter[30];
|
||||
if (sample & 0x80000000)
|
||||
sum += sincfilter[31];
|
||||
sumTemp = sum; // Copy register to static var for next call
|
||||
} else {
|
||||
if (sample & 0x00000001)
|
||||
sum += sincfilter[32];
|
||||
if (sample & 0x00000002)
|
||||
sum += sincfilter[33];
|
||||
if (sample & 0x00000004)
|
||||
sum += sincfilter[34];
|
||||
if (sample & 0x00000008)
|
||||
sum += sincfilter[35];
|
||||
if (sample & 0x00000010)
|
||||
sum += sincfilter[36];
|
||||
if (sample & 0x00000020)
|
||||
sum += sincfilter[37];
|
||||
if (sample & 0x00000040)
|
||||
sum += sincfilter[38];
|
||||
if (sample & 0x00000080)
|
||||
sum += sincfilter[39];
|
||||
if (sample & 0x00000100)
|
||||
sum += sincfilter[40];
|
||||
if (sample & 0x00000200)
|
||||
sum += sincfilter[41];
|
||||
if (sample & 0x00000400)
|
||||
sum += sincfilter[42];
|
||||
if (sample & 0x00000800)
|
||||
sum += sincfilter[43];
|
||||
if (sample & 0x00001000)
|
||||
sum += sincfilter[44];
|
||||
if (sample & 0x00002000)
|
||||
sum += sincfilter[45];
|
||||
if (sample & 0x00004000)
|
||||
sum += sincfilter[46];
|
||||
if (sample & 0x00008000)
|
||||
sum += sincfilter[47];
|
||||
if (sample & 0x00010000)
|
||||
sum += sincfilter[48];
|
||||
if (sample & 0x00020000)
|
||||
sum += sincfilter[49];
|
||||
if (sample & 0x00040000)
|
||||
sum += sincfilter[50];
|
||||
if (sample & 0x00080000)
|
||||
sum += sincfilter[51];
|
||||
if (sample & 0x00100000)
|
||||
sum += sincfilter[52];
|
||||
if (sample & 0x00200000)
|
||||
sum += sincfilter[53];
|
||||
if (sample & 0x00400000)
|
||||
sum += sincfilter[54];
|
||||
if (sample & 0x00800000)
|
||||
sum += sincfilter[55];
|
||||
if (sample & 0x01000000)
|
||||
sum += sincfilter[56];
|
||||
if (sample & 0x02000000)
|
||||
sum += sincfilter[57];
|
||||
if (sample & 0x04000000)
|
||||
sum += sincfilter[58];
|
||||
if (sample & 0x08000000)
|
||||
sum += sincfilter[59];
|
||||
if (sample & 0x10000000)
|
||||
sum += sincfilter[60];
|
||||
if (sample & 0x20000000)
|
||||
sum += sincfilter[61];
|
||||
if (sample & 0x40000000)
|
||||
sum += sincfilter[62];
|
||||
if (sample & 0x80000000)
|
||||
sum += sincfilter[63];
|
||||
sum += sumTemp; // Add static var from last call
|
||||
|
||||
if (removeDC) {
|
||||
// 'sum' is new raw audio value -- process it --------------------------
|
||||
uint16_t dcOffset;
|
||||
|
||||
dcSum += sum; // Accumulate long-term average for DC offset correction
|
||||
if (++dcCounter < DC_PERIOD) {
|
||||
// Interpolate between dcOffsetPrior and dcOffsetNext
|
||||
dcOffset = dcOffsetPrior +
|
||||
(dcOffsetNext - dcOffsetPrior) * dcCounter / DC_PERIOD;
|
||||
} else {
|
||||
// End of period reached, move 'next' to 'previous,' calc new 'next'
|
||||
// from avg
|
||||
dcOffsetPrior = dcOffset = dcOffsetNext;
|
||||
dcOffsetNext = dcSum / DC_PERIOD;
|
||||
dcCounter = dcSum = 0;
|
||||
}
|
||||
|
||||
// Adjust raw reading by DC offset to center (ish) it, scale by mic gain
|
||||
int32_t adjusted = ((int32_t)sum - dcOffset) * micGain / 256;
|
||||
|
||||
// Go back to uint16_t space and clip to 16-bit range
|
||||
adjusted += 32768;
|
||||
if (adjusted > 65535)
|
||||
adjusted = 65535;
|
||||
else if (adjusted < 0)
|
||||
adjusted = 0;
|
||||
|
||||
*value = adjusted;
|
||||
} else {
|
||||
*value = sum;
|
||||
}
|
||||
}
|
||||
evenWord = !evenWord;
|
||||
|
||||
return evenWord;
|
||||
}
|
||||
|
||||
// Adjust input gain, higher = amplify, 1.0 = normal, no adjustment
|
||||
void Adafruit_ZeroPDMSPI::setMicGain(float g) {
|
||||
if (g > (65535.0 / 256.0))
|
||||
g = (65535.0 / 256.0); // sic
|
||||
else if (g < 0.0)
|
||||
g = 0.0;
|
||||
micGain = (uint16_t)(g * 256.0 + 0.5);
|
||||
}
|
||||
|
||||
/// @cond DISABLE
|
||||
#endif // defined(__SAMD51__)
|
||||
/// @endcond DISABLE
|
||||
Reference in New Issue
Block a user