From 30e2f49e184f7daa247b9a6edd16e14701d0b916 Mon Sep 17 00:00:00 2001 From: BriscoeTech Date: Fri, 10 Jul 2020 15:47:47 -0700 Subject: [PATCH 1/7] Fixed/Added SPI DMA features in SPI library. * Bug Fix. SPI DMA required to wait for both read and write dmas to complete before we can say the entire spi transfer is completed. * Bug Fix. A read only spi dma is not possible because both dmas are required for spi signal timing to work properly. Verified on oscilloscope. Just call the function and pass it empty buffers if you dont care about the rx or tx results, and ignore them. * Bug Fix. SPI DMA buffers were initialized once, and subsequent calls would not allow for new buffer pointers. * Bug/Feature Added. Previous dma was not truly asynchronous. Was poling for dma completion. Callback added to allow for true asynchronous transfers. Dma is now rtos friendly. * Added example program to show how to do a spi dma and setup a callback function when completed. --- libraries/SPI/SPI.cpp | 235 +++++++++--------- libraries/SPI/SPI.h | 22 +- .../examples/zerodma_spi1/zerodma_spi1.ino | 144 +++++++++++ 3 files changed, 269 insertions(+), 132 deletions(-) create mode 100644 libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino diff --git a/libraries/SPI/SPI.cpp b/libraries/SPI/SPI.cpp index 48ae32c95..eb7a7f047 100644 --- a/libraries/SPI/SPI.cpp +++ b/libraries/SPI/SPI.cpp @@ -235,155 +235,146 @@ 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; + + // read and write dmas are both done + if(spiPtr[channel]->dma_read_done && spiPtr[channel]->dma_write_done) + { + // call the callback function the user specified + spiPtr[channel]->userDmaCallback(); + } +} + +// 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; + + // read and write dmas are both done + if(spiPtr[channel]->dma_read_done && spiPtr[channel]->dma_write_done) + { + // call the callback function the user specified + spiPtr[channel]->userDmaCallback(); + } } -void SPIClass::transfer(const void* txbuf, void* rxbuf, size_t count, - bool block) { +// 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, size_t count, void (*functionToCallWhenComplete)(void) ) +{ + // save this function to call when the dma is done + userDmaCallback = functionToCallWhenComplete; - // 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( + //****************************** + // 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 + count, // Count 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 + count); // Count + } - // 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) + count, // Count 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) + count); // Count } -} -// Waits for a prior in-background DMA transfer to complete. -void SPIClass::waitForTransfer(void) { - while(dma_busy); + //****************************** + // clear the flags + // fire the dma transactions + dma_read_done = false; + dma_write_done = false; + readChannel.startJob(); + writeChannel.startJob(); } void SPIClass::attachInterrupt() { diff --git a/libraries/SPI/SPI.h b/libraries/SPI/SPI.h index 48c3cb7b0..b6e501ffb 100644 --- a/libraries/SPI/SPI.h +++ b/libraries/SPI/SPI.h @@ -117,9 +117,8 @@ 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, size_t count, void (*functionToCallWhenComplete)(void) ); //dma // Transaction Functions void usingInterrupt(int interruptNumber); @@ -169,13 +168,16 @@ 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); + // objects and functions used for dma transfer + Adafruit_ZeroDMA readChannel; + Adafruit_ZeroDMA writeChannel; + DmacDescriptor *readDescriptor = NULL; + DmacDescriptor *writeDescriptor = NULL; + volatile bool dma_write_done = false; + volatile bool dma_read_done = false; + static void dmaCallback_read(Adafruit_ZeroDMA *dma); + static void dmaCallback_write(Adafruit_ZeroDMA *dma); + 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..5b006af0e --- /dev/null +++ b/libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino @@ -0,0 +1,144 @@ +// 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 +#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 Date: Sun, 12 Jul 2020 12:06:36 -0700 Subject: [PATCH 2/7] * added support for original spi transfers functions that require poling for completion. * changed parameters count to be uint32_t instead of size_t. Dma transfer setup functions accept count as uint32_t. This provides more transparency on max byte size to user calling function. --- libraries/SPI/SPI.cpp | 41 ++++++++++++++++--- libraries/SPI/SPI.h | 8 +++- .../examples/zerodma_spi1/zerodma_spi1.ino | 23 +++++++++-- 3 files changed, 61 insertions(+), 11 deletions(-) diff --git a/libraries/SPI/SPI.cpp b/libraries/SPI/SPI.cpp index eb7a7f047..d696a5ee9 100644 --- a/libraries/SPI/SPI.cpp +++ b/libraries/SPI/SPI.cpp @@ -261,7 +261,6 @@ void SPIClass::transfer(void* txbuf, void* rxbuf, size_t count) } } - // Pointer to SPIClass object, one per DMA channel. static SPIClass *spiPtr[DMAC_CH_NUM] = { 0 }; // Legit inits list to NULL @@ -279,8 +278,12 @@ void SPIClass::dmaCallback_read(Adafruit_ZeroDMA *dma) // read and write dmas are both done if(spiPtr[channel]->dma_read_done && spiPtr[channel]->dma_write_done) { - // call the callback function the user specified - spiPtr[channel]->userDmaCallback(); + // is a user specified callback to call on completion + if(spiPtr[channel]->userDmaCallback != NULL) + { + // call the callback function the user specified + spiPtr[channel]->userDmaCallback(); + } } } @@ -298,15 +301,41 @@ void SPIClass::dmaCallback_write(Adafruit_ZeroDMA *dma) // read and write dmas are both done if(spiPtr[channel]->dma_read_done && spiPtr[channel]->dma_write_done) { - // call the callback function the user specified - spiPtr[channel]->userDmaCallback(); + // is a user specified callback to call on completion + if(spiPtr[channel]->userDmaCallback != NULL) + { + // call the callback function the user specified + spiPtr[channel]->userDmaCallback(); + } + } +} + +// dma transfer function for spi with pole for completion +void SPIClass::transfer(const void* txbuf, void* rxbuf, uint32_t count, bool block) +{ + // start the dma transfer, but do not specify a user callback function, will pole for completion instead + transfer(txbuf, rxbuf, count, NULL); + + // if this function should automatically wait for completion, otherwise user must do manually + if(block) + { + waitForTransfer(); } } +// Waits for a prior in-background DMA transfer to complete. +void SPIClass::waitForTransfer(void) +{ + while( !dma_read_done || !dma_write_done ) + { + // 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, size_t count, void (*functionToCallWhenComplete)(void) ) +void SPIClass::transfer(void* txbuf, void* rxbuf, uint32_t count, void (*functionToCallWhenComplete)(void) ) { // save this function to call when the dma is done userDmaCallback = functionToCallWhenComplete; diff --git a/libraries/SPI/SPI.h b/libraries/SPI/SPI.h index b6e501ffb..728cedefe 100644 --- a/libraries/SPI/SPI.h +++ b/libraries/SPI/SPI.h @@ -118,7 +118,9 @@ class SPIClass { uint16_t transfer16(uint16_t data); void transfer(void *buf, size_t count); void transfer(void* txbuf, void* rxbuf, size_t count); //non dma - void transfer(void* txbuf, void* rxbuf, size_t count, void (*functionToCallWhenComplete)(void) ); //dma + void transfer(const void* txbuf, void* rxbuf, uint32_t count, bool block = true); //dma pole for completion + void waitForTransfer(void); //dma pole for completion + void transfer(void* txbuf, void* rxbuf, uint32_t count, void (*functionToCallWhenComplete)(void) ); //dma asynchronous // Transaction Functions void usingInterrupt(int interruptNumber); @@ -175,9 +177,11 @@ class SPIClass { DmacDescriptor *writeDescriptor = NULL; volatile bool dma_write_done = false; volatile bool dma_read_done = false; + size_t dma_bytes_remaining; static void dmaCallback_read(Adafruit_ZeroDMA *dma); static void dmaCallback_write(Adafruit_ZeroDMA *dma); - void (*userDmaCallback)(void) = NULL; //function pointer to users dma callback function + 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 index 5b006af0e..71a2b1d0d 100644 --- a/libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino +++ b/libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino @@ -104,8 +104,9 @@ void loop() // send the command byte //SPI.transfer(255); - #if(1) - // dma transfer + #define DMA_TEST 3 //select the type of dma to test + #if(DMA_TEST == 1) + // asyncronous dma transfer // reset the transaction flag dmaDone = false; @@ -120,9 +121,25 @@ void loop() // wait here until transfer is completed while(!dmaDone); // this is updated by our callback function - #else + #elif(DMA_TEST == 2) + // dma transfer pole for completion + // calls the dma transfer, this call will block + SPI.transfer(send_memory, receive_memory, DATA_LENGTH, true); //dma + + #elif(DMA_TEST == 3) + // dma transfer pole for completion + // calls the dma transfer, this call will not block + SPI.transfer(send_memory, receive_memory, DATA_LENGTH, false); //dma + + // can do other things here... + + // wait here until transfer is completed + SPI.waitForTransfer(); + + #elif(DMA_TEST == 4) //non dma transfer SPI.transfer(send_memory, receive_memory, DATA_LENGTH); + #endif // disable Slave Select From c4fb4306b647057f90a0c6c2a00610ebacb7eefa Mon Sep 17 00:00:00 2001 From: BriscoeTech Date: Sun, 12 Jul 2020 14:52:56 -0700 Subject: [PATCH 3/7] added the automatic firing of multiple dma transactions if the transfer size is over 65535 bytes. This should work for poling or asynchronous dma transfers. * addDescriptor and changeDescriptor in Adafruit_ZeroDMA function signature changed. Count parameter changed to be a uint16_t instead of uint32_t. Reflects the limitation that desc->BTCNT.reg is a uint16_t and cannot actually accept a uint32_t value. * Not tested on hardware yet, pushed so it can be read by others. Will test on hardware soon. Need to make sure line 312 and 313 pointer assignment math in checkDmaComplete actually works properly. --- .../Adafruit_ZeroDMA/Adafruit_ZeroDMA.cpp | 4 +- libraries/Adafruit_ZeroDMA/Adafruit_ZeroDMA.h | 4 +- libraries/SPI/SPI.cpp | 97 +++++++++++++++---- libraries/SPI/SPI.h | 18 +++- 4 files changed, 94 insertions(+), 29 deletions(-) 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 d696a5ee9..f605d6f1c 100644 --- a/libraries/SPI/SPI.cpp +++ b/libraries/SPI/SPI.cpp @@ -275,16 +275,8 @@ void SPIClass::dmaCallback_read(Adafruit_ZeroDMA *dma) // flag this part of the dma done spiPtr[channel]->dma_read_done = true; - // read and write dmas are both done - if(spiPtr[channel]->dma_read_done && spiPtr[channel]->dma_write_done) - { - // is a user specified callback to call on completion - if(spiPtr[channel]->userDmaCallback != NULL) - { - // call the callback function the user specified - spiPtr[channel]->userDmaCallback(); - } - } + // see if the entire transaction is completed + checkDmaComplete(channel); } // dma callback when the write part is completed @@ -298,15 +290,43 @@ void SPIClass::dmaCallback_write(Adafruit_ZeroDMA *dma) // 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) { - // is a user specified callback to call on completion - if(spiPtr[channel]->userDmaCallback != NULL) + // are more bytes that need to be transfered + // fire another dma transaction + if( (spiPtr[channel]->dma_bytes_remaining) > 0) { - // call the callback function the user specified - spiPtr[channel]->userDmaCallback(); + // initiate another transfer for the next section of bytes + // update buffer pointers offsets + // use the same user callback as last time if any + void* txbuf = spiPtr[channel]->txbuf_last + DMA_MAX_TRANSFER_SIZE; + void* rxbuf = 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(); + } + + } + } } @@ -326,7 +346,7 @@ void SPIClass::transfer(const void* txbuf, void* rxbuf, uint32_t count, bool blo // Waits for a prior in-background DMA transfer to complete. void SPIClass::waitForTransfer(void) { - while( !dma_read_done || !dma_write_done ) + while( !dma_complete ) { // do nothing, wait for transfer completion } @@ -337,9 +357,42 @@ void SPIClass::waitForTransfer(void) // 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) ) { - // save this function to call when the dma is done + // 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) @@ -349,7 +402,7 @@ void SPIClass::transfer(void* txbuf, void* rxbuf, uint32_t count, void (*functio readDescriptor = readChannel.addDescriptor( (void *)getDataRegister(), // Source address (SPI data reg) rxbuf, // Dest address - count, // Count + bytesThisPass, // bytes to transfer DMA_BEAT_SIZE_BYTE, // Bytes/hwords/words false, // Don't increment source address true); // Increment dest address @@ -366,7 +419,7 @@ void SPIClass::transfer(void* txbuf, void* rxbuf, uint32_t count, void (*functio readDescriptor, (void *)getDataRegister(), // Source address (SPI data reg) rxbuf, // Dest address - count); // Count + bytesThisPass); // bytes to transfer } // If the TX DMA channel is not yet allocated... @@ -377,7 +430,7 @@ void SPIClass::transfer(void* txbuf, void* rxbuf, uint32_t count, void (*functio writeDescriptor = writeChannel.addDescriptor( txbuf, // Source address (void *)getDataRegister(), // Dest (SPI data register) - count, // Count + bytesThisPass, // bytes to transfer DMA_BEAT_SIZE_BYTE, // Bytes/hwords/words true, // Increment source address false); // Don't increment dest address @@ -394,14 +447,16 @@ void SPIClass::transfer(void* txbuf, void* rxbuf, uint32_t count, void (*functio writeDescriptor, txbuf, // Source address (void *)getDataRegister(), // Dest (SPI data register) - count); // Count + bytesThisPass); // bytes to transfer } //****************************** // clear the flags - // fire the dma transactions dma_read_done = false; dma_write_done = false; + dma_complete = false; + + // fire the dma transactions readChannel.startJob(); writeChannel.startJob(); } diff --git a/libraries/SPI/SPI.h b/libraries/SPI/SPI.h index 728cedefe..1a95fa1d4 100644 --- a/libraries/SPI/SPI.h +++ b/libraries/SPI/SPI.h @@ -170,16 +170,26 @@ class SPIClass { char interruptSave; uint32_t interruptMask; - // objects and functions used for dma transfer + //*********************************************************** + // 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; - volatile bool dma_read_done = false; - size_t dma_bytes_remaining; + + 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; // number of bytes remaining for future dma transactions + volatile bool dma_complete = false; // all transactions completed and no bytes remaining + void* txbuf_last; // pointer to buffer last used + void* rxbuf_last; // 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 }; From 70c42fb9d759e3d098e2b87753fd6bc508d083db Mon Sep 17 00:00:00 2001 From: BriscoeTech Date: Sun, 12 Jul 2020 14:59:54 -0700 Subject: [PATCH 4/7] fixed spelling mistake --- libraries/SPI/SPI.cpp | 4 ++-- libraries/SPI/SPI.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/SPI/SPI.cpp b/libraries/SPI/SPI.cpp index f605d6f1c..a3daed756 100644 --- a/libraries/SPI/SPI.cpp +++ b/libraries/SPI/SPI.cpp @@ -330,10 +330,10 @@ void SPIClass::checkDmaComplete(uint8_t channel) } } -// dma transfer function for spi with pole for completion +// dma transfer function for spi with poll for completion void SPIClass::transfer(const void* txbuf, void* rxbuf, uint32_t count, bool block) { - // start the dma transfer, but do not specify a user callback function, will pole for completion instead + // start the dma transfer, but do not specify a user callback function, will poll for completion instead transfer(txbuf, rxbuf, count, NULL); // if this function should automatically wait for completion, otherwise user must do manually diff --git a/libraries/SPI/SPI.h b/libraries/SPI/SPI.h index 1a95fa1d4..517d0e9c4 100644 --- a/libraries/SPI/SPI.h +++ b/libraries/SPI/SPI.h @@ -118,8 +118,8 @@ class SPIClass { uint16_t transfer16(uint16_t data); void transfer(void *buf, size_t count); void transfer(void* txbuf, void* rxbuf, size_t count); //non dma - void transfer(const void* txbuf, void* rxbuf, uint32_t count, bool block = true); //dma pole for completion - void waitForTransfer(void); //dma pole for completion + void transfer(const void* txbuf, void* rxbuf, uint32_t count, bool block = true); //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 From 41aee6e62535afe7ed7d6a8c7d3c8a34d62e19bb Mon Sep 17 00:00:00 2001 From: BriscoeTech Date: Sun, 12 Jul 2020 15:23:27 -0700 Subject: [PATCH 5/7] fixed spelling error --- libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino b/libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino index 71a2b1d0d..aa8b1cc5f 100644 --- a/libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino +++ b/libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino @@ -122,12 +122,12 @@ void loop() while(!dmaDone); // this is updated by our callback function #elif(DMA_TEST == 2) - // dma transfer pole for completion + // dma transfer poll for completion // calls the dma transfer, this call will block SPI.transfer(send_memory, receive_memory, DATA_LENGTH, true); //dma #elif(DMA_TEST == 3) - // dma transfer pole for completion + // dma transfer poll for completion // calls the dma transfer, this call will not block SPI.transfer(send_memory, receive_memory, DATA_LENGTH, false); //dma From e79066c4fff2b2432438def03429b7c0cb677197 Mon Sep 17 00:00:00 2001 From: BriscoeTech Date: Thu, 6 Aug 2020 16:03:27 -0700 Subject: [PATCH 6/7] * commented out non dma function for now. Its function signature conflicts with dma transfer with optional boolean for blocking. I dont know of a instance where the non dma is a requirement or better, so it is commented out for now. * initialized variables so to avoid constructor error message in sloeber * changed void pointer math to have a explicit type cast to uint8_t, void pointer math was generating compiler error and we know we are doing byte math. * added explicit typecast of null function pointer, this prevents ambiguity on what function signature we are calling and prevents compiler warnings in sloeber --- libraries/SPI/SPI.cpp | 11 +++++++---- libraries/SPI/SPI.h | 16 ++++++++-------- .../SPI/examples/zerodma_spi1/zerodma_spi1.ino | 4 ++-- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/libraries/SPI/SPI.cpp b/libraries/SPI/SPI.cpp index a3daed756..3c7296c7e 100644 --- a/libraries/SPI/SPI.cpp +++ b/libraries/SPI/SPI.cpp @@ -235,6 +235,7 @@ 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) @@ -260,6 +261,7 @@ void SPIClass::transfer(void* txbuf, void* rxbuf, size_t count) } } } +*/ // Pointer to SPIClass object, one per DMA channel. static SPIClass *spiPtr[DMAC_CH_NUM] = { 0 }; // Legit inits list to NULL @@ -309,8 +311,9 @@ void SPIClass::checkDmaComplete(uint8_t channel) // initiate another transfer for the next section of bytes // update buffer pointers offsets // use the same user callback as last time if any - void* txbuf = spiPtr[channel]->txbuf_last + DMA_MAX_TRANSFER_SIZE; - void* rxbuf = spiPtr[channel]->rxbuf_last + DMA_MAX_TRANSFER_SIZE; + // (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 @@ -331,10 +334,10 @@ void SPIClass::checkDmaComplete(uint8_t channel) } // dma transfer function for spi with poll for completion -void SPIClass::transfer(const void* txbuf, void* rxbuf, uint32_t count, bool block) +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, NULL); + transfer(txbuf, rxbuf, count, (void (*)(void))NULL ); // if this function should automatically wait for completion, otherwise user must do manually if(block) diff --git a/libraries/SPI/SPI.h b/libraries/SPI/SPI.h index 517d0e9c4..637a6bddf 100644 --- a/libraries/SPI/SPI.h +++ b/libraries/SPI/SPI.h @@ -117,8 +117,8 @@ class SPIClass { byte transfer(uint8_t data); uint16_t transfer16(uint16_t data); void transfer(void *buf, size_t count); - void transfer(void* txbuf, void* rxbuf, size_t count); //non dma - void transfer(const void* txbuf, void* rxbuf, uint32_t count, bool block = true); //dma poll for completion + //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 @@ -180,12 +180,12 @@ class SPIClass { 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; // number of bytes remaining for future dma transactions - volatile bool dma_complete = false; // all transactions completed and no bytes remaining - void* txbuf_last; // pointer to buffer last used - void* rxbuf_last; // pointer to buffer last used + 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); diff --git a/libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino b/libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino index aa8b1cc5f..50ab7bbd7 100644 --- a/libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino +++ b/libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino @@ -104,14 +104,14 @@ void loop() // send the command byte //SPI.transfer(255); - #define DMA_TEST 3 //select the type of dma to test + #define DMA_TEST 1 //select the type of dma to test #if(DMA_TEST == 1) // asyncronous dma transfer // reset the transaction flag dmaDone = false; // calls the dma transfer, this call does not block - // will call the callback frunction when the dma is completed + // will call the callback function when the dma is completed SPI.transfer(send_memory, receive_memory, DATA_LENGTH, callback_dmaDone); //dma // can do other things here... From 604f1df5fe3cab5b6efc92a08b8ad88c4ccfc4ec Mon Sep 17 00:00:00 2001 From: BriscoeTech Date: Fri, 7 Aug 2020 13:49:25 -0700 Subject: [PATCH 7/7] removed Math.h from example casuing build check errors * removed non dma test from the example since it is commented out in library now --- libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino b/libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino index 50ab7bbd7..31162816f 100644 --- a/libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino +++ b/libraries/SPI/examples/zerodma_spi1/zerodma_spi1.ino @@ -3,7 +3,6 @@ // Callback will be called when the dma is completed #include -#include //#define PRINT_MEMMORY_BUFFERS #define SS 2 @@ -136,10 +135,10 @@ void loop() // wait here until transfer is completed SPI.waitForTransfer(); - #elif(DMA_TEST == 4) - //non dma transfer - SPI.transfer(send_memory, receive_memory, DATA_LENGTH); - + //#elif(DMA_TEST == 4) + // //non dma transfer + // SPI.transfer(send_memory, receive_memory, DATA_LENGTH); + // #endif // disable Slave Select