ItsyBitsy M4 DMAC ADC Compile Error

Hi All
I have been working with the Adafruit ItsyBitsy M4 Express (SamD51), trying to use the DMAC with ADC. The code that MartinL demostrated how to settup the ADC and DMAC ADC_DMA_Sequencing8.ino works on the M4. However, when I add any include files I get a compile error "exit status 1 Error compiling for board Adafruit ItsyBitsy M4 (SAMD51)."

I was wondering what I am doing wrong and has anyone seen this problem.

#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include "PCF8574.h"
#include <elapsedMillis.h>
#include <Adafruit_DotStar.h>
#include <XPT2046_Calibrated.h>
///////////////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////////////
PCF8574 PCF(0x38); //address according to NXP USA Inc. PCF8574AT
///////////////////////////////////////////////////////////////////////////////////////
// There is only one pixel on the board
// Use these pin definitions for the ItsyBitsy dotstar
///////////////////////////////////////////////////////////////////////////////////////
#ifdef SAMD51
#define DATAPIN 8
#define CLOCKPIN 6
#else
#define DATAPIN 41
#define CLOCKPIN 40
#endif
#define NUMPIXELS 1
Adafruit_DotStar strip(NUMPIXELS, DATAPIN, CLOCKPIN, DOTSTAR_BRG);

// Use SAMD51's DMAC to read the ADC0 and ADC1 on A4 and A5 (Metro M4 or A2 and A3 on Feather M4) respectively

volatile boolean results0Part0Ready = false;
volatile boolean results0Part1Ready = false;
volatile boolean results1Part0Ready = false;
volatile boolean results1Part1Ready = false;
uint16_t adcResults0[1024]; // ADC results array 0
uint16_t adcResults1[1024]; // 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

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

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)&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) * 512; // Place it in the adcResults0 array
descriptor.btcnt = 512; // 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)&adcResults0[512] + sizeof(uint16_t) * 512; // Place it in the adcResults1 array
descriptor.btcnt = 512; // 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[0], &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 DMAC Channel 0
NVIC_EnableIRQ(DMAC_0_IRQn); // Connect DMAC Channel 0 to Nested Vector Interrupt Controller (NVIC)

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

DMAC->Channel[1].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(ADC1_DMAC_ID_RESRDY) | // Set DMAC to trigger when ADC1 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 ADC1 RESULT register
descriptor.dstaddr = (uint32_t)adcResults1 + sizeof(uint16_t) * 512; // Place it in the adcResults1 array
descriptor.btcnt = 512; // 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 1 after block transfer
memcpy(&descriptor_section[1], &descriptor, sizeof(descriptor)); // Copy the descriptor to the descriptor section
descriptor.descaddr = (uint32_t)&descriptor_section[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[512] + sizeof(uint16_t) * 512; // Place it in the adcResults1 array
descriptor.btcnt = 512; // 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 1 after block transfer
memcpy(&linked_descriptor[1], &descriptor, sizeof(descriptor)); // Copy the descriptor to the descriptor section

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

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

ADC1->INPUTCTRL.bit.MUXPOS = ADC_INPUTCTRL_MUXPOS_AIN1_Val; // Set the analog input to A3
while(ADC1->SYNCBUSY.bit.INPUTCTRL); // Wait for synchronization
ADC1->SAMPCTRL.bit.SAMPLEN = 0x02; // Extend sampling time by SAMPCTRL ADC cycles (12 + 1 + 2)/750kHz = 20us = 50kHz
while(ADC1->SYNCBUSY.bit.SAMPCTRL); // Wait for synchronization
ADC1->CTRLB.reg = ADC_CTRLB_RESSEL_12BIT | // Set ADC resolution to 12 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 = ADC_INPUTCTRL_MUXPOS_AIN2_Val; // Set the analog input to A2
while(ADC0->SYNCBUSY.bit.INPUTCTRL); // Wait for synchronization
ADC0->SAMPCTRL.bit.SAMPLEN = 0x02; // Extend sampling time by SAMPCTRL ADC cycles (12 + 1 + 2)/750kHz = 20us = 50kHz
while(ADC0->SYNCBUSY.bit.SAMPCTRL); // Wait for synchronization
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.reg = ADC_CTRLA_PRESCALER_DIV64; // 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[0].CHCTRLA.bit.ENABLE = 1; // Enable DMAC channel 0
DMAC->Channel[1].CHCTRLA.bit.ENABLE = 1; // Enable DMAC channel 1
}

void loop()
{
if (results0Part0Ready) // Display the results in results0 array
{
Serial.println(F("Results0 Part0"));
for (uint32_t i = 0; i < 512; i++)
{
Serial.print(i);
Serial.print(F(": "));
Serial.println(adcResults0[i]);
}
Serial.println();
results0Part0Ready = false; // Clear the results0 ready flag
}
if (results0Part1Ready) // Display the results in results1 array
{
Serial.println(F("Results0 Part1"));
for (uint32_t i = 512; i < 1024; i++)
{
Serial.print(i);
Serial.print(F(": "));
Serial.println(adcResults0[i]);
}
Serial.println();
results0Part1Ready = false; // Clear the results1 ready flag
}
if (results1Part0Ready) // Display the results in results0 array
{
Serial.println(F("Results1 Part0"));
for (uint32_t i = 0; i < 512; i++)
{
Serial.print(i);
Serial.print(F(": "));
Serial.println(adcResults1[i]);
}
Serial.println();
results1Part0Ready = false; // Clear the results0 ready flag
}
if (results1Part1Ready) // Display the results in results1 array
{
Serial.println(F("Results1 Part1"));
for (uint32_t i = 512; i < 1024; i++)
{
Serial.print(i);
Serial.print(F(": "));
Serial.println(adcResults1[i]);
}
Serial.println();
results1Part1Ready = false; // Clear the results1 ready flag
}
}

void DMAC_0_Handler() // Interrupt handler for DMAC channel 0
{
static uint8_t count0 = 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 (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_1_Handler() // Interrupt handler for DMAC channel 0
{
static uint8_t count1 = 0; // Initialise the count
if (DMAC->Channel[1].CHINTFLAG.bit.SUSP) // Check if DMAC channel 0 has been suspended (SUSP)
{
DMAC->Channel[1].CHCTRLB.reg = DMAC_CHCTRLB_CMD_RESUME; // Restart the DMAC on channel 0
DMAC->Channel[1].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);
}
}

@oldfirmware Have you tried commenting out the library #include lines, (plus any initialisation lines of code), to see which one of them causes the compilation error?

Are all of the libraries SAMD51 compatible? I'd imagine the Adafruit ones are.

By the way, if you select your code in your post and click on the </> preformatted text button, it will it more readable.

Hello MartinL, Thank you for replying. Yes I started out by with no #includes, then added the SPI and it failed to compile. The same thing happened no matter what single include I used. The only clue I have is if I change the DMAC->Channel[0] to a different number. I also know I need to change the ISR's. This causes the ADC channel's to read incorrectly. I have caused this error on other the ItsyBitsy M0 when I exceeded the SRam capacity.
Thank you for any help you can offer
(First time posting thanks for the suggestion)

#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include "PCF8574.h"
#include "wiring_private.h" // pinPeripheral() function
#include <elapsedMillis.h>
#include <Adafruit_DotStar.h>
#include <XPT2046_Calibrated.h>
///////////////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////////////
PCF8574 PCF(0x38);      //address according to NXP USA Inc. PCF8574AT 
///////////////////////////////////////////////////////////////////////////////////////
//      There is only one pixel on the board
//      Use these pin definitions for the ItsyBitsy M4
///////////////////////////////////////////////////////////////////////////////////////
#ifdef __SAMD51__
#define DATAPIN     8
#define CLOCKPIN    6
#else
#define DATAPIN     41
#define CLOCKPIN    40
#endif
#define NUMPIXELS   1 
Adafruit_DotStar strip(NUMPIXELS, DATAPIN, CLOCKPIN, DOTSTAR_BRG);

Hello MartinL,
Looking closely at the error in verbose mode I found the following that points to duplicate defination. It seems that ADAFruit_ZeroDMA.ccp is loaded anytime I use their #include. I guess I could use your help with changing the DMAC Channels.

C:\Users\XXXXXXXXX\AppData\Local\Arduino15\packages\adafruit\hardware\samd\1.7.3\libraries\Adafruit_ZeroDMA/Adafruit_ZeroDMA.cpp:122: multiple definition of `DMAC_0_Handler'; 
sketch\ADC_DMA_Sequencing8.ino.cpp.o:C:\Users\.myname.\ADC_DMA_Sequencing8/ADC_DMA_Sequencing8.ino:194: first defined here

collect2.exe: error: ld returned 1 exit status

exit status 1

Error compiling for board Adafruit ItsyBitsy M4 (SAMD51).

@oldfirmware I'm just looking that the issue as well. As you mention, it looks as though Adafruit have integrated their AdaFruit_ZeroDMA library into SPI.

Like you suggest, I can get the code to compile by moving on to the DMAC channels 2 and 3 instead:

// Use SAMD51's DMAC to read the ADC0 and ADC1 on A4 and A5 (Metro M4 or A2 and A3 on Feather M4) respectively

#include <SPI.h>

volatile boolean results0Part0Ready = false;
volatile boolean results0Part1Ready = false;
volatile boolean results1Part0Ready = false;
volatile boolean results1Part1Ready = false;
uint16_t adcResults0[1024];                                  // ADC results array 0
uint16_t adcResults1[1024];                                  // 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

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
   
  DMAC->Channel[2].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) * 512;        // Place it in the adcResults0 array
  descriptor.btcnt = 512;                                                     // 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[2], &descriptor, sizeof(descriptor));            // Copy the descriptor to the descriptor section
  descriptor.descaddr = (uint32_t)&descriptor_section[2];                     // 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[512] + sizeof(uint16_t) * 512;  // Place it in the adcResults1 array
  descriptor.btcnt = 512;                                                     // 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[0], &descriptor, sizeof(descriptor));             // Copy the descriptor to the descriptor section

  NVIC_SetPriority(DMAC_2_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for DMAC Channel 3
  NVIC_EnableIRQ(DMAC_2_IRQn);         // Connect DMAC Channel 0 to Nested Vector Interrupt Controller (NVIC)
  
  DMAC->Channel[2].CHINTENSET.reg = DMAC_CHINTENSET_SUSP;                     // Activate the suspend (SUSP) interrupt on DMAC channel 2
  
  DMAC->Channel[3].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(ADC1_DMAC_ID_RESRDY) |  // Set DMAC to trigger when ADC1 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 ADC1 RESULT register
  descriptor.dstaddr = (uint32_t)adcResults1 + sizeof(uint16_t) * 512;        // Place it in the adcResults1 array
  descriptor.btcnt = 512;                                                     // 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 1 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)&ADC1->RESULT.reg;                           // Take the result from the ADC0 RESULT register
  descriptor.dstaddr = (uint32_t)&adcResults1[512] + sizeof(uint16_t) * 512;  // Place it in the adcResults1 array
  descriptor.btcnt = 512;                                                     // 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 1 after block transfer
  memcpy(&linked_descriptor[1], &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 1 to Nested Vector Interrupt Controller (NVIC)
  
  DMAC->Channel[3].CHINTENSET.reg = DMAC_CHINTENSET_SUSP;                     // Activate the suspend (SUSP) interrupt on DMAC channel 0
  DMAC->Channel[3].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL0;
  
  ADC1->INPUTCTRL.bit.MUXPOS = ADC_INPUTCTRL_MUXPOS_AIN1_Val;   // Set the analog input to A3
  while(ADC1->SYNCBUSY.bit.INPUTCTRL);                // Wait for synchronization
  ADC1->SAMPCTRL.bit.SAMPLEN = 0x02;                  // Extend sampling time by SAMPCTRL ADC cycles (12 + 1 + 2)/750kHz = 20us = 50kHz 
  while(ADC1->SYNCBUSY.bit.SAMPCTRL);                 // Wait for synchronization
  ADC1->CTRLB.reg = ADC_CTRLB_RESSEL_12BIT |          // Set ADC resolution to 12 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 = ADC_INPUTCTRL_MUXPOS_AIN2_Val;     // Set the analog input to A2
  while(ADC0->SYNCBUSY.bit.INPUTCTRL);                // Wait for synchronization
  ADC0->SAMPCTRL.bit.SAMPLEN = 0x02;                  // Extend sampling time by SAMPCTRL ADC cycles (12 + 1 + 2)/750kHz = 20us = 50kHz
  while(ADC0->SYNCBUSY.bit.SAMPCTRL);                 // Wait for synchronization  
  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.reg = ADC_CTRLA_PRESCALER_DIV64;        // 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
}

void loop() 
{  
  if (results0Part0Ready)                                  // Display the results in results0 array
  {
    Serial.println(F("Results0 Part0"));
    for (uint32_t i = 0; i < 512; i++)
    {
      Serial.print(i);
      Serial.print(F(": "));
      Serial.println(adcResults0[i]);
    }
    Serial.println();
    results0Part0Ready = false;                            // Clear the results0 ready flag
  }
  if (results0Part1Ready)                                  // Display the results in results1 array
  {
    Serial.println(F("Results0 Part1"));
    for (uint32_t i = 512; i < 1024; i++)
    {
      Serial.print(i);
      Serial.print(F(": "));
      Serial.println(adcResults0[i]);
    }
    Serial.println();
    results0Part1Ready = false;                            // Clear the results1 ready flag
  }
  if (results1Part0Ready)                                  // Display the results in results0 array
  {
    Serial.println(F("Results1 Part0"));
    for (uint32_t i = 0; i < 512; i++)
    {
      Serial.print(i);
      Serial.print(F(": "));
      Serial.println(adcResults1[i]);
    }
    Serial.println();
    results1Part0Ready = false;                            // Clear the results0 ready flag
  }
  if (results1Part1Ready)                                  // Display the results in results1 array
  {
    Serial.println(F("Results1 Part1"));
    for (uint32_t i = 512; i < 1024; i++)
    {
      Serial.print(i);
      Serial.print(F(": "));
      Serial.println(adcResults1[i]);
    }
    Serial.println();
    results1Part1Ready = false;                            // Clear the results1 ready flag
  }
}

void DMAC_2_Handler()                                           // Interrupt handler for DMAC channel 0
{
  static uint8_t count0 = 0;                                    // Initialise the count 
  if (DMAC->Channel[2].CHINTFLAG.bit.SUSP)                      // Check if DMAC channel 0 has been suspended (SUSP) 
  {  
    DMAC->Channel[2].CHCTRLB.reg = DMAC_CHCTRLB_CMD_RESUME;     // Restart the DMAC on channel 0
    DMAC->Channel[2].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_3_Handler()                                           // Interrupt handler for DMAC channel 0
{
  static uint8_t count1 = 0;                                    // Initialise the count 
  if (DMAC->Channel[3].CHINTFLAG.bit.SUSP)                      // Check if DMAC channel 0 has been suspended (SUSP) 
  {  
    DMAC->Channel[3].CHCTRLB.reg = DMAC_CHCTRLB_CMD_RESUME;     // Restart the DMAC on channel 0
    DMAC->Channel[3].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);
  }
}

Thank you for your quick reply. I will try the suggested code.

That worked with all the includes. Thank you again for your assistance.

MartinL
Forgot to include your name in my Thank you's

Hello MartinL
Well, I through this would work. The code runs only one time through. I did the instantiate of the
Adafruit_ILI9341 and the XPT2046_Calibrated.

#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <PCF8574.h>
#include "TestData.h"
#include <wiring_private.h> // pinPeripheral() function
#include <elapsedMillis.h>
#include <Adafruit_DotStar.h>
#include <XPT2046_Calibrated.h>
//#include <XPT2046_Touchscreen.h>
///////////////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////////////
PCF8574 PCF(0x38);      //address according to NXP USA Inc. PCF8574AT 
///////////////////////////////////////////////////////////////////////////////////////
//      There is only one pixel on the board
//      Use these pin definitions for the ItsyBitsy M4
///////////////////////////////////////////////////////////////////////////////////////
#ifdef __SAMD51__
#define DATAPIN     8
#define CLOCKPIN    6
#else
#define DATAPIN     41
#define CLOCKPIN    40
#endif
#define NUMPIXELS   1 
Adafruit_DotStar strip(NUMPIXELS, DATAPIN, CLOCKPIN, DOTSTAR_BRG);
///////////////////////////////////////////////////////////////////////////////////////
//      For the Adafruit shield, these are the default.
///////////////////////////////////////////////////////////////////////////////////////
#define SPI_PERIPHERAL        SPI
#define TFT_CS     13
#define TFT_DC     12
#define TFT_RST    2
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
///////////////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////////////
#define CS_PIN  9
#define TIRQ_PIN  7
//XPT2046_Touchscreen ts(CS_PIN, TIRQ_PIN);  // Param 2 - Touch IRQ Pin - interrupt enabled polling
XPT2046_Calibrated ts(CS_PIN, TIRQ_PIN);  // Param 2 - Touch IRQ Pin - interrupt enabled polling
static uint16_t const SCREEN_WIDTH    = 320;
static uint16_t const SCREEN_HEIGHT   = 240;
static uint8_t  const SCREEN_ROTATION = 1U;

enum class PointID { NONE = -1, A, B, C, COUNT };

// source points used for calibration
static TS_Point _screenPoint[] = {
  TS_Point( 8,  7),   // point A
  TS_Point(312, 153), // point B
  TS_Point(197, 225)  // point C
};

#define VERIFY_CALIBRATION

// touchscreen points used for calibration verification
static TS_Point _touchPoint[] = {
  TS_Point(3779, 3711), // point A
  TS_Point( 382, 1596), // point B
  TS_Point(1666,  446), // point C
};

static TS_Calibration cal(
    _screenPoint[(int)PointID::A], _touchPoint[(int)PointID::A],
    _screenPoint[(int)PointID::B], _touchPoint[(int)PointID::B],
    _screenPoint[(int)PointID::C], _touchPoint[(int)PointID::C],
    SCREEN_WIDTH,
    SCREEN_HEIGHT
);
///////////////////////////////////////////////////////////////////////////////////////

Then uncommented the following respective begin.---() statments

////////////////////////////////////////////////////////////////////////////////////////////////////////
//      Uncomment the following two lines and the DMAC will only run once will only run time    
//    tft.begin();
//    ts.begin();

and I get one Pass through the code.
My request is can you duplicate it?
Thank you for your time.

@oldfirmware I think the issue is that there's a conflict, both DMAC code and the Adafruit SPI library are attempting to control the descriptor base and write-back addresses.

You could try commenting out the lines:

//DMAC->BASEADDR.reg = (uint32_t)descriptor_section;                          // Specify the location of the descriptors
//DMAC->WRBADDR.reg = (uint32_t)wrb;

Then replace descriptor_section[] entries with Adafruit's _descriptor[] array (defined in their Adafruit_ZeroDMA.cpp file).

Here's Adafruit's definition (from Adafruit_ZeroDMA.cpp):

// DMA descriptor list entry point (and writeback buffer) per channel
__attribute__((__aligned__(16))) static DmacDescriptor ///< 128 bit alignment
    _descriptor[DMAC_CH_NUM] SECTION_DMAC_DESCRIPTOR,  ///< Descriptor table
    _writeback[DMAC_CH_NUM] SECTION_DMAC_DESCRIPTOR;   ///< Writeback table

Note that this will require SPI.h to be included.

Hi MartinL thanks again, I will try that. I removed the SPI.h thinking of a conflict. Did not think that I needed to use their descriptor, but it makes sense.
Again thank you for your help and your time.

Hi @oldfirmware, did you ever manage to get custom DMA code to be compatible with Arduino's SPI library? did @MartinL most recent suggestion solve it for you? I'd be very interested in any solutions you found.

I wanted to mention to anyone else who stumbles on this thread that the solution I ended up with is realizing that Arduino's SPI library falls back on a non-DMA implementation of SPI.

There is this comment in the dmaAllocate(void) function:
// NOT FATAL if channel or descriptor allocation fails.
// transfer() function will fall back on a manual byte-by-byte loop.

So if you for instance commend out the entirety of dmaAllocate, then transfer() will send data byte by byte and you use your custom DMA code.

Hi jmc1228
Yes I have ADAFruit ZeroDMA working! Err...Kinda. I took some of the code that MartinL and spliced it into the ZeroDMA code using their add.Descriptor. I have two channels of DMA and Two ADC running. However, at this time I have not been able to verify the speed of the analog read. I have moved on to trying to get the Analog Comparator to generate an interrupt. The test would be to feed a known frequency and be able to sink in the incoming signal. The following is a snip-it of the code.

void dma_init() {
    static Adafruit_ZeroDMA Top_Ch_dma, Bottom_Ch_dma, out_dma;
    {   Top_Ch_dma.allocate();
        auto desc = Top_Ch_dma.addDescriptor(
                    (void *)(&ADC0->RESULT.reg),
                    adc_buffer[0],
                    SAMPLE_BLOCK_LENGTH,
                    DMA_BEAT_SIZE_HWORD,
                    false,
                    true);
        desc->BTCTRL.bit.BLOCKACT = DMA_BLOCK_ACTION_INT;

        desc = Top_Ch_dma.addDescriptor(
                   (void *)(&ADC0->RESULT.reg),
                   &adc_buffer[0][SAMPLE_BLOCK_LENGTH],
                   SAMPLE_BLOCK_LENGTH,
                   DMA_BEAT_SIZE_HWORD,
                   false,
                   true);
        desc->BTCTRL.bit.BLOCKACT = DMA_BLOCK_ACTION_INT;
        Top_Ch_dma.loop(true);
        Top_Ch_dma.setTrigger(0x44);
        Top_Ch_dma.setAction(DMA_TRIGGER_ACTON_BEAT);
        Top_Ch_dma.setCallback(Top_Ch_complete);
        Top_Ch_dma.startJob();
    }//--------------------------------------------------------
    {   Bottom_Ch_dma.allocate();
        auto desc = Bottom_Ch_dma.addDescriptor(
                    (void *)(&ADC1->RESULT.reg),
                    adc_buffer[1],
                    SAMPLE_BLOCK_LENGTH,
                    DMA_BEAT_SIZE_HWORD,
                    false,
                    true);
        desc->BTCTRL.bit.BLOCKACT = DMA_BLOCK_ACTION_INT;
        desc = Bottom_Ch_dma.addDescriptor(
                   (void *)(&ADC1->RESULT.reg),
                   &adc_buffer[1][SAMPLE_BLOCK_LENGTH],
                   SAMPLE_BLOCK_LENGTH,
                   DMA_BEAT_SIZE_HWORD,
                   false,
                   true);
        desc->BTCTRL.bit.BLOCKACT = DMA_BLOCK_ACTION_INT;
        Bottom_Ch_dma.loop(true);
        Bottom_Ch_dma.setTrigger(0x46);
        Bottom_Ch_dma.setAction(DMA_TRIGGER_ACTON_BEAT);
        Bottom_Ch_dma.setCallback(Bottom_Ch_complete);
        Bottom_Ch_dma.startJob();
    }//--------------------------------------------------------
    {   out_dma.allocate();
        auto desc = out_dma.addDescriptor(
                        dac_buffer,
                        (void *)(&DAC->DATA[0]),
                        4096,
                        DMA_BEAT_SIZE_HWORD,
                        true,
                        false);
        desc->BTCTRL.bit.BLOCKACT = DMA_BLOCK_ACTION_INT;
        out_dma.loop(true);
        out_dma.setTrigger(0x16);
        out_dma.setAction(DMA_TRIGGER_ACTON_BEAT);
        out_dma.startJob();
    }
}
/////////////////////////////////////////////////////////////////////////////////////////
void dac_init() {
    // Disable DAC
    DAC->CTRLA.bit.ENABLE = 0;
    while (DAC->SYNCBUSY.bit.ENABLE || DAC->SYNCBUSY.bit.SWRST);

    // Use an external reference voltage (see errata; the internal reference is busted)
    DAC->CTRLB.reg = DAC_CTRLB_REFSEL_VREFPB;
    while (DAC->SYNCBUSY.bit.ENABLE || DAC->SYNCBUSY.bit.SWRST);

    // Enable channel 0
    DAC->DACCTRL[0].bit.ENABLE = 1;
    while (DAC->SYNCBUSY.bit.ENABLE || DAC->SYNCBUSY.bit.SWRST);

    // Enable DAC
    DAC->CTRLA.bit.ENABLE = 1;
    while (DAC->SYNCBUSY.bit.ENABLE || DAC->SYNCBUSY.bit.SWRST);
}
//const (
//    A0  = PA02  // ADC/AIN[0]
//    A1  = PA05  // ADC/AIN[2]
//    A2  = PB08  // ADC/AIN[3]
//    A3  = PB09  // ADC/AIN[4]
//    A4  = PA04  // ADC/AIN[5]
//    A5  = PA06  // ADC/AIN[10]
//)
//
//////////////////////////////////////////////////////////////////////////////////////////
void    init_ADC() {
////    Bottom Display    
    ADC1->INPUTCTRL.bit.MUXPOS = ADC_INPUTCTRL_MUXPOS_AIN1_Val;   // Set the analog input to A3 OK
    while (ADC1->SYNCBUSY.bit.INPUTCTRL);               // Wait for synchronization
    ADC1->SAMPCTRL.bit.SAMPLEN = 0x02;                  // Extend sampling time by SAMPCTRL ADC cycles (12 + 1 + 2)/750kHz = 20us = 50kHz
    while (ADC1->SYNCBUSY.bit.SAMPCTRL);                // Wait for synchronization
    ADC1->CTRLB.reg = ADC_CTRLB_RESSEL_8BIT |           // Set ADC resolution to 12 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
/////   Top Display
    ADC0->INPUTCTRL.bit.MUXPOS = ADC_INPUTCTRL_MUXPOS_AIN2_Val;     // Set the analog input to A2 OK
//    ADC0->INPUTCTRL.bit.MUXPOS = ADC_INPUTCTRL_MUXPOS_AIN4_Val;     // Set the analog input to A4 OK
//    ADC0->INPUTCTRL.bit.MUXPOS = ADC_INPUTCTRL_MUXPOS_AIN6_Val;     // Set the analog input to A5 OK
    while (ADC0->SYNCBUSY.bit.INPUTCTRL);               // Wait for synchronization
    ADC0->SAMPCTRL.bit.SAMPLEN = 0x02;                  // Extend sampling time by SAMPCTRL ADC cycles (12 + 1 + 2)/750kHz = 20us = 50kHz
    while (ADC0->SYNCBUSY.bit.SAMPCTRL);                // Wait for synchronization
    ADC0->CTRLB.reg = ADC_CTRLB_RESSEL_8BIT |           // 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.reg = ADC_CTRLA_PRESCALER_DIV64;        // 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
}`

Hi @oldfirmware

The speed of the analogRead() is determined by the ADC's mode, resolution, generic clock source, sample control register (SAMPCTRL) and prescaler.

In the example code using the the ADC in freerun mode, 12-bit resolution, 48MHz generic clock source, sample control = 0x02 and divide by 64 prescaler:

ADC sample time= (resolution + ADC mode + SAMPCTRL) * prescaler / generic clock source

The ADC in freerun mode adds an additional clock cycle per conversion:

ADC sample time = (12 + 1 + 2) * 64 / 48MHz = 20us

or in other words, 50,000 samples per second.

You can see it verified with the similar code at the bottom of this post: https://forum.arduino.cc/t/samd51-multiplexer-read-issue/899610/7.

The OP required a sample rate of 200kHz, which simply required the prescaler to be changed (from your code) to divide by 16. The ADCs were tested with a 1kHz sine wave input with the results being sent to the Arduino IDE's Serial Plotter.

At a 200kHz sample rate a 1kHz sine wave will be sampled 200 times over its period. From the serial plotter output, you can see that start of the sine wave period is sample 3721 and that it ends around 3921, which is appears to be around 200 samples. You could get a more accurate indication by analysing the results from the console.

Hi @jmc1228

The issue here, is simply that the Ardafruit's SPI functionality has been enhanced to include DMA transfer using their Adafruit_ZeroDMA wrapper library.

The Adafruit_ZeroDMA library naturally assumes control of the SAMD51's DMAC registers, but this can conflict with any manual register setting performed outside the library itself.

The solution is to take advantage of Adafruit_ZeroDMA and allowing it to initialise the DMAC. It should then be possible to use DMAC registers directly, provided you keep out of the library's way by selecting higher DMAC channels that are unlikely to be used. The SAMD51 has 32 DMAC channels in total.

Speed verification is related to code correctly developed.

@Jmc1228
The reason I had to use Adafruit_ZeroDMA library because of the following includes

#include "SPI.h"
#include <Adafruit_ZeroDMA.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <PCF8574.h>
#include "TestData.h"
#include <wiring_private.h> // pinPeripheral() function
#include <elapsedMillis.h>
#include <Adafruit_DotStar.h>
#include <XPT2046_Calibrated.h>
#include "SAMD51_InterruptTimer.h"