DMA data logger (Adafruit Feather M4) to SD card using SdFat

I have a data logger that takes in inputs from 4 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);
  }
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.