diff --git a/cores/arduino/wiring.c b/cores/arduino/wiring.c index 0a1395d75..fca81bfbf 100644 --- a/cores/arduino/wiring.c +++ b/cores/arduino/wiring.c @@ -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 ; diff --git a/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino b/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino new file mode 100644 index 000000000..87c455de5 --- /dev/null +++ b/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino @@ -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 + +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); + } +} diff --git a/libraries/I2S/examples/SimpleTone/SimpleTone.ino b/libraries/I2S/examples/SimpleTone/SimpleTone.ino new file mode 100644 index 000000000..baab7a7a3 --- /dev/null +++ b/libraries/I2S/examples/SimpleTone/SimpleTone.ino @@ -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 + +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++; +} diff --git a/libraries/I2S/keywords.txt b/libraries/I2S/keywords.txt new file mode 100644 index 000000000..a57ee1466 --- /dev/null +++ b/libraries/I2S/keywords.txt @@ -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 diff --git a/libraries/I2S/library.properties b/libraries/I2S/library.properties new file mode 100644 index 000000000..ed172e96a --- /dev/null +++ b/libraries/I2S/library.properties @@ -0,0 +1,9 @@ +name=I2S +version=1.0 +author=Arduino +maintainer=Arduino +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 diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp new file mode 100644 index 000000000..0e6e9a285 --- /dev/null +++ b/libraries/I2S/src/I2S.cpp @@ -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 +#include + +#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 diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h new file mode 100644 index 000000000..a4107ef23 --- /dev/null +++ b/libraries/I2S/src/I2S.h @@ -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 + +#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 diff --git a/libraries/I2S/src/utility/DMA.cpp b/libraries/I2S/src/utility/DMA.cpp new file mode 100644 index 000000000..74eb58d48 --- /dev/null +++ b/libraries/I2S/src/utility/DMA.cpp @@ -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 + +#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; diff --git a/libraries/I2S/src/utility/DMA.h b/libraries/I2S/src/utility/DMA.h new file mode 100644 index 000000000..19b7b6250 --- /dev/null +++ b/libraries/I2S/src/utility/DMA.h @@ -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; diff --git a/libraries/I2S/src/utility/I2SDoubleBuffer.cpp b/libraries/I2S/src/utility/I2SDoubleBuffer.cpp new file mode 100644 index 000000000..a9009fd57 --- /dev/null +++ b/libraries/I2S/src/utility/I2SDoubleBuffer.cpp @@ -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 + +#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; +} diff --git a/libraries/I2S/src/utility/I2SDoubleBuffer.h b/libraries/I2S/src/utility/I2SDoubleBuffer.h new file mode 100644 index 000000000..e58a7a607 --- /dev/null +++ b/libraries/I2S/src/utility/I2SDoubleBuffer.h @@ -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 +#include + +#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 diff --git a/libraries/I2S/src/utility/SAMD21_I2SDevice.h b/libraries/I2S/src/utility/SAMD21_I2SDevice.h new file mode 100644 index 000000000..261583921 --- /dev/null +++ b/libraries/I2S/src/utility/SAMD21_I2SDevice.h @@ -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 + +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; +}; diff --git a/variants/arduino_zero/variant.h b/variants/arduino_zero/variant.h index de4e0828a..6594bc04d 100644 --- a/variants/arduino_zero/variant.h +++ b/variants/arduino_zero/variant.h @@ -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 diff --git a/variants/mkr1000/variant.h b/variants/mkr1000/variant.h index 145e47753..0c8ac4f9e 100644 --- a/variants/mkr1000/variant.h +++ b/variants/mkr1000/variant.h @@ -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)