initial commit
This commit is contained in:
@@ -0,0 +1,472 @@
|
||||
/*!
|
||||
* @file Adafruit_ZeroPDM.cpp
|
||||
*
|
||||
* @mainpage Adafruit ZeroPDM Library
|
||||
*
|
||||
* @section intro_sec Introduction
|
||||
*
|
||||
* Adafruit Arduino Zero / Feather M0 I2S audio library.
|
||||
*
|
||||
* @section author Author
|
||||
*
|
||||
* Author: Tony DiCola & Limor "ladyada" Fried
|
||||
*
|
||||
* @section license License
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2016 Adafruit Industries
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include "Adafruit_ZeroPDM.h"
|
||||
|
||||
#include "WVariant.h" // EPioType defined here
|
||||
#include "wiring_private.h" // pinPeripheral() function
|
||||
|
||||
/// @cond DISABLE
|
||||
#if defined(I2S)
|
||||
/// @endcond DISABLE
|
||||
|
||||
// Define macros for debug output that optimize out when debug mode is disabled.
|
||||
#ifdef DEBUG
|
||||
#define DEBUG_PRINT(...) \
|
||||
DEBUG_PRINTER.print(__VA_ARGS__) //!< Turns debug print on
|
||||
#define DEBUG_PRINTLN(...) \
|
||||
DEBUG_PRINTER.println(__VA_ARGS__) //!< Turns debug printline on
|
||||
#else
|
||||
#define DEBUG_PRINT(...)
|
||||
#define DEBUG_PRINTLN(...)
|
||||
#endif
|
||||
|
||||
Adafruit_ZeroPDM::Adafruit_ZeroPDM(int clockpin, int datapin, uint8_t gclk)
|
||||
: _gclk(gclk), _clk(clockpin), _data(datapin) {}
|
||||
|
||||
bool Adafruit_ZeroPDM::begin(void) {
|
||||
// check the pins are valid!
|
||||
_clk_pin = _clk_mux = _data_pin = _data_mux = 0;
|
||||
|
||||
// Clock pin, can only be one of 3 options
|
||||
uint32_t clockport = g_APinDescription[_clk].ulPort;
|
||||
uint32_t clockpin = g_APinDescription[_clk].ulPin;
|
||||
if ((clockport == 0) && (clockpin == 10)) {
|
||||
// PA10
|
||||
_i2sclock = I2S_CLOCK_UNIT_0;
|
||||
#if defined(PIN_PA10G_I2S_SCK0)
|
||||
_clk_pin = PIN_PA10G_I2S_SCK0;
|
||||
_clk_mux = MUX_PA10G_I2S_SCK0;
|
||||
#elif defined(PIN_PA10J_I2S_SCK0)
|
||||
_clk_pin = PIN_PA10J_I2S_SCK0;
|
||||
_clk_mux = MUX_PA10J_I2S_SCK0;
|
||||
#endif
|
||||
} else if ((clockport == 1) && (clockpin == 10)) {
|
||||
// PB11
|
||||
_i2sclock = I2S_CLOCK_UNIT_1;
|
||||
#if defined(PIN_PB11G_I2S_SCK1)
|
||||
_clk_pin = PIN_PB11G_I2S_SCK1;
|
||||
_clk_mux = MUX_PB11G_I2S_SCK1;
|
||||
#elif defined(PIN_PB11J_I2S_SCK1)
|
||||
_clk_pin = PIN_PB11J_I2S_SCK1;
|
||||
_clk_mux = MUX_PB11J_I2S_SCK1;
|
||||
#endif
|
||||
} else if ((clockport == 0) && (clockpin == 20)) {
|
||||
// PA20
|
||||
_i2sclock = I2S_CLOCK_UNIT_0;
|
||||
#if defined(PIN_PA20G_I2S_SCK0)
|
||||
_clk_pin = PIN_PA20G_I2S_SCK0;
|
||||
_clk_mux = MUX_PA20G_I2S_SCK0;
|
||||
#elif defined(PIN_PA20J_I2S_SCK0)
|
||||
_clk_pin = PIN_PA20J_I2S_SCK0;
|
||||
_clk_mux = MUX_PA20J_I2S_SCK0;
|
||||
#endif
|
||||
} else {
|
||||
DEBUG_PRINTLN("Clock isnt on a valid pin");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Data pin, can only be one of 3 options
|
||||
uint32_t datapin = g_APinDescription[_data].ulPin;
|
||||
uint32_t dataport = g_APinDescription[_data].ulPort;
|
||||
if ((dataport == 0) && (datapin == 7)) {
|
||||
// PA07
|
||||
_i2sserializer = I2S_SERIALIZER_0;
|
||||
#if defined(PIN_PA07G_I2S_SD0)
|
||||
_data_pin = PIN_PA07G_I2S_SD0;
|
||||
_data_mux = MUX_PA07G_I2S_SD0;
|
||||
#elif defined(PIN_PA07J_I2S_SD0)
|
||||
_data_pin = PIN_PA07J_I2S_SD0;
|
||||
_data_mux = MUX_PA07J_I2S_SD0;
|
||||
#endif
|
||||
} else if ((dataport == 0) && (datapin == 8)) {
|
||||
// PA08
|
||||
_i2sserializer = I2S_SERIALIZER_1;
|
||||
#if defined(PIN_PA08G_I2S_SD1)
|
||||
_data_pin = PIN_PA08G_I2S_SD1;
|
||||
_data_mux = MUX_PA08G_I2S_SD1;
|
||||
#elif defined(PIN_PA08J_I2S_SD1)
|
||||
_data_pin = PIN_PA08J_I2S_SD1;
|
||||
_data_mux = MUX_PA08J_I2S_SD1;
|
||||
#endif
|
||||
} else if ((dataport == 0) && (datapin == 19)) {
|
||||
// PA19
|
||||
_i2sserializer = I2S_SERIALIZER_0;
|
||||
#if defined(PIN_PA19G_I2S_SD0)
|
||||
_data_pin = PIN_PA19G_I2S_SD0;
|
||||
_data_mux = MUX_PA19G_I2S_SD0;
|
||||
#elif defined(PIN_PA19J_I2S_SD0)
|
||||
_data_pin = PIN_PA19J_I2S_SD0;
|
||||
_data_mux = MUX_PA19J_I2S_SD0;
|
||||
#endif
|
||||
} else {
|
||||
DEBUG_PRINTLN("Data isnt on a valid pin");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_SAMD_ZERO)
|
||||
// Initialize I2S module from the ASF.
|
||||
// replace "status_code res = i2s_init(&_i2s_instance, I2S);
|
||||
// if (res != STATUS_OK) {
|
||||
// DEBUG_PRINT("i2s_init failed with result: "); DEBUG_PRINTLN(res);
|
||||
// return false;
|
||||
// }" with:
|
||||
|
||||
/* Enable the user interface clock in the PM */
|
||||
// system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBC, PM_APBCMASK_I2S);
|
||||
PM->APBCMASK.reg |= PM_APBCMASK_I2S;
|
||||
|
||||
/* Status check */
|
||||
uint32_t ctrla = I2S->CTRLA.reg;
|
||||
if (ctrla & I2S_CTRLA_ENABLE) {
|
||||
if (ctrla & (I2S_CTRLA_SEREN1 | I2S_CTRLA_SEREN0 | I2S_CTRLA_CKEN1 |
|
||||
I2S_CTRLA_CKEN0)) {
|
||||
// return STATUS_BUSY;
|
||||
return false;
|
||||
} else {
|
||||
// return STATUS_ERR_DENIED;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/* Initialize module */
|
||||
_hw = I2S;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Adafruit_ZeroPDM::end(void) {
|
||||
#if defined(ARDUINO_SAMD_ZERO)
|
||||
while (_hw->SYNCBUSY.reg & I2S_SYNCBUSY_ENABLE)
|
||||
; // Sync wait
|
||||
_hw->CTRLA.reg &= ~I2S_SYNCBUSY_ENABLE;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Adafruit_ZeroPDM::configure(uint32_t sampleRateHz, boolean stereo) {
|
||||
// Convert bit per sample int into explicit ASF values.
|
||||
|
||||
// Disable I2S while it is being reconfigured to prevent unexpected output.
|
||||
end();
|
||||
|
||||
/******************************* Set the GCLK generator config and enable it.
|
||||
* *************/
|
||||
{
|
||||
#if defined(ARDUINO_SAMD_ZERO)
|
||||
/* Cache new register configurations to minimize sync requirements. */
|
||||
uint32_t new_genctrl_config = (_gclk << GCLK_GENCTRL_ID_Pos);
|
||||
uint32_t new_gendiv_config = (_gclk << GCLK_GENDIV_ID_Pos);
|
||||
|
||||
/* Select the requested source clock for the generator */
|
||||
// Set the clock generator to use the 48mhz main CPU clock and divide it
|
||||
// down to the SCK frequency.
|
||||
new_genctrl_config |= GCLK_SOURCE_DFLL48M << GCLK_GENCTRL_SRC_Pos;
|
||||
uint32_t division_factor =
|
||||
F_CPU / (sampleRateHz * 16); // 16 clocks for 16 stereo bits
|
||||
|
||||
/* Set division factor */
|
||||
if (division_factor > 1) {
|
||||
/* Check if division is a power of two */
|
||||
if (((division_factor & (division_factor - 1)) == 0)) {
|
||||
/* Determine the index of the highest bit set to get the
|
||||
* division factor that must be loaded into the division
|
||||
* register */
|
||||
|
||||
uint32_t div2_count = 0;
|
||||
|
||||
uint32_t mask;
|
||||
for (mask = (1UL << 1); mask < division_factor; mask <<= 1) {
|
||||
div2_count++;
|
||||
}
|
||||
|
||||
/* Set binary divider power of 2 division factor */
|
||||
new_gendiv_config |= div2_count << GCLK_GENDIV_DIV_Pos;
|
||||
new_genctrl_config |= GCLK_GENCTRL_DIVSEL;
|
||||
} else {
|
||||
/* Set integer division factor */
|
||||
|
||||
new_gendiv_config |= (division_factor) << GCLK_GENDIV_DIV_Pos;
|
||||
|
||||
/* Enable non-binary division with increased duty cycle accuracy */
|
||||
new_genctrl_config |= GCLK_GENCTRL_IDC;
|
||||
}
|
||||
}
|
||||
|
||||
noInterrupts(); // cpu_irq_enter_critical();
|
||||
|
||||
while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY)
|
||||
; // Wait for synchronization
|
||||
*((uint8_t*)&GCLK->GENDIV.reg) = _gclk; /* Select the correct generator */
|
||||
|
||||
while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY)
|
||||
; // Wait for synchronization
|
||||
GCLK->GENDIV.reg =
|
||||
new_gendiv_config; /* Write the new generator configuration */
|
||||
|
||||
while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY)
|
||||
; // Wait for synchronization
|
||||
GCLK->GENCTRL.reg =
|
||||
new_genctrl_config | (GCLK->GENCTRL.reg & GCLK_GENCTRL_GENEN);
|
||||
|
||||
// Replace "system_gclk_gen_enable(_gclk);" with:
|
||||
|
||||
while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY)
|
||||
; // Wait for synchronization
|
||||
*((uint8_t*)&GCLK->GENCTRL.reg) =
|
||||
_gclk; /* Select the requested generator */
|
||||
|
||||
while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY)
|
||||
; // Wait for synchronization
|
||||
GCLK->GENCTRL.reg |= GCLK_GENCTRL_GENEN; /* Enable generator */
|
||||
|
||||
interrupts(); // cpu_irq_leave_critical();
|
||||
#else
|
||||
(void)sampleRateHz; // Prevent compiler warning
|
||||
#endif
|
||||
}
|
||||
|
||||
/******************************* Configure I2S clock *************/
|
||||
{
|
||||
#if defined(ARDUINO_SAMD_ZERO)
|
||||
/* Status check */
|
||||
/* Busy ? */
|
||||
if (_hw->SYNCBUSY.reg & (I2S_SYNCBUSY_CKEN0 << _i2sclock)) {
|
||||
return false; // return STATUS_BUSY;
|
||||
}
|
||||
/* Already enabled ? */
|
||||
if (_hw->CTRLA.reg & (I2S_CTRLA_CKEN0 << _i2sclock)) {
|
||||
return false; // return STATUS_ERR_DENIED;
|
||||
}
|
||||
|
||||
/***************************** Initialize Clock Unit *************/
|
||||
uint32_t clkctrl =
|
||||
// I2S_CLKCTRL_MCKOUTINV | // mck out not inverted
|
||||
// I2S_CLKCTRL_SCKOUTINV | // sck out not inverted
|
||||
// I2S_CLKCTRL_FSOUTINV | // fs not inverted
|
||||
// I2S_CLKCTRL_MCKEN | // Disable MCK output
|
||||
// I2S_CLKCTRL_MCKSEL | // Disable MCK output
|
||||
// I2S_CLKCTRL_SCKSEL | // SCK source is GCLK
|
||||
// I2S_CLKCTRL_FSINV | // do not invert frame sync
|
||||
// I2S_CLKCTRL_FSSEL | // Configure FS generation from SCK clock.
|
||||
// I2S_CLKCTRL_BITDELAY | // No bit delay (PDM)
|
||||
0;
|
||||
|
||||
clkctrl |= I2S_CLKCTRL_MCKOUTDIV(0);
|
||||
clkctrl |= I2S_CLKCTRL_MCKDIV(0);
|
||||
clkctrl |= I2S_CLKCTRL_NBSLOTS(1); // STEREO is '1' (subtract one from #)
|
||||
clkctrl |= I2S_CLKCTRL_FSWIDTH(
|
||||
I2S_FRAME_SYNC_WIDTH_SLOT); // Frame Sync (FS) Pulse is 1 Slot width
|
||||
if (stereo) {
|
||||
clkctrl |= I2S_CLKCTRL_SLOTSIZE(I2S_SLOT_SIZE_16_BIT);
|
||||
} else {
|
||||
clkctrl |= I2S_CLKCTRL_SLOTSIZE(I2S_SLOT_SIZE_32_BIT);
|
||||
}
|
||||
|
||||
/* Write clock unit configurations */
|
||||
_hw->CLKCTRL[_i2sclock].reg = clkctrl;
|
||||
|
||||
/* Select general clock source */
|
||||
const uint8_t i2s_gclk_ids[2] = {I2S_GCLK_ID_0, I2S_GCLK_ID_1};
|
||||
|
||||
/* Cache the new config to reduce sync requirements */
|
||||
uint32_t new_clkctrl_config =
|
||||
(i2s_gclk_ids[_i2sclock] << GCLK_CLKCTRL_ID_Pos);
|
||||
|
||||
/* Select the desired generic clock generator */
|
||||
new_clkctrl_config |= _gclk << GCLK_CLKCTRL_GEN_Pos;
|
||||
|
||||
/* Disable generic clock channel */
|
||||
noInterrupts();
|
||||
|
||||
/* Select the requested generator channel */
|
||||
*((uint8_t*)&GCLK->CLKCTRL.reg) = i2s_gclk_ids[_i2sclock];
|
||||
|
||||
/* Switch to known-working source so that the channel can be disabled */
|
||||
uint32_t prev_gen_id = GCLK->CLKCTRL.bit.GEN;
|
||||
GCLK->CLKCTRL.bit.GEN = 0;
|
||||
|
||||
/* Disable the generic clock */
|
||||
GCLK->CLKCTRL.reg &= ~GCLK_CLKCTRL_CLKEN;
|
||||
while (GCLK->CLKCTRL.reg & GCLK_CLKCTRL_CLKEN)
|
||||
; /* Wait for clock to become disabled */
|
||||
|
||||
/* Restore previous configured clock generator */
|
||||
GCLK->CLKCTRL.bit.GEN = prev_gen_id;
|
||||
|
||||
/* Write the new configuration */
|
||||
GCLK->CLKCTRL.reg = new_clkctrl_config;
|
||||
|
||||
// enable it
|
||||
*((uint8_t*)&GCLK->CLKCTRL.reg) =
|
||||
i2s_gclk_ids[_i2sclock]; /* Select the requested generator channel */
|
||||
GCLK->CLKCTRL.reg |= GCLK_CLKCTRL_CLKEN; /* Enable the generic clock */
|
||||
|
||||
interrupts();
|
||||
|
||||
/* Initialize pins */
|
||||
pinPeripheral(_clk, (EPioType)_clk_mux);
|
||||
#else
|
||||
(void)stereo; // Prevent compiler warning
|
||||
#endif
|
||||
}
|
||||
|
||||
/***************************** Configure I2S serializer *************/
|
||||
{
|
||||
#if defined(ARDUINO_SAMD_ZERO)
|
||||
|
||||
/* Status check */
|
||||
/* Busy ? */
|
||||
while (_hw->SYNCBUSY.reg &
|
||||
((I2S_SYNCBUSY_SEREN0 | I2S_SYNCBUSY_DATA0) << _i2sserializer)) {
|
||||
// return STATUS_BUSY;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Already enabled ? */
|
||||
if (_hw->CTRLA.reg & (I2S_CTRLA_CKEN0 << _i2sserializer)) {
|
||||
// return STATUS_ERR_DENIED;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Initialize Serializer */
|
||||
uint32_t serctrl =
|
||||
// I2S_SERCTRL_RXLOOP | // Dont use loopback mode
|
||||
// I2S_SERCTRL_DMA | // Single DMA channel for all I2S channels
|
||||
// I2S_SERCTRL_MONO | // Dont use MONO mode
|
||||
// I2S_SERCTRL_SLOTDIS7 | // Dont have any slot disabling
|
||||
// I2S_SERCTRL_SLOTDIS6 |
|
||||
// I2S_SERCTRL_SLOTDIS5 |
|
||||
// I2S_SERCTRL_SLOTDIS4 |
|
||||
// I2S_SERCTRL_SLOTDIS3 |
|
||||
// I2S_SERCTRL_SLOTDIS2 |
|
||||
// I2S_SERCTRL_SLOTDIS1 |
|
||||
// I2S_SERCTRL_SLOTDIS0 |
|
||||
I2S_SERCTRL_BITREV | // Do not transfer LSB first (MSB first!)
|
||||
// I2S_SERCTRL_WORDADJ | // Data NOT left in word
|
||||
I2S_SERCTRL_SLOTADJ | // Data is left in slot
|
||||
// I2S_SERCTRL_TXSAME | // Pad 0 on underrun
|
||||
0;
|
||||
|
||||
// Configure clock unit to use with serializer, and set serializer as an
|
||||
// output.
|
||||
if (_i2sclock < 2) {
|
||||
serctrl |= (_i2sclock ? I2S_SERCTRL_CLKSEL : 0);
|
||||
} else {
|
||||
return false; // return STATUS_ERR_INVALID_ARG;
|
||||
}
|
||||
if (stereo) {
|
||||
serctrl |= I2S_SERCTRL_SERMODE(
|
||||
I2S_SERIALIZER_PDM2); // Serializer is used to receive PDM data on
|
||||
// each clock edge
|
||||
} else {
|
||||
serctrl |= I2S_SERCTRL_SERMODE(I2S_SERIALIZER_RECEIVE); // act like I2S
|
||||
}
|
||||
|
||||
// Configure serializer data size.
|
||||
serctrl |= I2S_SERCTRL_DATASIZE(
|
||||
I2S_DATA_SIZE_32BIT); // anything other than 32 bits is ridiculous to
|
||||
// manage, force this to be 32
|
||||
|
||||
serctrl |= I2S_SERCTRL_TXDEFAULT(
|
||||
I2S_LINE_DEFAULT_0) | /** Output default value is 0 */
|
||||
I2S_SERCTRL_EXTEND(
|
||||
I2S_DATA_PADDING_0); /** Padding 0 in case of under-run */
|
||||
|
||||
/* Write Serializer configuration */
|
||||
_hw->SERCTRL[_i2sserializer].reg = serctrl;
|
||||
|
||||
/* Initialize pins */
|
||||
// Enable SD pin. See Adafruit_ZeroI2S.h for default pin value.
|
||||
pinPeripheral(_data, (EPioType)_data_mux);
|
||||
#endif
|
||||
}
|
||||
|
||||
/***************************** Enable everything configured above
|
||||
* *************/
|
||||
|
||||
#if defined(ARDUINO_SAMD_ZERO)
|
||||
// Replace "i2s_enable(&_i2s_instance);" with:
|
||||
while (_hw->SYNCBUSY.reg & I2S_SYNCBUSY_ENABLE)
|
||||
; // Sync wait
|
||||
_hw->CTRLA.reg |= I2S_SYNCBUSY_ENABLE;
|
||||
|
||||
// Replace "i2s_clock_unit_enable(&_i2s_instance, _i2sclock);" with:
|
||||
uint32_t cken_bit = I2S_CTRLA_CKEN0 << _i2sclock;
|
||||
while (_hw->SYNCBUSY.reg & cken_bit)
|
||||
; // Sync wait
|
||||
_hw->CTRLA.reg |= cken_bit;
|
||||
|
||||
// Replace "i2s_serializer_enable(&_i2s_instance, _i2sserializer);" with:
|
||||
uint32_t seren_bit = I2S_CTRLA_SEREN0 << _i2sserializer;
|
||||
while (_hw->SYNCBUSY.reg & seren_bit)
|
||||
; // Sync wait
|
||||
_hw->CTRLA.reg |= seren_bit;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t Adafruit_ZeroPDM::read(void) {
|
||||
// Read the sample from the I2S data register.
|
||||
// This will wait for the I2S hardware to be ready to send the byte.
|
||||
// return i2s_serializer_read_wait(&_i2s_instance, _i2sserializer);
|
||||
|
||||
// replace i2s_serializer_read_wait with deASF'd code:
|
||||
{
|
||||
#if defined(ARDUINO_SAMD_ZERO)
|
||||
uint32_t sync_bit, ready_bit;
|
||||
uint32_t data;
|
||||
ready_bit = I2S_INTFLAG_RXRDY0 << _i2sserializer;
|
||||
while (!(_hw->INTFLAG.reg & ready_bit)) {
|
||||
/* Wait until ready to transmit */
|
||||
}
|
||||
sync_bit = I2S_SYNCBUSY_DATA0 << _i2sserializer;
|
||||
while (_hw->SYNCBUSY.reg & sync_bit) {
|
||||
/* Wait sync */
|
||||
}
|
||||
/* Read data */
|
||||
data = _hw->DATA[_i2sserializer].reg;
|
||||
_hw->INTFLAG.reg = ready_bit;
|
||||
return data;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// @cond DISABLE
|
||||
#endif // I2S defined
|
||||
/// @endcond DISABLE
|
||||
@@ -0,0 +1,321 @@
|
||||
/*!
|
||||
* @file Adafruit_ZeroPDM.h
|
||||
*/
|
||||
#ifndef ADAFRUIT_ZEROPDM_H
|
||||
#define ADAFRUIT_ZEROPDM_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
/// @cond DISABLE
|
||||
#if defined(I2S)
|
||||
/// @endcond DISABLE
|
||||
|
||||
// Uncomment to enable debug message output.
|
||||
#define DEBUG //!< Enables or disables debug
|
||||
|
||||
#define DEBUG_PRINTER \
|
||||
Serial //!< Define where debug output is printed (the native USB port on the
|
||||
//!< Zero).
|
||||
|
||||
/*!
|
||||
* ZeroPDM driver
|
||||
*/
|
||||
class Adafruit_ZeroPDM {
|
||||
public:
|
||||
/*!
|
||||
* @brief Create a new instance of an I2S audio transmitter.
|
||||
* Can specify the pins to use and the generic clock ID to use for driving the
|
||||
* I2S hardware (default is GCLK 3).
|
||||
* @param clockpin Clock pin
|
||||
* @param datapin Data pin
|
||||
* @param gclk Clock generator
|
||||
*/
|
||||
Adafruit_ZeroPDM(int clockpin, int datapin, uint8_t gclk = 3);
|
||||
|
||||
/*!
|
||||
* @brief Initialize the I2S audio receiver.
|
||||
* @return Returns whether or not initialization was successful
|
||||
*/
|
||||
bool begin();
|
||||
/*!
|
||||
* @brief Ends the connection
|
||||
*/
|
||||
void end();
|
||||
|
||||
/*!
|
||||
* @brief Configure the transmitter with the sample rate (in hertz)
|
||||
* @param sampleRateHz Sample rate (in hertz)
|
||||
* @param stereo Whether stereo is enabled
|
||||
* @return Returns whether or not configuration was successful
|
||||
*/
|
||||
bool configure(uint32_t sampleRateHz, boolean stereo);
|
||||
|
||||
/*!
|
||||
* @brief Read a single sample from the I2S subsystem. Will wait until the
|
||||
* I2S hardware is ready to receive the sample.
|
||||
* @return Returns the data read over I2S
|
||||
*/
|
||||
uint32_t read(void);
|
||||
|
||||
/*!
|
||||
* @brief Gets the I2S serializer
|
||||
* @return Returns the I2S serializer
|
||||
*/
|
||||
uint8_t getSerializer(void) {
|
||||
return _i2sserializer;
|
||||
};
|
||||
|
||||
/*!
|
||||
* @brief Reads data from I2S buffer
|
||||
* @param buffer Buffer to read from
|
||||
* @param bufsiz Size of buffer
|
||||
* @return Returns the data read over I2S
|
||||
*/
|
||||
bool read(uint32_t* buffer, int bufsiz);
|
||||
|
||||
private:
|
||||
uint8_t _gclk;
|
||||
int _clk, _data;
|
||||
uint32_t _clk_pin, _clk_mux, _data_pin, _data_mux;
|
||||
uint8_t _i2sserializer;
|
||||
uint8_t _i2sclock;
|
||||
|
||||
I2s* _hw;
|
||||
};
|
||||
|
||||
#ifndef I2S_H_INCLUDED
|
||||
|
||||
/**
|
||||
* Master Clock (MCK) source selection
|
||||
*/
|
||||
enum i2s_master_clock_source {
|
||||
/** Master Clock (MCK) is from general clock */
|
||||
I2S_MASTER_CLOCK_SOURCE_GCLK,
|
||||
/** Master Clock (MCK) is from MCK input pin */
|
||||
I2S_MASTER_CLOCK_SOURCE_MCKPIN
|
||||
};
|
||||
|
||||
/**
|
||||
* Serial Clock (SCK) source selection
|
||||
*/
|
||||
enum i2s_serial_clock_source {
|
||||
/** Serial Clock (SCK) is divided from Master Clock */
|
||||
I2S_SERIAL_CLOCK_SOURCE_MCKDIV,
|
||||
/** Serial Clock (SCK) is input from SCK input pin */
|
||||
I2S_SERIAL_CLOCK_SOURCE_SCKPIN
|
||||
};
|
||||
|
||||
/**
|
||||
* Data delay from Frame Sync (FS)
|
||||
*/
|
||||
enum i2s_data_delay {
|
||||
/** Left Justified (no delay) */
|
||||
I2S_DATA_DELAY_0,
|
||||
/** I2S data delay (1-bit delay) */
|
||||
I2S_DATA_DELAY_1,
|
||||
/** Left Justified (no delay) */
|
||||
I2S_DATA_DELAY_LEFT_JUSTIFIED = I2S_DATA_DELAY_0,
|
||||
/** I2S data delay (1-bit delay) */
|
||||
I2S_DATA_DELAY_I2S = I2S_DATA_DELAY_1
|
||||
};
|
||||
|
||||
/**
|
||||
* Frame Sync (FS) source
|
||||
*/
|
||||
enum i2s_frame_sync_source {
|
||||
/** Frame Sync (FS) is divided from I2S Serial Clock */
|
||||
I2S_FRAME_SYNC_SOURCE_SCKDIV,
|
||||
/** Frame Sync (FS) is input from FS input pin */
|
||||
I2S_FRAME_SYNC_SOURCE_FSPIN
|
||||
};
|
||||
|
||||
/**
|
||||
* Frame Sync (FS) output pulse width
|
||||
*/
|
||||
enum i2s_frame_sync_width {
|
||||
/** Frame Sync (FS) Pulse is 1 Slot width */
|
||||
I2S_FRAME_SYNC_WIDTH_SLOT,
|
||||
/** Frame Sync (FS) Pulse is half a Frame width */
|
||||
I2S_FRAME_SYNC_WIDTH_HALF_FRAME,
|
||||
/** Frame Sync (FS) Pulse is 1 Bit width */
|
||||
I2S_FRAME_SYNC_WIDTH_BIT,
|
||||
/** 1-bit wide Frame Sync (FS) per Data sample, only used when Data transfer
|
||||
* is requested */
|
||||
I2S_FRAME_SYNC_WIDTH_BURST
|
||||
};
|
||||
|
||||
/**
|
||||
* Time Slot Size in number of I2S serial clocks (bits)
|
||||
*/
|
||||
enum i2s_slot_size {
|
||||
/** 8-bit slot */
|
||||
I2S_SLOT_SIZE_8_BIT,
|
||||
/** 16-bit slot */
|
||||
I2S_SLOT_SIZE_16_BIT,
|
||||
/** 24-bit slot */
|
||||
I2S_SLOT_SIZE_24_BIT,
|
||||
/** 32-bit slot */
|
||||
I2S_SLOT_SIZE_32_BIT
|
||||
};
|
||||
|
||||
/**
|
||||
* DMA channels usage for I2S
|
||||
*/
|
||||
enum i2s_dma_usage {
|
||||
/** Single DMA channel for all I2S channels */
|
||||
I2S_DMA_USE_SINGLE_CHANNEL_FOR_ALL,
|
||||
/** One DMA channel per data channel */
|
||||
I2S_DMA_USE_ONE_CHANNEL_PER_DATA_CHANNEL
|
||||
};
|
||||
|
||||
/**
|
||||
* I2S data format, to extend mono data to 2 channels
|
||||
*/
|
||||
enum i2s_data_format {
|
||||
/** Normal mode, keep data to its right channel */
|
||||
I2S_DATA_FORMAT_STEREO,
|
||||
/** Assume input is mono data for left channel, the data is duplicated to
|
||||
* right channel */
|
||||
I2S_DATA_FORMAT_MONO
|
||||
};
|
||||
|
||||
/**
|
||||
* I2S data bit order
|
||||
*/
|
||||
enum i2s_bit_order {
|
||||
/** Transfer Data Most Significant Bit first (Default for I2S protocol) */
|
||||
I2S_BIT_ORDER_MSB_FIRST,
|
||||
/** Transfer Data Least Significant Bit first */
|
||||
I2S_BIT_ORDER_LSB_FIRST
|
||||
};
|
||||
|
||||
/**
|
||||
* I2S data bit padding
|
||||
*/
|
||||
enum i2s_bit_padding {
|
||||
/** Padding with 0 */
|
||||
I2S_BIT_PADDING_0,
|
||||
/** Padding with 1 */
|
||||
I2S_BIT_PADDING_1,
|
||||
/** Padding with MSBit */
|
||||
I2S_BIT_PADDING_MSB,
|
||||
/** Padding with LSBit */
|
||||
I2S_BIT_PADDING_LSB,
|
||||
};
|
||||
|
||||
/**
|
||||
* I2S data word adjust
|
||||
*/
|
||||
enum i2s_data_adjust {
|
||||
/** Data is right adjusted in word */
|
||||
I2S_DATA_ADJUST_RIGHT,
|
||||
/** Data is left adjusted in word */
|
||||
I2S_DATA_ADJUST_LEFT
|
||||
};
|
||||
|
||||
/**
|
||||
* I2S data word size
|
||||
*/
|
||||
enum i2s_data_size {
|
||||
/** 32-bit */
|
||||
I2S_DATA_SIZE_32BIT,
|
||||
/** 24-bit */
|
||||
I2S_DATA_SIZE_24BIT,
|
||||
/** 20-bit */
|
||||
I2S_DATA_SIZE_20BIT,
|
||||
/** 18-bit */
|
||||
I2S_DATA_SIZE_18BIT,
|
||||
/** 16-bit */
|
||||
I2S_DATA_SIZE_16BIT,
|
||||
/** 16-bit compact stereo */
|
||||
I2S_DATA_SIZE_16BIT_COMPACT,
|
||||
/** 8-bit */
|
||||
I2S_DATA_SIZE_8BIT,
|
||||
/** 8-bit compact stereo */
|
||||
I2S_DATA_SIZE_8BIT_COMPACT
|
||||
};
|
||||
|
||||
/**
|
||||
* I2S data slot adjust
|
||||
*/
|
||||
enum i2s_slot_adjust {
|
||||
/** Data is right adjusted in slot */
|
||||
I2S_SLOT_ADJUST_RIGHT,
|
||||
/** Data is left adjusted in slot */
|
||||
I2S_SLOT_ADJUST_LEFT
|
||||
};
|
||||
|
||||
/**
|
||||
* I2S data padding
|
||||
*/
|
||||
enum i2s_data_padding {
|
||||
/** Padding 0 in case of under-run */
|
||||
I2S_DATA_PADDING_0,
|
||||
/** Padding last data in case of under-run */
|
||||
I2S_DATA_PADDING_SAME_AS_LAST,
|
||||
/** Padding last data in case of under-run
|
||||
* (abbr. \c I2S_DATA_PADDING_SAME_AS_LAST) */
|
||||
I2S_DATA_PADDING_LAST = I2S_DATA_PADDING_SAME_AS_LAST,
|
||||
/** Padding last data in case of under-run
|
||||
* (abbr. \c I2S_DATA_PADDING_SAME_AS_LAST) */
|
||||
I2S_DATA_PADDING_SAME = I2S_DATA_PADDING_SAME_AS_LAST
|
||||
};
|
||||
|
||||
/**
|
||||
* I2S line default value when slot disabled
|
||||
*/
|
||||
enum i2s_line_default_state {
|
||||
/** Output default value is 0 */
|
||||
I2S_LINE_DEFAULT_0,
|
||||
/** Output default value is 1 */
|
||||
I2S_LINE_DEFAULT_1,
|
||||
/** Output default value is high impedance */
|
||||
I2S_LINE_DEFAULT_HIGH_IMPEDANCE = 3,
|
||||
/** Output default value is high impedance
|
||||
* (abbr. \c I2S_LINE_DEFAULT_HIGH_IMPEDANCE) */
|
||||
I2S_LINE_DEFAULT_HIZ = I2S_LINE_DEFAULT_HIGH_IMPEDANCE
|
||||
};
|
||||
|
||||
/**
|
||||
* I2S Serializer mode
|
||||
*/
|
||||
enum i2s_serializer_mode {
|
||||
/** Serializer is used to receive data */
|
||||
I2S_SERIALIZER_RECEIVE,
|
||||
/** Serializer is used to transmit data */
|
||||
I2S_SERIALIZER_TRANSMIT,
|
||||
/** Serializer is used to receive PDM data on each clock edge */
|
||||
I2S_SERIALIZER_PDM2
|
||||
};
|
||||
|
||||
/**
|
||||
* I2S clock unit selection
|
||||
*/
|
||||
enum i2s_clock_unit {
|
||||
/** Clock Unit channel 0 */
|
||||
I2S_CLOCK_UNIT_0,
|
||||
/** Clock Unit channel 1 */
|
||||
I2S_CLOCK_UNIT_1,
|
||||
/** Number of Clock Unit channels */
|
||||
I2S_CLOCK_UNIT_N
|
||||
};
|
||||
|
||||
/**
|
||||
* I2S Serializer selection
|
||||
*/
|
||||
enum i2s_serializer {
|
||||
/** Serializer channel 0 */
|
||||
I2S_SERIALIZER_0,
|
||||
/** Serializer channel 1 */
|
||||
I2S_SERIALIZER_1,
|
||||
/** Number of Serializer channels */
|
||||
I2S_SERIALIZER_N
|
||||
};
|
||||
|
||||
#endif // I2S header include
|
||||
|
||||
/// @cond DISABLE
|
||||
#endif // I2S defined
|
||||
/// @endcond DISABLE
|
||||
|
||||
#endif // ADAFRUIT_ZEROPDM_H
|
||||
@@ -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
|
||||
@@ -0,0 +1,69 @@
|
||||
/*!
|
||||
* @file Adafruit_ZeroPDMSPI.h
|
||||
*/
|
||||
#ifndef ADAFRUIT_ZEROPDMSPI_H
|
||||
#define ADAFRUIT_ZEROPDMSPI_H
|
||||
|
||||
/// @cond DISABLE
|
||||
#if defined(__SAMD51__)
|
||||
/// @endcond DISABLE
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <SPI.h>
|
||||
|
||||
/*!
|
||||
* ZeroPDM SPI driver
|
||||
*/
|
||||
class Adafruit_ZeroPDMSPI {
|
||||
public:
|
||||
/*!
|
||||
* @brief Create a new instance of an PDM audio transmitter over SPI
|
||||
* @param theSPI SPI to use
|
||||
*/
|
||||
Adafruit_ZeroPDMSPI(SPIClass* theSPI);
|
||||
|
||||
/*!
|
||||
* @brief Initialize the SPI audio receiver.
|
||||
* @param freq Frequency (in Hertz)
|
||||
* @return Returns if connection was successful
|
||||
*/
|
||||
bool begin(uint32_t freq);
|
||||
|
||||
/*!
|
||||
* @brief Alternates 0 to 1 to let you know that data is ready to be read
|
||||
* @param value Variable to set the reading to
|
||||
* @param removeDC Whether or not to remove the DC offset
|
||||
* @return Returns if the word is even or not
|
||||
*/
|
||||
bool decimateFilterWord(uint16_t* value, bool removeDC = true);
|
||||
|
||||
/*!
|
||||
* @brief Sets the mic gain
|
||||
* @param g Mic gain
|
||||
*/
|
||||
void setMicGain(float g = 1.0);
|
||||
|
||||
/*!
|
||||
* @brief Sample rate
|
||||
*/
|
||||
float sampleRate;
|
||||
|
||||
private:
|
||||
SPIClass* _spi = NULL;
|
||||
Sercom* _sercom = NULL;
|
||||
IRQn_Type _irq;
|
||||
volatile uint32_t* _dataReg;
|
||||
|
||||
uint16_t dcCounter = 0; // Rolls over every DC_PERIOD samples
|
||||
uint32_t dcSum = 0; // Accumulates DC_PERIOD samples
|
||||
uint16_t dcOffsetPrior = 32768; // DC offset interpolates linearly
|
||||
uint16_t dcOffsetNext = 32768; // between these two values
|
||||
|
||||
uint16_t micGain = 256; // 1:1
|
||||
};
|
||||
|
||||
/// @cond DISABLE
|
||||
#endif // defined(__SAMD51__)
|
||||
/// @endcond DISABLE
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Adafruit Industries
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Adafruit Zero PDM Library [](https://github.com/adafruit/Adafruit_ZeroPDM/actions)[](http://adafruit.github.io/Adafruit_ZeroPDM/html/index.html)
|
||||
|
||||
PDM microphone library for the Arduino Zero / Adafruit Feather M0 (SAMD21 processor).
|
||||
@@ -0,0 +1,77 @@
|
||||
#include "Adafruit_ZeroPDM.h"
|
||||
|
||||
#define SAMPLERATE_HZ 22000
|
||||
#define DECIMATION 64
|
||||
|
||||
// Create PDM receiver object, with Clock and Data pins used (not all pins available)
|
||||
Adafruit_ZeroPDM pdm = Adafruit_ZeroPDM(1, 4); // Metro M0 or Arduino zero
|
||||
//Adafruit_ZeroPDM pdm = Adafruit_ZeroPDM(34, 35); // CPlay express
|
||||
|
||||
#define SERIALPORT Serial
|
||||
|
||||
// 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
|
||||
|
||||
|
||||
void setup() {
|
||||
pinMode(13, OUTPUT);
|
||||
|
||||
// Configure serial port.
|
||||
while (!SERIALPORT);
|
||||
SERIALPORT.begin(115200);
|
||||
SERIALPORT.println("SAMD PDM Demo");
|
||||
|
||||
// Initialize the PDM/I2S receiver
|
||||
if (!pdm.begin()) {
|
||||
SERIALPORT.println("Failed to initialize I2S/PDM!");
|
||||
while (1);
|
||||
}
|
||||
SERIALPORT.println("PDM initialized");
|
||||
|
||||
// Configure PDM receiver, sample rate
|
||||
if (!pdm.configure(SAMPLERATE_HZ * DECIMATION / 16, true)) {
|
||||
SERIALPORT.println("Failed to configure PDM");
|
||||
while (1);
|
||||
}
|
||||
SERIALPORT.println("PDM configured");
|
||||
|
||||
// use analog output A0 @ full rez
|
||||
analogWriteResolution(10);
|
||||
pinMode(A0, OUTPUT);
|
||||
}
|
||||
|
||||
|
||||
void loop() {
|
||||
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);
|
||||
|
||||
// "echo" the analog value we've calculated out to A0
|
||||
runningsum /= 64 ; // convert 16 bit -> 10 bit
|
||||
analogWrite(A0, runningsum);
|
||||
|
||||
// note that we cannot print the the serial port fast enough to keep up!
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
#include <Adafruit_ZeroPDM.h>
|
||||
#include <Adafruit_ZeroDMA.h>
|
||||
#include "utility/dma.h"
|
||||
|
||||
// Create PDM receiver object, with Clock and Data pins used (not all pins available)
|
||||
Adafruit_ZeroPDM pdm = Adafruit_ZeroPDM(1, 4); // Metro M0 or Arduino zero
|
||||
//Adafruit_ZeroPDM pdm = Adafruit_ZeroPDM(34, 35); // CPlay express
|
||||
|
||||
// a windowed sinc filter for 44 khz, 64 samples
|
||||
#define SAMPLERATE_HZ 16000
|
||||
#define DECIMATION 64
|
||||
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};
|
||||
|
||||
// We'll use DMA to transfer one chunk of data from PDM/I2S for us
|
||||
Adafruit_ZeroDMA myDMA;
|
||||
// DMA status codes returned by some functions
|
||||
ZeroDMAstatus stat;
|
||||
// The memory we'll be reading from I2S
|
||||
#define DMA_DATA_LENGTH (DECIMATION / 16) // 16 bits per 'I2S sample'
|
||||
uint32_t dest_memory[DMA_DATA_LENGTH];
|
||||
|
||||
// Callback for end-of-DMA-transfer
|
||||
volatile bool transfer_is_done; // Done yet?
|
||||
void dma_callback(Adafruit_ZeroDMA *dma) {
|
||||
transfer_is_done = true;
|
||||
}
|
||||
|
||||
void setup() {
|
||||
pinMode(13, OUTPUT);
|
||||
|
||||
// Configure serial port.
|
||||
while (!Serial);
|
||||
Serial.begin(115200);
|
||||
Serial.println("SAMD PDM DMA Demo");
|
||||
|
||||
// Initialize the PDM/I2S receiver
|
||||
if (!pdm.begin()) {
|
||||
Serial.println("Failed to initialize I2S/PDM!");
|
||||
while (1);
|
||||
}
|
||||
Serial.println("PDM initialized");
|
||||
|
||||
// Configure PDM receiver, sample rate
|
||||
if (!pdm.configure(SAMPLERATE_HZ * DECIMATION / 16, true)) {
|
||||
Serial.println("Failed to configure PDM");
|
||||
while (1);
|
||||
}
|
||||
Serial.println("PDM configured");
|
||||
|
||||
// use analog output A0 @ full rez
|
||||
analogWriteResolution(10);
|
||||
pinMode(A0, OUTPUT);
|
||||
|
||||
Serial.println("Configuring DMA trigger");
|
||||
void *i2s_data_reg;
|
||||
|
||||
if (pdm.getSerializer() == I2S_SERIALIZER_0) {
|
||||
myDMA.setTrigger(0x29); // I2S RX 0 Trigger (see DS Table 19-8. Peripheral Trigger Source)
|
||||
i2s_data_reg = (void *)&I2S->DATA[0]; // Serializer 0
|
||||
} else {
|
||||
myDMA.setTrigger(0x2A); // I2S RX 1 Trigger (see DS Table 19-8. Peripheral Trigger Source)
|
||||
i2s_data_reg = (void *)&I2S->DATA[1]; // Serializer 1
|
||||
}
|
||||
myDMA.setAction(DMA_TRIGGER_ACTON_BEAT);
|
||||
|
||||
Serial.print("Allocating DMA channel...");
|
||||
stat = myDMA.allocate();
|
||||
myDMA.printStatus(stat);
|
||||
|
||||
Serial.println("Setting up transfer");
|
||||
myDMA.addDescriptor(
|
||||
i2s_data_reg, // move data from here
|
||||
dest_memory, // move data to here
|
||||
DMA_DATA_LENGTH, // this many...
|
||||
DMA_BEAT_SIZE_WORD, // bytes/hword/words
|
||||
false, // increment source addr?
|
||||
true); // increment dest addr?
|
||||
|
||||
Serial.println("Adding callback");
|
||||
// register_callback() can optionally take a second argument
|
||||
// (callback type), default is DMA_CALLBACK_TRANSFER_DONE
|
||||
myDMA.setCallback(dma_callback);
|
||||
|
||||
// kick it off
|
||||
transfer_is_done = false;
|
||||
stat = myDMA.startJob();
|
||||
}
|
||||
|
||||
|
||||
#define ADAPDM_REPEAT_LOOP_16(X) X X X X X X X X X X X X X X X X
|
||||
|
||||
void loop() {
|
||||
uint16_t runningsum = 0;
|
||||
uint16_t *sinc_ptr = sincfilter;
|
||||
|
||||
while(!transfer_is_done); // Chill until DMA transfer completes
|
||||
|
||||
// save the samples
|
||||
uint32_t samples[DMA_DATA_LENGTH];
|
||||
memcpy(samples, dest_memory, 4 * DMA_DATA_LENGTH); // 32 bits (4 bytes) per sample
|
||||
|
||||
// start next DMA xfer!
|
||||
transfer_is_done = false;
|
||||
stat = myDMA.startJob();
|
||||
|
||||
PORT->Group[0].OUTSET.reg = 1 << 17;
|
||||
for (uint8_t samplenum=0; samplenum < (DECIMATION/16) ; samplenum++) {
|
||||
uint16_t sample = samples[samplenum] & 0xFFFF;
|
||||
|
||||
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;
|
||||
}
|
||||
sinc_ptr++;
|
||||
sample >>= 1;
|
||||
}
|
||||
)
|
||||
}
|
||||
PORT->Group[0].OUTCLR.reg = 1 << 17;
|
||||
|
||||
// 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);
|
||||
|
||||
// "echo" the analog value we've calculated out to A0
|
||||
runningsum /= 64 ; // convert 16 bit -> 10 bit
|
||||
analogWrite(A0, runningsum);
|
||||
|
||||
// note that we cannot print the the serial port fast enough to keep up!
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#include <SPI.h>
|
||||
#include <Adafruit_ZeroPDMSPI.h>
|
||||
|
||||
#define PDM_SPI SPI2 // PDM mic SPI peripheral
|
||||
#define PDM_SERCOM_HANDLER SERCOM3_0_Handler
|
||||
|
||||
Adafruit_ZeroPDMSPI pdmspi(&PDM_SPI);
|
||||
|
||||
// INTERRUPT HANDLERS ------------------------------------------------------
|
||||
volatile int16_t pdmLastReading = 0;
|
||||
void PDM_SERCOM_HANDLER(void) {
|
||||
digitalWrite(13, HIGH);
|
||||
uint16_t v = 0;
|
||||
if (pdmspi.decimateFilterWord(&v)) {
|
||||
pdmLastReading = (int32_t)v - 32768;
|
||||
}
|
||||
digitalWrite(13, LOW);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
while (!Serial) delay(10);
|
||||
Serial.begin(115200);
|
||||
pinMode(13, OUTPUT);
|
||||
delay(100);
|
||||
Serial.println("PDM Mic test");
|
||||
Serial.println("-------------------");
|
||||
|
||||
pdmspi.begin(16000);
|
||||
Serial.print("Final PDM frequency: "); Serial.println(pdmspi.sampleRate);
|
||||
|
||||
// Set up analog output ------------------------------------------
|
||||
analogWriteResolution(12);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
analogWrite(A0, 2048 + pdmLastReading / 16);
|
||||
//Serial.println(voiceLastReading);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
name=Adafruit Zero PDM Library
|
||||
version=1.2.4
|
||||
author=Adafruit
|
||||
maintainer=Adafruit <info@adafruit.com>
|
||||
sentence=PDM Microphone Input library for Arduino Zero / Adafruit Feather M0 (SAMD21 processor).
|
||||
paragraph=PDM Microphone Input library for the Arduino Zero / Adafruit Feather M0 (SAMD21 processor).
|
||||
category=Signal Input/Output
|
||||
url=https://github.com/adafruit/Adafruit_ZeroPDM
|
||||
architectures=samd
|
||||
depends=Adafruit Zero DMA Library
|
||||
Reference in New Issue
Block a user