Setting up ISR for ADC on Zero

Hey!

As usual you bet me to it, thank you once again. And ive been using SAMD21 for like 3 years now and still find it confusing at times,and if you say SAMD21/51 datasheets are more digestible…jeezz I could only imagine other datasheets for micro-controllers with 1500+ pages that im not accustomed too…

But I have changed DMAC channels to 3,4 and DMAC handlers to 3,4 as well. I only included SD and then commented out SD ,and added SdFat at top as test to check if it compiles. And it compiles!

EDIT: What I have working so far.Using SD lib. When adcResultsXpartX is read, I create a new file,write data to SD in for loop ,close file.I get results what I expect when tying a A4 or A5 to GND and feeding a sinvewave into other input.Awesome! But this creates 4 files.

I tried creating/opening 1 file when adcResults0part0 starts ,write data to SD until adcReslts1part1 ,and close file when adcResults1part1 ends, but no luck.

SAMD51_DMAC_2CH_SDFAT_TEST2_06082020.ino (13.3 KB)

Hi geryuu123,

…jeezz I could only imagine other datasheets for micro-controllers with 1500+ pages that im not accustomed too…

After attempting to decipher the STM32 datasheets, I never complain about the SAMD documentation.

I guess it’s now a matter of integrating the ADC/DMAC code with the SDFat library and going through the operation of synchronizing them.

It should be the ADC/DMAC input that is driving your system. This means that reading the ADC should be considered a high priority task, while writing to the SD card should be lower. Fortunately, the DMAC has an arbiter to process higher priority tasks over lower ones.

By default the DMAC arbiter processes lower numbered channels over higher numbered ones, channel 0 is the highest, channel 32 is the lowest. However, this is the opposite of what you require, because the SDFat library uses channels 0, 1 and 2, while the ADC/DMAC uses 3 and 4. To swap channel priority it’s necessary to set the priority levels for each channel using Channel Priority Level (CHPRILVLx) registers. There’s one CHPRILVLx register per channel.

Talking of datasheets, according to the SAMD51’s, each DMAC channel has four priority levels: 0 lowest and 3 highest, (note that this is confusingly opposite from the default channel priority), although in the register CMSIS definitions for the SAMD51 there are seven levels. I guess for now we’ll assume there’s four (0 lowest to 3 highest).

First it’s necessary to activate the priority levels, to activate all four priority levels:

DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);                // Enable the DMAC peripheral

This has already been included in the ADC/DMAC code example.

Next, to change the individal DMAC channel priority for example on channel 3 and 4 to level 3 (highest):

DMAC->Channel[3].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL3;
DMAC->Channel[4].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL3;

This should be included in the setup() portion of your sketch.

This will place the ADC/DMAC channels at a higher level than the SDFat library. This causes the arbiter to suspend any DMAC SD card transfer when the ADC requires servicing, placing the SD card context into the write back descriptor as a pending channel. The pending channel is processing once the ADC transfer has completed, assuming that isn’t preempted again.

I’ve checked Adafruit’s SDFat library and it doesn’t appear that they set the priority of their channels using the Adrafruit_ZeroDMA wrapper library function: setPriority(), therefore these channels should be at priority level 0 (lowest). In any case, there’s nothing to stop you from setting them to 0 if they’re not.

Hey,

After re-reading DMAC section of datasheet, CHPRILVL for DMAC channels 3,4 can be changed to 0x3(highest priority)...but when trying to place:

DMAC->CHPRILVL[3].reg = DMAC_CHPRILVL_PRILVL_LVL3;
DMAC->CHPRILVL[4].reg = DMAC_CHPRILVL_PRILVL_LVL3;

error I get when compiling: 'struct Dmac' has no member named 'CHPRILVL'....

I tried going into folder but got lost,to try and see what they define for CHPRILVL.But its odd since this is a register. Dont understand why this wouldnt work.

Hi geryuu123,

My apologies, I got my syntax a bit mixed up, it should be:

DMAC->Channel[3].CHPRILVL.reg = DMAC_CHPRILEVL_PRILVL_LVL3;
DMAC->Channel[4].CHPRILVL.reg = DMAC_CHPRILEVL_PRILVL_LVL3;

I've corrected the code in the post above.

Hey,

Yeah I was trying to DMAC->CHPRILVL3.reg…but I tried your correction:

DMAC->Channel[3].CHPRILVL.reg = DMAC_CHPRILEVL_PRILVL_LVL3;
DMAC->Channel[4].CHPRILVL.reg = DMAC_CHPRILEVL_PRILVL_LVL3;

Still same error, so I just:

DMAC->Channel[3].CHPRILVL.reg = 0x3;
DMAC->Channel[4].CHPRILVL.reg = 0x3;

Since accoridng to datasheet 0x3 is highest priority…

And seems to work now, and compiles. So my next question would be whats the best way to write to SD card? As of right now, everytime a buffer is read/displayed I create a new file and close file once buffer is finished.

This creates 4 files.Works, but I think itd be better to have 1 file for the 4 buffers combined. I tried this but for some reason it hangs. In future id like to be able to to have 0.5 seconds worth of data dumped to SD.I uploaded my current version of attempting 1 file for all 4 buffers.And note I used SD lib, I plan on testing SdFat later today, I know SdFat is better but just wanted to do quick test with SD lib.

EDIT:Got SdFat to initialize SD card finally. Had to lower SPI speed down to 4mhz instead of default 50mhz…good hour of time debugging.

SAMD51_DMAC_2CH_SD_TEST06092020.ino (16.7 KB)

Hi geryuu123,

Sorry, I should have pushed it throught the compiler, the corrected and compiled lines are:

DMAC->Channel[3].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL3;
DMAC->Channel[4].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL3;

The register definitions for all the SAMD51 peripherals are (on my Windows machine) at the following location:

C:UsersComputerAppDataLocalArduino15packagesarduinotoolsCMSIS-Atmel1.2.0CMSISDeviceATMELsamd51includecomponent

The DMAC definitions are (unsuprisingly) stored in the "dmac.h" file.

Hey,

Thank you, I always get lost in the folders and get frustrated. Its about a 50/50 chance I find the defines im looking for, for anything.....

Hey,

So after running into issues of code hanging when writing to SD I went back and double-checked DMAC changes to channels 3,4 and changing interrupt handlers() into your “ADC_DMA_Sequencing8” code and and same issue arises. I uploaded your altered code of “ADC_DMA_Sequencing8”. There isnt SD stuff or anything,just changed setup of DMAC and handlers.

For me it reads and prints results0part0 and results1part0 and then stops acquiring.Any ideas? I thought I setup DMAC channels correctly

ADC_DMA_Sequencing8_Altered_DMAC_Channels.ino (13.2 KB)

Hi geryuu123,

I downloaded your “ADC_DMA_Sequencing_Altered_DMAC_Channels.ino” file, uncommented the CHPRILVL lines and ran it.

I’m unable to reproduce the issue. I left the board acquiring data for around 15 minutes and it appeared to work just fine.

One issue is that the SAMD51’s native USB port is unable to keep up with the amount of data produced by the ADC/DMAC channels. I suspected this because the sequencing goes: Result0Part0, Result0Part1, Result1Part0, Result1Part1, rather than the expected: Result0Part0, Result1Part0, Result0Part1, Result1Part1. Testing by inserting the micros() function into the 4 display routines confirms this is the case:

if (results0Part0Ready)                                  // Display the results in results0 array
  {
    Serial.println(F("Results0 Part0"));
    Serial.println(micros());
    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
  }

However, the oscilloscope output also confirms that the ADC/DMAC combination is working correctly, calling the ISRs at the right time (every 10.24ms). The data is there, the challenge is how to extract the 2048 bytes (512 * 2 bytes (16-bit half word) * 2 channels) of data in 10.24ms?

Hey,

So the only way I got it working normally was uinstalling Arduino and Arduino15 and re-installing and it works…no idea why,and I dont care lol. I tried this as a last ditch effort.

But extracting the data in 10.24ms is a challenge…

But before I fixed my issue I made INTERLEAVE mode work as a test.And this is w/o DMA. Tried to make it as simple as possible. It works but have a few questions:

The SD results are rough(see picture),just fed a 1kHz sinewave into 1 of channels,unfortunately clipping. But I currently have setup:
DUALSEL = 0x1
PRESCALER set to 32. So 48MHz/32= 1.5MHz(to high for ADC,but works)
SAMPLEN=0x00

But if I try PRESCALER at 64 and SAMPLEN 0x2 the roughness of signals are even worse.What would the best solution be? Averaging? Or did I set this up wrong? It works but unfortunately I dont have my oscilloscope with me,currently out of town, and only have a function gen. So I dump data to SD and read it in matlab.

Code is below.Got rid of SD stuff so its simpler to read.

And another question is in ADC setup of DUALSEL. I couldnt figure out if I use ADC0.CTRLA or ADC1.CTRLA when turning on DUALSEL.So I just put both lol.It compiles.

//setup channel bool
bool ch = 0;



uint16_t adcResults0[30000];                                  // ADC results array 0
uint16_t adcResults1[30000];                                  // ADC results array 1
uint16_t val0;
uint16_t val1;
uint16_t ind0 = 0;
uint16_t ind1 = 0;





void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);                                                       // Start the native USB port
  while (!Serial);                                                            // Wait for the console to open




  //ADC SETUP

  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 = 0x00;                  // Extend sampling time by SAMPCTRL ADC cycles
  while (ADC1->SYNCBUSY.bit.SAMPCTRL);                // Wait for synchronization
  ADC1->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 (ADC1->SYNCBUSY.bit.REFCTRL);                   // Wait for synchronizationv
  ADC1->CTRLB.reg = ADC_CTRLB_RESSEL_12BIT;                // Set ADC to 12bit
  while (ADC1->SYNCBUSY.bit.CTRLB);                   // Wait for synchronization
  ADC1->CTRLB.bit.FREERUN = 0X00;               // Set ADC to single run mode
  while (ADC1->SYNCBUSY.bit.CTRLB);                   // Wait for synchronizationv
  ADC1->CTRLA.bit.SLAVEEN = 1;                        // Set ADC1 to slave, ADC0 to master, both share CTRLA register


  ADC0->CTRLA.bit.DUALSEL  = 0x1;                      //Interleave on
  ADC1->CTRLA.bit.DUALSEL  = 0x1;                      //Interleave on

  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 = 0x00;                  // Extend sampling time by SAMPCTRL ADC cycles
  while (ADC0->SYNCBUSY.bit.SAMPCTRL);                // 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);
  ADC0->CTRLB.reg = ADC_CTRLB_RESSEL_12BIT;                // Set ADC to 12bit
  while (ADC0->SYNCBUSY.bit.CTRLB);                   // Wait for synchronization
  ADC0->CTRLB.bit.FREERUN = 0X00;               // Set ADC to single run mode
  while (ADC0->SYNCBUSY.bit.CTRLB);                   // Wait for synchronization
  ADC0->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV32;        // Divide Clock ADC GCLK by 32 (48MHz/32 = 1.5MHz)
  ADC0->CTRLA.bit.ENABLE = 1;                         // Enable the ADC
  while (ADC0->SYNCBUSY.bit.ENABLE);                  // Wait for synchronization

  delay(1000);


}

void loop() {


  // DATASHEET INTERLEAVED Section -> START event or software trigger will alternatingly start a conversion on ADC0 and ADC1 in INTERLEAVE mode.

  ADC0->SWTRIG.bit.START = 1; //start adc conversion
  while (ADC0->SYNCBUSY.bit.SWTRIG);                  // Wait for synchronization

  //If ch=1,read result of ADC1
  if (ch == 1) {
    val1 = ADC1->RESULT.reg;                                        //copy data to variable
    adcResults1[ind1++] = val1;                                     // ADC results array 1


  }
  //if ch=0,read result of ADC0
  if (ch == 0) {
    val0 = ADC0->RESULT.reg;                                        //copy data to variable
    adcResults0[ind0++] = val0;                                     // ADC results array 0


  }
  ch = !ch;                                                         //swap which adc is next to be sampled



  //If buffers are full dump to SD
  if ((ind1 >= 30000) && (ind0 >= 30000)) {

    //SD code
    //create new file
    //use for loop and dump to SD

    //RESET index's of buffers
    ind1 = 0;
    ind0 = 0;


  }

}

Hi geryu123,

The SD results are rough(see picture),just fed a 1kHz sinewave into 1 of channels,unfortunately clipping.

Looking at your "interleave2.PNG" image, the ADC range from 0 to 4095 is right for 12-bit resolution, however the amplitude appears too high, hence the clipping.

Might I ask what the amplitude and DC offset voltage of the sine wave input is?

As you're using the ADC's 2.2297V voltage reference, the input sine wave should be no greater than this and at a DC offset of around half at 1.15V.

Hey,

I currently have a op-amp setup with a virtual GND of 2.2V. So I dont have to set a offset on my function generator.I have a few times accidentally forget to set a DC offset and fried boards. Which is why I built a simple circuit.I can control the gain with a digi-pot but even at lowest gain a 4mV(the lowest amplitude my generator can go) 1kHz sinewave clips.I do plan on trying using a function gen app for phone but I dont have an extra pair of headsets to destroy.

And I forgot I dropped baseline to 0 in Matlab by subtracting 2200 from all values in buffer.

Another holdback is not having my oscilloscope.

But the reason I thought I mightve setup INTERLEAVE wrong was because when using your DMAC code,sinewaves were nice and smooth.

Hey,

Believe I have fixed it!

I was reading through datasheet and forgot INTFLAG is set when a conversion is ready,so by adding this to main loop:

ADC0->SWTRIG.bit.START = 1; //start adc conversion
  while (ADC0->SYNCBUSY.bit.SWTRIG);                  // Wait for synchronization
  while (!ADC0->INTFLAG.bit.RESRDY); //wait for adc data to be ready

Uploaded pic as well,but some recordings will have random jitter…need to figure out why.Guess is bc I have ADC set to high in setup.

Hi geryuu123,

From your waveform image, it still looks as though the input voltage is exceeding the 0V to 2.23V analog input range.

It might be better to use the SAMD51's DAC to generate an sine wave test output. That way there's no chance of applying voltage levels that exceed the ADC input limits.

Here's some example code that uses the DAC, TCC0 timer and the DMAC to generate a 1kHz sine wave with an amplitude of 0V to 2.2V on analog pin A0:

// SAMD51: Output 1kHz sine wave 0V to 2.2V amplitude on analog pin A0 using the DAC and DMAC
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
volatile dmacdescriptor descriptor_section[DMAC_CH_NUM] __attribute__ ((aligned (16)));    // DMAC channel descriptors
dmacdescriptor descriptor __attribute__ ((aligned (16)));                                  // Place holder descriptor

void setup()
{
  for (uint16_t i = 0; i < 256; i++)                                // Calculate the sine table with 256 entries
  {
    sintable[i] = (uint16_t)((sinf(2 * PI * (float)i / 256) * 1339) + 1340);
  }

  analogWriteResolution(12);                                        // Set the DAC's resolution to 12-bits
  analogWrite(A0, 0);                                               // Initialise the DAC
 
  DMAC->BASEADDR.reg = (uint32_t)descriptor_section;                // Set the descriptor section base address
  DMAC->WRBADDR.reg = (uint32_t)wrb;                                // Set the write-back descriptor base adddress
  DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);      // Enable the DMAC and priority levels

  DMAC->Channel[5].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(TCC0_DMAC_ID_OVF) |   // Set DMAC to trigger when TCC0 timer overflows
                                 DMAC_CHCTRLA_TRIGACT_BURST;                // DMAC burst transfer
  descriptor.descaddr = (uint32_t)&descriptor_section[5];                   // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&sintable[0] + 256 * sizeof(uint16_t);     // Read the current value in the sine table
  descriptor.dstaddr = (uint32_t)&DAC->DATA[0].reg;                         // Copy it into the DAC data register
  descriptor.btcnt = 256;                                                   // This takes the number of sine table entries = 256 beats
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                  // Set the beat size to 16-bits (Half Word)
                      DMAC_BTCTRL_SRCINC |                          // Increment the source address every beat
                      DMAC_BTCTRL_VALID;                            // Flag the descriptor as valid
  memcpy((void*)&descriptor_section[5], &descriptor, sizeof(dmacdescriptor));  // Copy to the channel 0 descriptor 

  GCLK->PCHCTRL[TCC0_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |         // Enable perhipheral channel for TCC0
                                    GCLK_PCHCTRL_GEN_GCLK1;      // Connect generic clock 1 at 48MHz
 
  TCC0->PER.reg = 187;                             // 1kHz sine wave, 256 samples: 48MHz / (1000 * 256) - 1
  while(TCC0->SYNCBUSY.bit.PER);                   // Wait for synchronization
  TCC0->CTRLA.bit.ENABLE = 1;                      // Enable the TCC0 output
  while (TCC0->SYNCBUSY.bit.ENABLE);               // Wait for synchronization
 
  DMAC->Channel[5].CHCTRLA.bit.ENABLE = 1;         // Enable DMAC ADC on channel 5
}

void loop() {}

It uses the DMAC on channel 5 and is operates independently of the CPU.

Here's the waveform output on A0:

Sinewave.png

Sinewave.png

Hi geryuu123,

Further to the last post, it's possible to test that the ADC/DMAC combination is working correctly, by using the test sine wave on A0.

The following code tests ADC0 by generating the 1kHz sine wave using DAC0/DMAC on A0. The A0 output is then plugged into the ADC0 input on A4 (on Metro M4 or A2 on Feather M4). The ADC0 is running at 50000 samples/s. Instead of writing the results to an array using the DMAC, the DMAC instead copies it to the DAC1 for output on A1.

Here's the test code:

// Use SAMD51's DMAC to generate a 1kHz sine wave 0 to 2.2V amplitude on A0 using DAC0,
// read the ADC0 on A4 (Metro M4 or A2 on Feather M4) nd output the result on A1 using DAC1

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

void setup() {
  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

  for (uint16_t i = 0; i < 256; i++)                                // Calculate the sine table with 256 entries
  {
    sintable[i] = (uint16_t)((sinf(2 * PI * (float)i / 256) * 1339) + 1340);
  }

  analogWriteResolution(12);                                        // Set the DAC's resolution to 12-bits
  analogWrite(A0, 0);                                               // Initialise DAC0
  analogWrite(A1, 0);                                               // Initialise DAC1
 
  DMAC->Channel[5].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(TCC0_DMAC_ID_OVF) |   // Set DMAC to trigger when TCC0 timer overflows
                                 DMAC_CHCTRLA_TRIGACT_BURST;                // DMAC burst transfer
  descriptor.descaddr = (uint32_t)&descriptor_section[5];                   // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&sintable[0] + 256 * sizeof(uint16_t);     // Read the current value in the sine table
  descriptor.dstaddr = (uint32_t)&DAC->DATA[0].reg;                         // Copy it into the DAC data register
  descriptor.btcnt = 256;                                                   // This takes the number of sine table entries = 256 beats
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                  // Set the beat size to 16-bits (Half Word)
                      DMAC_BTCTRL_SRCINC |                          // Increment the source address every beat
                      DMAC_BTCTRL_VALID;                            // Flag the descriptor as valid
  memcpy((void*)&descriptor_section[5], &descriptor, sizeof(dmacdescriptor));  // Copy to the channel 0 descriptor 

  GCLK->PCHCTRL[TCC0_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |         // Enable perhipheral channel for TCC0
                                    GCLK_PCHCTRL_GEN_GCLK1;      // Connect generic clock 1 at 48MHz
 
  TCC0->PER.reg = 187;                             // 1kHz sine wave, 256 samples: 48MHz / (1000 * 256) - 1
  while(TCC0->SYNCBUSY.bit.PER);                   // Wait for synchronization
  TCC0->CTRLA.bit.ENABLE = 1;                      // Enable the TCC0 output
  while (TCC0->SYNCBUSY.bit.ENABLE);               // Wait for synchronization
 
  DMAC->Channel[5].CHCTRLA.bit.ENABLE = 1;         // Enable DMAC ADC on channel 5
  
  DMAC->Channel[3].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL3;                  // Set the channel 3 priority to level 3
  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)&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)&DAC->DATA[1].reg;                           // Place it in the DAC1 DATA register
  descriptor.btcnt = 1;                                                       // Beat count
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                            // Beat size is HWORD (16-bits)                    
                      DMAC_BTCTRL_VALID;                                      // Descriptor is valid                
  memcpy(&descriptor_section[3], &descriptor, sizeof(descriptor));            // Copy the descriptor to the descriptor section
  
  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 = 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[3].CHCTRLA.bit.ENABLE = 1;            // Enable DMAC channel 3
}

void loop(){}

Here's DAC0's 1kHz test sine wave on A0 (yellow), and the resconstructed signal from ADC0 at 50000 samples/s via DAC1 on A1 (light blue):

Sinewave2.png

As I mentioned, the ADC data is being captured correctly, it just needs to be transferred to an SD card rather than the DAC. Temporarily suspending the DMAC channels and calling the DMAC interrupts, as shown in the earlier code example, gives the CPU a window of opportunity to transfer this data using an SD card library.

Sinewave2.png

Hey,

Sorry for late reply,took a short trip. But I still need to alter my code to write DAC results to SD from A0.But as a quick test I just tested my current code with INTERLEAVE with bare microcontroller,no op-amps,etc…And I commented out REFCTRL code so code isnt referenced to INTVCC0(1/2V VDDANA).

I fed a 1kHz sinewave,2.2Vpp,with a 1.1V offset(seen in photos). And since im not back to my place with oscilloscope, I specifically used a 1kHz sinewave.

If im sampling at 50kHz(50,000 samples per second) and feed a 1kHz sinewave, I should see 50 samples per sinewave(1mS). 50kHz/1kHz = 50…If that makes sense, and I do see 50 points.

But once I get back ill test with scope and get back to you.

MartinL:
Hi geryuu123,

Further to the last post, it's possible to test that the ADC/DMAC combination is working correctly, by using the test sine wave on A0.

The following code tests ADC0 by generating the 1kHz sine wave using DAC0/DMAC on A0. The A0 output is then plugged into the ADC0 input on A4 (on Metro M4 or A2 on Feather M4). The ADC0 is running at 50000 samples/s. Instead of writing the results to an array using the DMAC, the DMAC instead copies it to the DAC1 for output on A1.

Here's the test code:

// Use SAMD51's DMAC to generate a 1kHz sine wave 0 to 2.2V amplitude on A0 using DAC0,

// read the ADC0 on A4 (Metro M4 or A2 on Feather M4) nd output the result on A1 using DAC1

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

void setup() {
  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

for (uint16_t i = 0; i < 256; i++)                                // Calculate the sine table with 256 entries
  {
    sintable[i] = (uint16_t)((sinf(2 * PI * (float)i / 256) * 1339) + 1340);
  }

analogWriteResolution(12);                                        // Set the DAC's resolution to 12-bits
  analogWrite(A0, 0);                                              // Initialise DAC0
  analogWrite(A1, 0);                                              // Initialise DAC1

DMAC->Channel[5].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(TCC0_DMAC_ID_OVF) |  // Set DMAC to trigger when TCC0 timer overflows
                                DMAC_CHCTRLA_TRIGACT_BURST;                // DMAC burst transfer
  descriptor.descaddr = (uint32_t)&descriptor_section[5];                  // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&sintable[0] + 256 * sizeof(uint16_t);    // Read the current value in the sine table
  descriptor.dstaddr = (uint32_t)&DAC->DATA[0].reg;                        // Copy it into the DAC data register
  descriptor.btcnt = 256;                                                  // This takes the number of sine table entries = 256 beats
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                  // Set the beat size to 16-bits (Half Word)
                      DMAC_BTCTRL_SRCINC |                          // Increment the source address every beat
                      DMAC_BTCTRL_VALID;                            // Flag the descriptor as valid
  memcpy((void*)&descriptor_section[5], &descriptor, sizeof(dmacdescriptor));  // Copy to the channel 0 descriptor

GCLK->PCHCTRL[TCC0_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |        // Enable perhipheral channel for TCC0
                                    GCLK_PCHCTRL_GEN_GCLK1;      // Connect generic clock 1 at 48MHz

TCC0->PER.reg = 187;                            // 1kHz sine wave, 256 samples: 48MHz / (1000 * 256) - 1
  while(TCC0->SYNCBUSY.bit.PER);                  // Wait for synchronization
  TCC0->CTRLA.bit.ENABLE = 1;                      // Enable the TCC0 output
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization

DMAC->Channel[5].CHCTRLA.bit.ENABLE = 1;        // Enable DMAC ADC on channel 5
 
  DMAC->Channel[3].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL3;                  // Set the channel 3 priority to level 3
  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)&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)&DAC->DATA[1].reg;                          // Place it in the DAC1 DATA register
  descriptor.btcnt = 1;                                                      // Beat count
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                            // Beat size is HWORD (16-bits)                   
                      DMAC_BTCTRL_VALID;                                      // Descriptor is valid               
  memcpy(&descriptor_section[3], &descriptor, sizeof(descriptor));            // Copy the descriptor to the descriptor section
 
  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 = 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[3].CHCTRLA.bit.ENABLE = 1;            // Enable DMAC channel 3
}

void loop(){}



Here's DAC0's 1kHz test sine wave on A0 (yellow), and the resconstructed signal from ADC0 at 50000 samples/s via DAC1 on A1 (light blue):

![Sinewave2.png|640x384](upload://g5v2JZL3HCZzWEFCB7Hguf9jByY.png)

As I mentioned, the ADC data is being captured correctly, it just needs to be transferred to an SD card rather than the DAC. Temporarily suspending the DMAC channels and calling the DMAC interrupts, as shown in the earlier code example, gives the CPU a window of opportunity to transfer this data using an SD card library.

Hey,
Sorry for delay but Im back at my place with my oscilloscope....And after copy/pasting your code above. Doesnt quite work when testing on my oscilloscope. A0 works fines, produces sinewave @ 1khZ....A1,has nothing but noise....I can see you set sinewave to DAC0,and then set ADC0 to DAC1 ,but how do you set DAC0 output to ADC0?

But I am now able to get 2 DMAC channels to record data by toggling 2 channels and storing the data into 1 buffer(but split this buffer into 2 buffers in DMAC).Using a linked descriptor....
When buffer1 or buffer2 is full, trigger ISR flag
If ISR occurs,process data(ie...write to SD) and aquire other buffer in background
Samplerate 50kHz-> 20uS
bufferMaxSize = 450002 =>90000
Time to fill a buffer = 20uS
45000 = 900mS
Time to fill both buffers 900mS*2 = 1800mS
Time to write both buffers to SD = 1400mS (on average) @ 12MHz
So if (write buffer to SD time < time to aquire buffer)..... This works which is awesome...but my function generator signals I make are still clipping and produce errors.........
Sent code, and a pic.I don't think I'm missing anything....
Func.gen = 1khz sinewave,1.1V,550mV offset fed into A4

SAMD51_ALTERNATE_2CH_SD_10132020.ino (12.6 KB)

Hi geryuu123,

Looking at your code in “SAMD51_ALTERNATE_2CH_SD_10103020.ino”, I noticed that the DMAC descriptor on channel 4 is written to twice. The second entry overwriting the first.

Wouldn’t it be easier to capture the alternate analog channels direct to the 180Kbytes of the uint16_t array as a set of interleaved readings, and then step through every other reading when writing to the SD card?

Ah, thank you, fixed it!

But what do you mean by "step through"?

Have interleaved,fill buffer,and every other reading I write to SD? Using DMA?

Hi geryuu123,

Ah, thank you, fixed it!

Glad to hear that it's now working.