Go Down

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

geryuu123

May 20, 2020, 05:46 am Last Edit: May 20, 2020, 05:47 am by geryuu123
Hello,

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

MartinL

Hi geryuu123,

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

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

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

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

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

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

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

MartinL

#2
May 20, 2020, 10:32 am Last Edit: May 20, 2020, 10:37 am by MartinL
... and here's an example that does the same thing using the SAMD21's Direct Memory Access Controller (DMAC) instead of the CPU, again with the ADC in free run mode:

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

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

typedef struct           // DMAC descriptor structure
{
  uint16_t btctrl;
  uint16_t btcnt;
  uint32_t srcaddr;
  uint32_t dstaddr;
  uint32_t descaddr;
} dmacdescriptor ;

volatile dmacdescriptor wrb[DMAC_CH_NUM] __attribute__ ((aligned (16)));          // Write-back DMAC descriptors
dmacdescriptor descriptor_section[DMAC_CH_NUM] __attribute__ ((aligned (16)));    // DMAC channel descriptors
dmacdescriptor descriptor __attribute__ ((aligned (16)));                         // Place holder descriptor

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

  DMAC->BASEADDR.reg = (uint32_t)descriptor_section;                          // Specify the location of the descriptors
  DMAC->WRBADDR.reg = (uint32_t)wrb;                                          // Specify the location of the write back descriptors
  DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);                // Enable the DMAC peripheral

  DMAC->CHID.reg = DMAC_CHID_ID(0);                                           // Select DMAC channel 0
  DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(0) | DMAC_CHCTRLB_TRIGSRC(ADC_DMAC_ID_RESRDY) | DMAC_CHCTRLB_TRIGACT_BEAT;
  descriptor.descaddr = (uint32_t)0;                                          // Set up descriptor
  descriptor.srcaddr = (uint32_t)&ADC->RESULT.reg;                            // Take the result from the ADC RESULT register
  descriptor.dstaddr = (uint32_t)&adcResult[0] + sizeof(uint16_t) * SAMPLE_NO;            // Place it in the adcResult array
  descriptor.btcnt = SAMPLE_NO;                                               // Beat count is SAMPLE_NO
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                            // Beat size is HWORD (16-bits)
                      DMAC_BTCTRL_DSTINC |                                    // Increment the destination address
                      DMAC_BTCTRL_VALID;                                      // Descriptor is valid
  memcpy(&descriptor_section[0], &descriptor, sizeof(descriptor));            // Copy the descriptor to the descriptor section

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

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

geryuu123

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

MartinL

#4
May 22, 2020, 10:03 am Last Edit: May 22, 2020, 10:06 am by MartinL
Hi geryuu123,

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

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

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

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

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

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

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

typedef struct           // DMAC descriptor structure
{
  uint16_t btctrl;
  uint16_t btcnt;
  uint32_t srcaddr;
  uint32_t dstaddr;
  uint32_t descaddr;
} dmacdescriptor ;

volatile dmacdescriptor wrb[DMAC_CH_NUM] __attribute__ ((aligned (16)));          // Write-back DMAC descriptors
dmacdescriptor descriptor_section[DMAC_CH_NUM] __attribute__ ((aligned (16)));    // DMAC channel descriptors
dmacdescriptor descriptor __attribute__ ((aligned (16)));                         // Place holder descriptor

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

  DMAC->BASEADDR.reg = (uint32_t)descriptor_section;                          // Specify the location of the descriptors
  DMAC->WRBADDR.reg = (uint32_t)wrb;                                          // Specify the location of the write back descriptors
  DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);                // Enable the DMAC peripheral

  DMAC->Channel[0].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(ADC0_DMAC_ID_SEQ) |     // Set DMAC to trigger on ADC0 DMAC sequence
                                 DMAC_CHCTRLA_TRIGACT_BURST;                  // DMAC burst transfer
  descriptor.descaddr = (uint32_t)&descriptor_section[0];                     // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)inputCtrl + sizeof(uint32_t) * 2;            // Configure the DMAC to set the
  descriptor.dstaddr = (uint32_t)&ADC0->DSEQDATA.reg;                         // Write the INPUT CTRL
  descriptor.btcnt = 2;                                                       // Beat count is 2
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_WORD |                             // Beat size is WORD (32-bits)
                      DMAC_BTCTRL_SRCINC |                                    // Increment the source address
                      DMAC_BTCTRL_VALID;                                      // Descriptor is valid
  memcpy(&descriptor_section[0], &descriptor, sizeof(descriptor));            // Copy the descriptor to the descriptor section
   
  DMAC->Channel[1].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(ADC0_DMAC_ID_RESRDY) |  // Set DMAC to trigger when ADC0 result is ready
                                 DMAC_CHCTRLA_TRIGACT_BURST;                  // DMAC burst transfer
  descriptor.descaddr = (uint32_t)&descriptor_section[1];                     // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&ADC0->RESULT.reg;                           // Take the result from the ADC0 RESULT register
  descriptor.dstaddr = (uint32_t)adcResult + sizeof(uint16_t) * 2;            // Place it in the adcResult array
  descriptor.btcnt = 2;                                                       // Beat count is 1
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                            // Beat size is HWORD (16-bits)
                      DMAC_BTCTRL_DSTINC |                                    // Increment the destination address
                      DMAC_BTCTRL_VALID;                                      // Descriptor is valid
  memcpy(&descriptor_section[1], &descriptor, sizeof(descriptor));            // Copy the descriptor to the descriptor section

  ADC0->INPUTCTRL.bit.MUXPOS = 0x0;                   // Set the analog input to A0
  while(ADC0->SYNCBUSY.bit.INPUTCTRL);                // Wait for synchronization
  ADC0->SAMPCTRL.bit.SAMPLEN = 0x00;                  // Set max Sampling Time Length to half divided ADC clock pulse (2.66us)
  while(ADC0->SYNCBUSY.bit.SAMPCTRL);                 // Wait for synchronization 
  ADC0->DSEQCTRL.reg = ADC_DSEQCTRL_AUTOSTART |       // Auto start a DMAC conversion upon ADC0 DMAC sequence completion
                       ADC_DSEQCTRL_INPUTCTRL;        // Change the ADC0 INPUTCTRL register on DMAC sequence
  ADC0->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV256;       // Divide Clock ADC GCLK by 256 (48MHz/256 = 187.5kHz)
  ADC0->CTRLB.reg = ADC_CTRLB_RESSEL_12BIT;           // Set ADC resolution to 12 bits
  while(ADC0->SYNCBUSY.bit.CTRLB);                    // Wait for synchronization
  ADC0->CTRLA.bit.ENABLE = 1;                         // Enable the ADC
  while(ADC0->SYNCBUSY.bit.ENABLE);                   // Wait for synchronization
  ADC0->SWTRIG.bit.START = 1;                         // Initiate a software trigger to start an ADC conversion
  while(ADC0->SYNCBUSY.bit.SWTRIG);                   // Wait for synchronization
  DMAC->Channel[0].CHCTRLA.bit.ENABLE = 1;            // Enable DMAC Sequencing on channel 0
  DMAC->Channel[1].CHCTRLA.bit.ENABLE = 1;            // Enable DMAC ADC on channel1
  delay(1);                                           // Wait a millisecond
}

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

geryuu123

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

Go Up