Skip to content

Commit 6194fc3

Browse files
authored
Merge pull request arduino#116 from adafruit/pb-spi
Merge Adafruit_ZeroDMA into SAMD core libraries
2 parents 3f79b9d + 9a07f41 commit 6194fc3

File tree

11 files changed

+1359
-0
lines changed

11 files changed

+1359
-0
lines changed

libraries/Adafruit_ZeroDMA/Adafruit_ZeroDMA.cpp

+654
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#ifndef _ADAFRUIT_ZERODMA_H_
2+
#define _ADAFRUIT_ZERODMA_H_
3+
4+
#include "Arduino.h"
5+
#include "utility/dma.h"
6+
7+
// Status codes returned by some DMA functions and/or held in
8+
// a channel's jobStatus variable.
9+
enum ZeroDMAstatus {
10+
DMA_STATUS_OK = 0,
11+
DMA_STATUS_ERR_NOT_FOUND,
12+
DMA_STATUS_ERR_NOT_INITIALIZED,
13+
DMA_STATUS_ERR_INVALID_ARG,
14+
DMA_STATUS_ERR_IO,
15+
DMA_STATUS_ERR_TIMEOUT,
16+
DMA_STATUS_BUSY,
17+
DMA_STATUS_SUSPEND,
18+
DMA_STATUS_ABORTED,
19+
DMA_STATUS_JOBSTATUS = -1 // For printStatus() function
20+
};
21+
22+
class Adafruit_ZeroDMA {
23+
public:
24+
Adafruit_ZeroDMA(void);
25+
26+
// DMA channel functions
27+
ZeroDMAstatus allocate(void), // Allocates DMA channel
28+
startJob(void),
29+
free(void); // Deallocates DMA channel
30+
void trigger(void) const,
31+
setTrigger(uint8_t trigger),
32+
setAction(dma_transfer_trigger_action action),
33+
setCallback(void (*callback)(Adafruit_ZeroDMA *) = NULL,
34+
dma_callback_type type = DMA_CALLBACK_TRANSFER_DONE),
35+
loop(boolean flag),
36+
suspend(void) const,
37+
resume(void),
38+
abort(void),
39+
setPriority(dma_priority pri) const,
40+
printStatus(ZeroDMAstatus s = DMA_STATUS_JOBSTATUS) const;
41+
uint8_t getChannel(void) const { return channel; }
42+
43+
// DMA descriptor functions
44+
DmacDescriptor *addDescriptor(void *src, void *dst, uint32_t count = 0,
45+
dma_beat_size size = DMA_BEAT_SIZE_BYTE,
46+
bool srcInc = true, bool dstInc = true,
47+
uint32_t stepSize = DMA_ADDRESS_INCREMENT_STEP_SIZE_1,
48+
bool stepSel = DMA_STEPSEL_DST);
49+
void changeDescriptor(DmacDescriptor *d, void *src = NULL,
50+
void *dst = NULL, uint32_t count = 0);
51+
bool isActive(void) const;
52+
53+
void _IRQhandler(uint8_t flags); // DO NOT TOUCH
54+
55+
56+
protected:
57+
uint8_t channel;
58+
volatile enum ZeroDMAstatus jobStatus;
59+
bool hasDescriptors;
60+
bool loopFlag;
61+
uint8_t peripheralTrigger;
62+
dma_transfer_trigger_action triggerAction;
63+
void (*callback[DMA_CALLBACK_N])(Adafruit_ZeroDMA *);
64+
};
65+
66+
#endif // _ADAFRUIT_ZERODMA_H_

libraries/Adafruit_ZeroDMA/LICENSE

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2016 Adafruit Industries
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
22+

libraries/Adafruit_ZeroDMA/README.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Adafruit_ZeroDMA
2+
DMA helper/wrapped for ATSAMD21 such as Arduino Zero & Feather M0
3+
4+
Current version of this library no longer requires Adafruit_ASFcore as a prerequisite. However...IT BREAKS COMPATIBILITY WITH PRIOR VERSIONS. Function names, calling sequence and return types/values have changed. See examples!
5+
6+
Item(s) in 'utility' directory are much pared-down derivatives of Atmel ASFcore 3 files. Please keep their original copyright and license intact when editing.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Simple ZeroDMA example -- an equivalent to the memcpy() function.
2+
// Decause it uses DMA, unlike memcpy(), your code could be doing other
3+
// things simultaneously while the copy operation runs.
4+
5+
#include <Adafruit_ZeroDMA.h>
6+
#include "utility/dma.h"
7+
8+
Adafruit_ZeroDMA myDMA;
9+
ZeroDMAstatus stat; // DMA status codes returned by some functions
10+
11+
// The memory we'll be moving:
12+
#define DATA_LENGTH 1024
13+
uint8_t source_memory[DATA_LENGTH],
14+
destination_memory[DATA_LENGTH];
15+
16+
volatile bool transfer_is_done = false; // Done yet?
17+
18+
// Callback for end-of-DMA-transfer
19+
void dma_callback(Adafruit_ZeroDMA *dma) {
20+
transfer_is_done = true;
21+
}
22+
23+
void setup() {
24+
uint32_t t;
25+
pinMode(LED_BUILTIN, OUTPUT); // Onboard LED can be used for precise
26+
digitalWrite(LED_BUILTIN, LOW); // benchmarking with an oscilloscope
27+
Serial.begin(115200);
28+
while(!Serial); // Wait for Serial monitor before continuing
29+
30+
Serial.println("DMA test: memory copy");
31+
32+
Serial.print("Allocating DMA channel...");
33+
stat = myDMA.allocate();
34+
myDMA.printStatus(stat);
35+
36+
Serial.println("Setting up transfer");
37+
myDMA.addDescriptor(source_memory, destination_memory, DATA_LENGTH);
38+
39+
Serial.println("Adding callback");
40+
// register_callback() can optionally take a second argument
41+
// (callback type), default is DMA_CALLBACK_TRANSFER_DONE
42+
myDMA.setCallback(dma_callback);
43+
44+
// Fill the source buffer with incrementing bytes, dest buf with 0's
45+
for(uint32_t i=0; i<DATA_LENGTH; i++) source_memory[i] = i;
46+
memset(destination_memory, 0, DATA_LENGTH);
47+
48+
// Show the destination buffer is empty before transfer
49+
Serial.println("Destination buffer before transfer:");
50+
dump();
51+
52+
Serial.println("Starting transfer job");
53+
stat = myDMA.startJob();
54+
myDMA.printStatus(stat);
55+
56+
Serial.println("Triggering DMA transfer...");
57+
t = micros();
58+
digitalWrite(LED_BUILTIN, HIGH);
59+
myDMA.trigger();
60+
61+
// Your code could do other things here while copy happens!
62+
63+
while(!transfer_is_done); // Chill until DMA transfer completes
64+
65+
digitalWrite(LED_BUILTIN, LOW);
66+
t = micros() - t; // Elapsed time
67+
68+
Serial.print("Done! ");
69+
Serial.print(t);
70+
Serial.println(" microseconds");
71+
72+
Serial.println("Destination buffer after transfer:");
73+
dump();
74+
75+
// Now repeat the same operation, but 'manually' using memcpy() (not DMA):
76+
t = micros();
77+
digitalWrite(LED_BUILTIN, HIGH);
78+
memcpy(destination_memory, source_memory, DATA_LENGTH);
79+
digitalWrite(LED_BUILTIN, LOW);
80+
t = micros() - t; // Elapsed time
81+
Serial.print("Same operation without DMA: ");
82+
Serial.print(t);
83+
Serial.println(" microseconds");
84+
}
85+
86+
// Show contents of destination_memory[] array
87+
void dump() {
88+
for(uint32_t i=0; i<DATA_LENGTH; i++) {
89+
Serial.print(destination_memory[i], HEX); Serial.print(' ');
90+
if ((i & 15) == 15) Serial.println();
91+
}
92+
}
93+
94+
void loop() { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// DMA-based SPI buffer write. This is transmit-only as written, i.e.
2+
// not equivalent to Arduino's SPI.transfer() which both sends and
3+
// receives a single byte or word. Also, this is single-buffered to
4+
// demonstrate a simple SPI write case. See zerodma_spi2.ino for an
5+
// example using double buffering (2 buffers alternating fill & transmit).
6+
7+
#include <SPI.h>
8+
#include <Adafruit_ZeroDMA.h>
9+
#include "utility/dma.h"
10+
11+
Adafruit_ZeroDMA myDMA;
12+
ZeroDMAstatus stat; // DMA status codes returned by some functions
13+
14+
// The memory we'll be issuing to SPI:
15+
#define DATA_LENGTH 2048
16+
uint8_t source_memory[DATA_LENGTH];
17+
18+
volatile bool transfer_is_done = false; // Done yet?
19+
20+
// Callback for end-of-DMA-transfer
21+
void dma_callback(Adafruit_ZeroDMA *dma) {
22+
transfer_is_done = true;
23+
}
24+
25+
void setup() {
26+
uint32_t t;
27+
pinMode(LED_BUILTIN, OUTPUT); // Onboard LED can be used for precise
28+
digitalWrite(LED_BUILTIN, LOW); // benchmarking with an oscilloscope
29+
Serial.begin(115200);
30+
while(!Serial); // Wait for Serial monitor before continuing
31+
32+
Serial.println("DMA test: SPI data out");
33+
34+
SPI.begin();
35+
36+
Serial.println("Configuring DMA trigger");
37+
#ifdef __SAMD51__
38+
// SERCOM2 is the 'native' SPI SERCOM on Metro M4
39+
myDMA.setTrigger(SERCOM2_DMAC_ID_TX);
40+
#else
41+
// SERCOM4 is the 'native' SPI SERCOM on most M0 boards
42+
myDMA.setTrigger(SERCOM4_DMAC_ID_TX);
43+
#endif
44+
myDMA.setAction(DMA_TRIGGER_ACTON_BEAT);
45+
46+
Serial.print("Allocating DMA channel...");
47+
stat = myDMA.allocate();
48+
myDMA.printStatus(stat);
49+
50+
Serial.println("Setting up transfer");
51+
myDMA.addDescriptor(
52+
source_memory, // move data from here
53+
#ifdef __SAMD51__
54+
(void *)(&SERCOM2->SPI.DATA.reg), // to here (M4)
55+
#else
56+
(void *)(&SERCOM4->SPI.DATA.reg), // to here (M0)
57+
#endif
58+
DATA_LENGTH, // this many...
59+
DMA_BEAT_SIZE_BYTE, // bytes/hword/words
60+
true, // increment source addr?
61+
false); // increment dest addr?
62+
63+
Serial.println("Adding callback");
64+
// register_callback() can optionally take a second argument
65+
// (callback type), default is DMA_CALLBACK_TRANSFER_DONE
66+
myDMA.setCallback(dma_callback);
67+
68+
// Fill the source buffer with incrementing bytes
69+
for(uint32_t i=0; i<DATA_LENGTH; i++) source_memory[i] = i;
70+
71+
// Start SPI transaction at 12 MHz
72+
SPI.beginTransaction(SPISettings(12000000, MSBFIRST, SPI_MODE0));
73+
74+
Serial.println("Starting transfer job");
75+
t = micros();
76+
digitalWrite(LED_BUILTIN, HIGH);
77+
78+
// Because we've configured a peripheral trigger (SPI), there's
79+
// no need to manually trigger transfer, it starts up on its own.
80+
stat = myDMA.startJob();
81+
82+
// Your code could do other things here while SPI write happens!
83+
84+
while(!transfer_is_done); // Chill until DMA transfer completes
85+
86+
digitalWrite(LED_BUILTIN, LOW);
87+
t = micros() - t; // Elapsed time
88+
89+
SPI.endTransaction();
90+
myDMA.printStatus(stat); // Results of start_transfer_job()
91+
92+
Serial.print("Done! ");
93+
Serial.print(t);
94+
Serial.println(" microseconds");
95+
}
96+
97+
void loop() { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Double-buffered DMA on auxiliary SPI peripheral on pins 11/12/13.
2+
// Continuously alternates between two data buffers...one is filled
3+
// with new data as the other is being transmitted.
4+
5+
#include <SPI.h>
6+
#include <Adafruit_ZeroDMA.h>
7+
#include "utility/dma.h"
8+
#include "wiring_private.h" // pinPeripheral() function
9+
10+
// Declare our own SPI peripheral 'mySPI' on pins 11/12/13:
11+
// (Do not call this SPI1; Arduino Zero and Metro M0 already
12+
// have an SPI1 (the EDBG interface) and it won't compile.)
13+
SPIClass mySPI(
14+
&sercom1, // -> Sercom peripheral
15+
34, // MISO pin (also digital pin 12)
16+
37, // SCK pin (also digital pin 13)
17+
35, // MOSI pin (also digital pin 11)
18+
SPI_PAD_0_SCK_1, // TX pad (MOSI, SCK pads)
19+
SERCOM_RX_PAD_3); // RX pad (MISO pad)
20+
21+
Adafruit_ZeroDMA myDMA;
22+
ZeroDMAstatus stat; // DMA status codes returned by some functions
23+
24+
// Data we'll issue to mySPI. There are TWO buffers; one being
25+
// filled with new data while the other's being transmitted in
26+
// the background.
27+
#define DATA_LENGTH 512
28+
uint8_t source_memory[2][DATA_LENGTH],
29+
buffer_being_filled = 0, // Index of 'filling' buffer
30+
buffer_value = 0; // Value of fill
31+
32+
volatile bool transfer_is_done = true; // Done yet?
33+
34+
// Callback for end-of-DMA-transfer
35+
void dma_callback(Adafruit_ZeroDMA *dma) {
36+
transfer_is_done = true;
37+
}
38+
39+
DmacDescriptor *desc; // DMA descriptor address (so we can change contents)
40+
41+
void setup() {
42+
Serial.begin(115200);
43+
while(!Serial); // Wait for Serial monitor before continuing
44+
45+
Serial.println("DMA test: SPI data out");
46+
47+
mySPI.begin();
48+
// Assign pins 11, 12, 13 to SERCOM functionality
49+
pinPeripheral(11, PIO_SERCOM);
50+
pinPeripheral(12, PIO_SERCOM);
51+
pinPeripheral(13, PIO_SERCOM);
52+
53+
// Configure DMA for SERCOM1 (our 'mySPI' port on 11/12/13)
54+
Serial.println("Configuring DMA trigger");
55+
myDMA.setTrigger(SERCOM1_DMAC_ID_TX);
56+
myDMA.setAction(DMA_TRIGGER_ACTON_BEAT);
57+
58+
Serial.print("Allocating DMA channel...");
59+
stat = myDMA.allocate();
60+
myDMA.printStatus(stat);
61+
62+
desc = myDMA.addDescriptor(
63+
source_memory[buffer_being_filled], // move data from here
64+
(void *)(&SERCOM1->SPI.DATA.reg), // to here
65+
DATA_LENGTH, // this many...
66+
DMA_BEAT_SIZE_BYTE, // bytes/hword/words
67+
true, // increment source addr?
68+
false); // increment dest addr?
69+
70+
Serial.println("Adding callback");
71+
// register_callback() can optionally take a second argument
72+
// (callback type), default is DMA_CALLBACK_TRANSFER_DONE
73+
myDMA.setCallback(dma_callback);
74+
}
75+
76+
void loop() {
77+
// Fill buffer with new data. The other buffer might
78+
// still be transmitting in the background via DMA.
79+
memset(source_memory[buffer_being_filled], buffer_value, DATA_LENGTH);
80+
81+
// Wait for prior transfer to complete before starting new one...
82+
Serial.print("Waiting on prior transfer...");
83+
while(!transfer_is_done) Serial.write('.');
84+
mySPI.endTransaction();
85+
Serial.println("Done!");
86+
87+
// Modify the DMA descriptor using the newly-filled buffer as source...
88+
myDMA.changeDescriptor(desc, // DMA descriptor address
89+
source_memory[buffer_being_filled]); // New src; dst & count don't change
90+
91+
// Begin new transfer...
92+
Serial.println("Starting new transfer job");
93+
mySPI.beginTransaction(SPISettings(12000000, MSBFIRST, SPI_MODE0));
94+
transfer_is_done = false; // Reset 'done' flag
95+
stat = myDMA.startJob(); // Go!
96+
myDMA.printStatus(stat);
97+
98+
// Switch buffer indices so the alternate buffer is filled/xfer'd
99+
// on the next pass.
100+
buffer_being_filled = 1 - buffer_being_filled;
101+
buffer_value++;
102+
}
103+

0 commit comments

Comments
 (0)