Search code examples
c++arduino

SAMD21G18 advanced ADC functions


I'm currently working on an library for the ADC of the SAMD21G18 but I'm running into problems. The basics are working but when I change the number of samples something appears to be not working as intended. The Basic Idea is that I can get a measurement from an given Pin with the settings I want. The reason I write this library is that I can have full control of the ADC and could read an Pin once or have the ADC in freerunning mode to allow for parallel Code execution. (I'm also trying to implement an function which gives me the voltage measured with the given voltage reference)

Here is the basic usage adc.init();followed by int value = adc.readPin_once(0x05, INT1V, SAMPLENUM_1, GAIN_2x);
The expected result would be lets say 23725.
But when I change the sample number like so:
int value = adc.readPin_once(0x05, INT1V, SAMPLENUM_2, GAIN_2x);
I would expect 2x 23725 or I would expect 23725 but non of these happens. I get 87800. (Maybe I should give the desired resolution of the result in the function call as well) This is my goal:

When I use the code I expect an normalized result. So even if I change all of the settings I want to get one result converted to an 16bit int.

Here is the .cpp :
Excuse the long code but I already shortened it to what I think could cause the issue

.cpp


/**
Library für den ADC des  ATSAMD21G18
Author: J.Neufeld & E.Wolf
Date: 09/2024
*/

#include "ADC_MC.h"

#include <Arduino.h>
#include <inttypes.h>

ADC_MC::ADC_MC() {
  this->VCC = 3.3;
  this->REF_select = 0;
  this->SMPR = 0;
  this->GAIN = 0;
}
ADC_MC::ADC_MC(double VCC) {
  this->VCC = VCC;
}

void ADC_MC::init() {

  ADC->CTRLA.bit.ENABLE = 0;  // Disable the ADC, has do be disabled during configuration
  // Enable the APBC clock for the ADC
  REG_PM_APBCMASK |= PM_APBCMASK_ADC;

  SYSCTRL->OSC8M.reg |= SYSCTRL_OSC8M_ENABLE;

  // Setup clock GCLK3 for no div factor
  GCLK->GENDIV.reg |= GCLK_GENDIV_ID(3) | GCLK_GENDIV_DIV(0);
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY)
    ;

  // configure the generator of the generic clock, which is 8MHz clock
  GCLK->GENCTRL.reg |= GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSC8M | GCLK_GENCTRL_ID(3);
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY)
    ;

  // enable clock, set gen clock number, and ID to where the clock goes (30 is ADC)
  GCLK->CLKCTRL.reg |= GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN(3) | GCLK_CLKCTRL_ID(30);
  while (GCLK->STATUS.bit.SYNCBUSY)
    ;
ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_10BIT_Val;
  ADC->CTRLB.bit.PRESCALER = 0x00;  // No prescaler
  set_REFERENCE(INT1V);
  set_SAMPLENUM(SAMPLENUM_1);
  set_GAIN(GAIN_DIV2);
  
}


  REG_ADC_SAMPCTRL |= ADC_SAMPCTRL_SAMPLEN(0);  // min 4 or impedance is to small and voltage reading is incorrect
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY)  // Wait for MCU to finish setting registers
    ;
}


/* Setzt due Spannungsreferenz des ADCs
Options are INTVCC1 (1/1.48 VCC), INTVCC0 (1/2 VCC), INT1V (1V), VREFA (external), VREFB (external)*/
void ADC_MC::set_REFERENCE(uint8_t REF_select) {
  this->REF_select = REF_select;
  ADC->CTRLA.bit.ENABLE = 0;  // Schaltet den ADC aus um Einstellungen zu ändern
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY)
    ;                  // Wait for MCU to finish setting registers
  switch (REF_select)  // Options are INTVCC1 (1/2 VDDANA), INTVCC0 (1/1.48 VDDANA), INT1V (1V), VREFA (external), VREFB (external)
  {
    case INTVCC1:
      ADC->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_INTVCC1_Val;  // 1/2 von VDDANA

      break;
    case INTVCC0:
      ADC->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_INTVCC0_Val;  // 1/1.48 von VDDANA

      break;
    case INT1V:
      ADC->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_INT1V_Val;  // 1 V reference voltage

      break;
    case VREFA:
      ADC->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_AREFA_Val;  // External reference on VREFA pin

      break;
    case VREFB:
      ADC->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_AREFB_Val;  // External reference on VREFB pin

      break;
    default:
      ADC->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_INT1V_Val;  // 1 V reference voltage

      break;
  }
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY)  // Wait for MCU to finish setting registers
    ;
}
/* Setzt die Anzahl an Samples die der ADC nimmt (je höher desto besser)*/
void ADC_MC::set_SAMPLENUM(uint8_t SMPR) {
  this->SMPR = SMPR;
  ADC->CTRLA.bit.ENABLE = 0;  // Schaltet den ADC aus um Einstellungen zu ändern
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY)
    ;            // Wait for MCU to finish setting registers
    switch (SMPR) {
        case SAMPLENUM_1:
            ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_1_Val;
            ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_10BIT_Val;
            ADC->CTRLB.bit.PRESCALER = 0x00; // No prescaler
            break;
        case SAMPLENUM_2:
            ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_2_Val;
            ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_12BIT_Val;
            ADC->CTRLB.bit.PRESCALER = 0x01; // Prescaler 1
            break;
        case SAMPLENUM_4:
            ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_4_Val;
            ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_12BIT_Val;
            ADC->CTRLB.bit.PRESCALER = 0x02; // Prescaler 2
            break;
        case SAMPLENUM_8:
            ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_8_Val;
            ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_12BIT_Val;
            ADC->CTRLB.bit.PRESCALER = 0x03; // Prescaler 3
            break;
        case SAMPLENUM_16:
            ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_16_Val;
            ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_16BIT_Val;
            ADC->CTRLB.bit.PRESCALER = 0x04; // Prescaler 4
            break;
        case SAMPLENUM_32:
            ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_32_Val;
            ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_16BIT_Val;
            ADC->CTRLB.bit.PRESCALER = 0x05; // Prescaler 5
            break;
        case SAMPLENUM_64:
            ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_64_Val;
            ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_16BIT_Val;
            ADC->CTRLB.bit.PRESCALER = 0x06; // Prescaler 6
            break;
        case SAMPLENUM_128:
            ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_128_Val;
            ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_16BIT_Val;
            ADC->CTRLB.bit.PRESCALER = 0x07; // Prescaler 7
            break;
        case SAMPLENUM_256:
            ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_256_Val;
            ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_16BIT_Val;
            ADC->CTRLB.bit.PRESCALER = 0x08; // Prescaler 8
            break;
        case SAMPLENUM_512:
            ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_512_Val;
            ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_16BIT_Val;
            ADC->CTRLB.bit.PRESCALER = 0x09; // Prescaler 9
            break;
        case SAMPLENUM_1024:
            ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_1024_Val;
            ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_16BIT_Val;
            ADC->CTRLB.bit.PRESCALER = 0x0A; // Prescaler 10
            break;
        default:
            ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_1_Val;
            ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_10BIT_Val;
            ADC->CTRLB.bit.PRESCALER = 0x00; // No prescaler
            break;
    }
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY)  // Wait for MCU to finish setting registers
    ;
}
/* Setzt den GAIN des ADC */
void ADC_MC::set_GAIN(uint8_t GAIN) {
  this->GAIN = GAIN;
  ADC->CTRLA.bit.ENABLE = 0;  // Schaltet den ADC aus um Einstellungen zu ändern
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY)
    ;            // Wait for MCU to finish setting registers
  switch (GAIN)  // Select Gain
  {
    case GAIN_DIV2:
      ADC->INPUTCTRL.bit.GAIN = ADC_INPUTCTRL_GAIN_DIV2_Val;  // Gain = 0.5, default Gain
      break;
    case GAIN_1x:
      ADC->INPUTCTRL.bit.GAIN = ADC_INPUTCTRL_GAIN_1X_Val;  // Gain = 1
      break;
    case GAIN_2x:
      ADC->INPUTCTRL.bit.GAIN = ADC_INPUTCTRL_GAIN_2X_Val;  // Gain = 2
      break;
    case GAIN_4x:
      ADC->INPUTCTRL.bit.GAIN = ADC_INPUTCTRL_GAIN_4X_Val;  // Gain = 4
      break;
    case GAIN_8x:
      ADC->INPUTCTRL.bit.GAIN = ADC_INPUTCTRL_GAIN_8X_Val;  // Gain = 8
      break;
    case GAIN_16x:
      ADC->INPUTCTRL.bit.GAIN = ADC_INPUTCTRL_GAIN_16X_Val;  // Gain = 16
      break;
    default:
      ADC->INPUTCTRL.bit.GAIN = ADC_INPUTCTRL_GAIN_DIV2_Val;  // Gain = 0.5, default Gain
      break;
  }
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY)  // Wait for MCU to finish setting registers
    ;
}


bool ADC_MC::enable() {
  ADC->CTRLA.bit.ENABLE = 1;
  while (ADC->STATUS.bit.SYNCBUSY == 1)
    ;
}

/* Stellt die Einstellungen ein auf dem ADC */
void ADC_MC::set_SETTINGS(uint8_t pin, uint8_t REF_select, uint8_t SMPR, uint8_t GAIN) {
  this->REF_select = REF_select;
  this->SMPR = SMPR;
  this->GAIN = GAIN;
  ADC->CTRLA.bit.ENABLE = 0;  // Schaltet den ADC aus um Einstellungen zu ändern
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY)
    ;  // Wait for MCU to finish setting registers
       // Freerun-Modus aktivieren
  set_FREERUN(0);
  ADC->INPUTCTRL.bit.MUXPOS = pin;  // Hier wird der gemessende Pin festgelegt
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY)
    ;  // Wait for MCU to finish setting registers

  ADC->AVGCTRL.reg |= ADC_AVGCTRL_ADJRES(0);    // | ADC_AVGCTRL_SAMPLENUM(0x8);
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY)  // Wait for MCU to finish setting registers
    ;

  set_REFERENCE(REF_select);
  set_SAMPLENUM(SMPR);
  set_GAIN(GAIN);
}

/* ließt den angegebenden Pin einmal aus */
int ADC_MC::readPin_once(uint8_t pin, uint8_t REF_select, uint8_t SMPR, uint8_t GAIN) {

  set_SETTINGS(pin, REF_select, SMPR, GAIN);

  ADC->CTRLA.bit.ENABLE = 1;
  while (ADC->STATUS.bit.SYNCBUSY == 1)
    ;
  ADC->SWTRIG.reg = ADC_SWTRIG_START;  // Start conversion
  while (!ADC->INTFLAG.bit.RESRDY)     // Wait for the conversion to complete
  {
  }
  int ADC_result = ADC->RESULT.reg;
  // ADC_result = ADC_result_bin; // * (ADC_FSR / ADC_resolution); // convert binary value to decimal voltage
  return ADC_result;
}


.H Here are the defines if important

/*Library for ADC module from ATSAMD21G18 MCU*/

#include <Arduino.h>
#include <inttypes.h>


#define INTVCC1 -1
#define INTVCC0 0
#define INT1V 1
#define VREFA -3
#define VREFB -4

#define SAMPLENUM_1 1
#define SAMPLENUM_2 2
#define SAMPLENUM_4 4
#define SAMPLENUM_8 8
#define SAMPLENUM_16 16
#define SAMPLENUM_32 32
#define SAMPLENUM_64 64
#define SAMPLENUM_128 128
#define SAMPLENUM_256 256
#define SAMPLENUM_512 512
#define SAMPLENUM_1024 1024

#define GAIN_DIV2 -2
#define GAIN_1x 1
#define GAIN_2x 2
#define GAIN_4x 4
#define GAIN_8x 8
#define GAIN_16x 16

Can anyone help?


Solution

  • I was able to fix it. The issue was in the set_SAMPLENUM Here is the code for the interrested:

    void ADC_MC::set_SAMPLENUM(uint8_t SMPR) {
      this->SMPR = SMPR;
      ADC->CTRLA.bit.ENABLE = 0;  // Schaltet den ADC aus um Einstellungen zu ändern
      while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY)
        ;  // Wait for MCU to finish setting registers
      switch (SMPR) {
        case SAMPLENUM_1:
          ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_1_Val;
          ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_12BIT_Val;
          ADC->AVGCTRL.bit.ADJRES = 0x0;
          break;
        case SAMPLENUM_2:
          ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_2_Val;
          ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_12BIT_Val;
          ADC->AVGCTRL.bit.ADJRES = 0x1;
          break;
        case SAMPLENUM_4:
          ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_4_Val;
          ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_16BIT_Val;
          ADC->AVGCTRL.bit.ADJRES = 0x2;
          ADC->AVGCTRL.bit.SAMPLENUM = 0x2;
          break;
        case SAMPLENUM_8:
          ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_8_Val;
          ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_16BIT_Val;
          ADC->AVGCTRL.bit.ADJRES = 0x3;
          break;
        case SAMPLENUM_16:
          ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_16_Val;
          ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_16BIT_Val;
          ADC->AVGCTRL.bit.ADJRES = 0x4;
          break;
        case SAMPLENUM_32:
          ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_32_Val;
          ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_16BIT_Val;
          ADC->AVGCTRL.bit.ADJRES = 0x4;
          break;
        case SAMPLENUM_64:
          ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_64_Val;
          ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_16BIT_Val;
          ADC->AVGCTRL.bit.ADJRES = 0x4;
          break;
        case SAMPLENUM_128:
          ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_128_Val;
          ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_16BIT_Val;
          ADC->AVGCTRL.bit.ADJRES = 0x4;
          break;
        case SAMPLENUM_256:
          ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_256_Val;
          ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_16BIT_Val;
          ADC->AVGCTRL.bit.ADJRES = 0x4;
          break;
        case SAMPLENUM_512:
          ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_512_Val;
          ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_16BIT_Val;
          ADC->AVGCTRL.bit.ADJRES = 0x4;
          break;
        case SAMPLENUM_1024:
          ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_1024_Val;
          ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_16BIT_Val;
          ADC->AVGCTRL.bit.ADJRES = 0x4;
          break;
        default:
          ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_1_Val;
          ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_12BIT_Val;
          ADC->AVGCTRL.bit.ADJRES = 0x0;
          break;
      }
      while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY)  // Wait for MCU to finish setting registers
        ;
    }