Go Down

Topic: Setting up ISR for ADC on Zero (Read 11392 times) previous topic - next topic

MartinL

#30
Jun 15, 2020, 12:02 pm Last Edit: Jun 15, 2020, 12:08 pm by MartinL
Hi geryu123,

Quote
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.


geryuu123

#31
Jun 15, 2020, 07:20 pm Last Edit: Jun 15, 2020, 07:25 pm by geryuu123
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.


geryuu123

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:

Code: [Select]
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.


MartinL

#33
Jun 16, 2020, 10:46 am Last Edit: Jun 16, 2020, 12:11 pm by MartinL
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:

Code: [Select]
// 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:


MartinL

#34
Jun 16, 2020, 11:53 am Last Edit: Jun 16, 2020, 01:02 pm by 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:

Code: [Select]
// 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):



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.

geryuu123

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.

geryuu123

#36
Oct 15, 2020, 08:10 am Last Edit: Oct 15, 2020, 08:30 am by geryuu123
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:

Code: [Select]
// 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):



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 = 45000*2 =>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






MartinL

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?

geryuu123

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?

MartinL

Hi geryuu123,

Quote
Ah, thank you, fixed it!
Glad to hear that it's now working.

Go Up