greiman / SdFat

Arduino FAT16/FAT32 exFAT Library
MIT License
1.05k stars 497 forks source link

sd.begin(SPI_CONFIG) breaks my DMA ADC datalogger on the SAMD51 #483

Open cdd0042 opened 1 month ago

cdd0042 commented 1 month ago

``I have a data logger that takes in inputs from 2 ADCs and writes them to 2 circular buffers using DMA. This part works well. I am able to read data in and get it to the buffers at about 2.4 Mb/s.

The issue I was running into was how to store it. I eventually figured out how to use SdFat with the SPI configuration that utilizes adafruit's zeroDMA library to handle the SD card writes. I was able to write buffers to the SD card at about 2.7 Mb/s in my stand alone test code. This should be fast enough to keep up with the DMA data logger.

The issues arose when I tried to combine the two. Have data incoming over DMA get written to the SD card. The DMA initialization and setup all works perfectly until sd.begin(SD_CONFIG) is called. I am not sure what is going wrong or what is conflicting in the SdFat library with my DMA logger.

Any help would be great.

// WIP: Use SAMD51's DMA Sequencing to read A0, A1, A3, and A4 on ADC0 and read A2 on ADC1 without CPU intervention and write them to an SD Card. Optional AC trigger and DAC sine wave generator

#include "FreeStack.h"
#include "SdFat.h"
#include "sdios.h"

#define error(s) sd.errorHalt(&Serial, F(s))
const uint8_t SD_CS_PIN = 5;
#define SPI_CLOCK SD_SCK_MHZ(50)
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)

SdFat sd;
File file;

const size_t BUF_SIZE = 512;

volatile boolean results0Part0Ready = false;
volatile boolean results0Part1Ready = false;
volatile boolean results1Part0Ready = false;
volatile boolean results1Part1Ready = false;
uint16_t adcResults0[BUF_SIZE*2];                                                       // ADC results array 0
uint16_t adcResults1[BUF_SIZE*2];                                                       // ADC results array 1

uint16_t r0p0[BUF_SIZE];
uint16_t r0p1[BUF_SIZE];
uint16_t r1p0[BUF_SIZE];
uint16_t r1p1[BUF_SIZE];

// ADC0 INPUTCTRL register MUXPOS settings 
uint32_t inputCtrl0[] = { ADC_INPUTCTRL_MUXPOS_AIN0,                              // AIN0 = A0
                          ADC_INPUTCTRL_MUXPOS_AIN5,                              // AIN5 = A1
                          ADC_INPUTCTRL_MUXPOS_AIN3,                              // AIN3 = A3
                          ADC_INPUTCTRL_MUXPOS_AIN4 };                            // AIN4 = A4

//uint16_t sintable[256];                                                         // Sine table

typedef struct                                                                    // DMAC descriptor structure
{
  uint16_t btctrl;
  uint16_t btcnt;
  uint32_t srcaddr;
  uint32_t dstaddr;
  uint32_t descaddr;
} dmacdescriptor ;

volatile dmacdescriptor wrb[DMAC_CH_NUM] __attribute__ ((aligned (16)));          // Write-back DMAC descriptors
dmacdescriptor descriptor_section[DMAC_CH_NUM] __attribute__ ((aligned (16)));    // DMAC channel descriptors
dmacdescriptor descriptor __attribute__ ((aligned (16)));                         // Place holder descriptor

dmacdescriptor linked_descriptor[2] __attribute__ ((aligned (16)));               // Linked descriptors

void setup() {
  Serial.begin(115200);                                                           // Start the native USB port
  while(!Serial);                                                                 // Wait for the console to open

  pinMode(10, OUTPUT);                                                            // Initialise the output on D10 for debug purposes
  pinMode(11, OUTPUT);                                                            // Initialise the output on D11 for debug purposes

  DMAC->BASEADDR.reg = (uint32_t)descriptor_section;                              // Specify the location of the descriptors
  DMAC->WRBADDR.reg = (uint32_t)wrb;                                              // Specify the location of the write back descriptors
  DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);                    // Enable the DMAC peripheral

  // ADC0
  DMAC->Channel[5].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL3;                      // Set DMAC channel 2 to priority level 3 (highest)
  DMAC->Channel[5].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(ADC0_DMAC_ID_SEQ) |         // Set DMAC to trigger on ADC0 DMAC sequence
                                 DMAC_CHCTRLA_TRIGACT_BURST;                      // DMAC burst transfer
  descriptor.descaddr = (uint32_t)&descriptor_section[5];                         // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)inputCtrl0 + sizeof(uint32_t) * 4;               // Configure the DMAC to set the
  descriptor.dstaddr = (uint32_t)&ADC0->DSEQDATA.reg;                             // Write the INPUT CTRL 
  descriptor.btcnt = 4;                                                           // Beat count is 2
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_WORD |                                 // Beat size is WORD (32-bits)
                      DMAC_BTCTRL_SRCINC |                                        // Increment the source address
                      DMAC_BTCTRL_VALID;                                          // Descriptor is valid
  memcpy(&descriptor_section[5], &descriptor, sizeof(descriptor));                // Copy the descriptor to the descriptor section

  DMAC->Channel[3].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL3;                      // Set DMAC channel 3 to priority level 3 (highest)
  DMAC->Channel[3].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(ADC0_DMAC_ID_RESRDY) |      // Set DMAC to trigger when ADC0 result is ready
                                 DMAC_CHCTRLA_TRIGACT_BURST;                      // DMAC burst transfer
  descriptor.descaddr = (uint32_t)&linked_descriptor[0];                          // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&ADC0->RESULT.reg;                               // Take the result from the ADC0 RESULT register
  descriptor.dstaddr = (uint32_t)adcResults0 + sizeof(uint16_t) * BUF_SIZE;           // Place it in the adcResults0 array
  descriptor.btcnt = BUF_SIZE;                                                        // Beat count
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                                // Beat size is HWORD (16-bits)
                      DMAC_BTCTRL_DSTINC |                                        // Increment the destination address
                      DMAC_BTCTRL_VALID |                                         // Descriptor is valid
                      DMAC_BTCTRL_BLOCKACT_SUSPEND;                               // Suspend DMAC channel 3 after block transfer
  memcpy(&descriptor_section[3], &descriptor, sizeof(descriptor));                // Copy the descriptor to the descriptor section
  descriptor.descaddr = (uint32_t)&descriptor_section[3];                         // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&ADC0->RESULT.reg;                               // Take the result from the ADC0 RESULT register
  descriptor.dstaddr = (uint32_t)&adcResults0[BUF_SIZE] + sizeof(uint16_t) * BUF_SIZE;    // Place it in the adcResults1 array
  descriptor.btcnt = BUF_SIZE;                                                        // Beat count
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                                // Beat size is HWORD (16-bits)
                      DMAC_BTCTRL_DSTINC |                                        // Increment the destination address
                      DMAC_BTCTRL_VALID |                                         // Descriptor is valid
                      DMAC_BTCTRL_BLOCKACT_SUSPEND;                               // Suspend DMAC channel 3 after block transfer
  memcpy(&linked_descriptor[0], &descriptor, sizeof(descriptor));                 // Copy the descriptor to the descriptor section

  NVIC_SetPriority(DMAC_3_IRQn, 0);                                               // Set the Nested Vector Interrupt Controller (NVIC) priority for DMAC Channel 3
  NVIC_EnableIRQ(DMAC_3_IRQn);                                                    // Connect DMAC Channel 0 to Nested Vector Interrupt Controller (NVIC)

  DMAC->Channel[3].CHINTENSET.reg = DMAC_CHINTENSET_SUSP;                         // Activate the suspend (SUSP) interrupt on DMAC channel 3

  // ADC1
  DMAC->Channel[4].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL3;                      // Set DMAC channel 4 to priority level 3 (highest)
  DMAC->Channel[4].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(ADC1_DMAC_ID_RESRDY) |      // Set DMAC to trigger when ADC0 result is ready
                                 DMAC_CHCTRLA_TRIGACT_BURST;                      // DMAC burst transfer
  descriptor.descaddr = (uint32_t)&linked_descriptor[1];                          // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&ADC1->RESULT.reg;                               // Take the result from the ADC0 RESULT register
  descriptor.dstaddr = (uint32_t)adcResults1 + sizeof(uint16_t) * BUF_SIZE;           // Place it in the adcResults0 array
  descriptor.btcnt = BUF_SIZE;                                                        // Beat count
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                                // Beat size is HWORD (16-bits)
                      DMAC_BTCTRL_DSTINC |                                        // Increment the destination address
                      DMAC_BTCTRL_VALID |                                         // Descriptor is valid
                      DMAC_BTCTRL_BLOCKACT_SUSPEND;                               // Suspend DMAC channel 4 after block transfer
  memcpy(&descriptor_section[4], &descriptor, sizeof(descriptor));                // Copy the descriptor to the descriptor section
  descriptor.descaddr = (uint32_t)&descriptor_section[4];                         // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&ADC1->RESULT.reg;                               // Take the result from the ADC0 RESULT register
  descriptor.dstaddr = (uint32_t)&adcResults1[BUF_SIZE] + sizeof(uint16_t) * BUF_SIZE;    // Place it in the adcResults1 array
  descriptor.btcnt = BUF_SIZE;                                                        // Beat count
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                                // Beat size is HWORD (16-bits)
                      DMAC_BTCTRL_DSTINC |                                        // Increment the destination address
                      DMAC_BTCTRL_VALID |                                         // Descriptor is valid
                      DMAC_BTCTRL_BLOCKACT_SUSPEND;                               // Suspend DMAC channel 4 after block transfer
  memcpy(&linked_descriptor[1], &descriptor, sizeof(descriptor));                 // Copy the descriptor to the descriptor section

  NVIC_SetPriority(DMAC_4_IRQn, 0);                                               // Set the Nested Vector Interrupt Controller (NVIC) priority for DMAC Channel 4
  NVIC_EnableIRQ(DMAC_4_IRQn);                                                    // Connect DMAC Channel 0 to Nested Vector Interrupt Controller (NVIC)

  DMAC->Channel[4].CHINTENSET.reg = DMAC_CHINTENSET_SUSP;                         // Activate the suspend (SUSP) interrupt on DMAC channel 0

  // ADC Register Settings
  ADC1->INPUTCTRL.bit.MUXPOS = ADC_INPUTCTRL_MUXPOS_AIN0_Val;                     // Set the analog input to A2
  while(ADC1->SYNCBUSY.bit.INPUTCTRL);                                            // Wait for synchronization
  ADC1->SAMPCTRL.bit.SAMPLEN = 0x00;                                              // Extend sampling time by SAMPCTRL ADC cycles (12 + 1 + 2)/750kHz = 20us = 50kHz 
  while(ADC1->SYNCBUSY.bit.SAMPCTRL);                                             // Wait for synchronization
  ADC1->DSEQCTRL.reg = ADC_DSEQCTRL_AUTOSTART |                                   // Auto start a DMAC conversion upon ADC0 DMAC sequence completion
                       ADC_DSEQCTRL_INPUTCTRL;                                    // Change the ADC0 INPUTCTRL register on DMAC sequence
  ADC1->CTRLB.reg = ADC_CTRLB_RESSEL_8BIT;// |                                    // Set ADC resolution to 8 bits 
                   // ADC_CTRLB_FREERUN;                                          // Set ADC to free run mode  
  while(ADC1->SYNCBUSY.bit.CTRLB);                                                // Wait for synchronization
  ADC1->CTRLA.bit.SLAVEEN = 1;                                                    // Set ADC1 to slave, ADC0 to master, both share CTRLA register

  ADC0->INPUTCTRL.bit.MUXPOS = 0x0;                                               // Set the analog input to A0
  while(ADC0->SYNCBUSY.bit.INPUTCTRL);                                            // Wait for synchronization
  ADC0->SAMPCTRL.bit.SAMPLEN = 0x00;                                              // Extend sampling time by SAMPCTRL ADC cycles (12 + 1 + 2)/750kHz = 20us = 50kHz
  while(ADC0->SYNCBUSY.bit.SAMPCTRL);                                             // Wait for synchronization  
  ADC0->DSEQCTRL.reg = ADC_DSEQCTRL_AUTOSTART |                                   // Auto start a DMAC conversion upon ADC0 DMAC sequence completion
                       ADC_DSEQCTRL_INPUTCTRL;                                    // Change the ADC0 INPUTCTRL register on DMAC sequence
  ADC0->CTRLB.reg = ADC_CTRLB_RESSEL_8BIT;// |                                    // Set ADC resolution to 8 bits 
                    //ADC_CTRLB_FREERUN;                                          // Set ADC to free run mode        
  while(ADC0->SYNCBUSY.bit.CTRLB);                                                // Wait for synchronization
  ADC0->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV4;                                     // Divide Clock ADC GCLK by 64 (48MHz/64 = 750kHz) (12 + 1)/750kHz = 17.3us sample time 
  ADC0->CTRLA.bit.ENABLE = 1;                                                     // Enable the ADC
  while(ADC0->SYNCBUSY.bit.ENABLE);                                               // Wait for synchronization
  ADC0->SWTRIG.bit.START = 1;                                                     // Initiate a software trigger to start an ADC conversion
  while(ADC0->SYNCBUSY.bit.SWTRIG);                                               // Wait for synchronization

  DMAC->Channel[5].CHCTRLA.bit.ENABLE = 1;                                        // Enable DMAC channel 2
  DMAC->Channel[3].CHCTRLA.bit.ENABLE = 1;                                        // Enable DMAC channel 3
  DMAC->Channel[4].CHCTRLA.bit.ENABLE = 1;                                        // Enable DMAC channel 4
  delay(1);                                                                       // Wait a millisecond

  Serial.println(F("Type any character to start"));
  while (!Serial.available()){
    yield();
  }
  sd.begin(SD_CONFIG);
  file.open("data.dat", O_RDWR | O_CREAT | O_TRUNC | O_APPEND);

  for(int i = 0; i < 10; i++){
    Serial.print(F("Iteration: "));
    Serial.println(i+1);
    //----------------------------------------
    //----------------------------------------
      if (results0Part0Ready)                                                         // Display the results in results0 array
      {
        Serial.println(F("Results0 Part0: "));
        for (uint32_t i = 0; i < BUF_SIZE; i++) {
          r0p0[i] = adcResults0[i];

          Serial.print(i);
          Serial.print(F(": "));
          Serial.println(adcResults0[i]);
        }

        if (file.write(r0p0, BUF_SIZE) != BUF_SIZE) {
            error("write failed");
        }
        file.sync();
        Serial.println(F("Successfully Written"));       
        results0Part0Ready = false;                                                   // Clear the results0 ready flag
      }
      else {
        Serial.println(F("--- RESULTS 00 NOT READY ---"));
      }

    //----------------------------------------
    //----------------------------------------
      if (results0Part1Ready)                                                         // Display the results in results1 array
      {
        Serial.println(F("Results0 Part1: "));
        for (uint32_t i = BUF_SIZE; i < BUF_SIZE*2; i++) {
          r0p1[i-BUF_SIZE] = adcResults0[i];

          Serial.print(i);
          Serial.print(F(": "));
          Serial.println(adcResults0[i]);
        }

        if (file.write(r0p1, BUF_SIZE) != BUF_SIZE) {
            error("write failed");
        }
        file.sync();
        Serial.println(F("Successfully Written"));       
        results0Part1Ready = false;                                                   // Clear the results1 ready flag
      }
      else {
        Serial.println(F("--- RESULTS 01 NOT READY ---"));
      }
    //----------------------------------------
    //----------------------------------------
      if (results1Part0Ready)                                                         // Display the results in results0 array
      {

        Serial.println(F("Results1 Part0: "));
        for (uint32_t i = 0; i < BUF_SIZE; i++) {
          r1p0[i] = adcResults1[i];

          Serial.print(i);
          Serial.print(F(": "));
          Serial.println(adcResults1[i]);
        }

        if (file.write(r1p0, BUF_SIZE) != BUF_SIZE) {
            error("write failed");
        }
        file.sync();
        Serial.println(F("Successfully Written"));        
        results1Part0Ready = false;                                                   // Clear the results0 ready flag
      }
      else {
        Serial.println(F("--- RESULTS 10 NOT READY ---"));
      }
    //----------------------------------------
    //----------------------------------------
      if (results1Part1Ready)                                                         // Display the results in results1 array
      {
        Serial.println(F("Results1 Part1: "));
        for (uint32_t i = BUF_SIZE; i < BUF_SIZE*2; i++) {
          r1p1[i-BUF_SIZE] = adcResults1[i];

          Serial.print(i);
          Serial.print(F(": "));
          Serial.println(adcResults1[i]);
        }

        if (file.write(r1p1, BUF_SIZE) != BUF_SIZE) {
            error("write failed");
        }
        file.sync();
        Serial.println(F("Successfully Written"));       
        results1Part1Ready = false;                                                   // Clear the results1 ready flag
      }
      else {
        Serial.println(F("--- RESULTS 11 NOT READY ---"));
      }
    //----------------------------------------
    //----------------------------------------
  }

  file.close();
  sd.end();
}

void loop() 
{  

}

void DMAC_3_Handler()                                                             // Interrupt handler for DMAC channel 3
{
  static uint8_t count0 = 0;                                                      // Initialise the count 
  if (DMAC->Channel[3].CHINTFLAG.bit.SUSP)                                        // Check if DMAC channel 3 has been suspended (SUSP) 
  {  
    DMAC->Channel[3].CHCTRLB.reg = DMAC_CHCTRLB_CMD_RESUME;                       // Restart the DMAC on channel 3
    DMAC->Channel[3].CHINTFLAG.bit.SUSP = 1;                                      // Clear the suspend (SUSP)interrupt flag
    if (count0)                                                                   // Test if the count0 is 1
    {
      results0Part1Ready = true;                                                  // Set the results 0 part 1 ready flag
    }
    else
    {
      results0Part0Ready = true;                                                  // Set the results 0 part 0 ready flag
    }
    count0 = (count0 + 1) % 2;                                                    // Toggle the count0 between 0 and 1 
    digitalWrite(10, HIGH);                                                       // Toggle the output high then low on D10 for debug purposes
    digitalWrite(10, LOW);
  }
}

void DMAC_4_Handler()                                                             // Interrupt handler for DMAC channel 4
{
  static uint8_t count1 = 0;                                                      // Initialise the count 
  if (DMAC->Channel[4].CHINTFLAG.bit.SUSP)                                        // Check if DMAC channel 4 has been suspended (SUSP) 
  {  
    DMAC->Channel[4].CHCTRLB.reg = DMAC_CHCTRLB_CMD_RESUME;                       // Restart the DMAC on channel 4
    DMAC->Channel[4].CHINTFLAG.bit.SUSP = 1;                                      // Clear the suspend (SUSP)interrupt flag
    if (count1)                                                                   // Test if the count1 is 1
    {
      results1Part1Ready = true;                                                  // Set the results 1 part 1 ready flag
    }
    else
    {
      results1Part0Ready = true;                                                  // Set the results 1 part 0 ready flag
    }
    count1 = (count1 + 1) % 2;                                                    // Toggle the count1 between 0 and 1 
    digitalWrite(11, HIGH);                                                       // Toggle the output high then low on D11 for debug purposes
    digitalWrite(11, LOW);
  }
}
greiman commented 4 weeks ago

I can't help with Adafruit's SAMD51 DMA. I tried to use it in one of my apps and gave up.

I suggest you use the Adafruit port of SdFat and post issues with Adafruit.

I was going publish fast examples using Adafruit's DMA for SAMD but decided it was too flaky.

cdd0042 commented 4 weeks ago

Hey Greiman,

That makes sense. It has been tricky.

I started with the Adafruit Fork of SdFat, but I was trying to achieve high write speeds to keep up with my datalogging ADCs and I don't think the Adafruit Fork supports the SAMD51 DMA based SPI transfers.

I used this example to get my high write speeds. I was able to replicate it with the normal SdFat library: https://github.com/greiman/SdFat/issues/434

I was just curious if you knew anything about which registers the begin() method uses so that I could identify any conflicts in my DMA datalogger. I can't find the root begin() anywhere.

greiman commented 4 weeks ago

I was just curious if you knew anything about which registers the begin() method uses so that I could identify any conflicts in my DMA datalogger. I can't find the root begin() anywhere.

I don't remember details about the SAMD driver.

Looks like Adafruit's SdFat is far behind the current SdFat and has no option to use their DMA SPI driver.

raider-snake commented 3 weeks ago

@greiman Is there anything in SdFat specifically when sd.begin(SD_CONFIG) is called that would enable the DMAC on a M4. Does SdFat have ZeroDMA as a dependency and if so where is that called in the library? I have done some testing with regard to the code above and found some interesting issue. I reconfigured the code write out the DMA controller and channel registers and added #define DEBUG_SD_BEGIN where I can easily configure if sd.begin is called or not. When sd.begin is not called (#define DEBUG_SD_BEGIN 0), the DMA runs normally and GPIO 10 and 11 (when on Grand Central board) pulse for 3us with a period of ~850 us indicating that the buffers are being filled and all of the DMA channels are sequencing properly (you can also see this in the registers that are printed). When sd.begin is called (#define DEBUG_SD_BEGIN 1), there is no pulsing on either GPIO. The registers indicate that something has changed the DMAC BASEADDR and WRB but none of the channel descriptor addresses. This causes CH2 (previously CH5) to throw a transfer error and suspend flag from the memory misalignment and halts the rest of the DMA sequence. Below is the code and serial monitor output which shows the error. (This error persists no matter if Spi_array_transfer 0 or 3 is used)

#include "SdFat.h"
#include "sdios.h"

#define error(s) sd.errorHalt(&Serial, F(s))
const uint8_t SD_CS_PIN = 83;
#define SPI_CLOCK SD_SCK_MHZ(50)
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)

SdFat sd;
File file;

// Debug Parameters
#define DEBUG_R0_P0 0
#define DEBUG_R0_P1 0
#define DEBUG_R1_P0 0
#define DEBUG_R1_P1 0
#define DEBUG_SD_BEGIN 1

volatile boolean results0Part0Ready = false;
volatile boolean results0Part1Ready = false;
volatile boolean results1Part0Ready = false;
volatile boolean results1Part1Ready = false;
uint16_t adcResults0[2048];                                                       // ADC results array 0
uint16_t adcResults1[2048];                                                       // ADC results array 1

// ADC0 INPUTCTRL register MUXPOS settings 
uint32_t inputCtrl0[] = { ADC_INPUTCTRL_MUXPOS_AIN0,                              // AIN0 = A0
                          ADC_INPUTCTRL_MUXPOS_AIN5,                              // AIN5 = A1
                          ADC_INPUTCTRL_MUXPOS_AIN3,                              // AIN3 = A3
                          ADC_INPUTCTRL_MUXPOS_AIN4 };                            // AIN4 = A4

//uint16_t sintable[256];                                                         // Sine table

typedef struct                                                                    // DMAC descriptor structure
{
  uint16_t btctrl;
  uint16_t btcnt;
  uint32_t srcaddr;
  uint32_t dstaddr;
  uint32_t descaddr;
} dmacdescriptor ;

volatile dmacdescriptor wrb[DMAC_CH_NUM] __attribute__ ((aligned (16)));          // Write-back DMAC descriptors
dmacdescriptor descriptor_section[DMAC_CH_NUM] __attribute__ ((aligned (16)));    // DMAC channel descriptors
dmacdescriptor descriptor __attribute__ ((aligned (16)));                         // Place holder descriptor

dmacdescriptor linked_descriptor[2] __attribute__ ((aligned (16)));               // Linked descriptors

void setup() {
  Serial.begin(115200);                                                           // Start the native USB port
  while(!Serial);                                                                 // Wait for the console to open

  #if DEBUG_SD_BEGIN
    // Initialize SD card before setting up DMA
    if (!sd.begin(SD_CONFIG)) {
      Serial.println("SD initialization failed!");
      while (1);  // Halt if SD card initialization fails
    } else {
      Serial.println("SD initialization succeeded!");
    }
  #endif

  pinMode(10, OUTPUT);                                                            // Initialise the output on D10 for debug purposes
  PORT->Group[PORTB].DIRSET.reg = PORT_PB23;                                      // Initialise the output on D11 for debug purposes TODO: make it autoselect based on board 

  /DMAC->BASEADDR.reg = (uint32_t)descriptor_section;                              // Specify the location of the descriptors
  /DMAC->WRBADDR.reg = (uint32_t)wrb;                                              // Specify the location of the write back descriptors
  // DMAC->BASEADDR.reg = 0x20002550;                                                // Specify the location of the descriptors
  // DMAC->WRBADDR.reg = 0x20002C70;                                                 // Specify the location of the write back descriptors
  DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);                    // Enable the DMAC peripheral

  // ADC0
  DMAC->Channel[2].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL3;                      // Set DMAC channel 2 to priority level 3 (highest)
  DMAC->Channel[2].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(ADC0_DMAC_ID_SEQ) |         // Set DMAC to trigger on ADC0 DMAC sequence
                                 DMAC_CHCTRLA_TRIGACT_BURST;                      // DMAC burst transfer
  descriptor.descaddr = (uint32_t)&descriptor_section[2];                         // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)inputCtrl0 + sizeof(uint32_t) * 4;               // Configure the DMAC to set the
  descriptor.dstaddr = (uint32_t)&ADC0->DSEQDATA.reg;                             // Write the INPUT CTRL 
  descriptor.btcnt = 4;                                                           // Beat count is 2
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_WORD |                                 // Beat size is WORD (32-bits)
                      DMAC_BTCTRL_SRCINC |                                        // Increment the source address
                      DMAC_BTCTRL_VALID;                                          // Descriptor is valid
  memcpy(&descriptor_section[2], &descriptor, sizeof(descriptor));                // Copy the descriptor to the descriptor section

  DMAC->Channel[3].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL3;                      // Set DMAC channel 3 to priority level 3 (highest)
  DMAC->Channel[3].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(ADC0_DMAC_ID_RESRDY) |      // Set DMAC to trigger when ADC0 result is ready
                                 DMAC_CHCTRLA_TRIGACT_BURST;                      // DMAC burst transfer
  descriptor.descaddr = (uint32_t)&linked_descriptor[0];                          // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&ADC0->RESULT.reg;                               // Take the result from the ADC0 RESULT register
  descriptor.dstaddr = (uint32_t)adcResults0 + sizeof(uint16_t) * 1024;           // Place it in the adcResults0 array
  descriptor.btcnt = 1024;                                                        // Beat count
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                                // Beat size is HWORD (16-bits)
                      DMAC_BTCTRL_DSTINC |                                        // Increment the destination address
                      DMAC_BTCTRL_VALID |                                         // Descriptor is valid
                      DMAC_BTCTRL_BLOCKACT_SUSPEND;                               // Suspend DMAC channel 3 after block transfer
  memcpy(&descriptor_section[3], &descriptor, sizeof(descriptor));                // Copy the descriptor to the descriptor section
  descriptor.descaddr = (uint32_t)&descriptor_section[3];                         // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&ADC0->RESULT.reg;                               // Take the result from the ADC0 RESULT register
  descriptor.dstaddr = (uint32_t)&adcResults0[1024] + sizeof(uint16_t) * 1024;    // Place it in the adcResults1 array
  descriptor.btcnt = 1024;                                                        // Beat count
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                                // Beat size is HWORD (16-bits)
                      DMAC_BTCTRL_DSTINC |                                        // Increment the destination address
                      DMAC_BTCTRL_VALID |                                         // Descriptor is valid
                      DMAC_BTCTRL_BLOCKACT_SUSPEND;                               // Suspend DMAC channel 3 after block transfer
  memcpy(&linked_descriptor[0], &descriptor, sizeof(descriptor));                 // Copy the descriptor to the descriptor section

  NVIC_SetPriority(DMAC_3_IRQn, 0);                                               // Set the Nested Vector Interrupt Controller (NVIC) priority for DMAC Channel 3
  NVIC_EnableIRQ(DMAC_3_IRQn);                                                    // Connect DMAC Channel 0 to Nested Vector Interrupt Controller (NVIC)

  DMAC->Channel[3].CHINTENSET.reg = DMAC_CHINTENSET_SUSP;                         // Activate the suspend (SUSP) interrupt on DMAC channel 3

  // ADC1
  DMAC->Channel[4].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL3;                      // Set DMAC channel 4 to priority level 3 (highest)
  DMAC->Channel[4].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(ADC1_DMAC_ID_RESRDY) |      // Set DMAC to trigger when ADC0 result is ready
                                 DMAC_CHCTRLA_TRIGACT_BURST;                      // DMAC burst transfer
  descriptor.descaddr = (uint32_t)&linked_descriptor[1];                          // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&ADC1->RESULT.reg;                               // Take the result from the ADC0 RESULT register
  descriptor.dstaddr = (uint32_t)adcResults1 + sizeof(uint16_t) * 1024;           // Place it in the adcResults0 array
  descriptor.btcnt = 1024;                                                        // Beat count
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                                // Beat size is HWORD (16-bits)
                      DMAC_BTCTRL_DSTINC |                                        // Increment the destination address
                      DMAC_BTCTRL_VALID |                                         // Descriptor is valid
                      DMAC_BTCTRL_BLOCKACT_SUSPEND;                               // Suspend DMAC channel 4 after block transfer
  memcpy(&descriptor_section[4], &descriptor, sizeof(descriptor));                // Copy the descriptor to the descriptor section
  descriptor.descaddr = (uint32_t)&descriptor_section[4];                         // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&ADC1->RESULT.reg;                               // Take the result from the ADC0 RESULT register
  descriptor.dstaddr = (uint32_t)&adcResults1[1024] + sizeof(uint16_t) * 1024;    // Place it in the adcResults1 array
  descriptor.btcnt = 1024;                                                        // Beat count
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                                // Beat size is HWORD (16-bits)
                      DMAC_BTCTRL_DSTINC |                                        // Increment the destination address
                      DMAC_BTCTRL_VALID |                                         // Descriptor is valid
                      DMAC_BTCTRL_BLOCKACT_SUSPEND;                               // Suspend DMAC channel 4 after block transfer
  memcpy(&linked_descriptor[1], &descriptor, sizeof(descriptor));                 // Copy the descriptor to the descriptor section

  NVIC_SetPriority(DMAC_4_IRQn, 0);                                               // Set the Nested Vector Interrupt Controller (NVIC) priority for DMAC Channel 4
  NVIC_EnableIRQ(DMAC_4_IRQn);                                                    // Connect DMAC Channel 0 to Nested Vector Interrupt Controller (NVIC)

  DMAC->Channel[4].CHINTENSET.reg = DMAC_CHINTENSET_SUSP;                         // Activate the suspend (SUSP) interrupt on DMAC channel 0

  // ADC Register Settings
  ADC1->INPUTCTRL.bit.MUXPOS = ADC_INPUTCTRL_MUXPOS_AIN0_Val;                     // Set the analog input to A2
  while(ADC1->SYNCBUSY.bit.INPUTCTRL);                                            // Wait for synchronization
  ADC1->SAMPCTRL.bit.SAMPLEN = 0x00;                                              // Extend sampling time by SAMPCTRL ADC cycles (12 + 1 + 2)/750kHz = 20us = 50kHz 
  while(ADC1->SYNCBUSY.bit.SAMPCTRL);                                             // Wait for synchronization
  ADC1->DSEQCTRL.reg = ADC_DSEQCTRL_AUTOSTART |                                   // Auto start a DMAC conversion upon ADC0 DMAC sequence completion
                       ADC_DSEQCTRL_INPUTCTRL;                                    // Change the ADC0 INPUTCTRL register on DMAC sequence
  ADC1->CTRLB.reg = ADC_CTRLB_RESSEL_8BIT;// |                                    // Set ADC resolution to 8 bits 
                   // ADC_CTRLB_FREERUN;                                          // Set ADC to free run mode  
  while(ADC1->SYNCBUSY.bit.CTRLB);                                                // Wait for synchronization
  ADC1->CTRLA.bit.SLAVEEN = 1;                                                    // Set ADC1 to slave, ADC0 to master, both share CTRLA register

  ADC0->INPUTCTRL.bit.MUXPOS = 0x0;                                               // Set the analog input to A0
  while(ADC0->SYNCBUSY.bit.INPUTCTRL);                                            // Wait for synchronization
  ADC0->SAMPCTRL.bit.SAMPLEN = 0x00;                                              // Extend sampling time by SAMPCTRL ADC cycles (12 + 1 + 2)/750kHz = 20us = 50kHz
  while(ADC0->SYNCBUSY.bit.SAMPCTRL);                                             // Wait for synchronization  
  ADC0->DSEQCTRL.reg = ADC_DSEQCTRL_AUTOSTART |                                   // Auto start a DMAC conversion upon ADC0 DMAC sequence completion
                       ADC_DSEQCTRL_INPUTCTRL;                                    // Change the ADC0 INPUTCTRL register on DMAC sequence
  ADC0->CTRLB.reg = ADC_CTRLB_RESSEL_8BIT;// |                                    // Set ADC resolution to 8 bits 
                    //ADC_CTRLB_FREERUN;                                          // Set ADC to free run mode        
  while(ADC0->SYNCBUSY.bit.CTRLB);                                                // Wait for synchronization
  ADC0->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV4;                                     // Divide Clock ADC GCLK by 64 (48MHz/64 = 750kHz) (12 + 1)/750kHz = 17.3us sample time 
  ADC0->CTRLA.bit.ENABLE = 1;                                                     // Enable the ADC
  while(ADC0->SYNCBUSY.bit.ENABLE);                                               // Wait for synchronization
  ADC0->SWTRIG.bit.START = 1;                                                     // Initiate a software trigger to start an ADC conversion
  while(ADC0->SYNCBUSY.bit.SWTRIG);                                               // Wait for synchronization

  DMAC->Channel[2].CHCTRLA.bit.ENABLE = 1;                                        // Enable DMAC channel 2
  DMAC->Channel[3].CHCTRLA.bit.ENABLE = 1;                                        // Enable DMAC channel 3
  DMAC->Channel[4].CHCTRLA.bit.ENABLE = 1;                                        // Enable DMAC channel 4
  delay(1);                                                                       // Wait a millisecond

  // Set a timer to call debugPrintStatus every 10 seconds TODO configure this. Will use R1P0 for meantime 
  //Timer1.initialize(10000000);  // 10 seconds
  //Timer1.attachInterrupt(debugPrintStatus);
}

void loop() 
{  
  debugPrintStatus();

  if (results0Part0Ready)                                                         // Display the results in results0 array
  {
    Serial.println(F("Results0 Part0"));

    #if DEBUG_R0_P0
      Serial.print("DMA3 SUSP interrupt. CHCTRLA: ");
      Serial.println(DMAC->Channel[3].CHCTRLA.reg, HEX);
      Serial.print("DMA3 BTCNT: ");
      Serial.println(descriptor_section[3].btcnt);
      Serial.print("DMA3 DSTADDR: ");
      Serial.println((uint32_t)descriptor_section[3].dstaddr, HEX);
      Serial.print("DMA3 SRCADDR: ");
      Serial.println((uint32_t)descriptor_section[3].srcaddr, HEX);
      Serial.print("ADC0 Result: ");
      Serial.println(ADC0->RESULT.reg);
    #endif

    Serial.println();
    results0Part0Ready = false;                                                   // Clear the results0 ready flag
  }

  if (results0Part1Ready)                                                         // Display the results in results1 array
  {
    Serial.println(F("Results0 Part1"));

    #if DEBUG_R0_P1
      Serial.print("DMA3 SUSP interrupt. CHCTRLA: ");
      Serial.println(DMAC->Channel[3].CHCTRLA.reg, HEX);
      Serial.print("DMA3 BTCNT: ");
      Serial.println(descriptor_section[3].btcnt);
      Serial.print("DMA3 DSTADDR: ");
      Serial.println((uint32_t)descriptor_section[3].dstaddr, HEX);
      Serial.print("DMA3 SRCADDR: ");
      Serial.println((uint32_t)descriptor_section[3].srcaddr, HEX);
      Serial.print("ADC0 Result: ");
      Serial.println(ADC0->RESULT.reg);
    #endif

    Serial.println();
    results0Part1Ready = false;                                                   // Clear the results1 ready flag
  }

  if (results1Part0Ready)                                                         // Display the results in results0 array
  {
    Serial.println(F("Results1 Part0"));

    #if DEBUG_R1_P0
      Serial.print("DMA4 SUSP interrupt. CHCTRLA: ");
      Serial.println(DMAC->Channel[4].CHCTRLA.reg, HEX);
      Serial.print("DMA4 BTCNT: ");
      Serial.println(descriptor_section[4].btcnt);
      Serial.print("DMA4 DSTADDR: ");
      Serial.println((uint32_t)descriptor_section[4].dstaddr, HEX);
      Serial.print("DMA4 SRCADDR: ");
      Serial.println((uint32_t)descriptor_section[4].srcaddr, HEX);
      Serial.print("ADC1 Result: ");
      Serial.println(ADC1->RESULT.reg);
    #endif

    Serial.println();
    results1Part0Ready = false;                                                   // Clear the results0 ready flag
  }

  if (results1Part1Ready)                                                         // Display the results in results1 array
  {
    Serial.println(F("Results1 Part1"));

    #if DEBUG_R1_P1
      Serial.print("DMA4 SUSP interrupt. CHCTRLA: ");
      Serial.println(DMAC->Channel[4].CHCTRLA.reg, HEX);
      Serial.print("DMA4 BTCNT: ");
      Serial.println(descriptor_section[4].btcnt);
      Serial.print("DMA4 DSTADDR: ");
      Serial.println((uint32_t)descriptor_section[4].dstaddr, HEX);
      Serial.print("DMA4 SRCADDR: ");
      Serial.println((uint32_t)descriptor_section[4].srcaddr, HEX);
      Serial.print("ADC1 Result: ");
      Serial.println(ADC1->RESULT.reg);
    #endif

    Serial.println();
    results1Part1Ready = false;                                                   // Clear the results1 ready flag
  }
}

void DMAC_3_Handler()                                                             // Interrupt handler for DMAC channel 3
{
  static uint8_t count0 = 0;                                                      // Initialise the count 
  if (DMAC->Channel[3].CHINTFLAG.bit.SUSP)                                        // Check if DMAC channel 3 has been suspended (SUSP) 
  {  
    DMAC->Channel[3].CHCTRLB.reg = DMAC_CHCTRLB_CMD_RESUME;                       // Restart the DMAC on channel 3
    DMAC->Channel[3].CHINTFLAG.bit.SUSP = 1;                                      // Clear the suspend (SUSP)interrupt flag
    if (count0)                                                                   // Test if the count0 is 1
    {
      results0Part1Ready = true;                                                  // Set the results 0 part 1 ready flag
    }
    else
    {
      results0Part0Ready = true;                                                  // Set the results 0 part 0 ready flag
    }
    count0 = (count0 + 1) % 2;                                                    // Toggle the count0 between 0 and 1 
    digitalWrite(10, HIGH);                                                       // Toggle the output high then low on D10 for debug purposes
    delayMicroseconds(3);                                                         // Short delay for visualization
    digitalWrite(10, LOW);
  }
}

void DMAC_4_Handler()                                                             // Interrupt handler for DMAC channel 4
{
  static uint8_t count1 = 0;                                                      // Initialise the count 
  if (DMAC->Channel[4].CHINTFLAG.bit.SUSP)                                        // Check if DMAC channel 4 has been suspended (SUSP) 
  {  
    DMAC->Channel[4].CHCTRLB.reg = DMAC_CHCTRLB_CMD_RESUME;                       // Restart the DMAC on channel 4
    DMAC->Channel[4].CHINTFLAG.bit.SUSP = 1;                                      // Clear the suspend (SUSP)interrupt flag
    if (count1)                                                                   // Test if the count1 is 1
    {
      results1Part1Ready = true;                                                  // Set the results 1 part 1 ready flag
    }
    else
    {
      results1Part0Ready = true;                                                  // Set the results 1 part 0 ready flag
    }
    count1 = (count1 + 1) % 2;                                                    // Toggle the count1 between 0 and 1 
    PORT->Group[PORTB].OUTSET.reg = PORT_PB23;    // Toggle D10 HIGH              // Toggle the output high then low on D11 for debug purposes
    delayMicroseconds(3);                                                         // Short delay for visualization
    PORT->Group[PORTB].OUTCLR.reg = PORT_PB23;    // Toggle D10 LOW
  }
}

void debugPrintStatus() {
  // Print out DMAC channel status and flags
  Serial.println(F("=== DMAC Channel Status ==="));
  for (int i = 0; i < 5; i++) {
    Serial.print(F("Channel ")); Serial.println(i);
    Serial.print(F("  CHCTRLA: ")); Serial.println(DMAC->Channel[i].CHCTRLA.reg, HEX);
    Serial.print(F("  CHCTRLB: ")); Serial.println(DMAC->Channel[i].CHCTRLB.reg, HEX);
    Serial.print(F("  CHINTFLAG: ")); Serial.println(DMAC->Channel[i].CHINTFLAG.reg, HEX);
    Serial.print(F("  CHINTENSET: ")); Serial.println(DMAC->Channel[i].CHINTENSET.reg, HEX);
    Serial.print(F("  CHINTENCLR: ")); Serial.println(DMAC->Channel[i].CHINTENCLR.reg, HEX);

    // Print descriptor information
    Serial.print(F("  Descriptor Address: ")); Serial.println((uint32_t)&descriptor_section[i], HEX);
    Serial.print(F("    BTCNT: ")); Serial.println(descriptor_section[i].btcnt);
    Serial.print(F("    SRCADDR: ")); Serial.println((uint32_t)descriptor_section[i].srcaddr, HEX);
    Serial.print(F("    DSTADDR: ")); Serial.println((uint32_t)descriptor_section[i].dstaddr, HEX);
    Serial.print(F("    DESCADDR: ")); Serial.println((uint32_t)descriptor_section[i].descaddr, HEX);
  }

  // Print ADC status
  Serial.println(F("=== ADC Status ==="));
  Serial.print(F("ADC0 RESULT: ")); Serial.println(ADC0->RESULT.reg, HEX);
  Serial.print(F("ADC0 INTFLAG: ")); Serial.println(ADC0->INTFLAG.reg, HEX);
  Serial.print(F("ADC0 STATUS: ")); Serial.println(ADC0->STATUS.reg, HEX);
  Serial.print(F("ADC1 RESULT: ")); Serial.println(ADC1->RESULT.reg, HEX);
  Serial.print(F("ADC1 INTFLAG: ")); Serial.println(ADC1->INTFLAG.reg, HEX);
  Serial.print(F("ADC1 STATUS: ")); Serial.println(ADC1->STATUS.reg, HEX);

  // Print other important registers
  Serial.println(F("=== Other Important Registers ==="));
  Serial.print(F("DMAC BASEADDR: ")); Serial.println(DMAC->BASEADDR.reg, HEX);
  Serial.print(F("DMAC WRBADDR: ")); Serial.println(DMAC->WRBADDR.reg, HEX);
  Serial.print(F("DMAC CTRL: ")); Serial.println(DMAC->CTRL.reg, HEX);
} ```

```SD ENABLED

=== DMAC Channel Status ===
Channel 0
  CHCTRLA: 200800
  CHCTRLB: 0
  CHINTFLAG: 0
  CHINTENSET: 2
  CHINTENCLR: 2
  Descriptor Address: 20002550
    BTCNT: 0
    SRCADDR: 0
    DSTADDR: 0
    DESCADDR: 0
Channel 1
  CHCTRLA: 200900
  CHCTRLB: 0
  CHINTFLAG: 0
  CHINTENSET: 2
  CHINTENCLR: 2
  Descriptor Address: 20002560
    BTCNT: 0
    SRCADDR: 0
    DSTADDR: 0
    DESCADDR: 0
Channel 2
  CHCTRLA: 204502
  CHCTRLB: 0
  CHINTFLAG: 5
  CHINTENSET: 0
  CHINTENCLR: 0
  Descriptor Address: 20002570
    BTCNT: 4
    SRCADDR: 20000014
    DSTADDR: 43001C34
    DESCADDR: 20002570
Channel 3
  CHCTRLA: 204402
  CHCTRLB: 0
  CHINTFLAG: 0
  CHINTENSET: 4
  CHINTENCLR: 4
  Descriptor Address: 20002580
    BTCNT: 1024
    SRCADDR: 43001C40
    DSTADDR: 20000D3E
    DESCADDR: 200027A0
Channel 4
  CHCTRLA: 204602
  CHCTRLB: 0
  CHINTFLAG: 0
  CHINTENSET: 4
  CHINTENCLR: 4
  Descriptor Address: 20002590
    BTCNT: 1024
    SRCADDR: 43002040
    DSTADDR: 20001D3E
    DESCADDR: 200027B0
=== ADC Status ===
ADC0 RESULT: 3B
ADC0 INTFLAG: 0
ADC0 STATUS: 0
ADC1 RESULT: 2E
ADC1 INTFLAG: 0
ADC1 STATUS: 0
=== Other Important Registers ===
DMAC BASEADDR: 20000120 // Former BASEADDR was 20000550 which is 16 byte aligned with CH0
DMAC WRBADDR: 20000320
DMAC CTRL: F02
greiman commented 3 weeks ago

@greiman Is there anything in SdFat specifically when sd.begin(SD_CONFIG) is called that would enable the DMAC on a M4.

SdFat does not know about DMA. It may cause DMA to be used if the SPI library is implemented with DMA.

SdFat only calls SPI.begin() with no arguments, SPI.beginTransaction(), SPI.transfer(), SPI.endTransaction() and possibly SPI.end() if sd.end() is called.

SdFat supports three SPI transfer signatures:

// Arduino standard library
uint8_t transfer(uint8_t data);  // Rarely DMA
void transfer(void* buf, size_t count);  // May be DMA

// Adafruit, Teensy, and other non Arduino boards.
void transfer(const void* txBuf, void* rxBuf, size_t count);  // Often uses DMA.

Use of array transfers requires editing SdFatConfig here.