DMAChannel dacdma;
#define PSPI0_CSDAC0 10 // teensy pin 10 = cs0_0
#define PSPI0_CSDAC1 9 // teensy pin 9 = cs0_1
#define DACCS0 (1<<0) // which CS0_x bit the two dacs are on. this is bit 0
#define DACCS1 (1<<1) // this is bit 1
#define DACDMA(data,cs,chan) SPI_PUSHR_CTAS(1) | (1ul<<31) | (cs<<16) | (0x0300) | ((chan)<<4) | ((data)>>12), \
SPI_PUSHR_CTAS(1) | (cs<<16) | (((data)<<4)&0xfff0)
u32 dacdmabuffer[8*4]={
DACDMA(32768,DACCS0,0), DACDMA(32768,DACCS1,0),
DACDMA(32768,DACCS0,1), DACDMA(32768,DACCS1,1),
DACDMA(32768,DACCS0,2), DACDMA(32768,DACCS1,2),
DACDMA(32768,DACCS0,3), DACDMA(32768,DACCS1,3),
DACDMA(32768,DACCS0,4), DACDMA(32768,DACCS1,4),
DACDMA(32768,DACCS0,5), DACDMA(32768,DACCS1,5),
DACDMA(32768,DACCS0,6), DACDMA(32768,DACCS1,6),
DACDMA(32768,DACCS0,7), DACDMA(32768,DACCS1,7),
};
inline void ClearSPIFinishedFlag() {
SPI0_SR = SPI_SR_TCF; // clear spi complete flag
}
inline void WaitForSPIFinishedFlag() {
while (!(SPI0_SR & SPI_SR_TCF)) yield(); // wait
}
void InitDAC() {
// set up DACs
// NB this setCS() hands the pins to the SPI unit on teensy
// otherwise the top bits of PUSHR have no effect. crucial! took me a while to find
u32 cs0=SPI.setCS(PSPI0_CSDAC0); // PSPI0_CSDACx is a teensy pin number from my design, must be a CS0_x pin
u32 cs1=SPI.setCS(PSPI0_CSDAC1);
assert(cs0==DACCS0); // did you set your defines right
assert(cs1==DACCS1); // did you set your defines right
u32 cs01=cs0|cs1; // you can even talk to both dacs at the same time...
SPI.begin();
SPISettings dacsettings(12*1000*1000,MSBFIRST, SPI_MODE0);
SPI.beginTransaction(dacsettings);
const static u32 ref_enable=(1<<27)+1;
ClearSPIFinishedFlag();
// uses SPI0 fifo to write two times in one go
SPI0_PUSHR = SPI_PUSHR_CTAS(1) | (1<<31) | (cs01<<16) | (ref_enable>>16);
SPI0_PUSHR = SPI_PUSHR_CTAS(1) | (cs01<<16) | (ref_enable&0xffff);
WaitForSPIFinishedFlag();
}
void InitDMA() {
dacdma.disable();
dacdma.destination((volatile uint32_t&)SPI0_PUSHR);
dacdma.disableOnCompletion();
dacdma.triggerAtHardwareEvent(DMAMUX_SOURCE_SPI0_TX);
dacdma.sourceBuffer(dacdmabuffer, sizeof(dacdmabuffer));
// ask for DMA pokes from SPI unit
SPI0_SR = 0xFF0F0000; // TODO what are these bits
SPI0_RSER = /*SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS |*/ SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS;
}
void KickDMABuffer(const u16 anaout[16]) { // a single sample across 16 channels
const u16 *src=anaout;
for (int chan=0;chan<8;++chan) {
u16 data0=*src++;
u16 data1=*src++;
// note that I alternate a register-width (2 writes) for dac0, then dac1, then dac0, etc
// ie alternate chips. also note that the dma buffer high u16 of each PUSHR is already setup
// so this code only touches the bottom u16 which has the data in it
// that gives each chip a chance to 'breathe' between each 32 bit write
// alternatively you can set up CS delays I believe via SPI registers
((u16*)dacdmabuffer)[chan*8+0] = (0x0300) | ((chan)<<4) | ((data0)>>12);
((u16*)dacdmabuffer)[chan*8+2] = ((data0)<<4);
((u16*)dacdmabuffer)[chan*8+4] = (0x0300) | ((chan)<<4) | ((data1)>>12);
((u16*)dacdmabuffer)[chan*8+6] = ((data1)<<4);
}
dacdma.enable();
}
main type thing {
InitDAC();
delay(1); // for good measure
InitDMA();
//... set up an IntervalTimer to do some DSP and call KickDMABuffer() for each sample. I ran at 32khz
}