initial commit

This commit is contained in:
2026-09-25 10:48:51 +10:00
commit b90beea8ae
1020 changed files with 131665 additions and 0 deletions
@@ -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 [![Build Status](https://github.com/adafruit/Adafruit_ZeroPDM/workflows/Arduino%20Library%20CI/badge.svg)](https://github.com/adafruit/Adafruit_ZeroPDM/actions)[![Documentation](https://github.com/adafruit/ci-arduino/blob/master/assets/doxygen_badge.svg)](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