Files
Studuino/libraries/Adafruit_Zero_PDM_Library/Adafruit_ZeroPDMSPI.cpp
T
2026-09-25 10:48:51 +10:00

297 lines
9.3 KiB
C++

/*!
* @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