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,719 @@
/*!
* @file Adafruit_ZeroDMA.cpp
*
* @mainpage Adafruit DMA Arduino library for SAMD microcontrollers.
*
* @section intro_sec Introduction
*
* This is the documentation for Adafruit's DMA library for SAMD
* microcontrollers on the Arduino platform. SAMD21 and SAMD51 lines
* are supported.
*
* Adafruit invests time and resources providing this open source code,
* please support Adafruit and open-source hardware by purchasing
* products from Adafruit!
*
* @section dependencies Dependencies
*
* @section author Author
*
* Written by Phil "PaintYourDragon" Burgess for Adafruit Industries,
* based partly on DMA insights from Atmel ASFCORE 3.
*
* @section license License
*
* MIT license, all text here must be included in any redistribution.
*
*/
#include <Adafruit_ZeroDMA.h>
#include <malloc.h> // memalign() function
#include <stdlib.h>
#ifdef USE_TINYUSB
// For Serial when selecting TinyUSB
#include <Adafruit_TinyUSB.h>
#endif
#ifdef DMAC_RESERVED_CHANNELS // SAMD core > 1.2.1
#include <dma.h> // _descriptor[] and _writeback[] are extern'd here
static volatile uint32_t _channelMask = DMAC_RESERVED_CHANNELS;
#else
#include "utility/dma.h"
static volatile uint32_t _channelMask = 0; // Bitmask of allocated channels
// DMA descriptor list entry point (and writeback buffer) per channel
__attribute__((__aligned__(16))) static DmacDescriptor ///< 128 bit alignment
_descriptor[DMAC_CH_NUM] SECTION_DMAC_DESCRIPTOR, ///< Descriptor table
_writeback[DMAC_CH_NUM] SECTION_DMAC_DESCRIPTOR; ///< Writeback table
#endif
// Pointer to ZeroDMA object for each channel is needed for the
// ISR (in C, outside of class context) to access callbacks.
static Adafruit_ZeroDMA *_dmaPtr[DMAC_CH_NUM] = {0}; // Init to NULL
// Adapted from ASF3 interrupt_sam_nvic.c:
static volatile unsigned long cpu_irq_critical_section_counter = 0;
static volatile unsigned char cpu_irq_prev_interrupt_state = 0;
static void cpu_irq_enter_critical(void) {
if (!cpu_irq_critical_section_counter) {
if (__get_PRIMASK() == 0) { // IRQ enabled?
__disable_irq(); // Disable it
__DMB();
cpu_irq_prev_interrupt_state = 1;
} else {
// Make sure the to save the prev state as false
cpu_irq_prev_interrupt_state = 0;
}
}
cpu_irq_critical_section_counter++;
}
static void cpu_irq_leave_critical(void) {
// Check if the user is trying to leave a critical section
// when not in a critical section
if (cpu_irq_critical_section_counter > 0) {
cpu_irq_critical_section_counter--;
// Only enable global interrupts when the counter
// reaches 0 and the state of the global interrupt flag
// was enabled when entering critical state */
if ((!cpu_irq_critical_section_counter) && cpu_irq_prev_interrupt_state) {
__DMB();
__enable_irq();
}
}
}
// CONSTRUCTOR -------------------------------------------------------------
// Constructor initializes Adafruit_ZeroDMA basics but does NOT allocate a
// DMA channel (that's done in allocate()) or start a job (that's done in
// startJob()). This is because constructors in a global context are called
// before a sketch's setup() function, which may have some other hardware
// initialization of its own, don't want it clobbering us.
Adafruit_ZeroDMA::Adafruit_ZeroDMA(void) {
channel = 0xFF; // Channel not yet allocated
jobStatus = DMA_STATUS_OK;
hasDescriptors = false; // No descriptors allocated yet
loopFlag = false;
peripheralTrigger = 0; // Software trigger only by default
triggerAction = DMA_TRIGGER_ACTON_TRANSACTION;
memset(callback, 0, sizeof(callback));
}
// TODO: add destructor? Should stop job, delete descriptors, free channel.
// INTERRUPT SERVICE ROUTINE -----------------------------------------------
/*!
@brief This is a C function that exists outside the Adafruit_ZeroDMA
context. DMA channel number is determined from the INTPEND
register, from this we get a ZeroDMA object pointer through the
_dmaPtr[] array. (It's done this way because jobStatus and
callback[] are protected elements in the ZeroDMA object -- we
can't touch them in C, but the next function after this, being
part of the ZeroDMA class, can.)
*/
#ifdef __SAMD51__
void DMAC_0_Handler(void) {
#else
void DMAC_Handler(void) {
#endif
cpu_irq_enter_critical();
uint8_t channel = DMAC->INTPEND.bit.ID; // Channel # causing interrupt
if (channel < DMAC_CH_NUM) {
Adafruit_ZeroDMA *dma;
if ((dma = _dmaPtr[channel])) { // -> Channel's ZeroDMA object
#ifdef __SAMD51__
// Call IRQ handler with channel #
dma->_IRQhandler(channel);
#else
DMAC->CHID.bit.ID = channel;
// Call IRQ handler with interrupt flag(s)
dma->_IRQhandler(DMAC->CHINTFLAG.reg);
#endif
}
}
cpu_irq_leave_critical();
}
#ifdef __SAMD51__
void DMAC_1_Handler(void) __attribute__((weak, alias("DMAC_0_Handler")));
void DMAC_2_Handler(void) __attribute__((weak, alias("DMAC_0_Handler")));
void DMAC_3_Handler(void) __attribute__((weak, alias("DMAC_0_Handler")));
void DMAC_4_Handler(void) __attribute__((weak, alias("DMAC_0_Handler")));
#endif
void Adafruit_ZeroDMA::_IRQhandler(uint8_t flags) {
#ifdef __SAMD51__
// 'flags' is initially passed in as channel number,
// from which we look up the actual interrupt flags...
flags = DMAC->Channel[flags].CHINTFLAG.reg;
#endif
if (flags & DMAC_CHINTENCLR_TERR) {
// Clear error flag
#ifdef __SAMD51__
DMAC->Channel[channel].CHINTFLAG.reg = DMAC_CHINTENCLR_TERR;
#else
DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_TERR;
#endif
jobStatus = DMA_STATUS_ERR_IO;
if (callback[DMA_CALLBACK_TRANSFER_ERROR])
callback[DMA_CALLBACK_TRANSFER_ERROR](this);
} else if (flags & DMAC_CHINTENCLR_TCMPL) {
// Clear transfer complete flag
#ifdef __SAMD51__
DMAC->Channel[channel].CHINTFLAG.reg = DMAC_CHINTENCLR_TCMPL;
#else
DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_TCMPL;
#endif
jobStatus = DMA_STATUS_OK;
if (callback[DMA_CALLBACK_TRANSFER_DONE])
callback[DMA_CALLBACK_TRANSFER_DONE](this);
} else if (flags & DMAC_CHINTENCLR_SUSP) {
// Clear channel suspend flag
#ifdef __SAMD51__
DMAC->Channel[channel].CHINTFLAG.reg = DMAC_CHINTENCLR_SUSP;
#else
DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_SUSP;
#endif
jobStatus = DMA_STATUS_SUSPEND;
if (callback[DMA_CALLBACK_CHANNEL_SUSPEND])
callback[DMA_CALLBACK_CHANNEL_SUSPEND](this);
}
}
// DMA CHANNEL FUNCTIONS ---------------------------------------------------
// Allocates channel for ZeroDMA object
ZeroDMAstatus Adafruit_ZeroDMA::allocate(void) {
if (channel < DMAC_CH_NUM)
return DMA_STATUS_OK; // Already alloc'd!
// Find index of first free DMA channel. As currently written,
// this "does not play well with others" as it assumes _channelMask
// is the final arbiter of channels in use (this is true only within
// this library -- but other DMA-driven code may have allocated its
// own channel(s) elsewhere, sometimes with an equally broken
// approach). A possible alternate approach, I haven't tested this
// yet, might be to loop through each channel, set DMAC->CHID.bit.ID
// and then test whether CHCTRLA.bit.ENABLE is set? But for now...
for (channel = 0; (channel < DMAC_CH_NUM) && (_channelMask & (1 << channel));
channel++)
;
// Doesn't help that code later does a software reset of the DMA
// controller, which would blow out other DMA-using libraries
// anyway (or they're just as likely to blow out this one).
// I think it's just an all-or-nothing affair...use one library
// for DMA everything, never mix and match.
if (channel >= DMAC_CH_NUM) // No free channel!
return DMA_STATUS_ERR_NOT_FOUND;
cpu_irq_enter_critical();
if (!_channelMask) { // No channels allocated yet; initialize DMA!
#if !defined(DMAC_RESERVED_CHANNELS)
#if (SAML21) || (SAML22) || (SAMC20) || (SAMC21)
PM->AHBMASK.bit.DMAC_ = 1;
#elif defined(__SAMD51__)
MCLK->AHBMASK.bit.DMAC_ = 1; // Initialize DMA clocks
#else
PM->AHBMASK.bit.DMAC_ = 1; // Initialize DMA clocks
PM->APBBMASK.bit.DMAC_ = 1;
#endif
DMAC->CTRL.bit.DMAENABLE = 0; // Disable DMA controller
DMAC->CTRL.bit.SWRST = 1; // Perform software reset
// Initialize descriptor list addresses
DMAC->BASEADDR.bit.BASEADDR = (uint32_t)_descriptor;
DMAC->WRBADDR.bit.WRBADDR = (uint32_t)_writeback;
memset(_descriptor, 0, sizeof(_descriptor));
memset(_writeback, 0, sizeof(_writeback));
// Re-enable DMA controller with all priority levels
DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xF);
#endif
// Enable DMA interrupt at lowest priority
#ifdef __SAMD51__
IRQn_Type irqs[] = {DMAC_0_IRQn, DMAC_1_IRQn, DMAC_2_IRQn, DMAC_3_IRQn,
DMAC_4_IRQn};
for (uint8_t i = 0; i < (sizeof irqs / sizeof irqs[0]); i++) {
NVIC_EnableIRQ(irqs[i]);
NVIC_SetPriority(irqs[i], (1 << __NVIC_PRIO_BITS) - 1);
}
#else
NVIC_EnableIRQ(DMAC_IRQn);
NVIC_SetPriority(DMAC_IRQn, (1 << __NVIC_PRIO_BITS) - 1);
#endif
}
_channelMask |= 1 << channel; // Mark channel as allocated
_dmaPtr[channel] = this; // Channel-index-to-object pointer
// Reset the allocated channel
#ifdef __SAMD51__
DMAC->Channel[channel].CHCTRLA.bit.ENABLE = 0;
DMAC->Channel[channel].CHCTRLA.bit.SWRST = 1;
#else
DMAC->CHID.bit.ID = channel;
DMAC->CHCTRLA.bit.ENABLE = 0;
DMAC->CHCTRLA.bit.SWRST = 1;
#endif
// Clear software trigger
DMAC->SWTRIGCTRL.reg &= ~(1 << channel);
// Configure default behaviors
#ifdef __SAMD51__
DMAC->Channel[channel].CHPRILVL.bit.PRILVL = 0;
DMAC->Channel[channel].CHCTRLA.bit.TRIGSRC = peripheralTrigger;
DMAC->Channel[channel].CHCTRLA.bit.TRIGACT = triggerAction;
DMAC->Channel[channel].CHCTRLA.bit.BURSTLEN =
DMAC_CHCTRLA_BURSTLEN_SINGLE_Val; // Single-beat burst length
#else
DMAC->CHCTRLB.bit.LVL = 0;
DMAC->CHCTRLB.bit.TRIGSRC = peripheralTrigger;
DMAC->CHCTRLB.bit.TRIGACT = triggerAction;
#endif
cpu_irq_leave_critical();
return DMA_STATUS_OK;
}
void Adafruit_ZeroDMA::setPriority(dma_priority pri) {
#ifdef __SAMD51__
DMAC->Channel[channel].CHPRILVL.bit.PRILVL = pri;
#else
DMAC->CHCTRLB.bit.LVL = pri;
#endif
}
// Deallocate DMA channel
// TODO: should this delete/deallocate the descriptor list?
ZeroDMAstatus Adafruit_ZeroDMA::free(void) {
ZeroDMAstatus status = DMA_STATUS_OK;
cpu_irq_enter_critical(); // jobStatus is volatile
#ifdef __SAMD51__
if (DMAC->Channel[channel].CHSTATUS.reg & DMAC_CHSTATUS_BUSY) {
#else
if (DMAC->CHSTATUS.reg & DMAC_CHSTATUS_BUSY) {
#endif
status = DMA_STATUS_BUSY; // Can't leave when busy
} else if ((channel < DMAC_CH_NUM) && (_channelMask & (1 << channel))) {
// Valid in-use channel; release it
_channelMask &= ~(1 << channel); // Clear bit
if (!_channelMask) { // No more channels in use?
#ifdef __SAMD51__
NVIC_DisableIRQ(DMAC_0_IRQn); // Disable DMA interrupt
DMAC->CTRL.bit.DMAENABLE = 0; // Disable DMA
MCLK->AHBMASK.bit.DMAC_ = 0; // Disable DMA clock
#else
NVIC_DisableIRQ(DMAC_IRQn); // Disable DMA interrupt
DMAC->CTRL.bit.DMAENABLE = 0; // Disable DMA
PM->APBBMASK.bit.DMAC_ = 0; // Disable DMA clocks
PM->AHBMASK.bit.DMAC_ = 0;
#endif
}
_dmaPtr[channel] = NULL;
channel = 0xFF;
} else {
status = DMA_STATUS_ERR_NOT_INITIALIZED; // Channel not in use
}
cpu_irq_leave_critical();
return status;
}
// Start DMA transfer job. Channel and descriptors should be allocated
// before calling this.
ZeroDMAstatus Adafruit_ZeroDMA::startJob(void) {
ZeroDMAstatus status = DMA_STATUS_OK;
cpu_irq_enter_critical(); // Job status is volatile
#ifdef __SAMD51__
if (DMAC->Channel[channel].CHSTATUS.reg & DMAC_CHSTATUS_BUSY) {
#else
if (DMAC->CHSTATUS.reg & DMAC_CHSTATUS_BUSY) {
#endif
status = DMA_STATUS_BUSY; // Resource is busy
} else if (channel >= DMAC_CH_NUM) {
status = DMA_STATUS_ERR_NOT_INITIALIZED; // Channel not in use
} else if (!hasDescriptors || (_descriptor[channel].BTCNT.reg <= 0)) {
status = DMA_STATUS_ERR_INVALID_ARG; // Bad transfer size
} else {
uint8_t i, interruptMask = 0;
for (i = 0; i < DMA_CALLBACK_N; i++)
if (callback[i])
interruptMask |= (1 << i);
jobStatus = DMA_STATUS_BUSY;
#ifdef __SAMD51__
DMAC->Channel[channel].CHINTENSET.reg =
DMAC_CHINTENSET_MASK & interruptMask;
DMAC->Channel[channel].CHINTENCLR.reg =
DMAC_CHINTENCLR_MASK & ~interruptMask;
DMAC->Channel[channel].CHCTRLA.bit.ENABLE = 1;
#else
DMAC->CHID.bit.ID = channel;
DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK & interruptMask;
DMAC->CHINTENCLR.reg = DMAC_CHINTENCLR_MASK & ~interruptMask;
DMAC->CHCTRLA.bit.ENABLE = 1; // Enable the transfer channel
#endif
}
cpu_irq_leave_critical();
return status;
}
// Set and enable callback function for ZeroDMA object. This can be called
// before or after channel and/or descriptors are allocated, but needs
// to be called before job is started.
void Adafruit_ZeroDMA::setCallback(void (*cb)(Adafruit_ZeroDMA *),
dma_callback_type type) {
callback[type] = cb;
}
// Suspend/resume don't quite do what I thought -- avoid using for now.
void Adafruit_ZeroDMA::suspend(void) {
cpu_irq_enter_critical();
#ifdef __SAMD51__
DMAC->Channel[channel].CHCTRLB.reg |= DMAC_CHCTRLB_CMD_SUSPEND;
#else
DMAC->CHID.bit.ID = channel;
DMAC->CHCTRLB.reg |= DMAC_CHCTRLB_CMD_SUSPEND;
#endif
cpu_irq_leave_critical();
}
#define MAX_JOB_RESUME_COUNT 10000 ///< Loop iteration threshold for timeout
void Adafruit_ZeroDMA::resume(void) {
cpu_irq_enter_critical(); // jobStatus is volatile
if (jobStatus == DMA_STATUS_SUSPEND) {
int count;
uint32_t bitMask = 1 << channel;
#ifdef __SAMD51__
DMAC->Channel[channel].CHCTRLB.reg |= DMAC_CHCTRLB_CMD_RESUME;
#else
DMAC->CHID.bit.ID = channel;
DMAC->CHCTRLB.reg |= DMAC_CHCTRLB_CMD_RESUME;
#endif
for (count = 0;
(count < MAX_JOB_RESUME_COUNT) && !(DMAC->BUSYCH.reg & bitMask);
count++)
;
jobStatus = (count < MAX_JOB_RESUME_COUNT) ? DMA_STATUS_BUSY
: DMA_STATUS_ERR_TIMEOUT;
}
cpu_irq_leave_critical();
}
// Abort is OK though.
void Adafruit_ZeroDMA::abort(void) {
if (channel <= DMAC_CH_NUM) {
cpu_irq_enter_critical();
#ifdef __SAMD51__
DMAC->Channel[channel].CHCTRLA.bit.ENABLE = 0; // Disable channel
#else
DMAC->CHID.bit.ID = channel; // Select channel
DMAC->CHCTRLA.reg = 0; // Disable
#endif
jobStatus = DMA_STATUS_ABORTED;
cpu_irq_leave_critical();
}
}
// Set DMA peripheral trigger.
// This can be done before or after channel is allocated.
void Adafruit_ZeroDMA::setTrigger(uint8_t trigger) {
peripheralTrigger = trigger; // Save value for allocate()
// If channel already allocated, configure peripheral trigger
// (old lib required configure before alloc -- either way OK now)
if (channel < DMAC_CH_NUM) {
cpu_irq_enter_critical();
#ifdef __SAMD51__
DMAC->Channel[channel].CHCTRLA.bit.TRIGSRC = trigger;
#else
DMAC->CHID.bit.ID = channel;
DMAC->CHCTRLB.bit.TRIGSRC = trigger;
#endif
cpu_irq_leave_critical();
}
}
// Set DMA trigger action.
// This can be done before or after channel is allocated.
void Adafruit_ZeroDMA::setAction(dma_transfer_trigger_action action) {
triggerAction = action; // Save value for allocate()
// If channel already allocated, configure trigger action
// (old lib required configure before alloc -- either way OK now)
if (channel < DMAC_CH_NUM) {
cpu_irq_enter_critical();
#ifdef __SAMD51__
DMAC->Channel[channel].CHCTRLA.bit.TRIGACT = action;
#else
DMAC->CHID.bit.ID = channel;
DMAC->CHCTRLB.bit.TRIGACT = action;
#endif
cpu_irq_leave_critical();
}
}
// Issue software trigger. Channel must be allocated & descriptors added!
void Adafruit_ZeroDMA::trigger(void) {
if ((channel <= DMAC_CH_NUM) & hasDescriptors)
DMAC->SWTRIGCTRL.reg |= (1 << channel);
}
uint8_t Adafruit_ZeroDMA::getChannel(void) { return channel; }
// DMA DESCRIPTOR FUNCTIONS ------------------------------------------------
// Allocates a new DMA descriptor (if needed) and appends it to the
// channel's descriptor list. Returns pointer to DmacDescriptor,
// or NULL on various errors. You'll want to keep the pointer for
// later if you need to modify or free the descriptor.
// Channel must be allocated first!
DmacDescriptor *Adafruit_ZeroDMA::addDescriptor(void *src, void *dst,
uint32_t count,
dma_beat_size size, bool srcInc,
bool dstInc, uint32_t stepSize,
bool stepSel) {
// Channel must be allocated first
if (channel >= DMAC_CH_NUM)
return NULL;
// Can't do while job's busy
#ifdef __SAMD51__
if (DMAC->Channel[channel].CHSTATUS.reg & DMAC_CHSTATUS_BUSY)
#else
if (DMAC->CHSTATUS.reg & DMAC_CHSTATUS_BUSY)
#endif
return NULL;
DmacDescriptor *desc;
// Scan descriptor list to find last entry. If an entry's
// DESCADDR value is 0, that's the end of the list and it's
// currently un-looped. If the DESCADDR value is the same
// as the first entry, that's the end of the list and it's
// looped. Either way, set the last entry's DESCADDR value
// to the new descriptor, and the descriptor's own DESCADDR
// will be set later either to 0 or the list head.
if (hasDescriptors) {
// DMA descriptors must be 128-bit (16 byte) aligned.
// memalign() is considered 'obsolete' but it's replacements
// (aligned_alloc() or posix_memalign()) are not currently
// available in the version of ARM GCC in use, but this is,
// so here we are.
if (!(desc = (DmacDescriptor *)memalign(16, sizeof(DmacDescriptor))))
return NULL;
DmacDescriptor *prev = &_descriptor[channel];
while (prev->DESCADDR.reg &&
(prev->DESCADDR.reg != (uint32_t)&_descriptor[channel])) {
prev = (DmacDescriptor *)prev->DESCADDR.reg;
}
prev->DESCADDR.reg = (uint32_t)desc;
} else {
desc = &_descriptor[channel];
}
hasDescriptors = true;
uint8_t bytesPerBeat; // Beat transfer size IN BYTES
switch (size) {
default:
bytesPerBeat = 1;
break;
case DMA_BEAT_SIZE_HWORD:
bytesPerBeat = 2;
break;
case DMA_BEAT_SIZE_WORD:
bytesPerBeat = 4;
break;
}
desc->BTCTRL.bit.VALID = true;
desc->BTCTRL.bit.EVOSEL = DMA_EVENT_OUTPUT_DISABLE;
desc->BTCTRL.bit.BLOCKACT = DMA_BLOCK_ACTION_NOACT;
desc->BTCTRL.bit.BEATSIZE = size;
desc->BTCTRL.bit.SRCINC = srcInc;
desc->BTCTRL.bit.DSTINC = dstInc;
desc->BTCTRL.bit.STEPSEL = stepSel;
desc->BTCTRL.bit.STEPSIZE = stepSize;
desc->BTCNT.reg = count;
desc->SRCADDR.reg = (uint32_t)src;
if (srcInc) {
if (stepSel) {
desc->SRCADDR.reg += bytesPerBeat * count * (1 << stepSize);
} else {
desc->SRCADDR.reg += bytesPerBeat * count;
}
}
desc->DSTADDR.reg = (uint32_t)dst;
if (dstInc) {
if (!stepSel) {
desc->DSTADDR.reg += bytesPerBeat * count * (1 << stepSize);
} else {
desc->DSTADDR.reg += bytesPerBeat * count;
}
}
desc->DESCADDR.reg = loopFlag ? (uint32_t)&_descriptor[channel] : 0;
return desc;
}
// Modify DMA descriptor with a new source address, destination address &
// block transfer count. All other attributes (including increment enables,
// etc.) are unchanged. Mostly for changing the data being pushed to a
// peripheral (DAC, SPI, whatev.)
void Adafruit_ZeroDMA::changeDescriptor(DmacDescriptor *desc, void *src,
void *dst, uint32_t count) {
uint8_t bytesPerBeat; // Beat transfer size IN BYTES
switch (desc->BTCTRL.bit.BEATSIZE) {
default:
bytesPerBeat = 1;
break;
case DMA_BEAT_SIZE_HWORD:
bytesPerBeat = 2;
break;
case DMA_BEAT_SIZE_WORD:
bytesPerBeat = 4;
break;
}
if (count)
desc->BTCNT.reg = count;
if (src) {
desc->SRCADDR.reg = (uint32_t)src;
if (desc->BTCTRL.bit.SRCINC) {
if (desc->BTCTRL.bit.STEPSEL) {
desc->SRCADDR.reg +=
desc->BTCNT.reg * bytesPerBeat * (1 << desc->BTCTRL.bit.STEPSIZE);
} else {
desc->SRCADDR.reg += desc->BTCNT.reg * bytesPerBeat;
}
}
}
if (dst) {
desc->DSTADDR.reg = (uint32_t)dst;
if (desc->BTCTRL.bit.DSTINC) {
if (!desc->BTCTRL.bit.STEPSEL) {
desc->DSTADDR.reg +=
desc->BTCNT.reg * bytesPerBeat * (1 << desc->BTCTRL.bit.STEPSIZE);
} else {
desc->DSTADDR.reg += desc->BTCNT.reg * bytesPerBeat;
}
}
}
// I think this code is here by accident -- disabling for now.
#if 0
cpu_irq_enter_critical();
jobStatus = DMA_STATUS_OK;
#ifdef __SAMD51__
DMAC->Channel[channel].CHCTRLA.bit.ENABLE = 1;
#else
DMAC->CHID.bit.ID = channel;
DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;
#endif
cpu_irq_leave_critical();
#endif
}
// TODO: delete descriptor, delete whole descriptor chain
// Select whether channel's descriptor list should repeat or not.
// This can be done before or after channel & any descriptors are allocated.
void Adafruit_ZeroDMA::loop(boolean flag) {
// The loop selection is 'sticky' -- that is, you can enable or
// disable looping before a descriptor list is built, or after
// the fact. This requires some extra steps in the library code
// but avoids a must-do-in-X-order constraint on user.
loopFlag = flag;
if (hasDescriptors) { // Descriptor list already started?
// Scan descriptor list to find last entry. If an entry's
// DESCADDR value is 0, that's the end of the list and it's
// currently un-looped. If the DESCADDR value is the same
// as the first entry, that's the end of the list and it's
// already looped.
DmacDescriptor *desc = &_descriptor[channel];
while (desc->DESCADDR.reg &&
(desc->DESCADDR.reg != (uint32_t)&_descriptor[channel])) {
desc = (DmacDescriptor *)desc->DESCADDR.reg;
}
// Loop or unloop descriptor list as appropriate
desc->DESCADDR.reg = loopFlag ? (uint32_t)&_descriptor[channel] : 0;
}
}
// MISCELLANY --------------------------------------------------------------
void Adafruit_ZeroDMA::printStatus(ZeroDMAstatus s) {
if (s == DMA_STATUS_JOBSTATUS)
s = jobStatus;
Serial.print("Status: ");
switch (s) {
case DMA_STATUS_OK:
Serial.println("OK");
break;
case DMA_STATUS_ERR_NOT_FOUND:
Serial.println("NOT FOUND");
break;
case DMA_STATUS_ERR_NOT_INITIALIZED:
Serial.println("NOT INITIALIZED");
break;
case DMA_STATUS_ERR_INVALID_ARG:
Serial.println("INVALID ARGUMENT");
break;
case DMA_STATUS_ERR_IO:
Serial.println("IO ERROR");
break;
case DMA_STATUS_ERR_TIMEOUT:
Serial.println("TIMEOUT");
break;
case DMA_STATUS_BUSY:
Serial.println("BUSY");
break;
case DMA_STATUS_SUSPEND:
Serial.println("SUSPENDED");
break;
case DMA_STATUS_ABORTED:
Serial.println("ABORTED");
break;
default:
Serial.print("Unknown 0x");
Serial.println((int)s);
break;
}
}
bool Adafruit_ZeroDMA::isActive() {
return _writeback[channel].BTCTRL.bit.VALID;
}
@@ -0,0 +1,246 @@
/*!
* @file Adafruit_ZeroDMA.h
*
* This is part of Adafruit's DMA library for SAMD microcontrollers on
* the Arduino platform. SAMD21 and SAMD51 lines are supported.
*
* Adafruit invests time and resources providing this open source code,
* please support Adafruit and open-source hardware by purchasing
* products from Adafruit!
*
* Written by Phil "PaintYourDragon" Burgess for Adafruit Industries,
* based partly on DMA insights from Atmel ASFCORE 3.
*
* MIT license, all text here must be included in any redistribution.
*
*/
#ifndef _ADAFRUIT_ZERODMA_H_
#define _ADAFRUIT_ZERODMA_H_
#include "Arduino.h"
#ifdef DMAC_RESERVED_CHANNELS // SAMD core > 1.2.1
#include <dma.h>
#else
#include "utility/dma.h"
#endif
/** Status codes returned by some DMA functions and/or held in
a channel's jobStatus variable. */
enum ZeroDMAstatus {
DMA_STATUS_OK = 0,
DMA_STATUS_ERR_NOT_FOUND,
DMA_STATUS_ERR_NOT_INITIALIZED,
DMA_STATUS_ERR_INVALID_ARG,
DMA_STATUS_ERR_IO,
DMA_STATUS_ERR_TIMEOUT,
DMA_STATUS_BUSY,
DMA_STATUS_SUSPEND,
DMA_STATUS_ABORTED,
DMA_STATUS_JOBSTATUS = -1 // For printStatus() function
};
/*!
@brief Class encapsulating DMA jobs and descriptors.
*/
class Adafruit_ZeroDMA {
public:
Adafruit_ZeroDMA(void);
// DMA channel functions
/*!
@brief Allocate channel for ZeroDMA object. This allocates a CHANNEL,
not a DESCRIPTOR.
@return ZeroDMAstatus type:
DMA_STATUS_OK on success.
DMA_STATUS_ERR_NOT_FOUND if no DMA channels are free.
*/
ZeroDMAstatus allocate(void);
/*!
@brief Start a previously allocated-and-configured DMA job.
@return ZeroDMAstatus type:
DMA_STATUS_OK on success.
DMA_STATUS_BUSY if resource is busy.
DMA_STATUS_ERR_NOT_INITIALIZED if attempting to start job
on a channel that failed to allocate.
DMA_STATUS_ERR_INVALID_ARG if a bad transfer size was specified.
*/
ZeroDMAstatus startJob(void);
/*!
@brief Deallocates a previously-allocated DMA channel. This deallocates
the CHANNEL, not any associated DESCRIPTORS.
@return ZeroDMAstatus type:
DMA_STATUS_OK on success.
DMA_STATUS_BUSY if channel is busy (can't deallocate while in use).
DMA_STATUS_ERR_NOT_INITIALIZED if channel isn't in use.
*/
ZeroDMAstatus free(void);
/*!
@brief Activate a previously allocated-and-configured DMA channel's
software trigger.
*/
void trigger(void);
/*!
@brief Set DMA peripheral trigger. This can be done before or after
channel is allocated.
@param trigger A device-specific DMA peripheral trigger ID, typically
defined in a header file associated with that chip.
Example triffer IDs might include SERCOM2_DMAC_ID_TX
(SERCOM transfer complete) ADC_DMAC_ID_RESRDY (ADC
results ready).
*/
void setTrigger(uint8_t trigger);
/*!
@brief Set DMA trigger action. This can be done before or after
channel is allocated.
@param action One of DMA_TRIGGER_ACTON_BLOCK, DMA_TRIGGER_ACTON_BEAT or
DMA_TRIGGER_ACTON_TRANSACTION for desired behavior.
*/
void setAction(dma_transfer_trigger_action action);
/*!
@brief Set and enable callback function for ZeroDMA object. This can be
called before or after channel and/or descriptors are allocated,
but needs to be called before job is started.
@param callback Pointer to callback function which accepts a pointer
to a ZeroDMA object (or NULL to disable callback).
@param type Which DMA operation to attach this function to (a
channel can have multiple callbacks assigned if needed,
one for each of these situations):
DMA_CALLBACK_DONE on successful completion of a DMA
transfer.
DMA_CALLBACK_TRANSFER_ERR if a bus error is detected
during an AHB access or when the DMAC fetches an
invalid descriptor.
DMA_CALLBACK_CHANNEL_SUSPEND when a channel is suspended.
*/
void setCallback(void (*callback)(Adafruit_ZeroDMA *) = NULL,
dma_callback_type type = DMA_CALLBACK_TRANSFER_DONE);
/*!
@brief Select whether a channel's descriptor list should repeat or not.
This can be called before or after channel and any descriptors
are allocated.
@param flag 'true' if DMA descriptor list should repeat indefinitely,
'false' if DMA transfer stops at end of descriptor list.
*/
void loop(boolean flag);
/*!
@brief Suspend a DMA channel. AVOID USING FOR NOW.
*/
void suspend(void);
/*!
@brief Resume a previously-suspended DMA channel. AVOID USING FOR NOW.
*/
void resume(void);
/*!
@brief Cancel a DMA transfer operation.
*/
void abort(void);
/*!
@brief Set DMA channel level priority.
@param pri DMA_PRIORITY_0 (lowest priority) through DMA_PRIORITY_3
(highest priority).
*/
void setPriority(dma_priority pri);
/*!
@brief Print (to Serial console) a string corresponding to a DMA
job status value.
@param s Job status as might be returned by allocate(), startJob(),
etc., e.g. DMA_STATUS_OK, DMA_STATUS_ERR_NOT_FOUND, ...
*/
void printStatus(ZeroDMAstatus s = DMA_STATUS_JOBSTATUS);
/*!
@brief Get the DMA channel index associated with a ZeroDMA object.
@return uint8_t Channel index (0 to DMAC_CH_NUM-1, or 0xFF).
*/
uint8_t getChannel(void);
// DMA descriptor functions
/*!
@brief Allocate and append a DMA descriptor to a channel's descriptor
list. Channel must be allocated first.
@param src Source address.
@param dst Destination address.
@param count Transfer count.
@param size Per-count transfer size (DMA_BEAT_SIZE_BYTE,
DMA_BEAT_SIZE_HWORD or DMA_BEAT_SIZE_WORD for 8, 16,
32 bits respectively).
@param srcInc If true, increment the source address following each
count.
@param dstInc If true, increment the destination address following
each count.
@param stepSize If source/dest address increment in use, this indicates
the 'step size' (allowing it to skip over elements).
DMA_ADDRESS_INCREMENT_STEP_SIZE_1 for a contiguous
transfer, "_SIZE_2 for alternate items, "_SIZE_4
8, 16, 32, 64 or 128 for other skip ranges.
@param stepSel DMA_STEPSEL_SRC or DMA_STEPSEL_DST depending which
pointer the step size should apply to (can't be used
on both simultaneously).
@return DmacDescriptor* Pointer to DmacDescriptor structure, or NULL
on various errors. Calling code should keep the
pointer for later if it needs to change or free
the descriptor.
*/
DmacDescriptor *
addDescriptor(void *src, void *dst, uint32_t count = 0,
dma_beat_size size = DMA_BEAT_SIZE_BYTE, bool srcInc = true,
bool dstInc = true,
uint32_t stepSize = DMA_ADDRESS_INCREMENT_STEP_SIZE_1,
bool stepSel = DMA_STEPSEL_DST);
/*!
@brief Change a previously-allocated DMA descriptor. Only the most
common settings (source, dest, count) are available here. For
anything more esoteric, you'll need to modify the descriptor
structure yourself.
@param d Pointer to descriptor structure (as returned by
addDescriptor()).
@param src New source address.
@param dst New destination address.
@param count New transfer count.
*/
void changeDescriptor(DmacDescriptor *d, void *src = NULL, void *dst = NULL,
uint32_t count = 0);
/*!
@brief Interrupt handler function, used internally by the library,
DO NOT TOUCH!
@param flags Channel number, actually.
*/
void _IRQhandler(uint8_t flags);
/*!
@brief Test if DMA transfer is in-progress. Might be better to use
callback and flag, unsure.
@return 'true' if DMA channel is busy, 'false' otherwise.
*/
bool isActive();
protected:
uint8_t channel; ///< DMA channel index (0 to DMAC_CH_NUM-1, or 0xFF)
volatile enum ZeroDMAstatus jobStatus; ///< Last known DMA job status
bool hasDescriptors; ///< 'true' if one or more descriptors assigned
bool loopFlag; ///< 'true' if descriptor chain loops back to start
uint8_t peripheralTrigger; ///< Value set by setTrigger()
dma_transfer_trigger_action triggerAction; ///< Value set by setAction()
void (*callback[DMA_CALLBACK_N])(Adafruit_ZeroDMA *); ///< Callback func *s
};
#endif // _ADAFRUIT_ZERODMA_H_
@@ -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,7 @@
# Adafruit_ZeroDMA library [![Build Status](https://github.com/adafruit/Adafruit_ZeroDMA/workflows/Arduino%20Library%20CI/badge.svg)](https://github.com/adafruit/Adafruit_ZeroDMA/actions)
DMA helper/wrapped for ATSAMD21 such as Arduino Zero &amp; Feather M0
Current version of this library no longer requires Adafruit_ASFcore as a prerequisite. However...IT BREAKS COMPATIBILITY WITH PRIOR VERSIONS. Function names, calling sequence and return types/values have changed. See examples!
Item(s) in 'utility' directory are much pared-down derivatives of Atmel ASFcore 3 files. Please keep their original copyright and license intact when editing.
@@ -0,0 +1,112 @@
#include <Adafruit_ZeroDMA.h>
#define ADC_PIN A4
#define SAMPLE_BLOCK_LENGTH 256
Adafruit_ZeroDMA ADC_DMA;
DmacDescriptor *dmac_descriptor_1;
DmacDescriptor *dmac_descriptor_2;
uint16_t adc_buffer[SAMPLE_BLOCK_LENGTH * 2];
uint16_t adc_sample_block[SAMPLE_BLOCK_LENGTH];
volatile bool filling_first_half = true;
volatile uint16_t *active_adc_buffer;
volatile bool adc_buffer_filled = false;
uint16_t peak_to_peak(uint16_t *data, int data_length){
int signalMax = 0;
int signalMin = 4096; // max value for 12 bit adc
for(int i=0; i<data_length; i++){
if ( data[i] > signalMax ) {
signalMax = data[i];
}
if (data[i] < signalMin ){
signalMin = data[i];
}
}
return signalMax - signalMin;
}
void dma_callback(Adafruit_ZeroDMA *dma) {
(void) dma;
if (filling_first_half) {
// DMA is filling the first half of the buffer, use data from the second half
active_adc_buffer = &adc_buffer[SAMPLE_BLOCK_LENGTH];
filling_first_half = false;
} else {
// DMA is filling the second half of the buffer, use data from the first half
active_adc_buffer = &adc_buffer[0];
filling_first_half = true;
}
adc_buffer_filled = true;
}
static void ADCsync() {
while (ADC->STATUS.bit.SYNCBUSY == 1);
}
void adc_init() {
analogRead(ADC_PIN);
ADC->CTRLA.bit.ENABLE = 0;
ADCsync();
ADC->INPUTCTRL.bit.GAIN = ADC_INPUTCTRL_GAIN_DIV2;
ADC->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_INTVCC1;
ADCsync();
ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[ADC_PIN].ulADCChannelNumber;
ADCsync();
ADC->AVGCTRL.reg = 0;
ADC->SAMPCTRL.reg = 2;
ADCsync();
ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV32 | ADC_CTRLB_FREERUN | ADC_CTRLB_RESSEL_12BIT;
ADCsync();
ADC->CTRLA.bit.ENABLE = 1;
ADCsync();
}
void dma_init(){
ADC_DMA.allocate();
ADC_DMA.setTrigger(ADC_DMAC_ID_RESRDY);
ADC_DMA.setAction(DMA_TRIGGER_ACTON_BEAT);
dmac_descriptor_1 = ADC_DMA.addDescriptor(
(void *)(&ADC->RESULT.reg),
adc_buffer,
SAMPLE_BLOCK_LENGTH,
DMA_BEAT_SIZE_HWORD,
false,
true);
dmac_descriptor_1->BTCTRL.bit.BLOCKACT = DMA_BLOCK_ACTION_INT;
dmac_descriptor_2 = ADC_DMA.addDescriptor(
(void *)(&ADC->RESULT.reg),
adc_buffer + SAMPLE_BLOCK_LENGTH,
SAMPLE_BLOCK_LENGTH,
DMA_BEAT_SIZE_HWORD,
false,
true);
dmac_descriptor_2->BTCTRL.bit.BLOCKACT = DMA_BLOCK_ACTION_INT;
ADC_DMA.loop(true);
ADC_DMA.setCallback(dma_callback);
}
void setup() {
Serial.begin(115200);
adc_init();
dma_init();
ADC_DMA.startJob();
}
void loop() {
if(adc_buffer_filled){
adc_buffer_filled=false;
memcpy(adc_sample_block, (const void*)active_adc_buffer, SAMPLE_BLOCK_LENGTH*sizeof(uint16_t));
Serial.println(peak_to_peak(adc_sample_block, SAMPLE_BLOCK_LENGTH));
}
}
@@ -0,0 +1,95 @@
// Simple ZeroDMA example -- an equivalent to the memcpy() function.
// Decause it uses DMA, unlike memcpy(), your code could be doing other
// things simultaneously while the copy operation runs.
#include <Adafruit_ZeroDMA.h>
#include "utility/dma.h"
Adafruit_ZeroDMA myDMA;
ZeroDMAstatus stat; // DMA status codes returned by some functions
// The memory we'll be moving:
#define DATA_LENGTH 1024
uint8_t source_memory[DATA_LENGTH],
destination_memory[DATA_LENGTH];
volatile bool transfer_is_done = false; // Done yet?
// Callback for end-of-DMA-transfer
void dma_callback(Adafruit_ZeroDMA *dma) {
(void) dma;
transfer_is_done = true;
}
void setup() {
uint32_t t;
pinMode(LED_BUILTIN, OUTPUT); // Onboard LED can be used for precise
digitalWrite(LED_BUILTIN, LOW); // benchmarking with an oscilloscope
Serial.begin(115200);
while(!Serial); // Wait for Serial monitor before continuing
Serial.println("DMA test: memory copy");
Serial.print("Allocating DMA channel...");
stat = myDMA.allocate();
myDMA.printStatus(stat);
Serial.println("Setting up transfer");
myDMA.addDescriptor(source_memory, destination_memory, DATA_LENGTH);
Serial.println("Adding callback");
// register_callback() can optionally take a second argument
// (callback type), default is DMA_CALLBACK_TRANSFER_DONE
myDMA.setCallback(dma_callback);
// Fill the source buffer with incrementing bytes, dest buf with 0's
for(uint32_t i=0; i<DATA_LENGTH; i++) source_memory[i] = i;
memset(destination_memory, 0, DATA_LENGTH);
// Show the destination buffer is empty before transfer
Serial.println("Destination buffer before transfer:");
dump();
Serial.println("Starting transfer job");
stat = myDMA.startJob();
myDMA.printStatus(stat);
Serial.println("Triggering DMA transfer...");
t = micros();
digitalWrite(LED_BUILTIN, HIGH);
myDMA.trigger();
// Your code could do other things here while copy happens!
while(!transfer_is_done); // Chill until DMA transfer completes
digitalWrite(LED_BUILTIN, LOW);
t = micros() - t; // Elapsed time
Serial.print("Done! ");
Serial.print(t);
Serial.println(" microseconds");
Serial.println("Destination buffer after transfer:");
dump();
// Now repeat the same operation, but 'manually' using memcpy() (not DMA):
t = micros();
digitalWrite(LED_BUILTIN, HIGH);
memcpy(destination_memory, source_memory, DATA_LENGTH);
digitalWrite(LED_BUILTIN, LOW);
t = micros() - t; // Elapsed time
Serial.print("Same operation without DMA: ");
Serial.print(t);
Serial.println(" microseconds");
}
// Show contents of destination_memory[] array
void dump() {
for(uint32_t i=0; i<DATA_LENGTH; i++) {
Serial.print(destination_memory[i], HEX); Serial.print(' ');
if ((i & 15) == 15) Serial.println();
}
}
void loop() { }
@@ -0,0 +1,98 @@
// DMA-based SPI buffer write. This is transmit-only as written, i.e.
// not equivalent to Arduino's SPI.transfer() which both sends and
// receives a single byte or word. Also, this is single-buffered to
// demonstrate a simple SPI write case. See zerodma_spi2.ino for an
// example using double buffering (2 buffers alternating fill & transmit).
#include <SPI.h>
#include <Adafruit_ZeroDMA.h>
#include "utility/dma.h"
Adafruit_ZeroDMA myDMA;
ZeroDMAstatus stat; // DMA status codes returned by some functions
// The memory we'll be issuing to SPI:
#define DATA_LENGTH 2048
uint8_t source_memory[DATA_LENGTH];
volatile bool transfer_is_done = false; // Done yet?
// Callback for end-of-DMA-transfer
void dma_callback(Adafruit_ZeroDMA *dma) {
(void) dma;
transfer_is_done = true;
}
void setup() {
uint32_t t;
pinMode(LED_BUILTIN, OUTPUT); // Onboard LED can be used for precise
digitalWrite(LED_BUILTIN, LOW); // benchmarking with an oscilloscope
Serial.begin(115200);
while(!Serial); // Wait for Serial monitor before continuing
Serial.println("DMA test: SPI data out");
SPI.begin();
Serial.println("Configuring DMA trigger");
#ifdef __SAMD51__
// SERCOM2 is the 'native' SPI SERCOM on Metro M4
myDMA.setTrigger(SERCOM2_DMAC_ID_TX);
#else
// SERCOM4 is the 'native' SPI SERCOM on most M0 boards
myDMA.setTrigger(SERCOM4_DMAC_ID_TX);
#endif
myDMA.setAction(DMA_TRIGGER_ACTON_BEAT);
Serial.print("Allocating DMA channel...");
stat = myDMA.allocate();
myDMA.printStatus(stat);
Serial.println("Setting up transfer");
myDMA.addDescriptor(
source_memory, // move data from here
#ifdef __SAMD51__
(void *)(&SERCOM2->SPI.DATA.reg), // to here (M4)
#else
(void *)(&SERCOM4->SPI.DATA.reg), // to here (M0)
#endif
DATA_LENGTH, // this many...
DMA_BEAT_SIZE_BYTE, // bytes/hword/words
true, // increment source addr?
false); // 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);
// Fill the source buffer with incrementing bytes
for(uint32_t i=0; i<DATA_LENGTH; i++) source_memory[i] = i;
// Start SPI transaction at 12 MHz
SPI.beginTransaction(SPISettings(12000000, MSBFIRST, SPI_MODE0));
Serial.println("Starting transfer job");
t = micros();
digitalWrite(LED_BUILTIN, HIGH);
// Because we've configured a peripheral trigger (SPI), there's
// no need to manually trigger transfer, it starts up on its own.
stat = myDMA.startJob();
// Your code could do other things here while SPI write happens!
while(!transfer_is_done); // Chill until DMA transfer completes
digitalWrite(LED_BUILTIN, LOW);
t = micros() - t; // Elapsed time
SPI.endTransaction();
myDMA.printStatus(stat); // Results of start_transfer_job()
Serial.print("Done! ");
Serial.print(t);
Serial.println(" microseconds");
}
void loop() { }
@@ -0,0 +1,107 @@
// Double-buffered DMA on auxiliary SPI peripheral on pins 11/12/13.
// Continuously alternates between two data buffers...one is filled
// with new data as the other is being transmitted.
#include <SPI.h>
#include <Adafruit_ZeroDMA.h>
#include "utility/dma.h"
#include "wiring_private.h" // pinPeripheral() function
// Declare our own SPI peripheral 'mySPI' on pins 11/12/13:
// (Do not call this SPI1; Arduino Zero and Metro M0 already
// have an SPI1 (the EDBG interface) and it won't compile.)
#if defined(ARDUINO_SAMD_ADAFRUIT)
SPIClass mySPI( // Adafruit SAMD board support calls this SPIClass
#else
SPIClassSAMD mySPI( // Arduino SAMD now calls it SPIClassSAMD
#endif
&sercom1, // -> Sercom peripheral
34, // MISO pin (also digital pin 12)
37, // SCK pin (also digital pin 13)
35, // MOSI pin (also digital pin 11)
SPI_PAD_0_SCK_1, // TX pad (MOSI, SCK pads)
SERCOM_RX_PAD_3); // RX pad (MISO pad)
Adafruit_ZeroDMA myDMA;
ZeroDMAstatus stat; // DMA status codes returned by some functions
// Data we'll issue to mySPI. There are TWO buffers; one being
// filled with new data while the other's being transmitted in
// the background.
#define DATA_LENGTH 512
uint8_t source_memory[2][DATA_LENGTH],
buffer_being_filled = 0, // Index of 'filling' buffer
buffer_value = 0; // Value of fill
volatile bool transfer_is_done = true; // Done yet?
// Callback for end-of-DMA-transfer
void dma_callback(Adafruit_ZeroDMA *dma) {
(void) dma;
transfer_is_done = true;
}
DmacDescriptor *desc; // DMA descriptor address (so we can change contents)
void setup() {
Serial.begin(115200);
while(!Serial); // Wait for Serial monitor before continuing
Serial.println("DMA test: SPI data out");
mySPI.begin();
// Assign pins 11, 12, 13 to SERCOM functionality
pinPeripheral(11, PIO_SERCOM);
pinPeripheral(12, PIO_SERCOM);
pinPeripheral(13, PIO_SERCOM);
// Configure DMA for SERCOM1 (our 'mySPI' port on 11/12/13)
Serial.println("Configuring DMA trigger");
myDMA.setTrigger(SERCOM1_DMAC_ID_TX);
myDMA.setAction(DMA_TRIGGER_ACTON_BEAT);
Serial.print("Allocating DMA channel...");
stat = myDMA.allocate();
myDMA.printStatus(stat);
desc = myDMA.addDescriptor(
source_memory[buffer_being_filled], // move data from here
(void *)(&SERCOM1->SPI.DATA.reg), // to here
DATA_LENGTH, // this many...
DMA_BEAT_SIZE_BYTE, // bytes/hword/words
true, // increment source addr?
false); // 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);
}
void loop() {
// Fill buffer with new data. The other buffer might
// still be transmitting in the background via DMA.
memset(source_memory[buffer_being_filled], buffer_value, DATA_LENGTH);
// Wait for prior transfer to complete before starting new one...
Serial.print("Waiting on prior transfer...");
while(!transfer_is_done) Serial.write('.');
mySPI.endTransaction();
Serial.println("Done!");
// Modify the DMA descriptor using the newly-filled buffer as source...
myDMA.changeDescriptor(desc, // DMA descriptor address
source_memory[buffer_being_filled]); // New src; dst & count don't change
// Begin new transfer...
Serial.println("Starting new transfer job");
mySPI.beginTransaction(SPISettings(12000000, MSBFIRST, SPI_MODE0));
transfer_is_done = false; // Reset 'done' flag
stat = myDMA.startJob(); // Go!
myDMA.printStatus(stat);
// Switch buffer indices so the alternate buffer is filled/xfer'd
// on the next pass.
buffer_being_filled = 1 - buffer_being_filled;
buffer_value++;
}
@@ -0,0 +1,9 @@
name=Adafruit Zero DMA Library
version=1.1.4
author=Adafruit
maintainer=Adafruit <info@adafruit.com>
sentence=DMA helper/wrapped for ATSAMD21 such as Arduino Zero & Feather M0
paragraph=DMA helper/wrapped for ATSAMD21 such as Arduino Zero & Feather M0
category=Signal Input/Output
url=https://github.com/adafruit/Adafruit_ZeroDMA
architectures=samd
@@ -0,0 +1,151 @@
#if !defined(DMAC_RESERVED_CHANNELS) // SAMD core > 1.2.1 has this built in
/**
* \file
*
* \brief SAM Direct Memory Access Controller Driver
*
* Copyright (C) 2014-2015 Atmel Corporation. All rights reserved.
*
* \asf_license_start
*
* \page License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The name of Atmel may not be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* 4. This software may only be redistributed and used in connection with an
* Atmel microcontroller product.
*
* THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
* EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* \asf_license_stop
*
*/
/*
* Support and FAQ: visit <a href="http://www.atmel.com/design-support/">Atmel
* Support</a>
*/
#ifndef DMA_H_INCLUDED
#define DMA_H_INCLUDED
// THIS IS A PARED-DOWN VERSION OF DMA.H FROM ATMEL ASFCORE 3.
// Please keep original copyright and license intact!
#ifdef __cplusplus
extern "C" {
#endif
#if (SAML21) || (SAML22) || (SAMC20) || (SAMC21) || defined(__DOXYGEN__) || \
defined(__SAMD51__)
#define FEATURE_DMA_CHANNEL_STANDBY
#endif
enum dma_transfer_trigger_action {
#ifdef __SAMD51__
// SAMD51 has a 'burst' transfer which can be set to one
// beat to accomplish same idea as SAMD21's 'beat' transfer.
// Trigger name is ACTON_BEAT for backward compatibility.
DMA_TRIGGER_ACTON_BLOCK = DMAC_CHCTRLA_TRIGACT_BLOCK_Val,
DMA_TRIGGER_ACTON_BEAT = DMAC_CHCTRLA_TRIGACT_BURST_Val,
DMA_TRIGGER_ACTON_TRANSACTION = DMAC_CHCTRLA_TRIGACT_TRANSACTION_Val,
#else
DMA_TRIGGER_ACTON_BLOCK = DMAC_CHCTRLB_TRIGACT_BLOCK_Val,
DMA_TRIGGER_ACTON_BEAT = DMAC_CHCTRLB_TRIGACT_BEAT_Val,
DMA_TRIGGER_ACTON_TRANSACTION = DMAC_CHCTRLB_TRIGACT_TRANSACTION_Val,
#endif
};
enum dma_callback_type {
// First item here is for any transfer errors. A transfer error is
// flagged if a bus error is detected during an AHB access or when
// the DMAC fetches an invalid descriptor
DMA_CALLBACK_TRANSFER_ERROR,
DMA_CALLBACK_TRANSFER_DONE,
DMA_CALLBACK_CHANNEL_SUSPEND,
DMA_CALLBACK_N, // Number of available callbacks
};
enum dma_beat_size {
DMA_BEAT_SIZE_BYTE = 0, // 8-bit
DMA_BEAT_SIZE_HWORD, // 16-bit
DMA_BEAT_SIZE_WORD, // 32-bit
};
enum dma_event_output_selection {
DMA_EVENT_OUTPUT_DISABLE = 0, // Disable event generation
DMA_EVENT_OUTPUT_BLOCK, // Event strobe when block xfer complete
DMA_EVENT_OUTPUT_RESERVED,
DMA_EVENT_OUTPUT_BEAT, // Event strobe when beat xfer complete
};
enum dma_block_action {
DMA_BLOCK_ACTION_NOACT = 0,
// Channel in normal operation and sets transfer complete interrupt
// flag after block transfer
DMA_BLOCK_ACTION_INT,
// Trigger channel suspend after block transfer and sets channel
// suspend interrupt flag once the channel is suspended
DMA_BLOCK_ACTION_SUSPEND,
// Sets transfer complete interrupt flag after a block transfer and
// trigger channel suspend. The channel suspend interrupt flag will
// be set once the channel is suspended.
DMA_BLOCK_ACTION_BOTH,
};
// DMA step selection. This bit determines whether the step size setting
// is applied to source or destination address.
enum dma_step_selection {
DMA_STEPSEL_DST = 0,
DMA_STEPSEL_SRC,
};
// Address increment step size. These bits select the address increment step
// size. The setting apply to source or destination address, depending on
// STEPSEL setting.
enum dma_address_increment_stepsize {
DMA_ADDRESS_INCREMENT_STEP_SIZE_1 = 0, // beat size * 1
DMA_ADDRESS_INCREMENT_STEP_SIZE_2, // beat size * 2
DMA_ADDRESS_INCREMENT_STEP_SIZE_4, // beat size * 4
DMA_ADDRESS_INCREMENT_STEP_SIZE_8, // etc...
DMA_ADDRESS_INCREMENT_STEP_SIZE_16,
DMA_ADDRESS_INCREMENT_STEP_SIZE_32,
DMA_ADDRESS_INCREMENT_STEP_SIZE_64,
DMA_ADDRESS_INCREMENT_STEP_SIZE_128,
};
// higher numbers are higher priority
enum dma_priority {
DMA_PRIORITY_0, // lowest (default)
DMA_PRIORITY_1,
DMA_PRIORITY_2,
DMA_PRIORITY_3, // highest
};
#ifdef __cplusplus
}
#endif
#endif // DMA_H_INCLUDED
#endif // DMAC_RESERVED_CHANNELS