Skip to content

Commit 3889fd0

Browse files
BriscoeTechBriscoeTech
authored andcommitted
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.
1 parent 196a29f commit 3889fd0

File tree

3 files changed

+269
-132
lines changed

3 files changed

+269
-132
lines changed

libraries/SPI/SPI.cpp

Lines changed: 113 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -235,155 +235,146 @@ void SPIClass::transfer(void *buf, size_t count)
235235
}
236236
}
237237

238+
// Non-DMA transfer function.
239+
// this was removed from the dma function and made its own
240+
void SPIClass::transfer(void* txbuf, void* rxbuf, size_t count)
241+
{
242+
uint8_t *txbuf8 = (uint8_t *)txbuf,
243+
*rxbuf8 = (uint8_t *)rxbuf;
244+
if(rxbuf8) {
245+
if(txbuf8) {
246+
// Writing and reading simultaneously
247+
while(count--) {
248+
*rxbuf8++ = _p_sercom->transferDataSPI(*txbuf8++);
249+
}
250+
} else {
251+
// Reading only
252+
while(count--) {
253+
*rxbuf8++ = _p_sercom->transferDataSPI(0xFF);
254+
}
255+
}
256+
} else if(txbuf) {
257+
// Writing only
258+
while(count--) {
259+
(void)_p_sercom->transferDataSPI(*txbuf8++);
260+
}
261+
}
262+
}
263+
264+
238265
// Pointer to SPIClass object, one per DMA channel.
239266
static SPIClass *spiPtr[DMAC_CH_NUM] = { 0 }; // Legit inits list to NULL
240267

241-
void SPIClass::dmaCallback(Adafruit_ZeroDMA *dma) {
242-
// dmaCallback() receives an Adafruit_ZeroDMA object. From this we can get
243-
// a channel number (0 to DMAC_CH_NUM-1, always unique per ZeroDMA object),
244-
// then locate the originating SPIClass object using array lookup, setting
245-
// the dma_busy element 'false' to indicate end of transfer.
246-
spiPtr[dma->getChannel()]->dma_busy = false;
268+
// dma callback when the read part is completed
269+
void SPIClass::dmaCallback_read(Adafruit_ZeroDMA *dma)
270+
{
271+
// dmaCallback() receives an Adafruit_ZeroDMA object. From this we can get
272+
// a channel number (0 to DMAC_CH_NUM-1, always unique per ZeroDMA object),
273+
// then locate the originating SPIClass object using array lookup, setting
274+
uint8_t channel = dma->getChannel();
275+
276+
// flag this part of the dma done
277+
spiPtr[channel]->dma_read_done = true;
278+
279+
// read and write dmas are both done
280+
if(spiPtr[channel]->dma_read_done && spiPtr[channel]->dma_write_done)
281+
{
282+
// call the callback function the user specified
283+
spiPtr[channel]->userDmaCallback();
284+
}
285+
}
286+
287+
// dma callback when the write part is completed
288+
void SPIClass::dmaCallback_write(Adafruit_ZeroDMA *dma)
289+
{
290+
// dmaCallback() receives an Adafruit_ZeroDMA object. From this we can get
291+
// a channel number (0 to DMAC_CH_NUM-1, always unique per ZeroDMA object),
292+
// then locate the originating SPIClass object using array lookup, setting
293+
uint8_t channel = dma->getChannel();
294+
295+
// flag this part of the dma done
296+
spiPtr[channel]->dma_write_done = true;
297+
298+
// read and write dmas are both done
299+
if(spiPtr[channel]->dma_read_done && spiPtr[channel]->dma_write_done)
300+
{
301+
// call the callback function the user specified
302+
spiPtr[channel]->userDmaCallback();
303+
}
247304
}
248305

249-
void SPIClass::transfer(const void* txbuf, void* rxbuf, size_t count,
250-
bool block) {
306+
// dma transfer function for spi
307+
// this function does not block, and dma will transfer in the background
308+
// the callback parameter should be passed in by the user, it is called when the dma is done
309+
void SPIClass::transfer(void* txbuf, void* rxbuf, size_t count, void (*functionToCallWhenComplete)(void) )
310+
{
311+
// save this function to call when the dma is done
312+
userDmaCallback = functionToCallWhenComplete;
251313

252-
// If receiving data and the RX DMA channel is not yet allocated...
253-
if(rxbuf && (readChannel.getChannel() >= DMAC_CH_NUM)) {
254-
if(readChannel.allocate() == DMA_STATUS_OK) {
255-
readDescriptor =
256-
readChannel.addDescriptor(
314+
//******************************
315+
// If the RX DMA channel is not yet allocated...
316+
if(readChannel.getChannel() >= DMAC_CH_NUM)
317+
{
318+
if(readChannel.allocate() == DMA_STATUS_OK)
319+
{
320+
readDescriptor = readChannel.addDescriptor(
257321
(void *)getDataRegister(), // Source address (SPI data reg)
258-
NULL, // Dest address (set later)
259-
0, // Count (set later)
322+
rxbuf, // Dest address
323+
count, // Count
260324
DMA_BEAT_SIZE_BYTE, // Bytes/hwords/words
261325
false, // Don't increment source address
262326
true); // Increment dest address
263327
readChannel.setTrigger(getDMAC_ID_RX());
264328
readChannel.setAction(DMA_TRIGGER_ACTON_BEAT);
329+
readChannel.setCallback(dmaCallback_read, DMA_CALLBACK_TRANSFER_DONE);
265330
spiPtr[readChannel.getChannel()] = this;
266-
// Since all RX transfers involve a TX, a
267-
// separate callback here is not necessary.
268331
}
269332
}
333+
else
334+
{
335+
// update to use the currently passed buffers
336+
readChannel.changeDescriptor(
337+
readDescriptor,
338+
(void *)getDataRegister(), // Source address (SPI data reg)
339+
rxbuf, // Dest address
340+
count); // Count
341+
}
270342

271-
// Unlike the rxbuf check above, where a RX DMA channel is allocated
272-
// only if receiving data (and channel not previously alloc'd), the
273-
// TX DMA channel is always needed, because even RX-only SPI requires
274-
// writing dummy bytes to the peripheral.
275-
if(writeChannel.getChannel() >= DMAC_CH_NUM) {
276-
if(writeChannel.allocate() == DMA_STATUS_OK) {
277-
writeDescriptor =
278-
writeChannel.addDescriptor(
279-
NULL, // Source address (set later)
343+
// If the TX DMA channel is not yet allocated...
344+
if(writeChannel.getChannel() >= DMAC_CH_NUM)
345+
{
346+
if(writeChannel.allocate() == DMA_STATUS_OK)
347+
{
348+
writeDescriptor = writeChannel.addDescriptor(
349+
txbuf, // Source address
280350
(void *)getDataRegister(), // Dest (SPI data register)
281-
0, // Count (set later)
351+
count, // Count
282352
DMA_BEAT_SIZE_BYTE, // Bytes/hwords/words
283353
true, // Increment source address
284354
false); // Don't increment dest address
285355
writeChannel.setTrigger(getDMAC_ID_TX());
286356
writeChannel.setAction(DMA_TRIGGER_ACTON_BEAT);
287-
writeChannel.setCallback(dmaCallback);
357+
writeChannel.setCallback(dmaCallback_write, DMA_CALLBACK_TRANSFER_DONE);
288358
spiPtr[writeChannel.getChannel()] = this;
289359
}
290360
}
291-
292-
if(writeDescriptor && (readDescriptor || !rxbuf)) {
293-
static const uint8_t dum = 0xFF; // Dummy byte for read-only xfers
294-
295-
// Initialize read descriptor dest address to rxbuf
296-
if(rxbuf) readDescriptor->DSTADDR.reg = (uint32_t)rxbuf;
297-
298-
// If reading only, set up writeDescriptor to issue dummy bytes
299-
// (set SRCADDR to &dum and SRCINC to 0). Otherwise, set SRCADDR
300-
// to txbuf and SRCINC to 1. Only needed once at start.
301-
if(rxbuf && !txbuf) {
302-
writeDescriptor->SRCADDR.reg = (uint32_t)&dum;
303-
writeDescriptor->BTCTRL.bit.SRCINC = 0;
304-
} else {
305-
writeDescriptor->SRCADDR.reg = (uint32_t)txbuf;
306-
writeDescriptor->BTCTRL.bit.SRCINC = 1;
307-
}
308-
309-
while(count > 0) {
310-
// Maximum bytes per DMA descriptor is 65,535 (NOT 65,536).
311-
// We could set up a descriptor chain, but that gets more
312-
// complex. For now, instead, break up long transfers into
313-
// chunks of 65,535 bytes max...these transfers are all
314-
// blocking, regardless of the "block" argument, except
315-
// for the last one which will observe the background request.
316-
// The fractional part is done first, so for any "partially
317-
// blocking" transfers like these at least it's the largest
318-
// single-descriptor transfer possible that occurs in the
319-
// background, rather than the tail end.
320-
int bytesThisPass;
321-
bool blockThisPass;
322-
if(count > 65535) { // Too big for 1 descriptor
323-
blockThisPass = true;
324-
bytesThisPass = count % 65535; // Fractional part
325-
if(!bytesThisPass) bytesThisPass = 65535;
326-
} else {
327-
blockThisPass = block;
328-
bytesThisPass = count;
329-
}
330-
331-
// Issue 'bytesThisPass' bytes...
332-
if(rxbuf) {
333-
// Reading, or reading + writing.
334-
// Set up read descriptor.
335-
// Src address doesn't change, only dest & count.
336-
// DMA needs address set to END of buffer, so
337-
// increment the address now, before the transfer.
338-
readDescriptor->DSTADDR.reg += bytesThisPass;
339-
readDescriptor->BTCNT.reg = bytesThisPass;
340-
// Start the RX job BEFORE the TX job!
341-
// That's the whole secret sauce to the two-channel transfer.
342-
// Nothing will actually happen until the write channel job
343-
// is also started.
344-
readChannel.startJob();
345-
}
346-
if(txbuf) {
347-
// DMA needs address set to END of buffer, so
348-
// increment the address now, before the transfer.
349-
writeDescriptor->SRCADDR.reg += bytesThisPass;
350-
}
351-
writeDescriptor->BTCNT.reg = bytesThisPass;
352-
dma_busy = true;
353-
writeChannel.startJob();
354-
count -= bytesThisPass;
355-
if(blockThisPass) {
356-
while(dma_busy);
357-
}
358-
}
359-
} else {
360-
// Non-DMA fallback.
361-
uint8_t *txbuf8 = (uint8_t *)txbuf,
362-
*rxbuf8 = (uint8_t *)rxbuf;
363-
if(rxbuf8) {
364-
if(txbuf8) {
365-
// Writing and reading simultaneously
366-
while(count--) {
367-
*rxbuf8++ = _p_sercom->transferDataSPI(*txbuf8++);
368-
}
369-
} else {
370-
// Reading only
371-
while(count--) {
372-
*rxbuf8++ = _p_sercom->transferDataSPI(0xFF);
373-
}
374-
}
375-
} else if(txbuf) {
376-
// Writing only
377-
while(count--) {
378-
(void)_p_sercom->transferDataSPI(*txbuf8++);
379-
}
380-
}
361+
else
362+
{
363+
// update to use the currently passed buffers
364+
writeChannel.changeDescriptor(
365+
writeDescriptor,
366+
txbuf, // Source address
367+
(void *)getDataRegister(), // Dest (SPI data register)
368+
count); // Count
381369
}
382-
}
383370

384-
// Waits for a prior in-background DMA transfer to complete.
385-
void SPIClass::waitForTransfer(void) {
386-
while(dma_busy);
371+
//******************************
372+
// clear the flags
373+
// fire the dma transactions
374+
dma_read_done = false;
375+
dma_write_done = false;
376+
readChannel.startJob();
377+
writeChannel.startJob();
387378
}
388379

389380
void SPIClass::attachInterrupt() {

libraries/SPI/SPI.h

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,8 @@ class SPIClass {
117117
byte transfer(uint8_t data);
118118
uint16_t transfer16(uint16_t data);
119119
void transfer(void *buf, size_t count);
120-
void transfer(const void* txbuf, void* rxbuf, size_t count,
121-
bool block = true);
122-
void waitForTransfer(void);
120+
void transfer(void* txbuf, void* rxbuf, size_t count); //non dma
121+
void transfer(void* txbuf, void* rxbuf, size_t count, void (*functionToCallWhenComplete)(void) ); //dma
123122

124123
// Transaction Functions
125124
void usingInterrupt(int interruptNumber);
@@ -169,13 +168,16 @@ class SPIClass {
169168
char interruptSave;
170169
uint32_t interruptMask;
171170

172-
// transfer(txbuf, rxbuf, count, block) uses DMA if possible
173-
Adafruit_ZeroDMA readChannel,
174-
writeChannel;
175-
DmacDescriptor *readDescriptor = NULL,
176-
*writeDescriptor = NULL;
177-
volatile bool dma_busy = false;
178-
static void dmaCallback(Adafruit_ZeroDMA *dma);
171+
// objects and functions used for dma transfer
172+
Adafruit_ZeroDMA readChannel;
173+
Adafruit_ZeroDMA writeChannel;
174+
DmacDescriptor *readDescriptor = NULL;
175+
DmacDescriptor *writeDescriptor = NULL;
176+
volatile bool dma_write_done = false;
177+
volatile bool dma_read_done = false;
178+
static void dmaCallback_read(Adafruit_ZeroDMA *dma);
179+
static void dmaCallback_write(Adafruit_ZeroDMA *dma);
180+
void (*userDmaCallback)(void) = NULL; //function pointer to users dma callback function
179181
};
180182

181183
#if SPI_INTERFACES_COUNT > 0

0 commit comments

Comments
 (0)