Here is a follow up. To make this work properly, I guess one would really need to write 32 bytes per request (rather than 4 bytes), ie dma.TCD->NBYTES_MLNO = 8 * NUM_CHANNELS; (= 8 bytes data per channel * 4 channels).
I've dropped in the code for the PDB from the onboard DAC object, and this works just fine. However, as soon as I make NBYTES_MLNO > 16 (and adjust CITER_ELINKNO/BITER_ELINKNO to sizeof(SPI_tx_buffer) / bytes_per_mlno)), the transmitted data will be corrupt. The channels (or some of them) still update, so things can't be entirely off the map, but it clearly doesn't work properly.
So this works fine:
Code:
uint16_t nbytes_mlno = ELEMENT_SIZE * 4; // = 16 bytes
dma.TCD->SADDR = SPI_tx_buffer.data();
dma.TCD->SOFF = ELEMENT_SIZE; // source offset per transaction
dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(DMA_TCD_ATTR_SIZE_32BIT) | DMA_TCD_ATTR_DSIZE(DMA_TCD_ATTR_SIZE_32BIT);
dma.TCD->NBYTES_MLNO = nbytes_mlno; // bytes/transaction
dma.TCD->SLAST = -sizeof(SPI_tx_buffer);
dma.TCD->DADDR = &SPI0_PUSHR;
dma.TCD->DOFF = 0; // destination offset
dma.TCD->CITER_ELINKNO = sizeof(SPI_tx_buffer) / nbytes_mlno; // bytes per major loop
dma.TCD->DLASTSGA = 0;
dma.TCD->BITER_ELINKNO = sizeof(SPI_tx_buffer) / nbytes_mlno;
dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
dma.triggerAtHardwareEvent(DMAMUX_SOURCE_PDB);
dma.enable();
SPI0_SR = 0xFF0F0000;
SPI0_RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS | SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS;
dma.attachInterrupt(isr);
But this doesn't (it'll transfer stuff, but the data is only partially intact):
Code:
uint16_t nbytes_mlno = ELEMENT_SIZE * 8; // = 32 bytes
dma.TCD->SADDR = SPI_tx_buffer.data();
dma.TCD->SOFF = ELEMENT_SIZE; // source offset per transaction
dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(DMA_TCD_ATTR_SIZE_32BIT) | DMA_TCD_ATTR_DSIZE(DMA_TCD_ATTR_SIZE_32BIT);
dma.TCD->NBYTES_MLNO = nbytes_mlno; // bytes/transaction
dma.TCD->SLAST = -sizeof(SPI_tx_buffer);
dma.TCD->DADDR = &SPI0_PUSHR;
dma.TCD->DOFF = 0; // destination offset
dma.TCD->CITER_ELINKNO = sizeof(SPI_tx_buffer) / nbytes_mlno; // bytes per major loop
dma.TCD->DLASTSGA = 0;
dma.TCD->BITER_ELINKNO = sizeof(SPI_tx_buffer) / nbytes_mlno;
dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
dma.triggerAtHardwareEvent(DMAMUX_SOURCE_PDB);
dma.enable();
SPI0_SR = 0xFF0F0000;
SPI0_RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS | SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS;
dma.attachInterrupt(isr);
Is it possible I'm hitting some limit here? (I've tried with both a T3.2 and T3.6, and they behave the same way). The gist of the matter seems to be that I can only transmit 16 bytes or so per minor loop to SPI0_PUSHR (bytes_per_mlno = 4 * 4) before the data will get corrupt. Is there any way I can print the DMA transfers?
Edit. I've tried whether it makes a difference when instead of dma.TCD->NBYTES_MLNO = bytes_per_mlno; I try with
Code:
dma.TCD->NBYTES_MLOFFYES = DMA_TCD_NBYTES_MLOFFYES_NBYTES(bytes_per_mlno) | DMA_TCD_NBYTES_SMLOE | DMA_TCD_NBYTES_MLOFFYES_MLOFF(mloff);
where (as above) bytes_per_mlno = 8 * NUM_CHANNELS; // 2 x 4 bytes per channel
When mloff = 0, it seems to work much the same way as above, ie with the minor loop off. For any other value of mloff things get stuck after 2-3 requests, however. Also, conceptually, I'm not entirely sure I get the interaction between SOFF and NBYTES_MLOFFYES_MLOFF. The former needs to be 0x4 in this case, but the latter? Do both get added to the source address after the minor loop is completed?
For fun, I've tried with a 8-channel DAC and this still works with NBYTES_MLNO = 4 (or 8 or 16), but speeding up the PDB instead (PDB0_MOD = PDB_PERIOD / NUM_CHANNELS):
Code:
#include <DmaChannel.h>
#include <spififo.h>
#include <array>
const uint16_t NUM_CHANNELS = 0x8;
const uint16_t ELEMENT_SIZE = 0x4; // = 32 bit
const size_t AUDIO_BLOCK_SAMPLES = 256;
DMAMEM static auto SPI_tx_buffer = [](){
std::array<uint32_t, AUDIO_BLOCK_SAMPLES * NUM_CHANNELS * 2> res;
for(auto& elem : res) elem = SPI_PUSHR_CONT | SPI_PUSHR_CTAS(1);
return res;
}();
DMAChannel dma;
#define SPICLOCK_30MHz (SPI_CTAR_PBR(0) | SPI_CTAR_BR(0) | SPI_CTAR_DBR)
#define INTREF_ENABLE 0x8000001;
#define DAC_CHANNEL_OFFSET 20
#define DAC_CS 15
#define LDAC 14
// DAC control bits:
const uint32_t DAC_CH_A = ((uint32_t) 0x0 << DAC_CHANNEL_OFFSET);
const uint32_t DAC_CH_B = ((uint32_t) 0x1 << DAC_CHANNEL_OFFSET);
const uint32_t DAC_CH_C = ((uint32_t) 0x2 << DAC_CHANNEL_OFFSET);
const uint32_t DAC_CH_D = ((uint32_t) 0x3 << DAC_CHANNEL_OFFSET);
const uint32_t DAC_CH_E = ((uint32_t) 0x4 << DAC_CHANNEL_OFFSET);
const uint32_t DAC_CH_F = ((uint32_t) 0x5 << DAC_CHANNEL_OFFSET);
const uint32_t DAC_CH_G = ((uint32_t) 0x6 << DAC_CHANNEL_OFFSET);
const uint32_t DAC_CH_H = ((uint32_t) 0x7 << DAC_CHANNEL_OFFSET);
const uint32_t pcsbits = 0x10 << 16; // DAC_CS = 15; see SPIFIFO.h
void spiSetup() {
pinMode(LDAC, OUTPUT);
digitalWrite(LDAC, LOW);
CORE_PIN7_CONFIG = PORT_PCR_DSE | PORT_PCR_MUX(2);
CORE_PIN13_CONFIG = PORT_PCR_DSE | PORT_PCR_MUX(2);
SPIFIFO.begin(DAC_CS, SPICLOCK_30MHz, SPI_MODE0);
// enable internal reference:
uint32_t _data = INTREF_ENABLE;
SPIFIFO.write16(_data >> 16, SPI_CONTINUE);
SPIFIFO.write16(_data);
SPIFIFO.read();
SPIFIFO.read();
}
uint32_t data = 0xFFFF;
void isr() {
uint32_t saddr;
int32_t *dest;
saddr = (uint32_t)(dma.TCD->SADDR);
dma.clearInterrupt();
if (saddr < (uint32_t)SPI_tx_buffer.data() + sizeof(SPI_tx_buffer) / 2) {
// DMA is transmitting the first half of the buffer
// so we must fill the second half
dest = (int32_t *)&SPI_tx_buffer[AUDIO_BLOCK_SAMPLES * NUM_CHANNELS];
data = 0xFFFF;
} else {
dest = (int32_t *)SPI_tx_buffer.data();
data = 0x0000;
}
for (uint16_t i = 0; i < AUDIO_BLOCK_SAMPLES / 2; i += NUM_CHANNELS * 2) {
// interleave channel A data with SPI_PUSHR upper two bytes (CS):
uint32_t _data = DAC_CH_A | (data << 4); // = DAC command (32 bit)
*dest++ = SPI_PUSHR_CONT | pcsbits | SPI_PUSHR_CTAS(1) | _data >> 16; // first 4 bytes
*dest++ = pcsbits | SPI_PUSHR_CTAS(1) | (_data & 0xFFFF); // second 4 bytes
// channel B data
_data = DAC_CH_B | (data << 4);
*dest++ = SPI_PUSHR_CONT | pcsbits | SPI_PUSHR_CTAS(1) | _data >> 16;
*dest++ = pcsbits | SPI_PUSHR_CTAS(1) | (_data & 0xFFFF);
// channel C data
_data = DAC_CH_C | (data << 4);
*dest++ = SPI_PUSHR_CONT | pcsbits | SPI_PUSHR_CTAS(1) | _data >> 16;
*dest++ = pcsbits | SPI_PUSHR_CTAS(1) | (_data & 0xFFFF);
// channel D data
_data = DAC_CH_D | (data << 4);
*dest++ = SPI_PUSHR_CONT | pcsbits | SPI_PUSHR_CTAS(1) | _data >> 16;
*dest++ = pcsbits | SPI_PUSHR_CTAS(1) | (_data & 0xFFFF);
// channel E data
_data = DAC_CH_E | (data << 4);
*dest++ = SPI_PUSHR_CONT | pcsbits | SPI_PUSHR_CTAS(1) | _data >> 16;
*dest++ = pcsbits | SPI_PUSHR_CTAS(1) | (_data & 0xFFFF);
// channel F data
_data = DAC_CH_F | (data << 4);
*dest++ = SPI_PUSHR_CONT | pcsbits | SPI_PUSHR_CTAS(1) | _data >> 16;
*dest++ = pcsbits | SPI_PUSHR_CTAS(1) | (_data & 0xFFFF);
// channel G data
_data = DAC_CH_G | (data << 4);
*dest++ = SPI_PUSHR_CONT | pcsbits | SPI_PUSHR_CTAS(1) | _data >> 16;
*dest++ = pcsbits | SPI_PUSHR_CTAS(1) | (_data & 0xFFFF);
// channel H data
_data = DAC_CH_H | (data << 4);
*dest++ = SPI_PUSHR_CONT | pcsbits | SPI_PUSHR_CTAS(1) | _data >> 16;
*dest++ = pcsbits | SPI_PUSHR_CTAS(1) | (_data & 0xFFFF);
}
}
#define PDB_CONFIG (PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_CONT | PDB_SC_PDBIE | PDB_SC_DMAEN)
#if F_BUS == 120000000
#define PDB_PERIOD (2720-1)
#elif F_BUS == 108000000
#define PDB_PERIOD (2448-1)
#elif F_BUS == 96000000
#define PDB_PERIOD (2176-1)
#elif F_BUS == 90000000
#define PDB_PERIOD (2040-1)
#elif F_BUS == 80000000
#define PDB_PERIOD (1813-1) // small ?? error
#elif F_BUS == 72000000
#define PDB_PERIOD (1632-1)
#elif F_BUS == 64000000
#define PDB_PERIOD (1451-1) // small ?? error
#elif F_BUS == 60000000
#define PDB_PERIOD (1360-1)
#elif F_BUS == 56000000
#define PDB_PERIOD (1269-1) // 0.026% error
#elif F_BUS == 54000000
#define PDB_PERIOD (1224-1)
#elif F_BUS == 48000000
#define PDB_PERIOD (1088-1)
#elif F_BUS == 40000000
#define PDB_PERIOD (907-1) // small ?? error
#elif F_BUS == 36000000
#define PDB_PERIOD (816-1)
#elif F_BUS == 24000000
#define PDB_PERIOD (544-1)
#elif F_BUS == 16000000
#define PDB_PERIOD (363-1) // 0.092% error
#else
#error "Unsupported F_BUS speed"
#endif
void setup() {
Serial.begin(115200);
delay(2000);
Serial.printf("SPI_tx_buffer: %x %u\n", (uint32_t) SPI_tx_buffer.data(), SPI_tx_buffer.size());
spiSetup();
if (!(SIM_SCGC6 & SIM_SCGC6_PDB)
|| (PDB0_SC & PDB_CONFIG) != PDB_CONFIG
|| PDB0_MOD != PDB_PERIOD
|| PDB0_IDLY != 1
|| PDB0_CH0C1 != 0x0101) {
SIM_SCGC6 |= SIM_SCGC6_PDB;
PDB0_IDLY = 1;
// hack ahead...
PDB0_MOD = PDB_PERIOD / NUM_CHANNELS;
PDB0_SC = PDB_CONFIG | PDB_SC_LDOK;
PDB0_SC = PDB_CONFIG | PDB_SC_SWTRIG;
PDB0_CH0C1 = 0x0101;
}
dma.TCD->SADDR = SPI_tx_buffer.data();
dma.TCD->SOFF = ELEMENT_SIZE; // source offset per transaction
dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(DMA_TCD_ATTR_SIZE_32BIT) | DMA_TCD_ATTR_DSIZE(DMA_TCD_ATTR_SIZE_32BIT);
dma.TCD->NBYTES_MLNO = ELEMENT_SIZE; // bytes/transaction = 4 bytes (~ SPI0_PUSHR)
dma.TCD->SLAST = -sizeof(SPI_tx_buffer);
dma.TCD->DADDR = &SPI0_PUSHR;
dma.TCD->DOFF = 0; // destination offset
dma.TCD->CITER_ELINKNO = sizeof(SPI_tx_buffer) / ELEMENT_SIZE; // major loop
dma.TCD->DLASTSGA = 0;
dma.TCD->BITER_ELINKNO = sizeof(SPI_tx_buffer) / ELEMENT_SIZE;
dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
//dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SPI0_TX);
dma.triggerAtHardwareEvent(DMAMUX_SOURCE_PDB);
dma.enable();
SPI0_SR = 0xFF0F0000;
SPI0_RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS | SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS;
dma.attachInterrupt(isr);
}
uint32_t dma_addr = 0;
void loop() {
uint32_t new_dma_addr = (uint32_t) dma.sourceAddress();
if(dma_addr != new_dma_addr) {
Serial.printf("DMA src: %x dest: %x\n", new_dma_addr, (uint32_t) dma.destinationAddress());
dma_addr = new_dma_addr;
delay(100);
}
}