Skip to content

Commit 91e43ef

Browse files
SPIClass DMA transfer() function WIP
1 parent 10b8b55 commit 91e43ef

File tree

2 files changed

+176
-0
lines changed

2 files changed

+176
-0
lines changed

libraries/SPI/SPI.cpp

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,170 @@ void SPIClass::transfer(void *buf, size_t count)
235235
}
236236
}
237237

238+
// Pointer to SPIClass object, one per DMA channel.
239+
static SPIClass *spiPtr[DMAC_CH_NUM] = { 0 }; // Inits list to NULL
240+
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;
247+
}
248+
249+
void SPIClass::transfer(const void* txbuf, void* rxbuf, size_t count,
250+
bool background) {
251+
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(
257+
(void *)getDataRegister(), // Source address (SPI data reg)
258+
NULL, // Dest address (set later)
259+
0, // Count (set later)
260+
DMA_BEAT_SIZE_BYTE, // Bytes/hwords/words
261+
false, // Don't increment source address
262+
true); // Increment dest address
263+
readChannel.setTrigger(getDMAC_ID_RX());
264+
readChannel.setAction(DMA_TRIGGER_ACTON_BEAT);
265+
readChannel.setCallback(dmaCallback);
266+
spiPtr[readChannel.getChannel()] = this;
267+
}
268+
}
269+
270+
// Unlike the rxbuf check above, where a RX DMA channel is allocated
271+
// only if receiving data (and channel not previously alloc'd), the
272+
// TX DMA channel is always needed, because even RX-only SPI requires
273+
// writing dummy bytes to the peripheral.
274+
if(writeChannel.getChannel() >= DMAC_CH_NUM) {
275+
if(writeChannel.allocate() == DMA_STATUS_OK) {
276+
writeDescriptor =
277+
writeChannel.addDescriptor(
278+
(void *)NULL, // Source address (set later)
279+
(void *)getDataRegister(), // Dest (SPI data register)
280+
0, // Count (set later)
281+
DMA_BEAT_SIZE_BYTE, // Bytes/hwords/words
282+
false, // Don't increment source address
283+
true); // Increment dest address
284+
writeChannel.setTrigger(getDMAC_ID_TX());
285+
writeChannel.setAction(DMA_TRIGGER_ACTON_BEAT);
286+
writeChannel.setCallback(dmaCallback);
287+
spiPtr[writeChannel.getChannel()] = this;
288+
}
289+
}
290+
291+
if(writeDescriptor) { // If this allocated, then use DMA
292+
static uint8_t dum = 0xFF; // Dummy byte for read-only transfers
293+
294+
// Initialize read descriptor dest address to rxbuf (even if unused)
295+
readDescriptor->DSTADDR.reg = (uint32_t)rxbuf;
296+
297+
// If reading only, set up writeDescriptor to issue dummy bytes
298+
// (set SRCADDR to &dum and SRCINC to 0). Otherwise, set SRCADDR
299+
// to txbuf and SRCINC to 1. Only needed once at start.
300+
if(rxbuf && !txbuf) {
301+
writeDescriptor->SRCADDR.reg = (uint32_t)&dum;
302+
writeDescriptor->BTCTRL.bit.SRCINC = 0;
303+
} else {
304+
writeDescriptor->SRCADDR.reg = (uint32_t)txbuf;
305+
writeDescriptor->BTCTRL.bit.SRCINC = 1;
306+
}
307+
308+
while(count > 0) {
309+
// Maximum bytes per DMA descriptor is 65,535 (NOT 65,536).
310+
// We could set up a descriptor chain, but that gets more
311+
// complex. For now, instead, break up long transfers into
312+
// chunks of 65,535 bytes max...these transfers are all
313+
// blocking, regardless of the "background" argument, except
314+
// for the last one which will observe the background request.
315+
// The fractional part is done first, so for any "partially
316+
// backgrounded" transfers like these at least it's the
317+
// largest single-descriptor transfer possible that occurs
318+
// in the background, rather than the tail end.
319+
int bytesThisPass;
320+
bool block;
321+
if(count > 65535) { // Too big for 1 descriptor
322+
block = true;
323+
bytesThisPass = count % 65535; // Fractional part
324+
if(!bytesThisPass) bytesThisPass = 65535;
325+
} else {
326+
block = !background;
327+
bytesThisPass = count;
328+
}
329+
330+
// Issue 'bytesThisPass' bytes...
331+
dma_busy = true;
332+
if(rxbuf) {
333+
// Reading, or reading + writing.
334+
// Set up read descriptor for reading.
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+
if(txbuf) {
341+
// Writing and reading simultaneously.
342+
// Set up write descriptor for writing real data.
343+
// Src address and count both change.
344+
// DMA needs address set to END of buffer, so
345+
// increment the address now, before the transfer.
346+
writeDescriptor->SRCADDR.reg += bytesThisPass;
347+
writeDescriptor->BTCNT.reg = bytesThisPass;
348+
} else {
349+
// Reading only.
350+
// Write descriptor was already set up for dummy
351+
// writes outside loop, only BTCNT needs set.
352+
writeDescriptor->SRCADDR.reg = (uint32_t)&dum;
353+
writeDescriptor->BTCNT.reg = bytesThisPass;
354+
}
355+
// Start the RX job BEFORE the TX job!
356+
// That's the whole secret sauce to the two-channel transfer.
357+
readChannel.startJob();
358+
} else if(txbuf) {
359+
// Writing only.
360+
// Set up write descriptor for writing real data.
361+
// DMA needs address set to END of buffer, so
362+
// increment the address now, before the transfer.
363+
writeDescriptor->SRCADDR.reg += bytesThisPass;
364+
writeDescriptor->BTCNT.reg = bytesThisPass;
365+
}
366+
writeChannel.startJob();
367+
count -= bytesThisPass;
368+
if(block) {
369+
while(dma_busy);
370+
}
371+
}
372+
} else {
373+
// Non-DMA fallback.
374+
uint8_t *txbuf8 = (uint8_t *)txbuf,
375+
*rxbuf8 = (uint8_t *)rxbuf;
376+
if(rxbuf8) {
377+
if(txbuf8) {
378+
// Writing and reading simultaneously
379+
while(count--) {
380+
*rxbuf8++ = _p_sercom->transferDataSPI(*txbuf8++);
381+
}
382+
} else {
383+
// Reading only
384+
while(count--) {
385+
*rxbuf8++ = _p_sercom->transferDataSPI(0xFF);
386+
}
387+
}
388+
} else if(txbuf) {
389+
// Writing only
390+
while(count--) {
391+
(void)_p_sercom->transferDataSPI(*txbuf8++);
392+
}
393+
}
394+
}
395+
}
396+
397+
// Waits for a prior in-background DMA transfer to complete.
398+
void SPIClass::waitForTransfer(void) {
399+
while(dma_busy);
400+
}
401+
238402
void SPIClass::attachInterrupt() {
239403
// Should be enableInterrupt()
240404
}

libraries/SPI/SPI.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#define _SPI_H_INCLUDED
2222

2323
#include <Arduino.h>
24+
#include <Adafruit_ZeroDMA.h>
2425

2526
// SPI_HAS_TRANSACTION means SPI has
2627
// - beginTransaction()
@@ -114,6 +115,9 @@ class SPIClass {
114115
byte transfer(uint8_t data);
115116
uint16_t transfer16(uint16_t data);
116117
void transfer(void *buf, size_t count);
118+
void transfer(const void* txbuf, void* rxbuf, size_t count,
119+
bool background = false);
120+
void waitForTransfer(void);
117121

118122
// Transaction Functions
119123
void usingInterrupt(int interruptNumber);
@@ -162,6 +166,14 @@ class SPIClass {
162166
uint8_t interruptMode;
163167
char interruptSave;
164168
uint32_t interruptMask;
169+
170+
// transfer(txbuf, rxbuf, count, background) uses DMA if possible
171+
Adafruit_ZeroDMA writeChannel,
172+
readChannel;
173+
DmacDescriptor *readDescriptor = NULL,
174+
*writeDescriptor = NULL;
175+
volatile bool dma_busy = false;
176+
static void dmaCallback(Adafruit_ZeroDMA *dma);
165177
};
166178

167179
#if SPI_INTERFACES_COUNT > 0

0 commit comments

Comments
 (0)