-
Notifications
You must be signed in to change notification settings - Fork 8
/
SPIdma.ino
192 lines (163 loc) · 5.49 KB
/
SPIdma.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// DMA memory to memory ZERO
// ch 18 beat burst block
// xdk sam0/drivers/dma/dma.c
// packages/arduino/tools/CMSIS/4.0.0-atmel/Device/ATMEL/samd21/include/component/dmac.h
// http://asf.atmel.com/docs/3.16.0/samd21/html/asfdoc_sam0_sercom_spi_dma_use_case.html
// assume normal SPI setup, then we take over with DMA
#include <SPI.h>
#define SPISPEED 4000000
const int SPISS = 10;
#define PRREG(x) Serial.print(#x" 0x"); Serial.println(x,HEX)
#define BYTES 1024
char txbuf[BYTES], rxbuf[BYTES];
void prmbs(char *lbl,unsigned long us,int bits) {
float mbs = (float)bits/us;
Serial.print(mbs,2); Serial.print(" mbs ");
Serial.print(us); Serial.print(" us ");
Serial.println(lbl);
}
// DMA 12 channels
typedef struct {
uint16_t btctrl;
uint16_t btcnt;
uint32_t srcaddr;
uint32_t dstaddr;
uint32_t descaddr;
} dmacdescriptor ;
volatile dmacdescriptor wrb[12] __attribute__ ((aligned (16)));
dmacdescriptor descriptor_section[12] __attribute__ ((aligned (16)));
dmacdescriptor descriptor __attribute__ ((aligned (16)));
static uint32_t chnltx = 0, chnlrx = 1; // DMA channels
enum XfrType { DoTX, DoRX, DoTXRX};
static XfrType xtype;
static uint8_t rxsink[1], txsrc[1] = {0xff};
volatile uint32_t dmadone;
void DMAC_Handler() {
// interrupts DMAC_CHINTENCLR_TERR DMAC_CHINTENCLR_TCMPL DMAC_CHINTENCLR_SUSP
uint8_t active_channel;
// disable irqs ?
__disable_irq();
active_channel = DMAC->INTPEND.reg & DMAC_INTPEND_ID_Msk; // get channel number
DMAC->CHID.reg = DMAC_CHID_ID(active_channel);
dmadone = DMAC->CHINTFLAG.reg;
DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_TCMPL; // clear
DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_TERR;
DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_SUSP;
__enable_irq();
}
void dma_init() {
// probably on by default
PM->AHBMASK.reg |= PM_AHBMASK_DMAC ;
PM->APBBMASK.reg |= PM_APBBMASK_DMAC ;
NVIC_EnableIRQ( DMAC_IRQn ) ;
DMAC->BASEADDR.reg = (uint32_t)descriptor_section;
DMAC->WRBADDR.reg = (uint32_t)wrb;
DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);
}
Sercom *sercom = (Sercom *)SERCOM4; // SPI SERCOM
void spi_xfr(void *txdata, void *rxdata, size_t n) {
uint32_t temp_CHCTRLB_reg;
// set up transmit channel
DMAC->CHID.reg = DMAC_CHID_ID(chnltx);
DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
DMAC->CHCTRLA.reg = DMAC_CHCTRLA_SWRST;
DMAC->SWTRIGCTRL.reg &= (uint32_t)(~(1 << chnltx));
temp_CHCTRLB_reg = DMAC_CHCTRLB_LVL(0) |
DMAC_CHCTRLB_TRIGSRC(SERCOM4_DMAC_ID_TX) | DMAC_CHCTRLB_TRIGACT_BEAT;
DMAC->CHCTRLB.reg = temp_CHCTRLB_reg;
DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK ; // enable all 3 interrupts
descriptor.descaddr = 0;
descriptor.dstaddr = (uint32_t) &sercom->SPI.DATA.reg;
descriptor.btcnt = n;
descriptor.srcaddr = (uint32_t)txdata;
descriptor.btctrl = DMAC_BTCTRL_VALID;
if (xtype != DoRX) {
descriptor.srcaddr += n;
descriptor.btctrl |= DMAC_BTCTRL_SRCINC;
}
memcpy(&descriptor_section[chnltx],&descriptor, sizeof(dmacdescriptor));
// rx channel enable interrupts
DMAC->CHID.reg = DMAC_CHID_ID(chnlrx);
DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
DMAC->CHCTRLA.reg = DMAC_CHCTRLA_SWRST;
DMAC->SWTRIGCTRL.reg &= (uint32_t)(~(1 << chnlrx));
temp_CHCTRLB_reg = DMAC_CHCTRLB_LVL(0) |
DMAC_CHCTRLB_TRIGSRC(SERCOM4_DMAC_ID_RX) | DMAC_CHCTRLB_TRIGACT_BEAT;
DMAC->CHCTRLB.reg = temp_CHCTRLB_reg;
DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK ; // enable all 3 interrupts
dmadone = 0;
descriptor.descaddr = 0;
descriptor.srcaddr = (uint32_t) &sercom->SPI.DATA.reg;
descriptor.btcnt = n;
descriptor.dstaddr = (uint32_t)rxdata;
descriptor.btctrl = DMAC_BTCTRL_VALID;
if (xtype != DoTX) {
descriptor.dstaddr += n;
descriptor.btctrl |= DMAC_BTCTRL_DSTINC;
}
memcpy(&descriptor_section[chnlrx],&descriptor, sizeof(dmacdescriptor));
// start both channels ? order matter ?
DMAC->CHID.reg = DMAC_CHID_ID(chnltx);
DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;
DMAC->CHID.reg = DMAC_CHID_ID(chnlrx);
DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;
while(!dmadone); // await DMA done isr
DMAC->CHID.reg = DMAC_CHID_ID(chnltx); //disable DMA to allow lib SPI
DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
DMAC->CHID.reg = DMAC_CHID_ID(chnlrx);
DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
}
void spi_write(void *data, size_t n) {
xtype = DoTX;
spi_xfr(data,rxsink,n);
}
void spi_read(void *data, size_t n) {
xtype = DoRX;
spi_xfr(txsrc,data,n);
}
void spi_transfer(void *txdata, void *rxdata, size_t n) {
xtype = DoTXRX;
spi_xfr(txdata,rxdata,n);
}
void setup() {
Serial.begin(9600); delay(1000);
pinMode (SPISS, OUTPUT);
digitalWrite(SPISS,HIGH);
SPI.begin();
SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE0));
dma_init();
}
void loop() {
int i, errs=0;
unsigned long t1;
Serial.print("SPI clk "); Serial.println(SPISPEED);
digitalWrite(SPISS,LOW);
for (i=0;i<BYTES;i++) txbuf[i] = i;
digitalWrite(SPISS,HIGH);
delayMicroseconds(15);
t1 = micros();
digitalWrite(SPISS,LOW);
spi_write(txbuf,BYTES);
digitalWrite(SPISS,HIGH);
t1 = micros() -t1;
prmbs("DMA write",t1,BYTES*8);
t1 = micros();
digitalWrite(SPISS,LOW);
spi_read(rxbuf,BYTES);
digitalWrite(SPISS,HIGH);
t1 = micros() -t1;
prmbs("DMA read",t1,BYTES*8);
t1 = micros();
digitalWrite(SPISS,LOW);
spi_transfer(txbuf,rxbuf,BYTES);
digitalWrite(SPISS,HIGH);
t1 = micros() -t1;
prmbs("DMA read/write",t1,BYTES*8);
t1 = micros();
digitalWrite(SPISS,LOW);
for(i=0;i<BYTES;i++) SPI.transfer(txbuf[i]);
digitalWrite(SPISS,HIGH);
t1 = micros() -t1;
prmbs("SPI transfer",t1,BYTES*8);
delay(3000);
}