diff --git a/libraries/Adafruit_ZeroDMA/Adafruit_ZeroDMA.cpp b/libraries/Adafruit_ZeroDMA/Adafruit_ZeroDMA.cpp index 0adb78470..f849d2bd7 100644 --- a/libraries/Adafruit_ZeroDMA/Adafruit_ZeroDMA.cpp +++ b/libraries/Adafruit_ZeroDMA/Adafruit_ZeroDMA.cpp @@ -449,7 +449,7 @@ bool Adafruit_ZeroDMA::isActive(void) const { DmacDescriptor *Adafruit_ZeroDMA::addDescriptor( void *src, void *dst, - uint32_t count, + uint16_t count, dma_beat_size size, bool srcInc, bool dstInc, @@ -537,7 +537,7 @@ DmacDescriptor *Adafruit_ZeroDMA::addDescriptor( // 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) { + void *src, void *dst, uint16_t count) { uint8_t bytesPerBeat; // Beat transfer size IN BYTES switch(desc->BTCTRL.bit.BEATSIZE) { diff --git a/libraries/Adafruit_ZeroDMA/Adafruit_ZeroDMA.h b/libraries/Adafruit_ZeroDMA/Adafruit_ZeroDMA.h index fc8461993..428ac3de6 100644 --- a/libraries/Adafruit_ZeroDMA/Adafruit_ZeroDMA.h +++ b/libraries/Adafruit_ZeroDMA/Adafruit_ZeroDMA.h @@ -41,13 +41,13 @@ class Adafruit_ZeroDMA { uint8_t getChannel(void) const { return channel; } // DMA descriptor functions - DmacDescriptor *addDescriptor(void *src, void *dst, uint32_t count = 0, + DmacDescriptor *addDescriptor(void *src, void *dst, uint16_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); void changeDescriptor(DmacDescriptor *d, void *src = NULL, - void *dst = NULL, uint32_t count = 0); + void *dst = NULL, uint16_t count = 0); bool isActive(void) const; void _IRQhandler(uint8_t flags); // DO NOT TOUCH diff --git a/libraries/SPI/SPI.cpp b/libraries/SPI/SPI.cpp index 48ae32c95..3c7296c7e 100644 --- a/libraries/SPI/SPI.cpp +++ b/libraries/SPI/SPI.cpp @@ -235,155 +235,233 @@ void SPIClass::transfer(void *buf, size_t count) } } +/* +// Non-DMA transfer function. +// this was removed from the dma function and made its own +void SPIClass::transfer(void* txbuf, void* rxbuf, size_t count) +{ + uint8_t *txbuf8 = (uint8_t *)txbuf, + *rxbuf8 = (uint8_t *)rxbuf; + if(rxbuf8) { + if(txbuf8) { + // Writing and reading simultaneously + while(count--) { + *rxbuf8++ = _p_sercom->transferDataSPI(*txbuf8++); + } + } else { + // Reading only + while(count--) { + *rxbuf8++ = _p_sercom->transferDataSPI(0xFF); + } + } + } else if(txbuf) { + // Writing only + while(count--) { + (void)_p_sercom->transferDataSPI(*txbuf8++); + } + } +} +*/ + // Pointer to SPIClass object, one per DMA channel. static SPIClass *spiPtr[DMAC_CH_NUM] = { 0 }; // Legit inits list to NULL -void SPIClass::dmaCallback(Adafruit_ZeroDMA *dma) { - // dmaCallback() receives an Adafruit_ZeroDMA object. From this we can get - // a channel number (0 to DMAC_CH_NUM-1, always unique per ZeroDMA object), - // then locate the originating SPIClass object using array lookup, setting - // the dma_busy element 'false' to indicate end of transfer. - spiPtr[dma->getChannel()]->dma_busy = false; +// dma callback when the read part is completed +void SPIClass::dmaCallback_read(Adafruit_ZeroDMA *dma) +{ + // dmaCallback() receives an Adafruit_ZeroDMA object. From this we can get + // a channel number (0 to DMAC_CH_NUM-1, always unique per ZeroDMA object), + // then locate the originating SPIClass object using array lookup, setting + uint8_t channel = dma->getChannel(); + + // flag this part of the dma done + spiPtr[channel]->dma_read_done = true; + + // see if the entire transaction is completed + checkDmaComplete(channel); } -void SPIClass::transfer(const void* txbuf, void* rxbuf, size_t count, - bool block) { +// dma callback when the write part is completed +void SPIClass::dmaCallback_write(Adafruit_ZeroDMA *dma) +{ + // dmaCallback() receives an Adafruit_ZeroDMA object. From this we can get + // a channel number (0 to DMAC_CH_NUM-1, always unique per ZeroDMA object), + // then locate the originating SPIClass object using array lookup, setting + uint8_t channel = dma->getChannel(); + + // flag this part of the dma done + spiPtr[channel]->dma_write_done = true; + + // see if the entire transaction is completed + checkDmaComplete(channel); + +} + +// see if the entire dma transaction is completed +// will automatically initiate another dma if we have bytes remaining to transfer +void SPIClass::checkDmaComplete(uint8_t channel) +{ + // read and write dmas are both done + if(spiPtr[channel]->dma_read_done && spiPtr[channel]->dma_write_done) + { + // are more bytes that need to be transfered + // fire another dma transaction + if( (spiPtr[channel]->dma_bytes_remaining) > 0) + { + // initiate another transfer for the next section of bytes + // update buffer pointers offsets + // use the same user callback as last time if any + // (uint8_t*) typecast needed as c does not like pointer math of void*, but we know we are doing byte math + void* txbuf = (uint8_t*)spiPtr[channel]->txbuf_last + DMA_MAX_TRANSFER_SIZE; + void* rxbuf = (uint8_t*)spiPtr[channel]->rxbuf_last + DMA_MAX_TRANSFER_SIZE; + spiPtr[channel]->transfer(txbuf, rxbuf, spiPtr[channel]->dma_bytes_remaining, spiPtr[channel]->userDmaCallback ); + } + // the transfer is completed, no bytes remaining + else + { + // flag as completed for anything poling for completion + spiPtr[channel]->dma_complete = true; + + // call the callback function the user specified if any + if(spiPtr[channel]->userDmaCallback != NULL) + { + spiPtr[channel]->userDmaCallback(); + } + + } + + } +} + +// dma transfer function for spi with poll for completion +void SPIClass::transfer(void* txbuf, void* rxbuf, uint32_t count, bool block) +{ + // start the dma transfer, but do not specify a user callback function, will poll for completion instead + transfer(txbuf, rxbuf, count, (void (*)(void))NULL ); + + // if this function should automatically wait for completion, otherwise user must do manually + if(block) + { + waitForTransfer(); + } +} - // If receiving data and the RX DMA channel is not yet allocated... - if(rxbuf && (readChannel.getChannel() >= DMAC_CH_NUM)) { - if(readChannel.allocate() == DMA_STATUS_OK) { - readDescriptor = - readChannel.addDescriptor( +// Waits for a prior in-background DMA transfer to complete. +void SPIClass::waitForTransfer(void) +{ + while( !dma_complete ) + { + // do nothing, wait for transfer completion + } +} + +// dma transfer function for spi +// this function does not block, and dma will transfer in the background +// the callback parameter should be passed in by the user, it is called when the dma is done +void SPIClass::transfer(void* txbuf, void* rxbuf, uint32_t count, void (*functionToCallWhenComplete)(void) ) +{ + // remember these buffer pointers + // will reuse if we have to do multiple dma transactions and pointer math + txbuf_last = txbuf; + rxbuf_last = rxbuf; + + // save this function to call when the entire dma is done + userDmaCallback = functionToCallWhenComplete; + + // Maximum bytes per DMA descriptor is 65,535 (NOT 65,536). + // We could set up a descriptor chain, but that gets more + // complex. For now, instead, break up long transfers into + // chunks of 65,535 bytes max...these transfers are all + // blocking, regardless of the "block" argument, except + // for the last one which will observe the background request. + // The fractional part is done first, so for any "partially + // blocking" transfers like these at least it's the largest + // single-descriptor transfer possible that occurs in the + // background, rather than the tail end. + uint16_t bytesThisPass; + + if(count > DMA_MAX_TRANSFER_SIZE) + { + // Too big for 1 descriptor + // will need to do multiple dma transfers + bytesThisPass = DMA_MAX_TRANSFER_SIZE; + + // remember bytes remaining for future transfers + dma_bytes_remaining = count - DMA_MAX_TRANSFER_SIZE; + } + else + { + // can do everything in one dma transfer + bytesThisPass = count; + dma_bytes_remaining = 0; + } + + //****************************** + // If the RX DMA channel is not yet allocated... + if(readChannel.getChannel() >= DMAC_CH_NUM) + { + if(readChannel.allocate() == DMA_STATUS_OK) + { + readDescriptor = readChannel.addDescriptor( (void *)getDataRegister(), // Source address (SPI data reg) - NULL, // Dest address (set later) - 0, // Count (set later) + rxbuf, // Dest address + bytesThisPass, // bytes to transfer DMA_BEAT_SIZE_BYTE, // Bytes/hwords/words false, // Don't increment source address true); // Increment dest address readChannel.setTrigger(getDMAC_ID_RX()); readChannel.setAction(DMA_TRIGGER_ACTON_BEAT); + readChannel.setCallback(dmaCallback_read, DMA_CALLBACK_TRANSFER_DONE); spiPtr[readChannel.getChannel()] = this; - // Since all RX transfers involve a TX, a - // separate callback here is not necessary. } } + else + { + // update to use the currently passed buffers + readChannel.changeDescriptor( + readDescriptor, + (void *)getDataRegister(), // Source address (SPI data reg) + rxbuf, // Dest address + bytesThisPass); // bytes to transfer + } - // Unlike the rxbuf check above, where a RX DMA channel is allocated - // only if receiving data (and channel not previously alloc'd), the - // TX DMA channel is always needed, because even RX-only SPI requires - // writing dummy bytes to the peripheral. - if(writeChannel.getChannel() >= DMAC_CH_NUM) { - if(writeChannel.allocate() == DMA_STATUS_OK) { - writeDescriptor = - writeChannel.addDescriptor( - NULL, // Source address (set later) + // If the TX DMA channel is not yet allocated... + if(writeChannel.getChannel() >= DMAC_CH_NUM) + { + if(writeChannel.allocate() == DMA_STATUS_OK) + { + writeDescriptor = writeChannel.addDescriptor( + txbuf, // Source address (void *)getDataRegister(), // Dest (SPI data register) - 0, // Count (set later) + bytesThisPass, // bytes to transfer DMA_BEAT_SIZE_BYTE, // Bytes/hwords/words true, // Increment source address false); // Don't increment dest address writeChannel.setTrigger(getDMAC_ID_TX()); writeChannel.setAction(DMA_TRIGGER_ACTON_BEAT); - writeChannel.setCallback(dmaCallback); + writeChannel.setCallback(dmaCallback_write, DMA_CALLBACK_TRANSFER_DONE); spiPtr[writeChannel.getChannel()] = this; } } - - if(writeDescriptor && (readDescriptor || !rxbuf)) { - static const uint8_t dum = 0xFF; // Dummy byte for read-only xfers - - // Initialize read descriptor dest address to rxbuf - if(rxbuf) readDescriptor->DSTADDR.reg = (uint32_t)rxbuf; - - // If reading only, set up writeDescriptor to issue dummy bytes - // (set SRCADDR to &dum and SRCINC to 0). Otherwise, set SRCADDR - // to txbuf and SRCINC to 1. Only needed once at start. - if(rxbuf && !txbuf) { - writeDescriptor->SRCADDR.reg = (uint32_t)&dum; - writeDescriptor->BTCTRL.bit.SRCINC = 0; - } else { - writeDescriptor->SRCADDR.reg = (uint32_t)txbuf; - writeDescriptor->BTCTRL.bit.SRCINC = 1; - } - - while(count > 0) { - // Maximum bytes per DMA descriptor is 65,535 (NOT 65,536). - // We could set up a descriptor chain, but that gets more - // complex. For now, instead, break up long transfers into - // chunks of 65,535 bytes max...these transfers are all - // blocking, regardless of the "block" argument, except - // for the last one which will observe the background request. - // The fractional part is done first, so for any "partially - // blocking" transfers like these at least it's the largest - // single-descriptor transfer possible that occurs in the - // background, rather than the tail end. - int bytesThisPass; - bool blockThisPass; - if(count > 65535) { // Too big for 1 descriptor - blockThisPass = true; - bytesThisPass = count % 65535; // Fractional part - if(!bytesThisPass) bytesThisPass = 65535; - } else { - blockThisPass = block; - bytesThisPass = count; - } - - // Issue 'bytesThisPass' bytes... - if(rxbuf) { - // Reading, or reading + writing. - // Set up read descriptor. - // Src address doesn't change, only dest & count. - // DMA needs address set to END of buffer, so - // increment the address now, before the transfer. - readDescriptor->DSTADDR.reg += bytesThisPass; - readDescriptor->BTCNT.reg = bytesThisPass; - // Start the RX job BEFORE the TX job! - // That's the whole secret sauce to the two-channel transfer. - // Nothing will actually happen until the write channel job - // is also started. - readChannel.startJob(); - } - if(txbuf) { - // DMA needs address set to END of buffer, so - // increment the address now, before the transfer. - writeDescriptor->SRCADDR.reg += bytesThisPass; - } - writeDescriptor->BTCNT.reg = bytesThisPass; - dma_busy = true; - writeChannel.startJob(); - count -= bytesThisPass; - if(blockThisPass) { - while(dma_busy); - } - } - } else { - // Non-DMA fallback. - uint8_t *txbuf8 = (uint8_t *)txbuf, - *rxbuf8 = (uint8_t *)rxbuf; - if(rxbuf8) { - if(txbuf8) { - // Writing and reading simultaneously - while(count--) { - *rxbuf8++ = _p_sercom->transferDataSPI(*txbuf8++); - } - } else { - // Reading only - while(count--) { - *rxbuf8++ = _p_sercom->transferDataSPI(0xFF); - } - } - } else if(txbuf) { - // Writing only - while(count--) { - (void)_p_sercom->transferDataSPI(*txbuf8++); - } - } + else + { + // update to use the currently passed buffers + writeChannel.changeDescriptor( + writeDescriptor, + txbuf, // Source address + (void *)getDataRegister(), // Dest (SPI data register) + bytesThisPass); // bytes to transfer } -} -// Waits for a prior in-background DMA transfer to complete. -void SPIClass::waitForTransfer(void) { - while(dma_busy); + //****************************** + // clear the flags + dma_read_done = false; + dma_write_done = false; + dma_complete = false; + + // fire the dma transactions + readChannel.startJob(); + writeChannel.startJob(); } void SPIClass::attachInterrupt() { diff --git a/libraries/SPI/SPI.h b/libraries/SPI/SPI.h index 48c3cb7b0..637a6bddf 100644 --- a/libraries/SPI/SPI.h +++ b/libraries/SPI/SPI.h @@ -117,9 +117,10 @@ class SPIClass { byte transfer(uint8_t data); uint16_t transfer16(uint16_t data); void transfer(void *buf, size_t count); - void transfer(const void* txbuf, void* rxbuf, size_t count, - bool block = true); - void waitForTransfer(void); + //void transfer(void* txbuf, void* rxbuf, size_t count); //non dma + void transfer(void* txbuf, void* rxbuf, uint32_t count, bool block); //dma poll for completion + void waitForTransfer(void); //dma poll for completion + void transfer(void* txbuf, void* rxbuf, uint32_t count, void (*functionToCallWhenComplete)(void) ); //dma asynchronous // Transaction Functions void usingInterrupt(int interruptNumber); @@ -169,13 +170,28 @@ class SPIClass { char interruptSave; uint32_t interruptMask; - // transfer(txbuf, rxbuf, count, block) uses DMA if possible - Adafruit_ZeroDMA readChannel, - writeChannel; - DmacDescriptor *readDescriptor = NULL, - *writeDescriptor = NULL; - volatile bool dma_busy = false; - static void dmaCallback(Adafruit_ZeroDMA *dma); + //*********************************************************** + // constants, objects, and functions used for dma transfers + + #define DMA_MAX_TRANSFER_SIZE 65535 // maximum bytes a dma can transfer per transaction + + Adafruit_ZeroDMA readChannel; + Adafruit_ZeroDMA writeChannel; + DmacDescriptor *readDescriptor = NULL; + DmacDescriptor *writeDescriptor = NULL; + + volatile bool dma_write_done = false; // true when read dma callback completes + volatile bool dma_read_done = false; // true when write dma callback completes + uint32_t dma_bytes_remaining = 0; // number of bytes remaining for future dma transactions + volatile bool dma_complete = false; // all transactions completed and no bytes remaining + void* txbuf_last = NULL; // pointer to buffer last used + void* rxbuf_last = NULL; // pointer to buffer last used + + static void dmaCallback_read(Adafruit_ZeroDMA *dma); + static void dmaCallback_write(Adafruit_ZeroDMA *dma); + static void checkDmaComplete(uint8_t channel); + void (*userDmaCallback)(void) = NULL; //function pointer to users dma callback function + }; #if SPI_INTERFACES_COUNT > 0 diff --git a/libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino b/libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino new file mode 100644 index 000000000..31162816f --- /dev/null +++ b/libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino @@ -0,0 +1,160 @@ +// DMA-based SPI buffer write. +// Will setup a dma transaction to read and write data over the spi +// Callback will be called when the dma is completed + +#include +//#define PRINT_MEMMORY_BUFFERS + +#define SS 2 + +// The memory we'll be issuing to SPI: +#define DATA_LENGTH 4096 +uint8_t send_memory[DATA_LENGTH]; +uint8_t receive_memory[DATA_LENGTH]; + +//****************************************************************************************** + +// Show contents of array +void dump(uint8_t *memory) +{ + const int BASE = 16; //hex formatting + const int PAD_AMMOUNT = 4; //digits + + for(uint32_t i=0; i