0

Itsy Bitsy M4 ADC1
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Itsy Bitsy M4 ADC1

by cantthinkofone on Wed Mar 17, 2021 5:30 pm

Reading 5 analog signals on A2-A6(aka D2). analogRead() works ok-ish, but I'm sure I'll need to squeeze a few more clock cycles out of the M4. So, started to dig a little deeper in order to use both ADC0 and ADC1 (and maybe take advantage of SLAVEEN on ADC1). Connecting any of those pins to ADC0 works well, but any attempt at using ADC1 returns the middle value consistently (simple test: all 5 wired to a 10K pot swinging between ~3V and ground). I'm pretty confident I've followed what's in the datasheet (unfortunately, the workbench is far away at the moment, and I can't cut-n-paste code at the moment). Is there something needed to get ADC1 to work besides what's in the datasheet?

Kinda' aside, is there a good explanation to properly handle pin muxing and configuration? I get the PORT[] array/structure, but getting lost in the details...

cantthinkofone
 
Posts: 2
Joined: Thu Mar 14, 2013 8:54 pm

Re: Itsy Bitsy M4 ADC1

by cantthinkofone on Tue Mar 30, 2021 3:42 pm

Found an example that would use both ADCs w/ the DMAs. However, I can't do that since I will need to switch channels in order to read 3 pots, and still keep the 2 primary channels in sync.

I'm closer to the workbench now, and can show some code.

Some stuff at global scope:
Code: Select all | TOGGLE FULL SIZE
//#define USE_DUAL_ADC
#ifdef USE_DUAL_ADC
#define MAX_ADC_LOOP      3
#else
#define MAX_ADC_LOOP      5
#endif

volatile uint16_t adc0_in,adc1_in;
volatile uint16_t pot0,pot1,pot2;

This is the startup/init:
Code: Select all | TOGGLE FULL SIZE
void
init_adc(void)
{
    // ADC Clock Config
    MCLK->APBDMASK.bit.ADC0_ = 1;
#ifdef USE_DUAL_ADC
    MCLK->APBDMASK.bit.ADC1_ = 1;
#endif

   // enable gen clock 1 as source for ADC channel
   GCLK->PCHCTRL[ADC0_GCLK_ID].reg =
       GCLK_PCHCTRL_GEN_GCLK1 | GCLK_PCHCTRL_CHEN;
   // note that if slaving adc1, it should use
   //  this clock from adc0
   
#ifdef USE_DUAL_ADC
   // setup ADC1
   Serial.println("setting up adc1");
   // repeat for ADC1
   ADC1->CTRLA.bit.SWRST = 1;
   while(ADC1->CTRLA.bit.SWRST);

   ADC1->CTRLA.reg = ADC_CTRLA_SLAVEEN;
//   ADC1->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV16;
   ADC1->REFCTRL.reg = ADC_REFCTRL_REFSEL_INTVCC1;
//   ADC1->REFCTRL.reg = ADC_REFCTRL_REFSEL_AREFA;
   ADC1->CTRLB.reg = ADC_CTRLB_RESSEL_12BIT;
   while(ADC1->SYNCBUSY.bit.CTRLB);

   ADC1->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND | ADC_INPUTCTRL_MUXPOS_AIN3;
   while(ADC1->SYNCBUSY.bit.INPUTCTRL);
#endif

   // After configuring ADC Clock, reset ADC registers
   ADC0->CTRLA.bit.SWRST = 1;
   
   Serial.println("setting up adc0");
   // Wait for ADC to finish reset, bit will stay high
   //  until reset clears the bit
   while(ADC0->CTRLA.bit.SWRST);

   // Divide 8MHz clock by 4 to obtain 2MHz clock to adc
   ADC0->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV4;
   
   // Select VDDANA (3.3V chip supply voltage as reference)
   ADC0->REFCTRL.reg = ADC_REFCTRL_REFSEL_INTVCC1;
   
   // Select AREF (tied to 3.3V on Metro M4 when jumper is closed)
//   ADC0->REFCTRL.reg = ADC_REFCTRL_REFSEL_AREFA;

   // Choose 12-bit resolution
   ADC0->CTRLB.reg = ADC_CTRLB_RESSEL_12BIT;

   // wait for sync
   while(ADC0->SYNCBUSY.bit.CTRLB);

   // set initial inputs, negitive always ground
   ADC0->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND | ADC_INPUTCTRL_MUXPOS_AIN2;

   // wait for sync
   while(ADC0->SYNCBUSY.bit.INPUTCTRL);

   // Enable ADC
   Serial.println("enabling adc0");
   ADC0->CTRLA.bit.ENABLE = 1;
   
   // wait for ADC to be ready
   while(ADC0->SYNCBUSY.bit.ENABLE);

#ifdef USE_DUAL_ADC
   // Enable ADC
   Serial.println("enabling adc1");
   ADC1->CTRLA.bit.ENABLE = 1;
//   
//   // wait for ADC to be ready
//   while(ADC1->SYNCBUSY.bit.ENABLE);
#endif
}


And the read:
Code: Select all | TOGGLE FULL SIZE
void
read_adc(bool full_read)
{
   uint16_t ret=0;
   int i,max_loop;

   if(full_read) {
      max_loop = MAX_ADC_LOOP;
   } else {
#ifdef USE_DUAL_ADC
      // XXX will take some more thought...
      // XXX since INPUTCTRL would only
      // XXX  need to change for the pot
      // XXX  reads
      max_loop = 1;
#else
      max_loop = 2;
#endif
   }

   for(i = 0;i < max_loop;i++) {
      // set up the correct channel
#ifdef USE_DUAL_ADC
      switch(i) {
         case 0:
            ADC0->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND |
                  ADC_INPUTCTRL_MUXPOS_AIN2;
            ADC1->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND |
                  ADC_INPUTCTRL_MUXPOS_AIN3;
            break;
         case 1:
            ADC0->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND |
                  ADC_INPUTCTRL_MUXPOS_AIN4;
            ADC1->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND |
                  ADC_INPUTCTRL_MUXPOS_AIN6;
            break;
         case 2:
            ADC0->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND |
                  ADC_INPUTCTRL_MUXPOS_AIN7;
            // just set it to something...
            ADC1->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND |
                  ADC_INPUTCTRL_MUXPOS_AIN6;
            break;
         default:
            return;
            break;
      }
#else
      switch(i) {
         case 0:
            ADC0->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND |
                  ADC_INPUTCTRL_MUXPOS_AIN2;
            break;
         case 1:
            ADC0->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND |
                  ADC_INPUTCTRL_MUXPOS_AIN3;
            break;
         case 2:
            ADC0->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND |
                  ADC_INPUTCTRL_MUXPOS_AIN4;
            break;
         case 3:
            ADC0->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND |
                  ADC_INPUTCTRL_MUXPOS_AIN6;
            break;
         case 4:
            ADC0->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND |
                  ADC_INPUTCTRL_MUXPOS_AIN7;
            break;
         default:
            return;
            break;
      }
#endif
      while(ADC0->SYNCBUSY.reg & ADC_SYNCBUSY_INPUTCTRL );
#ifdef USE_DUAL_ADC
      while(ADC1->SYNCBUSY.reg & ADC_SYNCBUSY_INPUTCTRL );
#endif

      // Start conversion
      ADC0->SWTRIG.bit.START = 1;
#ifdef USE_DUAL_ADC
      ADC1->SWTRIG.bit.START = 1;
#endif

      // wait
      while(ADC0->INTFLAG.bit.RESRDY == 0);   // Waiting for conversion to complete
#ifdef USE_DUAL_ADC
      while(ADC1->INTFLAG.bit.RESRDY == 0);   // Waiting for conversion to complete
#endif

      // Store the value
#ifdef USE_DUAL_ADC
      switch(i) {
         case 0:
            adc0_in = ADC0->RESULT.reg;
            adc1_in = ADC1->RESULT.reg;
            break;
         case 1:
            pot0 = ADC0->RESULT.reg;
            pot1 = ADC1->RESULT.reg;
            break;
         case 2:
            pot2 = ADC0->RESULT.reg;
            // already read pot1...
            break;
      }
#else
      switch(i) {
         case 0:
            adc0_in = ADC0->RESULT.reg;
            break;
         case 1:
            adc1_in = ADC0->RESULT.reg;
            break;
         case 2:
            pot0 = ADC0->RESULT.reg;
            break;
         case 3:
            pot1 = ADC0->RESULT.reg;
            break;
         case 4:
            pot2 = ADC0->RESULT.reg;
            break;
      }
#endif
   }
}


Without USE_DUAL_ADC, this will cycle through 5 channels, and get expected values off of the connected pots.

Turning on USE_DUAL_ADC and the values read from ADC1 are always about the midpoint of a 12 bit value (although it just occurs to me it may be where the pot is set at startup), and never changes.

Is it possible to use both ADCs in this manner, without using the DMA?

If so, what am I missing?

cantthinkofone
 
Posts: 2
Joined: Thu Mar 14, 2013 8:54 pm

Please be positive and constructive with your questions and comments.