initial commit
This commit is contained in:
@@ -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 [](https://github.com/adafruit/Adafruit_ZeroDMA/actions)
|
||||
|
||||
DMA helper/wrapped for ATSAMD21 such as Arduino Zero & 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
|
||||
Reference in New Issue
Block a user