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