Setting up ISR for ADC on Zero

Hello,

I was wondering if anyone has ever setup a ISR to collect an ADC sample and then put sample inside a buffer, for Zero. I tried this and posted it awhile ago, but I cannot find my post for the life of me.........

Hi geryuu123,

Here’s an example of how transfer a number of ADC samples to a memory array using an Interrupt Service Routine (ISR) with the ADC in free run mode:

// Use the SAMD21's ISR to transfer ADC results to buffer array in memory
#define SAMPLE_NO 8                                  // Define the number of ADC samples

volatile uint16_t adcResult[SAMPLE_NO] = {};         // ADC results buffer
volatile bool resultsReady = false;                  // Results ready flag

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

  ADC->INPUTCTRL.bit.MUXPOS = 0x0;                   // Set the analog input to A0
  while(ADC->STATUS.bit.SYNCBUSY);                   // Wait for synchronization
  ADC->SAMPCTRL.bit.SAMPLEN = 0x00;                  // Set max Sampling Time Length to half divided ADC clock pulse (2.66us)
  ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV512 |      // Divide Clock ADC GCLK by 512 (48MHz/512 = 93.7kHz)
                   ADC_CTRLB_RESSEL_12BIT |          // Set the ADC resolution to 12 bits
                   ADC_CTRLB_FREERUN;                // Set the ADC to free run
  while(ADC->STATUS.bit.SYNCBUSY);                   // Wait for synchronization  
  NVIC_SetPriority(ADC_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for the ADC to 0 (highest) 
  NVIC_EnableIRQ(ADC_IRQn);         // Connect the ADC to Nested Vector Interrupt Controller (NVIC)
  ADC->INTENSET.reg = ADC_INTENSET_RESRDY;           // Generate interrupt on result ready (RESRDY)
  ADC->CTRLA.bit.ENABLE = 1;                         // Enable the ADC
  while(ADC->STATUS.bit.SYNCBUSY);                   // Wait for synchronization
  ADC->SWTRIG.bit.START = 1;                         // Initiate a software trigger to start an ADC conversion
  while(ADC->STATUS.bit.SYNCBUSY);                   // Wait for synchronization
}

void loop()
{
  if (resultsReady)
  {                         
    for (uint16_t i = 0; i < SAMPLE_NO; i++)           // Display the results on the console
    {
      SerialUSB.print(i + 1);
      SerialUSB.print(F(": "));
      SerialUSB.println(adcResult[i]);
    }
    SerialUSB.println();
    delay(1000);                                       // Wait for 1 second
    resultsReady = false;                              // Clear the resultsReady flag
    ADC->CTRLA.bit.ENABLE = 1;                         // Enable the ADC
    while(ADC->STATUS.bit.SYNCBUSY);                   // Wait for synchronization
    ADC->SWTRIG.bit.START = 1;                         // Initiate a software trigger to start an ADC conversion
    while(ADC->STATUS.bit.SYNCBUSY);                   // Wait for synchronization
  }
}

void ADC_Handler()
{
  static uint16_t counter = 0;                       // Results counter
  
  if (ADC->INTFLAG.bit.RESRDY)                       // Check if the result ready (RESRDY) flag has been set
  { 
    ADC->INTFLAG.bit.RESRDY = 1;                     // Clear the RESRDY flag
    while(ADC->STATUS.bit.SYNCBUSY);                 // Wait for read synchronization
    adcResult[counter++] = ADC->RESULT.reg;          // Read the result;
    if (counter == SAMPLE_NO)                        // Once the required number of samples have been made
    {
      ADC->CTRLA.bit.ENABLE = 0;                     // Disable the ADC
      while(ADC->STATUS.bit.SYNCBUSY);               // Wait for synchronization
      counter = 0;                                   // Reset the counter
      resultsReady = true;                           // Set the resultsReady flag
    }
  }
}

… and here’s an example that does the same thing using the SAMD21’s Direct Memory Access Controller (DMAC) instead of the CPU, again with the ADC in free run mode:

// Use the SAMD21's DMAC to transfer ADC results to buffer array in memory
#define SAMPLE_NO 8                                                                      // Define the number of ADC samples

uint16_t adcResult[SAMPLE_NO] = {};                                                      // Store ADC values

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

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

  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

  DMAC->CHID.reg = DMAC_CHID_ID(0);                                           // Select DMAC channel 0
  DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(0) | DMAC_CHCTRLB_TRIGSRC(ADC_DMAC_ID_RESRDY) | DMAC_CHCTRLB_TRIGACT_BEAT;
  descriptor.descaddr = (uint32_t)0;                                          // Set up descriptor
  descriptor.srcaddr = (uint32_t)&ADC->RESULT.reg;                            // Take the result from the ADC RESULT register
  descriptor.dstaddr = (uint32_t)&adcResult[0] + sizeof(uint16_t) * SAMPLE_NO;            // Place it in the adcResult array
  descriptor.btcnt = SAMPLE_NO;                                               // Beat count is SAMPLE_NO
  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
  memcpy(&descriptor_section[0], &descriptor, sizeof(descriptor));            // Copy the descriptor to the descriptor section

  ADC->INPUTCTRL.bit.MUXPOS = 0x0;                   // Set the analog input to A0
  while(ADC->STATUS.bit.SYNCBUSY);                   // Wait for synchronization
  ADC->SAMPCTRL.bit.SAMPLEN = 0x00;                  // Set max Sampling Time Length to half divided ADC clock pulse (2.66us)
  ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV512 |      // Divide Clock ADC GCLK by 512 (48MHz/512 = 93.7kHz)
                   ADC_CTRLB_RESSEL_12BIT |          // Set the ADC resolution to 12 bits
                   ADC_CTRLB_FREERUN;                // Set the ADC to free run
  while(ADC->STATUS.bit.SYNCBUSY);                   // Wait for synchronization  
  ADC->CTRLA.bit.ENABLE = 1;                         // Enable the ADC
  while(ADC->STATUS.bit.SYNCBUSY);                   // Wait for synchronization
  ADC->SWTRIG.bit.START = 1;                         // Initiate a software trigger to start an ADC conversion
  while(ADC->STATUS.bit.SYNCBUSY);                   // Wait for synchronization
}

void loop()
{
  DMAC->CHID.reg = DMAC_CHID_ID(0);                  // Select DMAC channel 0
  DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;          // Enable the selected DMAC channel
  while(!DMAC->CHINTFLAG.bit.TCMPL);                 // Wait for the DMAC to transfer complete(TCMPL) interrupt flag
  DMAC->CHINTFLAG.bit.TCMPL = 1;                     // Clear the DMA transfer complete (TCMPL) interrupt flag
  for (uint16_t i = 0; i < SAMPLE_NO; i++)           // Display the results on the console
  {
    SerialUSB.print(i + 1);
    SerialUSB.print(F(": "));
    SerialUSB.println(adcResult[i]);
  }
  SerialUSB.println();
  delay(1000);                                       // Wait for 1 second
}

Thank you, wasnt expecting a reply so soon. But ill play with your code later today. And I have read datasheet and setup for DMA to ADC before, but always had issues with DMA syncing to ADC. My plan is to use 2 ADC channels(50kHz each) to fill an external SRAM,and once full transfer to SD using SdFat without missing any data while writing to external SRAM(ping/pong buffer).

Hi geryuu123,

I don't think that the SAMD21 will be able to meet your requirements.

The first issue is that two ADC channels are running at 50kHz, means that using the DMA to transfer the data is your only option, as an ISR operating at this frequency would overwhelm the CPU.

The second is that the SAMD21's DMA has no means of alternating the analog input channels (A0, A1, etc...) during operation. You could change analog channels using the DMA transfer complete (TCMPL) interrupt, but again we're back to the first issue, in that calling the ISR at 100kHz would overwhelm the CPU.

One possible solution however could be to use the SAMD21's cousin, the SAMD51.

The SAMD51 contains two ADC peripherals: ADC0 and ADC1, but also has a DMAC sequencing capability. The DMAC sequencing capability allows the ADC's control registers to be changed during operation. This means input channels can be alternately switched on a single ADC peripheral, for instance ADC0.

Here's some example code that uses the SAMD51's DMAC sequencing to switch between A0 and A1 on ADC0 during operation. DMAC channel 0 switches input channels, while channel 1 reads the data:

// Use SAMD51's DMA Sequencing to read both A0 and A1 without CPU intervention
uint16_t adcResult[2];                                  // A0 and A1 result array
uint32_t inputCtrl[] = { ADC_INPUTCTRL_MUXPOS_AIN0,     // ADC0 INPUTCTRL register MUXPOS settings AIN0 = A0, AIN5 = A1
                         ADC_INPUTCTRL_MUXPOS_AIN5 };   

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

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

  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

  DMAC->Channel[0].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[0];                     // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)inputCtrl + sizeof(uint32_t) * 2;            // Configure the DMAC to set the
  descriptor.dstaddr = (uint32_t)&ADC0->DSEQDATA.reg;                         // Write the INPUT CTRL 
  descriptor.btcnt = 2;                                                       // 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[0], &descriptor, sizeof(descriptor));            // Copy the descriptor to the descriptor section
   
  DMAC->Channel[1].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)&descriptor_section[1];                     // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&ADC0->RESULT.reg;                           // Take the result from the ADC0 RESULT register
  descriptor.dstaddr = (uint32_t)adcResult + sizeof(uint16_t) * 2;            // Place it in the adcResult array
  descriptor.btcnt = 2;                                                       // Beat count is 1
  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
  memcpy(&descriptor_section[1], &descriptor, sizeof(descriptor));            // Copy the descriptor to the descriptor section

  ADC0->INPUTCTRL.bit.MUXPOS = 0x0;                   // Set the analog input to A0
  while(ADC0->SYNCBUSY.bit.INPUTCTRL);                // Wait for synchronization
  ADC0->SAMPCTRL.bit.SAMPLEN = 0x00;                  // Set max Sampling Time Length to half divided ADC clock pulse (2.66us)
  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->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV256;       // Divide Clock ADC GCLK by 256 (48MHz/256 = 187.5kHz)
  ADC0->CTRLB.reg = ADC_CTRLB_RESSEL_12BIT;           // Set ADC resolution to 12 bits 
  while(ADC0->SYNCBUSY.bit.CTRLB);                    // Wait for synchronization
  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[0].CHCTRLA.bit.ENABLE = 1;            // Enable DMAC Sequencing on channel 0
  DMAC->Channel[1].CHCTRLA.bit.ENABLE = 1;            // Enable DMAC ADC on channel1
  delay(1);                                           // Wait a millisecond
}

void loop()
{                              
  Serial.println(adcResult[0]);                       // Display the A0 result
  Serial.println(adcResult[1]);                       // Display the A1 result
  Serial.println();                                   // Display newline
  delay(1000);                                        // Wait for 1 second
}
1 Like

Damn, wish I woulda seen your post. I definitely tried just alternating ADC channels during interrupt in DMA.....Didnt work lol. But I ordered a Metro M4 (SAMD51). I do have a Metro M4 but its a BETA and 4 hours away from where im currently at..So once I get it, ill upload code and let you know. Thanks.

Hey, so I got my Metro M4 today and ran your code.Works just fine. But since I havent really used this chip(SAMD51) before I decided to try converting some of my SAMD21 code to it and am having issues. Im not sure If I should make a new post since I dont see any SAMD forums besides Arduino Zero. But if I should let me know.

But what im trying to do is just setup an ADC pin (A2) in FREERUN MODE,record next X samples and fill buffer. When buffer is full,dump data to SD card. I feed a sinewave into A2 using my function generator.I have successfully done this,compiles and writes to SD, but my results make me scratch my head…Sinewave is messed up,it does resemble a sinewave at least…

What ive done:

void setup:
-setup GCLK1 to be used for ADC0
-setup AIN6(A2) INPUTCTRL
-setup PRESCALER,RESOLUTION,FREERUN MODE
-setup REFSEL INTVCC0 1/2 VDDANA.

void loop:
-get ADC sample
-fill buffer until full
-once full dump to SD

I uploaded code below.

CODE:

//SAMD51 TESTING CODE 1
//setup samd51 to record from 1 ADC channel(A2),if a threshold is passed ,fill buffer inside loop,when its full, dump data to SD card.
#include "Arduino.h"
#include "wiring_private.h"
#include <Wire.h>
#include <time.h>

#include <SD.h>








//setup SD stuff
const int chipSelect = 10;
String filename = "rec";
String fname;
File dataFile;



// RAM of SAMD51 is 192k
//setup buffer
#define NUMBER_OF_SAMPLES 75000
uint16_t circularBuffer[NUMBER_OF_SAMPLES];//circular buffer
int ind = -1;


//value from ADC(A2)
uint32_t valueRead;
//values that are split from valueRead for SD
byte firstHalf;
byte secondHalf;


byte tempByte;

//look for file that doesnt exist and then create it
void lookForFile()
{
  String tempName;
  for (int i = 0; i < 6000000; i++)
  {
    tempName = filename + i + ".txt";
    char charBuf[50];
    tempName.toCharArray(charBuf, 50);
    if (!SD.exists(charBuf)) //does file exist already on SD card
    {
      fname = filename + i + ".txt";
      break;
    }
  }
}


//setup digi-pot value
byte potVal = 200;


void setup() {
  delay(4000);
  Serial.begin(115200);
  Wire.begin(); // join i2c bus (address optional for master)
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    return;
  }
  Serial.println("card initialized.");

  //setup ADC
  fastADCsetup2();


  //setup digiPot stuff
  Wire.begin();
  //  delay(2000);
  Wire.beginTransmission(44); // transmit to device #44 (0x2c)
  // device address is specified in datasheet
  Wire.write(byte(0x00));            // sends instruction byte
  Wire.write(potVal);             // sends potentiometer value byte
  Wire.endTransmission();     // stop transmitting
  delay(1000);
  Wire.beginTransmission(45); // transmit to device #44 (0x2c)
  // device address is specified in datasheet
  Wire.write(byte(0x00));            // sends instruction byte
  Wire.write(potVal);             // sends potentiometer value byte
  Wire.endTransmission();     // stop transmitting


}


void loop() {






  while (1) //this is looping indefinitly
  {

    //get ADC result
    ADC0->SWTRIG.bit.START = 1; //start adc conversion
    while (!ADC0->INTFLAG.bit.RESRDY); //wait for adc data to be ready
    valueRead = ADC0->RESULT.reg;  //copy data to variable





    //store ADC result into buffer,and increment index
    circularBuffer[ind++] = valueRead;






    //if we have 75k samples,split by bitshifting and store in SD
    if (ind >= 75000)
    {

      lookForFile();
      char charBuf[50];
      fname.toCharArray(charBuf, 50);
      File dataFile = SD.open(charBuf, FILE_WRITE);



      //go through circularBuffer and split each sample into 2 bytes and transfer to SD
      for (int i = 0; i <= 75000; i++) {
        firstHalf = circularBuffer[i] >> 8;
        secondHalf = circularBuffer[i] & 0xff;


        dataFile.write(firstHalf);
        dataFile.write(secondHalf);

      }

      dataFile.close();
      ind = -1;

      Serial.println("ch1 file closed inside datafile");

    }


  }

}

///end of void loop

//adc fastsetup start
uint32_t fastADCsetup2() {


  MCLK->APBDMASK.bit.ADC0_ = 1;
  GCLK->PCHCTRL[ADC0_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK1 | GCLK_PCHCTRL_CHEN; // enable gen clock 1 as source for ADC channel

  // After configuring ADC Clock, reset ADC registers
  ADC0->CTRLA.bit.SWRST = 1;

  // Wait for ADC to finish reset, bit will stay high until reset clears the bit
  while (ADC0->CTRLA.bit.SWRST);


  ADC0->INPUTCTRL.bit.MUXPOS = 0x6;                   // Set the analog input to A2
  while (ADC0->SYNCBUSY.bit.INPUTCTRL);               // Wait for synchronization

  ADC0->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_INTVCC0_Val;//  2.2297 V Supply VDDANA //ADC_REFCTRL_REFSEL_INT1V_Val;   // ref is 1 V band gap internal

  while (ADC0->SYNCBUSY.bit.REFCTRL);                   // Wait for synchronizationv

  ADC0->SAMPCTRL.reg = 0x00;       //Minimal sample length is 1/2 CLK_ADC cycle

  while (ADC0->SYNCBUSY.bit.SAMPCTRL);                // Wait for synchronization

  ADC0->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV256;       // Divide Clock ADC GCLK by 256 (48MHz/256 = 187.5kHz)
  ADC0->CTRLB.reg = ADC_CTRLB_FREERUN | ADC_CTRLB_RESSEL_12BIT;           // Set ADC resolution to 12 bits
  while (ADC0->SYNCBUSY.bit.CTRLB);                   // Wait for synchronization
  ADC0->CTRLA.bit.ENABLE = 0x01;
  ADC0->SWTRIG.bit.START = 1; //start adc conversion
}

Any pointers would be great.

Hi geryuu123,

Here's an example that uses the SAMD51's DMAC to read analog pin A4 (on Metro M4, or A2 on Feather M4) and alternately writes the results to two arrays acting as a double buffer.

The DMAC has two linked descriptors 0 and 1. Descriptor 0 places the results in array: "adcResults0" and descriptor 1 in array: "adcResults1". The descriptor are circularly linked such that DMAC continously cycles between the two.

Also, the DMAC has been set up so that the channel is suspended and an interrupt called at the end of each descriptor. This allows for boolean flags to be set that indicate to the main loop() function when to display the buffered data on the console. The interrupt immediately resumes DMAC operation on channel 0 as soon as it is called.

The digital output D9 (on Metro M4, or D10 on Feather M4) is toggled high then low by the interrupt. This allows the descriptor processing time to be assessed.

Using an array of 256 elements and an ADC conversion time of (12 + 1) / (48MHz/256) = 69.3us gives a descriptor processing time of 256 * 69.3us = 17.75us. This matches the duration bewteen pulses seen on an oscilloscope.

The code follows in the next post:

// Use SAMD51's DMAC to read the ADC on A4 and alternately store results in two memory arrays
#define NO_RESULTS 256

volatile boolean results0Ready = false;
volatile boolean results1Ready = false;
uint16_t adcResults0[NO_RESULTS];                                  // ADC results array 0
uint16_t adcResults1[NO_RESULTS];                                  // ADC results array 1

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

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

  PORT->Group[PORTA].DIRSET.reg = PORT_PA20;                                  // Initialise the output on D9 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
   
  DMAC->Channel[0].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)&descriptor_section[1];                     // 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) * NO_RESULTS; // Place it in the adcResults0 array
  descriptor.btcnt = NO_RESULTS;                                              // 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 0 after block transfer
  memcpy(&descriptor_section[0], &descriptor, sizeof(descriptor));            // Copy the descriptor to the descriptor section
  descriptor.descaddr = (uint32_t)&descriptor_section[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)adcResults1 + sizeof(uint16_t) * NO_RESULTS; // Place it in the adcResults1 array
  descriptor.btcnt = NO_RESULTS;                                              // 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 0 after block transfer
  memcpy(&descriptor_section[1], &descriptor, sizeof(descriptor));            // Copy the descriptor to the descriptor section
  
  NVIC_SetPriority(DMAC_0_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC1 OVF to 0 (highest) 
  NVIC_EnableIRQ(DMAC_0_IRQn);         // Connect TCC1 to Nested Vector Interrupt Controller (NVIC)
  
  DMAC->Channel[0].CHINTENSET.reg = DMAC_CHINTENSET_SUSP;                     // Activate the suspend (SUSP) interrupt on DMAC channel 0
  
  ADC0->INPUTCTRL.bit.MUXPOS = ADC_INPUTCTRL_MUXPOS_AIN2_Val;     // Set the analog input to A4
  while(ADC0->SYNCBUSY.bit.INPUTCTRL);                // Wait for synchronization
  ADC0->SAMPCTRL.bit.SAMPLEN = 0x00;                  // Set max Sampling Time Length to half divided ADC clock pulse (2.66us)
  while(ADC0->SYNCBUSY.bit.SAMPCTRL);                 // Wait for synchronization  
  ADC0->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV256;       // Divide Clock ADC GCLK by 256 (48MHz/256 = 187.5kHz)
  ADC0->CTRLB.reg = ADC_CTRLB_RESSEL_12BIT |         // Set ADC resolution to 12 bits 
                    ADC_CTRLB_FREERUN;               // Set ADC to free run mode        
  while(ADC0->SYNCBUSY.bit.CTRLB);                    // Wait for synchronization
  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[0].CHCTRLA.bit.ENABLE = 1;            // Enable DMAC ADC on channel 1
}

void loop() 
{  
  if (results0Ready)                                  // Display the results in results0 array
  {
    Serial.println(F("Results0"));
    for (uint32_t i = 0; i < NO_RESULTS; i++)
    {
      Serial.print(i);
      Serial.print(F(": "));
      Serial.println(adcResults0[i]);
    }
    Serial.println();
    results0Ready = false;                            // Clear the results0 ready flag
  }
  if (results1Ready)                                  // Display the results in results1 array
  {
    Serial.println(F("Results1"));
    for (uint32_t i = 0; i < NO_RESULTS; i++)
    {
      Serial.print(i);
      Serial.print(F(": "));
      Serial.println(adcResults0[i]);
    }
    Serial.println();
    results1Ready = false;                            // Clear the results1 ready flag
  }
}

void DMAC_0_Handler()                                           // Interrupt handler for DMAC channel 0
{
  static uint8_t count = 0;                                     // Initialise the count 
  if (DMAC->Channel[0].CHINTFLAG.bit.SUSP)                      // Check if DMAC channel 0 has been suspended (SUSP) 
  {  
    DMAC->Channel[0].CHCTRLB.reg = DMAC_CHCTRLB_CMD_RESUME;     // Restart the DMAC on channel 0
    DMAC->Channel[0].CHINTFLAG.bit.SUSP = 1;                    // Clear the suspend (SUSP)interrupt flag
    if (count)                                                  // Test if the count is 1
    {
      results1Ready = true;                                     // Set the results 1 ready flag
    }
    else
    {
      results0Ready = true;                                     // Set the results 0 ready flag
    }
    count = (count + 1) % 2;                                    // Toggle the count between 0 and 1 
    PORT->Group[PORTA].OUTSET.reg = PORT_PA20;                  // Toggle the output high then low on D9 for debug purposes
    PORT->Group[PORTA].OUTCLR.reg = PORT_PA20; 
  }
}

Hey MartinL,

Thanks for reply. I was able to fix my issue. My problem was over-clipping....I expected signal to clip and just have a flatline for peaks, but if its overclipped you get random behavior,which ive never seen before,this is my conclusion.I lowered gain and sure enough I get a nice sinewave. And I tested it in single conversion mode and freerun mode just to be sure.

Your code is much appreciated,and after fixing my issue,ill try and alter the code you provided for DMAC 2 channels,and currently previous code you provided(Use SAMD51's DMAC to read the ADC on A4 and alternately store results in two memory arrays) to fill a buffer of 75k samples(12 resolution) @187kHz samplerate,and dump data to SD.

Another question, after going through data sheet:
I was wondering if its possible to use ADC0 and ADC1 to alternate channels. From what ive read ADC1 will be a slave of ADC0 by using same GLCK, but from datasheet it seems you can setup a 2nd channel using ADC1.INPUTCTRL. And can then use the triggerMode of either BOTH(both ADC's start simultaneously) or INTERLEAVED(alternates from ADC0 and ADC1).And then use this to fill buffers and dump data to SD.

Hi geryuu123,

It appears as though it's possible to set up ADC1 as a slave to ADC0. One point of ambiguity however, is that the datasheet states that it's not possible to run in free run (FREERUN) mode when the master and slave are set as INTERLEAVED. It's not clear in the datasheet, if the AUTOSTART bit in the DSEQCTRL register can allow the ADC to run continuously with the DMAC in software trigger (SWTRIG) mode. In the above example in post #4, the DMAC is operating continously due to AUTOSTART, rather than activating FREERUN.

Using the DMAC in conjuction with the ADC, it's possible to achieve the desired sample rate using the ADC's prescaler and sample control (SAMPCTRL) register.

Another issue with the DMAC is that the beat count BTCNT register is only 16-bits long, this means that it's only capable of storing 65535 samples. To increase this to 75000 it's necessary to link two descriptors together. Note that if you're using more than one DMAC channel it's better to create new linked descriptors in memory, for example:

uint16_t adcResults0[75000]                                                   // adcResults0 array

// ...

dmacdescriptor linked_descriptor __attribute__ ((aligned (16)));           // DMAC linked descriptor

void setup() {
// ...
  descriptor.descaddr = (uint32_t)&linked_descriptor;                         // Set up a circular descriptor to linked_descriptor
  descriptor.srcaddr = (uint32_t)&ADC0->RESULT.reg;                           // Take the result from the ADC0 RESULT register
  descriptor.dstaddr = (uint32_t)adcResults0 + sizeof(uint16_t) * 50000;      // Place it in the adcResults0 array
  descriptor.btcnt = 50000;                                                   // 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
  memcpy(&descriptor_section[0], &descriptor, sizeof(descriptor));            // Copy the descriptor to the descriptor section
  descriptor.descaddr = (uint32_t)&descriptor_section[0];                     // Set up a circular descriptor back to descriptor_secion[0]
  descriptor.srcaddr = (uint32_t)&ADC0->RESULT.reg;                           // Take the result from the ADC0 RESULT register
  descriptor.dstaddr = (uint32_t)&adcResults0[50000] + sizeof(uint16_t) * 25000;   // Place it in the adcResults0 array
  descriptor.btcnt = 25000;                                                   // 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 0 after block transfer
  memcpy(&linked_descriptor, &descriptor, sizeof(descriptor));                // Copy the descriptor to the linked descriptor 
// ...
}

Hey,

You are correct about BTCNT, forgot about it being 16 bits long. But ill play with triggermodes and ADC1 and try testing AUTOSTART with DMAC for a bit. But I think they couldve done a better job of explaining ADC1....

From CONTROL A (CTRLA) register in DUAL SEL:

0x0 BOTH Start event or software trigger will start a conversion on both ADCs
0x1 INTERLEAVE START event or software trigger will alternatingly start a conversion on ADC0 and
ADC1.
Note: The interleaved sampling is only usable in single conversion mode
(ADC.CTRLB.FREERUN=0).

Says itll "start a conversion on ADC0 AND ADC1"...So do I just software trigger ADC0,read result of ADC0.RESULT and ADC1.RESULT?.Or do I software trigger ADC0,read ADC0.RESULT,software trigger ADC0 again,read ADC1.RESULT, since it says it alternates...

Hey,

So I was able to get ADC1 running…incorrectly but running. RESULT gave same numbers between 250-350 even when pulling analog pin to GND.

And I tried using your post #4 code and altered it to just read A1,A2,and its REF to INCVTT0 (1/2 VDDANA). And changed BTCNT/adcResult to 65000. I tried just reading adcResult buffer in loop but noticed it skips alot,I tied A2 to GND as well and fed a sinewave into A1.

Id expect to see something like this: (example) A2 is tied to GND
A1: 2096
A2:15
A1:2100
A2:20

My guess is since DMAC is continously transfer data in background to buffer and then asking while inside the loop to display full adcResult buffer without knowing if adcResult is full is the issue.Is there a easy way to check and see when adcResult has 65000 results in it.

Heres code:

#include "Arduino.h"





// Use SAMD51's DMA Sequencing to read both A1 and A2 without CPU intervention
uint16_t adcResult[65000];                                  // A1 and A2 result array

uint32_t inputCtrl[] = { ADC_INPUTCTRL_MUXPOS_AIN5,     // ADC0 INPUTCTRL register MUXPOS settings AIN5 = A1, AIN6 = A2
                         ADC_INPUTCTRL_MUXPOS_AIN6
                       };


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 __attribute__ ((aligned (16)));           // DMAC linked descriptor


void setup() {
  Serial.begin(115200);                                                       // Start the native USB port



  while (!Serial);                                                            // Wait for the console to open

  
 



 


  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

  //DMAC channel 0 switches input channels, while channel 1 reads the data

  DMAC->Channel[0].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[0];                     // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)inputCtrl + sizeof(uint32_t) * 2;            // Configure the DMAC to set the inputs
  descriptor.dstaddr = (uint32_t)&ADC0->DSEQDATA.reg;                         // Write the INPUT CTRL
  descriptor.btcnt = 2;                                                       // 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[0], &descriptor, sizeof(descriptor));            // Copy the descriptor to the descriptor section

  DMAC->Channel[1].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)&descriptor_section[1];                     // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&ADC0->RESULT.reg;                           // Take the result from the ADC0 RESULT register
  descriptor.dstaddr = (uint32_t)adcResult + sizeof(uint16_t) * 65000;           // Place it in the adcResult array
  descriptor.btcnt = 65000;                                                       // 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
  memcpy(&descriptor_section[1], &descriptor, sizeof(descriptor));            // Copy the descriptor to the descriptor section

  ADC0->INPUTCTRL.bit.MUXPOS = 0x5;                   // Set the analog input to A1
  while (ADC0->SYNCBUSY.bit.INPUTCTRL);               // Wait for synchronization
  ADC0->SAMPCTRL.bit.SAMPLEN = 0x00;                  // Set max Sampling Time Length to half divided ADC clock pulse (2.66us)
  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->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV256;       // Divide Clock ADC GCLK by 256 (48MHz/256 = 187.5kHz)
  ADC0->CTRLB.reg = ADC_CTRLB_RESSEL_12BIT;           // Set ADC resolution to 12 bits
  while (ADC0->SYNCBUSY.bit.CTRLB);                   // Wait for synchronization
  ADC0->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_INTVCC0_Val;//  2.2297 V Supply VDDANA //ADC_REFCTRL_REFSEL_INT1V_Val;   // ref is 1 V band gap internal
  while (ADC0->SYNCBUSY.bit.REFCTRL);                   // Wait for synchronizationv

  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[0].CHCTRLA.bit.ENABLE = 1;            // Enable DMAC Sequencing on channel 0
  DMAC->Channel[1].CHCTRLA.bit.ENABLE = 1;            // Enable DMAC ADC on channel1
  delay(1000);                                           // Wait a millisecond





}

void loop()
{
//dump data to SD if adcResult is full
  for (int i = 0; i <= 65000; i++) {

    Serial.print("i = ");                       // Display the A1 result
    Serial.println(i);
    Serial.print("adcResult[i] = ");                       // Display the A1 result
    Serial.println(adcResult[i]);
  }
  
}

I can also post ADC1 code if youd like,doesnt work fully yet but compiles and ADC1 does have ADC0’s instances copied to it.

Hi geryuu123,

The following example code reads analog input signals on A4 and A5 (on Metro M4, A2 and A3 on the Feather M4), at a 50kHz sample rate, on both ADC0 and ADC1 in master/slave mode. ADC0 is read by DMAC channel 0, while ADC1 is read by channel 1.

Each DMAC channel uses two descriptors to copy two 512 half-word (16-bit) blocks into a corresponding 1024 half-word array. The descriptors are circular and continue filling alternate halves of the array. Meanwhile the DMAC's interrupt service routines for each channel continue to set the correponding flags, to allow the array half that isn't being filled to be displayed on the console.

I implemented the master/slave ADC combination as BOTH rather than INTERLEAVED, because INTERLEAVED would actually interleave the data in neighbouring array elements. This can be untangled by a subsequent transfer of the data to the SD card with custom code, but probably not by the SD card library. Using BOTH, each ADC can store its data in its own array.

The 50kHz sample rate is given by:

SAMPLE_RATE = (RESOLUTION + 1 + SAMPCTRL) / (ADC_GCLK / PRESCALER)

where:
RESOLUTION = 12-bit
SAMPCTRL = 2
ADC_GCLK = 48MHz
PRESCALER = 64

SAMPLE_RATE = (12 + 1 + 2) / (48MHz / 64) = 20us

1/20us = 50kHz

The oscilloscope output shows the sample rate 20us * 512 samples = 10.24ms, (note that I've increased the pulse widths to make them legible). The yellow channel is ADC0 on digital pin D10, while the light blue is ADC1 on digital pin D11:

Dual_ADC_Sampling.png

Here's the code:

Dual_ADC_Sampling.png

ADC_DMA_Sequencing8.ino (13.1 KB)

Hey,

Wow you went above and beyond. You didnt have to set it up for my specific application of 2 channels but you did and I thank you. I wouldve gotten there eventually, but I take little babysteps. And after going over your code I realized my errors for implementing ADC1, not only did I forget to implement the CNTRLB reg for ADC1(prescaler,freerun) but I was rather confused about setting up the GCLK. In the general description section in ADC of datasheet it says:

Each ADC requires a generic clock (GCLK_ADCx). This clock must be configured and enabled in the Generic Clock
Controller (GCLK) before using the ADC

But under the Master/slave description it says:

ADC1 will serve as a slave of ADC0 by writing a '1' to the Slave Enable bit in the Control A register of the ADC1
instance (ADC1.CTRLA.SLAVEEN). When enabled, GCLK_ADC0 clock and ADC0 controls are internally routed to
the ADC1 instance.

I wasnt sure if it meant each ADC needs it own GCLK,or if GCLK thats used for ADC0 is used for ADC1.

But errors aside, I tested your code and everything works as expected.If I tie ADC0 MUXPOS channel(A2 in my case) to GND I see exactly what I expected,value ranges from 0-60(just low values) in buffer.

But when I tie ADC1 MUXPOS channel(A1 in my case) to GND. I do see 0-60 values, but it is unstable and jumps all over place randomly all the way up to 3095.....

Im still trying to figure out why. I just tried:

REFCTRL- making sure ADC0 and ADC1 have same ref. In datasheet shows that you can setup REFCTRL for this.

DUALSEL - Making sure BOTH(0x0) was chosen

But cant figure out why ADC1 jumps around erratically..... Also note the only thing I changed in your code was what channels I used and tried adding REFCTRL and DUALSEL. I also tried your orginal chosen MUXPOS channels(A3 and A4 I think) with same results. Any MUXPOS I choose for ADC1 and tie to GND gives same erratic behavior.

Reading GCLK section in datasheet, PCHCTRL has a ADC0,ADC1 GCLK ID.....

Hi geryuu123,

Usually it's necessary to route the generic clock through to the peripherals, however Adafruit has already configured ADC0 and ADC1 in their core code, in the file "wiring.c". This connects ADC0 and ADC1 to GCLK1 operating at 48MHz. It also connects the negative input pin of the ADCs to GND.

As you mention, when the two ADCs are configured in master/slave mode ADC1 takes its GCLK from ADC0. The only other thing about master/slave mode is that both ADCs share ADC0's CTRLA register.

I've tested the code on both the Feather M4 and the Metro M4, tying the postive inputs to ground. I'm not seeing erratic values on either ADC. For the Feather I'm getting values around 4 to 7 and for the Metro around 10 to 15, which is sort of what I'd expect. On the Metro I'm using pins A4 and A5. On the Feather A2 and A3.

The erratic values sounds like the pin might be floating. Note that the SAMD51's analog pins: AIN0, AIN1, etc..., don't correspond to the Arduino analog pin numbers on the board: A0, A1, A2, etc... It's necessary to use the board's schematic diagram in conjuction with the Multiplexed Signals table in the SAMD51 datasheet to cross reference these connections.

Hey,

Yeah I was abit confused by wording of Master/slave section. And the schematic im using to determine which AIN PIN's are connected is: Adafruit Learning System

AIN5 - A1
AIN6 - A2

So in ADC setup:

ADC1->INPUTCTRL.bit.MUXPOS = 0x5; // Set the analog input to A1
ADC0->INPUTCTRL.bit.MUXPOS = 0x6; // Set the analog input to A2

Correct? All ive done to your code is just alter MUXPOS pins in code to A1,A2.

Hi geryuu123,

According to the Metro M4 schematic diagram, pins A1 and A2 are connected to port pins PA05 and PA06.

In the multiplexing signals table (section 6 of SAMD51 datasheet), PA05 and PA06 are connected to ADC0/AIN[5] and ADC0/AIN[6] respectively. Unfortuately, pin AIN5 isn’t connected to ADC1.

The analog input options on the Metro M4 are:

A0 - PA02 -ADC0/AIN[0]
A1 -PA05 - ADC0/AIN[5]
A2 - PA06 - ADC0/AIN[6]
A3 - PA04 - ADC0/AIN[4]
A4 -PB08 - ADC0/AIN[2] or ADC1/AIN[0]
A5 - PB09 - ADC0/AIN[3] or ADC1/AIN[1]

This leaves only A4 and A5 capable of providing an ADC1 input. (Other pins capable of providing an ADC1 input on the Metro M4 have been reserved for other uses or have not been broken out).

Hey,

Works as expected, thanks! When tying GND to either ADC0 or ADC1 I see what I expect,awesome!

And I thought I understood multiplex section but yeah…that isnt confusing whats so ever(sarcasm).I shoulda noted that their connected to PB09

"A4 -PB08 - ADC0/AIN[2] or ADC1/AIN[0]
A5 - PB09 - ADC0/AIN[3] or ADC1/AIN[1]"

But after testing and using AIN1 for ADC1 its A5 NOT what schematic of adafruit says,which says A5 is AIN3…then again im a dumb dumb.

But after confirming results I tried adding SD,and SdFat lib instances(ex:#include <SD.h>) and get errors:

libraries\Adafruit_ZeroDMA\Adafruit_ZeroDMA.cpp.o: In function `DMAC_4_Handler’:

Adafruit_ZeroDMA.cpp:(.text.DMAC_0_Handler+0x0): multiple definition of `DMAC_0_Handler’

sketch\SAMD51_2CH_DMA_SD_TEST_06072020.ino.cpp.o:SAMD51_2CH_DMA_SD_TEST_06072020.ino.cpp:(.text.DMAC_0_Handler+0x0): first defined here

Guess I need to dive into ZerDMA.cpp for a bit…unless you know of better way recording 2 adc channels storing into buffer and then dumping data to a SD card on Metro M4.

Hi geryuu123,

Cross referencing the all the different pins is both confusing and tedious. The SAMD21/SAMD51 datasheet is very good compared to some other microcontrollers, however some parts could benefit from clearer explanation.

It looks like Adafruit's SDFat library also uses the DMAC, if the definitions: USE_SAMD_DMA_RECV and USE_SAMD_DMA_SEND are non-zero:

/** Use Adafruit_ZeroDMA library if nonzero */
#define USE_SAMD_DMA_RECV 0
#define USE_SAMD_DMA_SEND 0

If the DMAC is activated, the library uses channels 0, 1 and 2:

// Three DMA channels are used. SPI DMA read requires two channels,
// one for the "dummy writes" to initiate SPI transfers, the other
// to read each incoming byte. Write requires only one channel, and
// there's probably ways to "recycle" the first of the read channels,
// but that'll take some work and is not done here, have plenty of
// channels to go around. Each channel uses one descriptor.
#define CHANNEL_READ_TX 0
#define CHANNEL_READ_RX 1
#define CHANNEL_WRITE   2

Therefore to avoid clashing with the library, just switch to DMAC channels 3 and 4. This also requires the DMAC interrupt service routines 3 and 4:

void DMAC_3_Handler() {}
void DMAC_4_Handler() {}

On the SAMD51 you've got 32 DMAC channels. Channels 0 to 3 have their own dedicated interrupt handler: DMAC_0_Handler(), DMAC_1_Handler(), etc..., while channels 4 to 31 share a single DMAC_4_Handler() function.