Setting up ISR for ADC on Zero

Hi geryuu123,

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

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

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

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

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

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

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

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

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

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

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

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

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

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