/*! * @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 #else #include "utility/dma.h" #endif /** Status codes returned by some DMA functions and/or held in a channel's jobStatus variable. */ enum DMAstatus { 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 DMA_class { public: DMA_class(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. */ DMAstatus 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. */ DMAstatus 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. */ DMAstatus 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)(DMA_class *) = 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(DMAstatus 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 DMAstatus 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])(DMA_class *); ///< Callback func *s }; #endif // _ADAFRUIT_ZERODMA_H_