Add I2S support

This commit is contained in:
Sandeep Mistry 2016-11-03 16:43:44 -04:00
parent 72b1dc1a9b
commit 5a033fed7d
14 changed files with 1536 additions and 0 deletions

View file

@ -65,6 +65,7 @@ void init( void )
// Capture error
while ( 1 ) ;
}
NVIC_SetPriority (SysTick_IRQn, (1 << __NVIC_PRIO_BITS) - 2); /* set Priority for Systick Interrupt (2nd lowest) */
// Clock PORT for Digital I/O
// PM->APBBMASK.reg |= PM_APBBMASK_PORT ;

View file

@ -0,0 +1,46 @@
/*
This example reads audio data from an Invensense's ICS43432 I2S microphone
breakout board, and prints out the samples to the Serial console. The
Serial Plotter built into the Arduino IDE can be used to plot the audio
data (Tools -> Serial Plotter)
Circuit:
* Arduino/Genuino Zero or MKR1000 board
* ICS43432:
* GND connected GND
* 3.3V connected 3.3V
* WS connected to pin 0 (Zero) or pin 3 (MKR1000)
* CLK connected to pin 1 (Zero) or pin 2 (MKR1000)
* SD connected to pin 9 (Zero) or pin A6 (MKR1000)
created 17 November 2016
by Sandeep Mistry
*/
#include <I2S.h>
void setup() {
// Open serial communications and wait for port to open:
// A baud rate of 115200 is used instead of 9600 for a faster data rate
// on non-native USB ports
Serial.begin(115200);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
// start I2S at 8 kHz with 32-bits per sample
if (I2S.begin(I2S_PHILIPS_MODE, 8000, 32)) {
Serial.println("Failed to initialize I2S!");
while (1); // do nothing
}
}
void loop() {
// read a sample
int sample = I2S.read();
if (sample) {
// if it's non-zero print value to serial
Serial.println(sample);
}
}

View file

@ -0,0 +1,53 @@
/*
This example generates a square wave based tone at a specified frequency
and sample rate. Then outputs the data using the I2S interface to a
MAX08357 I2S Amp Breakout board.
Circuit:
* Arduino/Genuino Zero or MKR1000 board
* MAX08357:
* GND connected GND
* VIN connected 5V
* LRC connected to pin 0 (Zero) or pin 3 (MKR1000)
* BCLK connected to pin 1 (Zero) or pin 2 (MKR1000)
* DIN connected to pin 9 (Zero) or pin A6 (MKR1000)
created 17 November 2016
by Sandeep Mistry
*/
#include <I2S.h>
const int frequency = 440; // frequency of square wave in Hz
const int amplitude = 500; // amplitude of square wave
const int sampleRate = 8000; // sample rate in Hz
const int halfWavelength = (sampleRate / frequency); // half wavelength of square wave
short sample = amplitude; // current sample value
int count = 0;
void setup() {
Serial.begin(9600);
Serial.println("I2S simple tone");
// start I2S at the sample rate with 16-bits per sample
if (I2S.begin(I2S_PHILIPS_MODE, sampleRate, 16)) {
Serial.println("Failed to initialize I2S!");
while (1); // do nothing
}
}
void loop() {
if (count % halfWavelength == 0) {
// invert the sample every half wavelength count multiple to generate square wave
sample = -1 * sample;
}
// write the same sample twice, once for left and once for the right channel
I2S.write(sample);
I2S.write(sample);
// increment the counter for the next sample
count++;
}

View file

@ -0,0 +1,25 @@
#######################################
# Syntax Coloring Map I2S
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
I2S KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
begin KEYWORD2
end KEYWORD2
onReceive KEYWORD2
onTransmit KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
I2S_PHILIPS_MODE LITERAL1
I2S_RIGHT_JUSTIFIED_MODE LITERAL1
I2S_LEFT_JUSTIFIED_MODE LITERAL1

View file

@ -0,0 +1,9 @@
name=I2S
version=1.0
author=Arduino
maintainer=Arduino <info@arduino.cc>
sentence=Enables the communication with devices that use the Inter-IC Sound (I2S) Bus. Specific implementation for Arduino Zero.
paragraph=
category=Communication
url=http://www.arduino.cc/en/Reference/I2S
architectures=samd

519
libraries/I2S/src/I2S.cpp Normal file
View file

@ -0,0 +1,519 @@
/*
Copyright (c) 2016 Arduino LLC. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <Arduino.h>
#include <wiring_private.h>
#include "utility/DMA.h"
#include "utility/SAMD21_I2SDevice.h"
static I2SDevice_SAMD21G18x i2sd(*I2S);
#include "I2S.h"
int I2SClass::_beginCount = 0;
I2SClass::I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, uint8_t sckPin, uint8_t fsPin) :
_deviceIndex(deviceIndex),
_clockGenerator(clockGenerator),
_sdPin(sdPin),
_sckPin(sckPin),
_fsPin(fsPin),
_state(I2S_STATE_IDLE),
_dmaChannel(-1),
_bitsPerSample(0),
_dmaTransferInProgress(false),
_onTransmit(NULL),
_onReceive(NULL)
{
}
int I2SClass::begin(int mode, long sampleRate, int bitsPerSample)
{
// master mode (driving clock and frame select pins - output)
return begin(mode, sampleRate, bitsPerSample, true);
}
int I2SClass::begin(int mode, int bitsPerSample)
{
// slave mode (not driving clock and frame select pin - input)
return begin(mode, 0, bitsPerSample, false);
}
int I2SClass::begin(int mode, long sampleRate, int bitsPerSample, bool driveClock)
{
if (_state != I2S_STATE_IDLE) {
return 1;
}
switch (mode) {
case I2S_PHILIPS_MODE:
case I2S_RIGHT_JUSTIFIED_MODE:
case I2S_LEFT_JUSTIFIED_MODE:
break;
default:
// invalid mode
return 1;
}
switch (bitsPerSample) {
case 8:
case 16:
case 32:
_bitsPerSample = bitsPerSample;
break;
default:
// invalid bits per sample
return 1;
}
// try to allocate a DMA channel
DMA.begin();
_dmaChannel = DMA.allocateChannel();
if (_dmaChannel < 0) {
// no DMA channel available
return 1;
}
if (_beginCount == 0) {
// enable the I2S interface
PM->APBCMASK.reg |= PM_APBCMASK_I2S;
// reset the device
i2sd.reset();
}
_beginCount++;
if (driveClock) {
// set up clock
enableClock(sampleRate * 2 * bitsPerSample);
i2sd.setSerialClockSelectMasterClockDiv(_deviceIndex);
i2sd.setFrameSyncSelectSerialClockDiv(_deviceIndex);
} else {
// use input signal from SCK and FS pins
i2sd.setSerialClockSelectPin(_deviceIndex);
i2sd.setFrameSyncSelectPin(_deviceIndex);
}
// disable device before continuing
i2sd.disable();
if (mode == I2S_PHILIPS_MODE) {
i2sd.set1BitDelay(_deviceIndex);
} else {
i2sd.set0BitDelay(_deviceIndex);
}
i2sd.setNumberOfSlots(_deviceIndex, 1);
i2sd.setSlotSize(_deviceIndex, bitsPerSample);
i2sd.setDataSize(_deviceIndex, bitsPerSample);
pinPeripheral(_sckPin, PIO_COM);
pinPeripheral(_fsPin, PIO_COM);
if (mode == I2S_RIGHT_JUSTIFIED_MODE) {
i2sd.setSlotAdjustedRight(_deviceIndex);
} else {
i2sd.setSlotAdjustedLeft(_deviceIndex);
}
i2sd.setClockUnit(_deviceIndex);
pinPeripheral(_sdPin, PIO_COM);
// done configure enable
i2sd.enable();
_doubleBuffer.reset();
return 0;
}
void I2SClass::end()
{
if (_dmaChannel > -1) {
DMA.freeChannel(_dmaChannel);
}
_state = I2S_STATE_IDLE;
_dmaTransferInProgress = false;
i2sd.disableSerializer(_deviceIndex);
i2sd.disableClockUnit(_deviceIndex);
// set the pins back to input mode
pinMode(_sdPin, INPUT);
pinMode(_fsPin, INPUT);
pinMode(_sckPin, INPUT);
disableClock();
_beginCount--;
if (_beginCount == 0) {
i2sd.disable();
// disable the I2S interface
PM->APBCMASK.reg &= ~PM_APBCMASK_I2S;
}
}
int I2SClass::available()
{
if (_state != I2S_STATE_RECEIVER) {
enableReceiver();
}
uint8_t enableInterrupts = ((__get_PRIMASK() & 0x1) == 0);
size_t avail;
// disable interrupts,
__disable_irq();
avail = _doubleBuffer.available();
if (_dmaTransferInProgress == false && _doubleBuffer.available() == 0) {
// no DMA transfer in progress, start a receive process
_dmaTransferInProgress = true;
DMA.transfer(_dmaChannel, i2sd.data(_deviceIndex), _doubleBuffer.data(), _doubleBuffer.availableForWrite());
// switch to the next buffer for user output (will be empty)
_doubleBuffer.swap();
}
if (enableInterrupts) {
// re-enable the interrupts
__enable_irq();
}
return avail;
}
union i2s_sample_t {
uint8_t b8;
int16_t b16;
int32_t b32;
};
int I2SClass::read()
{
i2s_sample_t sample;
sample.b32 = 0;
read(&sample, _bitsPerSample / 8);
if (_bitsPerSample == 32) {
return sample.b32;
} else if (_bitsPerSample == 16) {
return sample.b16;
} else if (_bitsPerSample == 8) {
return sample.b8;
} else {
return 0;
}
}
int I2SClass::peek()
{
uint8_t enableInterrupts = ((__get_PRIMASK() & 0x1) == 0);
i2s_sample_t sample;
sample.b32 = 0;
// disable interrupts,
__disable_irq();
_doubleBuffer.peek(&sample, _bitsPerSample / 8);
if (enableInterrupts) {
// re-enable the interrupts
__enable_irq();
}
if (_bitsPerSample == 32) {
return sample.b32;
} else if (_bitsPerSample == 16) {
return sample.b16;
} else if (_bitsPerSample == 8) {
return sample.b8;
} else {
return 0;
}
}
void I2SClass::flush()
{
// do nothing, writes are DMA triggered
}
size_t I2SClass::write(uint8_t data)
{
return write((int32_t)data);
}
size_t I2SClass::write(const uint8_t *buffer, size_t size)
{
return write((const void*)buffer, size);
}
size_t I2SClass::availableForWrite()
{
if (_state != I2S_STATE_TRANSMITTER) {
enableTransmitter();
}
uint8_t enableInterrupts = ((__get_PRIMASK() & 0x1) == 0);
size_t space;
// disable interrupts,
__disable_irq();
space = _doubleBuffer.availableForWrite();
if (enableInterrupts) {
// re-enable the interrupts
__enable_irq();
}
return space;
}
int I2SClass::read(void* buffer, size_t size)
{
if (_state != I2S_STATE_RECEIVER) {
enableReceiver();
}
uint8_t enableInterrupts = ((__get_PRIMASK() & 0x1) == 0);
// disable interrupts,
__disable_irq();
int read = _doubleBuffer.read(buffer, size);
if (_dmaTransferInProgress == false && _doubleBuffer.available() == 0) {
// no DMA transfer in progress, start a receive process
_dmaTransferInProgress = true;
DMA.transfer(_dmaChannel, i2sd.data(_deviceIndex), _doubleBuffer.data(), _doubleBuffer.availableForWrite());
// switch to the next buffer for user output (will be empty)
_doubleBuffer.swap();
}
if (enableInterrupts) {
// re-enable the interrupts
__enable_irq();
}
return read;
}
size_t I2SClass::write(int sample)
{
return write((int32_t)sample);
}
size_t I2SClass::write(int32_t sample)
{
if (_state != I2S_STATE_TRANSMITTER) {
enableTransmitter();
}
// this is a blocking write
while(!i2sd.txReady(_deviceIndex));
i2sd.writeData(_deviceIndex, sample);
i2sd.clearTxReady(_deviceIndex);
return 1;
}
size_t I2SClass::write(const void *buffer, size_t size)
{
if (_state != I2S_STATE_TRANSMITTER) {
enableTransmitter();
}
uint8_t enableInterrupts = ((__get_PRIMASK() & 0x1) == 0);
size_t written;
// disable interrupts,
__disable_irq();
written = _doubleBuffer.write(buffer, size);
if (_dmaTransferInProgress == false && _doubleBuffer.available()) {
// no DMA transfer in progress, start a transmit process
_dmaTransferInProgress = true;
DMA.transfer(_dmaChannel, _doubleBuffer.data(), i2sd.data(_deviceIndex), _doubleBuffer.available());
// switch to the next buffer for input
_doubleBuffer.swap();
}
if (enableInterrupts) {
// re-enable the interrupts
__enable_irq();
}
return written;
}
void I2SClass::onTransmit(void(*function)(void))
{
_onTransmit = function;
}
void I2SClass::onReceive(void(*function)(void))
{
_onReceive = function;
}
void I2SClass::enableClock(int divider)
{
// configure the clock divider
while (GCLK->STATUS.bit.SYNCBUSY);
GCLK->GENDIV.bit.ID = _clockGenerator;
GCLK->GENDIV.bit.DIV = SystemCoreClock / divider;
// use the DFLL as the source
while (GCLK->STATUS.bit.SYNCBUSY);
GCLK->GENCTRL.bit.ID = _clockGenerator;
GCLK->GENCTRL.bit.SRC = GCLK_GENCTRL_SRC_DFLL48M_Val;
GCLK->GENCTRL.bit.IDC = 1;
GCLK->GENCTRL.bit.GENEN = 1;
// enable
while (GCLK->STATUS.bit.SYNCBUSY);
GCLK->CLKCTRL.bit.ID = i2sd.glckId(_deviceIndex);
GCLK->CLKCTRL.bit.GEN = _clockGenerator;
GCLK->CLKCTRL.bit.CLKEN = 1;
while (GCLK->STATUS.bit.SYNCBUSY);
}
void I2SClass::disableClock()
{
while (GCLK->STATUS.bit.SYNCBUSY);
GCLK->GENCTRL.bit.ID = _clockGenerator;
GCLK->GENCTRL.bit.SRC = GCLK_GENCTRL_SRC_DFLL48M_Val;
GCLK->GENCTRL.bit.IDC = 1;
GCLK->GENCTRL.bit.GENEN = 0;
while (GCLK->STATUS.bit.SYNCBUSY);
GCLK->CLKCTRL.bit.ID = i2sd.glckId(_deviceIndex);
GCLK->CLKCTRL.bit.GEN = _clockGenerator;
GCLK->CLKCTRL.bit.CLKEN = 0;
while (GCLK->STATUS.bit.SYNCBUSY);
}
void I2SClass::enableTransmitter()
{
i2sd.setTxMode(_deviceIndex);
i2sd.enableClockUnit(_deviceIndex);
i2sd.enableSerializer(_deviceIndex);
DMA.incSrc(_dmaChannel);
DMA.onTransferComplete(_dmaChannel, I2SClass::onDmaTransferComplete);
DMA.setTriggerSource(_dmaChannel, i2sd.dmaTriggerSource(_deviceIndex));
DMA.setTransferWidth(_dmaChannel, _bitsPerSample);
_state = I2S_STATE_TRANSMITTER;
}
void I2SClass::enableReceiver()
{
i2sd.setRxMode(_deviceIndex);
i2sd.enableClockUnit(_deviceIndex);
i2sd.enableSerializer(_deviceIndex);
DMA.incDst(_dmaChannel);
DMA.onTransferComplete(_dmaChannel, I2SClass::onDmaTransferComplete);
DMA.setTriggerSource(_dmaChannel, i2sd.dmaTriggerSource(_deviceIndex));
DMA.setTransferWidth(_dmaChannel, _bitsPerSample);
_state = I2S_STATE_RECEIVER;
}
void I2SClass::onTransferComplete(void)
{
if (_state == I2S_STATE_TRANSMITTER) {
// transmit complete
if (_doubleBuffer.available()) {
// output is available to transfer, start the DMA process for the current buffer
DMA.transfer(_dmaChannel, _doubleBuffer.data(), i2sd.data(_deviceIndex), _doubleBuffer.available());
// swap to the next user buffer for input
_doubleBuffer.swap();
} else {
// no user data buffered to send
_dmaTransferInProgress = false;
}
// call the users transmit callback if provided
if (_onTransmit) {
_onTransmit();
}
} else {
// receive complete
if (_doubleBuffer.available() == 0) {
// the user has read all the current input, start the DMA process to fill it again
DMA.transfer(_dmaChannel, i2sd.data(_deviceIndex), _doubleBuffer.data(), _doubleBuffer.availableForWrite());
// swap to the next buffer that has previously been filled, so that the user can read it
_doubleBuffer.swap(_doubleBuffer.availableForWrite());
} else {
// user has not read current data, no free buffer to transfer into
_dmaTransferInProgress = false;
}
// call the users receveive callback if provided
if (_onReceive) {
_onReceive();
}
}
}
void I2SClass::onDmaTransferComplete(int channel)
{
#if I2S_INTERFACES_COUNT > 0
if (I2S._dmaChannel == channel) {
I2S.onTransferComplete();
}
#endif
}
#if I2S_INTERFACES_COUNT > 0
I2SClass I2S(I2S_DEVICE, I2S_CLOCK_GENERATOR, PIN_I2S_SD, PIN_I2S_SCK, PIN_I2S_FS);
#endif

112
libraries/I2S/src/I2S.h Normal file
View file

@ -0,0 +1,112 @@
/*
Copyright (c) 2016 Arduino LLC. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _I2S_H_INCLUDED
#define _I2S_H_INCLUDED
#include <Arduino.h>
#include "utility/I2SDoubleBuffer.h"
typedef enum {
I2S_PHILIPS_MODE,
I2S_RIGHT_JUSTIFIED_MODE,
I2S_LEFT_JUSTIFIED_MODE
} i2s_mode_t;
class I2SClass : public Stream
{
public:
// the device index and pins must map to the "COM" pads in Table 6-1 of the datasheet
I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, uint8_t sckPin, uint8_t fsPin);
// the SCK and FS pins are driven as outputs using the sample rate
int begin(int mode, long sampleRate, int bitsPerSample);
// the SCK and FS pins are inputs, other side controls sample rate
int begin(int mode, int bitsPerSample);
void end();
// from Stream
virtual int available();
virtual int read();
virtual int peek();
virtual void flush();
// from Print
virtual size_t write(uint8_t);
virtual size_t write(const uint8_t *buffer, size_t size);
virtual size_t availableForWrite();
int read(void* buffer, size_t size);
size_t write(int);
size_t write(int32_t);
size_t write(const void *buffer, size_t size);
void onTransmit(void(*)(void));
void onReceive(void(*)(void));
private:
int begin(int mode, long sampleRate, int bitsPerSample, bool driveClock);
void enableClock(int divider);
void disableClock();
void enableTransmitter();
void enableReceiver();
void onTransferComplete(void);
static void onDmaTransferComplete(int);
private:
typedef enum {
I2S_STATE_IDLE,
I2S_STATE_TRANSMITTER,
I2S_STATE_RECEIVER
} i2s_state_t;
static int _beginCount;
uint8_t _deviceIndex;
uint8_t _clockGenerator;
uint8_t _sdPin;
uint8_t _sckPin;
uint8_t _fsPin;
i2s_state_t _state;
int _dmaChannel;
int _bitsPerSample;
volatile bool _dmaTransferInProgress;
I2SDoubleBuffer _doubleBuffer;
void (*_onTransmit)(void);
void (*_onReceive)(void);
};
// "I2S" is already defined by the CMSIS device, undefine it so the I2SClass
// instance can be called I2S
#undef I2S
#if I2S_INTERFACES_COUNT > 0
extern I2SClass I2S;
#endif
#endif

View file

@ -0,0 +1,284 @@
/*
Copyright (c) 2016 Arduino LLC. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <Arduino.h>
#include "DMA.h"
int DMAClass::_beginCount = 0;
DMAClass::DMAClass() :
_channelMask(0)
{
memset(_transferCompleteCallbacks, 0x00, sizeof(_transferCompleteCallbacks));
memset(_transferErrorCallbacks, 0x00, sizeof(_transferErrorCallbacks));
memset(_descriptors, 0x00, sizeof(_descriptors));
memset(_descriptorsWriteBack, 0x00, sizeof(_descriptorsWriteBack));
}
DMAClass::~DMAClass()
{
}
void DMAClass::begin()
{
if (_beginCount == 0) {
// enable the DMA interface
PM->AHBMASK.bit.DMAC_ = 1;
PM->APBBMASK.bit.DMAC_ = 1;
// perform a reset
DMAC->CTRL.bit.SWRST = 1;
// configure the descriptor addresses
DMAC->BASEADDR.bit.BASEADDR = (uint32_t)_descriptors;
DMAC->WRBADDR.bit.WRBADDR = (uint32_t)_descriptorsWriteBack;
// enable with all levels
DMAC->CTRL.bit.LVLEN0 = 1;
DMAC->CTRL.bit.LVLEN1 = 1;
DMAC->CTRL.bit.LVLEN2 = 1;
DMAC->CTRL.bit.LVLEN3 = 1;
DMAC->CTRL.bit.DMAENABLE = 1;
// enable the interrupt at lowest priority
NVIC_EnableIRQ(DMAC_IRQn);
NVIC_SetPriority(DMAC_IRQn, (1 << __NVIC_PRIO_BITS) - 1);
}
_beginCount++;
}
void DMAClass::end()
{
_beginCount--;
if (_beginCount == 0) {
// disable the interrupt
NVIC_DisableIRQ(DMAC_IRQn);
// disable
DMAC->CTRL.bit.DMAENABLE = 0;
// disable the DMA interface
PM->APBBMASK.bit.DMAC_ = 0;
PM->AHBMASK.bit.DMAC_ = 0;
}
}
int DMAClass::allocateChannel()
{
int channel = -1;
// try to find a free DMA channel
for (int i = 0; i < NUM_DMA_CHANNELS; i++) {
if ((_channelMask & (1 << i)) == 0) {
// found one, set the mask bit to indicate it is allocated
_channelMask |= (1 << i);
// clear the descriptor for the channel
memset((void*)&_descriptors[i], 0x00, sizeof(_descriptors[i]));
// select the channel and reset it
DMAC->CHID.bit.ID = i;
DMAC->CHCTRLA.bit.ENABLE = 0;
DMAC->CHCTRLA.bit.SWRST = 1;
channel = i;
break;
}
}
return channel;
}
void DMAClass::freeChannel(int channel)
{
// select the channel and disable it
DMAC->CHID.bit.ID = channel;
DMAC->CHCTRLA.bit.ENABLE = 0;
_channelMask &= ~(1 << channel);
}
void DMAClass::setPriorityLevel(int channel, int level)
{
// select the channel and set priority level
DMAC->CHID.bit.ID = channel;
DMAC->CHCTRLB.bit.LVL = level;
}
void DMAClass::setTriggerSource(int channel, int source)
{
// select the channel and set a trigger source
DMAC->CHID.bit.ID = channel;
DMAC->CHCTRLB.bit.TRIGSRC = source;
if (DMAC->CHCTRLB.bit.TRIGSRC) {
// if it's not a software source (0), set trigger action a a beat
DMAC->CHCTRLB.bit.TRIGACT = DMAC_CHCTRLB_TRIGACT_BEAT_Val;
} else {
DMAC->CHCTRLB.bit.TRIGACT = DMAC_CHCTRLB_TRIGACT_BLOCK_Val;
}
}
void DMAClass::setTransferWidth(int channel, int transferWidth)
{
// select the channel and set transfer width
switch (transferWidth) {
case 8:
default:
_descriptors[channel].BTCTRL.bit.BEATSIZE = DMAC_BTCTRL_BEATSIZE_BYTE_Val;
break;
case 16:
_descriptors[channel].BTCTRL.bit.BEATSIZE = DMAC_BTCTRL_BEATSIZE_HWORD_Val;
break;
case 32:
_descriptors[channel].BTCTRL.bit.BEATSIZE = DMAC_BTCTRL_BEATSIZE_WORD_Val;
break;
}
}
void DMAClass::incSrc(int channel)
{
// select the channel and enable source increment
_descriptors[channel].BTCTRL.bit.STEPSEL = DMAC_BTCTRL_STEPSEL_SRC_Val;
_descriptors[channel].BTCTRL.bit.SRCINC = 1;
}
void DMAClass::incDst(int channel)
{
// select the channel and enable destination increment
_descriptors[channel].BTCTRL.bit.STEPSEL = DMAC_BTCTRL_STEPSEL_DST_Val;
_descriptors[channel].BTCTRL.bit.DSTINC = 1;
}
int DMAClass::transfer(int channel, void* src, void* dst, uint16_t size)
{
if (_descriptors[channel].BTCTRL.bit.VALID) {
// transfer in progress, fail
return 1;
}
// select the channel
DMAC->CHID.bit.ID = channel;
// disable event output generation and block actions
_descriptors[channel].BTCTRL.bit.EVOSEL = DMAC_BTCTRL_EVOSEL_DISABLE_Val;
_descriptors[channel].BTCTRL.bit.BLOCKACT = DMAC_BTCTRL_BLOCKACT_NOACT_Val;
// map beat size to transfer width in bytes
int transferWidth;
switch (_descriptors[channel].BTCTRL.bit.BEATSIZE) {
case DMAC_BTCTRL_BEATSIZE_BYTE_Val:
default:
transferWidth = 1;
break;
case DMAC_BTCTRL_BEATSIZE_HWORD_Val:
transferWidth = 2;
break;
case DMAC_BTCTRL_BEATSIZE_WORD_Val:
transferWidth = 4;
break;
}
// set step size to 1, source + destination addresses, no next descriptor block count
_descriptors[channel].BTCTRL.bit.STEPSIZE = DMAC_BTCTRL_STEPSIZE_X1_Val;
_descriptors[channel].SRCADDR.bit.SRCADDR = (uint32_t)src;
_descriptors[channel].DSTADDR.bit.DSTADDR = (uint32_t)dst;
_descriptors[channel].DESCADDR.bit.DESCADDR = 0;
_descriptors[channel].BTCNT.bit.BTCNT = size / transferWidth;
if (_descriptors[channel].BTCTRL.bit.SRCINC) {
// if increment source is set, the source address must be the end address
_descriptors[channel].SRCADDR.bit.SRCADDR += size;
}
if (_descriptors[channel].BTCTRL.bit.DSTINC) {
// if increment destination is set, the destination address must be the end address
_descriptors[channel].DSTADDR.bit.DSTADDR += size;
}
// validate the descriptor
_descriptors[channel].BTCTRL.bit.VALID = 1;
// enable channel and transfer error + complete interrupts
DMAC->CHINTENSET.bit.TERR = 1;
DMAC->CHINTENSET.bit.TCMPL = 1;
DMAC->CHCTRLA.bit.ENABLE = 1;
if (DMAC->CHCTRLB.bit.TRIGSRC == 0) {
// uses software trigger, so trigger it
DMAC->SWTRIGCTRL.reg |= (1 << channel);
}
return 0;
}
void DMAClass::onTransferComplete(int channel, void(*function)(int))
{
_transferCompleteCallbacks[channel] = function;
}
void DMAClass::onTransferError(int channel, void(*function)(int))
{
_transferErrorCallbacks[channel] = function;
}
void DMAClass::onService()
{
// get the channel and select it
int channel = DMAC->INTPEND.bit.ID;
DMAC->CHID.bit.ID = channel;
// invalidate the channel
_descriptors[channel].BTCTRL.bit.VALID = 0;
if (DMAC->CHINTFLAG.bit.TERR) {
// clear the error interrupt and call the error callback if there is one
DMAC->CHINTFLAG.bit.TERR = 1;
if (_transferErrorCallbacks[channel]) {
_transferErrorCallbacks[channel](channel);
}
}
if (DMAC->CHINTFLAG.bit.TCMPL) {
// clear the complete interrupt and call the callback if there is one
DMAC->CHINTFLAG.bit.TCMPL = 1;
if (_transferCompleteCallbacks[channel]) {
_transferCompleteCallbacks[channel](channel);
}
}
}
extern "C" {
void DMAC_Handler() {
DMA.onService();
}
}
DMAClass DMA;

View file

@ -0,0 +1,60 @@
/*
Copyright (c) 2016 Arduino LLC. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#define NUM_DMA_CHANNELS 1
/*
WARNING: The API for this class may change and it's not intended for public use!
*/
class DMAClass
{
public:
DMAClass();
virtual ~DMAClass();
void begin();
void end();
int allocateChannel();
void freeChannel(int channel);
void setPriorityLevel(int channel, int level);
void setTriggerSource(int channel, int source);
void setTransferWidth(int channel, int transferWidth);
void incSrc(int channel);
void incDst(int channel);
int transfer(int channel, void* src, void* dst, uint16_t size);
void onTransferComplete(int channel, void(*function)(int));
void onTransferError(int channel, void(*function)(int));
void onService();
private:
static int _beginCount;
uint32_t _channelMask;
void (*_transferCompleteCallbacks[NUM_DMA_CHANNELS])(int);
void (*_transferErrorCallbacks[NUM_DMA_CHANNELS])(int);
DmacDescriptor _descriptors[NUM_DMA_CHANNELS] __attribute__ ((aligned (16)));
DmacDescriptor _descriptorsWriteBack[NUM_DMA_CHANNELS] __attribute__ ((aligned (16)));
};
extern DMAClass DMA;

View file

@ -0,0 +1,120 @@
/*
Copyright (c) 2016 Arduino LLC. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <string.h>
#include "I2SDoubleBuffer.h"
I2SDoubleBuffer::I2SDoubleBuffer()
{
reset();
}
I2SDoubleBuffer::~I2SDoubleBuffer()
{
}
void I2SDoubleBuffer::reset()
{
_index = 0;
_length[0] = 0;
_length[1] = 0;
_readOffset[0] = 0;
_readOffset[1] = 0;
}
size_t I2SDoubleBuffer::availableForWrite()
{
return (I2S_BUFFER_SIZE - (_length[_index] - _readOffset[_index]));
}
size_t I2SDoubleBuffer::write(const void *buffer, size_t size)
{
size_t space = availableForWrite();
if (size > space) {
size = space;
}
if (size == 0) {
return 0;
}
memcpy(&_buffer[_index][_length[_index]], buffer, size);
_length[_index] += size;
return size;
}
size_t I2SDoubleBuffer::read(void *buffer, size_t size)
{
size_t avail = available();
if (size > avail) {
size = avail;
}
if (size == 0) {
return 0;
}
memcpy(buffer, &_buffer[_index][_readOffset[_index]], size);
_readOffset[_index] += size;
return size;
}
size_t I2SDoubleBuffer::peek(void *buffer, size_t size)
{
size_t avail = available();
if (size > avail) {
size = avail;
}
if (size == 0) {
return 0;
}
memcpy(buffer, &_buffer[_index][_readOffset[_index]], size);
return size;
}
void* I2SDoubleBuffer::data()
{
return (void*)_buffer[_index];
}
size_t I2SDoubleBuffer::available()
{
return _length[_index] - _readOffset[_index];
}
void I2SDoubleBuffer::swap(int length)
{
if (_index == 0) {
_index = 1;
} else {
_index = 0;
}
_length[_index] = length;
_readOffset[_index] = 0;
}

View file

@ -0,0 +1,50 @@
/*
Copyright (c) 2016 Arduino LLC. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _I2S_DOUBLE_BUFFER_H_INCLUDED
#define _I2S_DOUBLE_BUFFER_H_INCLUDED
#include <stddef.h>
#include <stdint.h>
#define I2S_BUFFER_SIZE 512
class I2SDoubleBuffer
{
public:
I2SDoubleBuffer();
virtual ~I2SDoubleBuffer();
void reset();
size_t availableForWrite();
size_t write(const void *buffer, size_t size);
size_t read(void *buffer, size_t size);
size_t peek(void *buffer, size_t size);
void* data();
size_t available();
void swap(int length = 0);
private:
uint8_t _buffer[2][I2S_BUFFER_SIZE];
volatile int _length[2];
volatile int _readOffset[2];
volatile int _index;
};
#endif

View file

@ -0,0 +1,236 @@
/*
Copyright (c) 2016 Arduino LLC. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include <Arduino.h>
class I2SDevice_SAMD21G18x {
public:
I2SDevice_SAMD21G18x(I2s& _i2s) :
i2s(_i2s)
{
// Empty
}
inline void reset() {
while(i2s.SYNCBUSY.bit.SWRST);
i2s.CTRLA.bit.SWRST = 1;
}
inline void disable() {
while(i2s.SYNCBUSY.bit.ENABLE);
i2s.CTRLA.bit.ENABLE = 0;
}
inline void enable() {
while(i2s.SYNCBUSY.bit.ENABLE);
i2s.CTRLA.bit.ENABLE = 1;
}
inline int glckId(int index) {
return (index == 0) ? I2S_GCLK_ID_0 : I2S_GCLK_ID_1;
}
inline void setSerialClockSelectMasterClockDiv(int index) {
i2s.CLKCTRL[index].bit.SCKSEL = I2S_CLKCTRL_SCKSEL_MCKDIV_Val;
}
inline void setSerialClockSelectPin(int index) {
i2s.CLKCTRL[index].bit.SCKSEL = I2S_CLKCTRL_SCKSEL_SCKPIN_Val;
}
inline void setFrameSyncSelectSerialClockDiv(int index) {
i2s.CLKCTRL[index].bit.FSSEL = I2S_CLKCTRL_FSSEL_SCKDIV_Val;
}
inline void setFrameSyncSelectPin(int index) {
i2s.CLKCTRL[index].bit.FSSEL = I2S_CLKCTRL_FSSEL_FSPIN_Val;
}
inline void set0BitDelay(int index) {
i2s.CLKCTRL[index].bit.BITDELAY = I2S_CLKCTRL_BITDELAY_LJ_Val;
}
inline void set1BitDelay(int index) {
i2s.CLKCTRL[index].bit.BITDELAY = I2S_CLKCTRL_BITDELAY_I2S_Val;
}
inline void setNumberOfSlots(int index, int nbslots) {
i2s.CLKCTRL[index].bit.NBSLOTS = nbslots;
}
inline void setSlotSize(int index, int size) {
switch (size) {
case 32:
i2s.CLKCTRL[index].bit.SLOTSIZE = I2S_CLKCTRL_SLOTSIZE_32_Val;
break;
case 24:
i2s.CLKCTRL[index].bit.SLOTSIZE = I2S_CLKCTRL_SLOTSIZE_24_Val;
break;
case 16:
i2s.CLKCTRL[index].bit.SLOTSIZE = I2S_CLKCTRL_SLOTSIZE_16_Val;
break;
case 8:
i2s.CLKCTRL[index].bit.SLOTSIZE = I2S_CLKCTRL_SLOTSIZE_8_Val;
break;
}
}
inline void setDataSize(int index, int size) {
switch (size) {
case 32:
i2s.SERCTRL[index].bit.DATASIZE = I2S_SERCTRL_DATASIZE_32_Val;
break;
case 24:
i2s.SERCTRL[index].bit.DATASIZE = I2S_SERCTRL_DATASIZE_24_Val;
break;
case 16:
i2s.SERCTRL[index].bit.DATASIZE = I2S_SERCTRL_DATASIZE_16_Val;
break;
case 8:
i2s.SERCTRL[index].bit.DATASIZE = I2S_SERCTRL_DATASIZE_8_Val;
break;
}
}
inline void setSlotAdjustedRight(int index) {
i2s.SERCTRL[index].bit.SLOTADJ = I2S_SERCTRL_SLOTADJ_RIGHT_Val;
}
inline void setSlotAdjustedLeft(int index) {
i2s.SERCTRL[index].bit.SLOTADJ = I2S_SERCTRL_SLOTADJ_LEFT_Val;
}
inline void setClockUnit(int index) {
i2s.SERCTRL[index].bit.CLKSEL = (index == 0) ? I2S_SERCTRL_CLKSEL_CLK0_Val : I2S_SERCTRL_CLKSEL_CLK1_Val;
}
inline void setTxMode(int index) {
i2s.SERCTRL[index].bit.SERMODE = I2S_SERCTRL_SERMODE_TX_Val;
}
inline void setRxMode(int index) {
i2s.SERCTRL[index].bit.SERMODE = I2S_SERCTRL_SERMODE_RX_Val;
}
inline void enableClockUnit(int index) {
if (index == 0) {
while(i2s.SYNCBUSY.bit.CKEN0);
i2s.CTRLA.bit.CKEN0 = 1;
} else {
while(i2s.SYNCBUSY.bit.CKEN1);
i2s.CTRLA.bit.CKEN1 = 1;
}
}
inline void disableClockUnit(int index) {
if (index == 0) {
while(i2s.SYNCBUSY.bit.CKEN0);
i2s.CTRLA.bit.CKEN0 = 0;
} else {
while(i2s.SYNCBUSY.bit.CKEN1);
i2s.CTRLA.bit.CKEN1 = 0;
}
}
inline void enableSerializer(int index) {
if (index == 0) {
while(i2s.SYNCBUSY.bit.SEREN0);
i2s.CTRLA.bit.SEREN0 = 1;
} else {
while(i2s.SYNCBUSY.bit.SEREN1);
i2s.CTRLA.bit.SEREN1 = 1;
}
}
inline void disableSerializer(int index) {
if (index == 0) {
while(i2s.SYNCBUSY.bit.SEREN0);
i2s.CTRLA.bit.SEREN0 = 0;
} else {
while(i2s.SYNCBUSY.bit.SEREN1);
i2s.CTRLA.bit.SEREN1 = 0;
}
}
inline int dmaTriggerSource(int index) {
if (i2s.SERCTRL[index].bit.SERMODE == I2S_SERCTRL_SERMODE_TX_Val) {
return (index == 0) ? I2S_DMAC_ID_TX_0 : I2S_DMAC_ID_TX_1;
} else {
return (index == 0) ? I2S_DMAC_ID_RX_0 : I2S_DMAC_ID_RX_1;
}
}
inline int txReady(int index) {
return (index == 0) ? i2s.INTFLAG.bit.TXRDY0 :i2s.INTFLAG.bit.TXRDY1;
}
inline void writeData(int index, int32_t value) {
if (index == 0) {
while (i2s.SYNCBUSY.bit.DATA0);
} else {
while (i2s.SYNCBUSY.bit.DATA1);
}
i2s.DATA[index].bit.DATA = value;
}
inline void clearTxReady(int index) {
if (index == 0) {
i2s.INTFLAG.bit.TXRDY0 = 1;
} else {
i2s.INTFLAG.bit.TXRDY1 = 1;
}
}
inline int rxReady(int index) {
return (index == 0) ? i2s.INTFLAG.bit.RXRDY0 :i2s.INTFLAG.bit.RXRDY1;
}
inline int32_t readData(int index) {
if (index == 0) {
while (i2s.SYNCBUSY.bit.DATA0);
} else {
while (i2s.SYNCBUSY.bit.DATA1);
}
return i2s.DATA[index].bit.DATA;
}
inline void clearRxReady(int index) {
if (index == 0) {
i2s.INTFLAG.bit.RXRDY0 = 1;
} else {
i2s.INTFLAG.bit.RXRDY1 = 1;
}
}
inline void* data(int index) {
return (void*)&i2s.DATA[index].reg;
}
private:
volatile I2s &i2s;
};

View file

@ -161,6 +161,17 @@ static const uint8_t SCL = PIN_WIRE_SCL;
#define PIN_USB_DM (28ul)
#define PIN_USB_DP (29ul)
/*
* I2S Interfaces
*/
#define I2S_INTERFACES_COUNT 1
#define I2S_DEVICE 0
#define I2S_CLOCK_GENERATOR 3
#define PIN_I2S_SD (9u)
#define PIN_I2S_SCK (1u)
#define PIN_I2S_FS (0u)
#ifdef __cplusplus
}
#endif

View file

@ -135,6 +135,16 @@ static const uint8_t SCL = PIN_WIRE_SCL;
#define PIN_USB_DP (23ul)
#define PIN_USB_HOST_ENABLE (24ul)
// I2S Interfaces
// --------------
#define I2S_INTERFACES_COUNT 1
#define I2S_DEVICE 0
#define I2S_CLOCK_GENERATOR 3
#define PIN_I2S_SD (PIN_A6)
#define PIN_I2S_SCK (2u)
#define PIN_I2S_FS (3u)
// Needed for WINC1501B (WiFi101) library
// --------------------------------------
#define WINC1501_RESET_PIN (30u)