You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
252 lines
9.1 KiB
252 lines
9.1 KiB
// Adafruit Circuit Playground microphone library
|
|
// by Phil Burgess / Paint Your Dragon.
|
|
// Fast Fourier transform section is derived from
|
|
// ELM-ChaN FFT library (see comments in ffft.S).
|
|
|
|
#include <Arduino.h>
|
|
#include "Adafruit_CPlay_Mic.h"
|
|
|
|
#if defined(ARDUINO_ARCH_SAMD)
|
|
|
|
#define SAMPLERATE_HZ 22000
|
|
#define DECIMATION 64
|
|
|
|
Adafruit_ZeroPDM Adafruit_CPlay_Mic::pdm = Adafruit_ZeroPDM(34, 35);
|
|
|
|
// a windowed sinc filter for 44 khz, 64 samples
|
|
uint16_t sincfilter[DECIMATION] = {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};
|
|
|
|
// a manual loop-unroller!
|
|
#define ADAPDM_REPEAT_LOOP_16(X) X X X X X X X X X X X X X X X X
|
|
|
|
static bool pdmConfigured = false;
|
|
|
|
#endif
|
|
|
|
#define DC_OFFSET (1023 / 3)
|
|
#define NOISE_THRESHOLD 3
|
|
|
|
/**************************************************************************/
|
|
/*!
|
|
@brief Reads ADC for given interval (in milliseconds, 1-65535). Uses ADC free-run mode w/polling on AVR.
|
|
Any currently-installed ADC interrupt handler will be temporarily
|
|
disabled while this runs.
|
|
@param ms the number of milliseconds to sample
|
|
@return max deviation from DC_OFFSET (e.g. 0-341)
|
|
@deprecated THIS FUNCTION IS DEPRECATED AND WILL BE REMOVED IN A FUTURE RELEASE.
|
|
please use soundPressureLevel(ms) instead
|
|
@note THIS FUNCTION IS DEPRECATED AND WILL BE REMOVED IN A FUTURE RELEASE.
|
|
please use soundPressureLevel(ms) instead
|
|
*/
|
|
/**************************************************************************/
|
|
int Adafruit_CPlay_Mic::peak(uint16_t ms) {
|
|
int val = soundPressureLevel(ms);
|
|
val = map(val, 56, 130, 0, 350);
|
|
return constrain(val, 0, 350);
|
|
}
|
|
|
|
/**************************************************************************/
|
|
/*!
|
|
@brief capture the passed number of samples and place them in buf.
|
|
@param buf the buffer to store the samples in
|
|
@param nSamples the number of samples to take
|
|
|
|
@note ON AVR: Captures ADC audio samples at maximum speed supported by 32u4 (9615 Hz).
|
|
Ostensibly for FFT code (below), but might have other uses. Uses ADC
|
|
free-run mode w/polling. Any currently-installed ADC interrupt handler
|
|
will be temporarily disabled while this runs. No other interrupts are
|
|
disabled; as long as interrupt handlers are minor (e.g. Timer/Counter 0
|
|
handling of millis() and micros()), this isn't likely to lose readings.
|
|
*/
|
|
/**************************************************************************/
|
|
void Adafruit_CPlay_Mic::capture(int16_t *buf, uint16_t nSamples) {
|
|
#ifdef __AVR__
|
|
uint8_t admux_save, adcsra_save, adcsrb_save, timsk0_save, channel;
|
|
int16_t adc;
|
|
|
|
channel = analogPinToChannel(4); // Pin A4 to ADC channel
|
|
admux_save = ADMUX; // Save ADC config registers
|
|
adcsra_save = ADCSRA;
|
|
adcsrb_save = ADCSRB;
|
|
|
|
// Init ADC free-run mode; f = ( 8MHz/prescaler ) / 13 cycles/conversion
|
|
ADCSRA = 0; // Stop ADC interrupt, if any
|
|
ADMUX = _BV(REFS0) | channel; // Aref=AVcc, channel sel, right-adj
|
|
ADCSRB = 0; // Free run mode, no high MUX bit
|
|
ADCSRA = _BV(ADEN) | // ADC enable
|
|
_BV(ADSC) | // ADC start
|
|
_BV(ADATE) | // Auto trigger
|
|
_BV(ADIF) | // Reset interrupt flag
|
|
_BV(ADPS2) | _BV(ADPS1); // 64:1 / 13 = 9615 Hz
|
|
|
|
// ADC conversion-ready bit is polled for each sample rather than using
|
|
// an interrupt; avoids contention with application or other library
|
|
// using ADC ISR for other things (there can be only one) while still
|
|
// providing the speed & precise timing of free-run mode (a loop of
|
|
// analogRead() won't get you that).
|
|
for(uint16_t i=0; i<nSamples; i++) {
|
|
while(!(ADCSRA & _BV(ADIF))); // Wait for ADC result
|
|
adc = ADC;
|
|
ADCSRA |= _BV(ADIF); // Clear bit
|
|
// FFT requires signed inputs; ADC output is unsigned. DC offset is
|
|
// NOT 512 on Circuit Playground because it uses a 1.1V OpAmp input
|
|
// as the midpoint, and may swing asymmetrically on the high side.
|
|
// Sign-convert and then clip range to +/- DC_OFFSET.
|
|
if(adc <= (DC_OFFSET - NOISE_THRESHOLD)) {
|
|
adc -= DC_OFFSET;
|
|
} else if(adc >= (DC_OFFSET + NOISE_THRESHOLD)) {
|
|
adc -= DC_OFFSET;
|
|
if(adc > (DC_OFFSET * 2)) adc = DC_OFFSET * 2;
|
|
} else {
|
|
adc = 0; // Below noise threshold
|
|
}
|
|
buf[i] = adc;
|
|
}
|
|
|
|
ADMUX = admux_save; // Restore ADC config
|
|
ADCSRB = adcsrb_save;
|
|
ADCSRA = adcsra_save;
|
|
(void)analogRead(A4); // Purge residue from ADC register
|
|
#elif defined(ARDUINO_ARCH_SAMD)
|
|
if(!pdmConfigured){
|
|
pdm.begin();
|
|
pdm.configure(SAMPLERATE_HZ * DECIMATION / 16, true);
|
|
pdmConfigured = true;
|
|
}
|
|
|
|
int16_t *ptr = buf;
|
|
while(ptr < (buf + nSamples)){
|
|
uint16_t runningsum = 0;
|
|
uint16_t *sinc_ptr = sincfilter;
|
|
|
|
for (uint8_t samplenum=0; samplenum < (DECIMATION/16) ; samplenum++) {
|
|
uint16_t sample = pdm.read() & 0xFFFF; // we read 16 bits at a time, by default the low half
|
|
|
|
ADAPDM_REPEAT_LOOP_16( // manually unroll loop: for (int8_t b=0; b<16; b++)
|
|
{
|
|
// start at the LSB which is the 'first' bit to come down the line, chronologically
|
|
// (Note we had to set I2S_SERCTRL_BITREV to get this to work, but saves us time!)
|
|
if (sample & 0x1) {
|
|
runningsum += *sinc_ptr; // do the convolution
|
|
}
|
|
sinc_ptr++;
|
|
sample >>= 1;
|
|
}
|
|
)
|
|
}
|
|
|
|
// since we wait for the samples from I2S peripheral, we dont need to delay, we will 'naturally'
|
|
// wait the right amount of time between analog writes
|
|
//Serial.println(runningsum);
|
|
|
|
runningsum /= 64 ; // convert 16 bit -> 10 bit
|
|
runningsum -= 512; // make it close to 0-offset signed
|
|
|
|
*ptr++ = runningsum;
|
|
}
|
|
#else
|
|
#error "no compatible architecture defined."
|
|
#endif
|
|
}
|
|
|
|
/**************************************************************************/
|
|
/*!
|
|
@brief Returns somewhat-calibrated sound pressure level.
|
|
@param ms Milliseconds to continuously sample microphone over, 10ms is a good start.
|
|
@returns Floating point Sound Pressure Level, tends to range from 40-120 db SPL
|
|
*/
|
|
/**************************************************************************/
|
|
float Adafruit_CPlay_Mic::soundPressureLevel(uint16_t ms){
|
|
float gain;
|
|
int16_t *ptr;
|
|
uint16_t len;
|
|
int16_t minVal = 52;
|
|
#ifdef __AVR__
|
|
gain = 1.3;
|
|
len = 9.615 * ms;
|
|
#elif defined(ARDUINO_ARCH_SAMD)
|
|
gain = 9;
|
|
len = (float)(SAMPLERATE_HZ/1000) * ms;
|
|
#else
|
|
#error "no compatible architecture defined."
|
|
#endif
|
|
int16_t data[len];
|
|
capture(data, len);
|
|
|
|
int16_t *end = data + len;
|
|
float pref = 0.00002;
|
|
|
|
/*******************************
|
|
* REMOVE DC OFFSET
|
|
******************************/
|
|
int32_t avg = 0;
|
|
ptr = data;
|
|
while(ptr < end) avg += *ptr++;
|
|
avg = avg/len;
|
|
|
|
ptr = data;
|
|
while(ptr < end) *ptr++ -= avg;
|
|
|
|
/*******************************
|
|
* GET MAX VALUE
|
|
******************************/
|
|
|
|
int16_t maxVal = 0;
|
|
ptr = data;
|
|
while(ptr < end){
|
|
int32_t v = abs(*ptr++);
|
|
if(v > maxVal) maxVal = v;
|
|
}
|
|
|
|
float conv = ((float)maxVal)/1023 * gain;
|
|
|
|
/*******************************
|
|
* CALCULATE SPL
|
|
******************************/
|
|
conv = 20 * log10(conv/pref);
|
|
|
|
if(isfinite(conv)) return conv;
|
|
else return minVal;
|
|
}
|
|
|
|
/**************************************************************************/
|
|
/*!
|
|
@brief 16 bit complex data type
|
|
*/
|
|
/**************************************************************************/
|
|
typedef struct {
|
|
int16_t r; ///< real portion
|
|
int16_t i; ///< imaginary portion
|
|
} complex_t;
|
|
|
|
extern "C" { // In ffft.S
|
|
void fft_input(const int16_t *, complex_t *),
|
|
fft_execute(complex_t *),
|
|
fft_output(complex_t *, uint16_t *);
|
|
}
|
|
|
|
/**************************************************************************/
|
|
/*!
|
|
@brief AVR ONLY: Performs one cycle of fast Fourier transform (FFT) with audio captured
|
|
from mic on A4. Output is 32 'bins,' each covering an equal range of
|
|
frequencies from 0 to 4800 Hz (i.e. 0-150 Hz, 150-300 Hz, 300-450, etc).
|
|
Needs about 450 bytes free RAM to operate.
|
|
@param spectrum the buffer to store the results in. Must be 32 bytes in length.
|
|
|
|
@note THIS FUNCTION IS DEPRECATED AND WILL BE REMOVED IN A FUTURE RELEASE.
|
|
*/
|
|
/**************************************************************************/
|
|
void Adafruit_CPlay_Mic::fft(
|
|
uint16_t *spectrum) { // Spectrum output buffer, uint16_t[32]
|
|
if(spectrum) {
|
|
int16_t capBuf[64]; // Audio capture buffer
|
|
complex_t butterfly[64]; // FFT "butterfly" buffer
|
|
|
|
capture(capBuf, 64); // Collect mic data into capBuf
|
|
fft_input(capBuf, butterfly); // Samples -> complex #s
|
|
fft_execute(butterfly); // Process complex data
|
|
fft_output(butterfly, spectrum); // Complex -> spectrum (32 bins)
|
|
}
|
|
}
|