Reading the system voltage on the new ATTiny 1-series

First time trying to directly modify the ADC registers for some new functionality.

I am trying to create a function to read the core voltage of the uC for the series 1 uC. Using DrAzzy's megaTinyCore, the information in App Note 2447 and the work done in ATtiny 0 Series programming on the cheap | Hackaday.io

the code looks like this

float MeasureVcc() {

  byte bacVREF,bacMUXPOS,bacCTRLC, bacCTRLA;

  // save the states of all the registers prior to reading 

//  bacVREF = VREF.CTRLA;
//  bacMUXPOS = ADC0.MUXPOS;
//  bacCTRLC = ADC0.CTRLC;
//  bacCTRLA = ADC0.CTRLA;
  
  /* The App Note AN2447 uses Atmel Start to configure Vref but we'll do it explicitly in our code*/
  VREF.CTRLA = VREF_ADC0REFSEL_1V1_gc;    /* Set the Vref to 1.1V*/

  /* The following section is directly taken from Microchip App Note AN2447 page 13*/

  ADC0.MUXPOS = ADC_MUXPOS_INTREF_gc    /* ADC internal reference, the Vbg*/;
  ADC0.CTRLC = ADC_PRESC_DIV4_gc        /* CLK_PER divided by 4 */
               | ADC_REFSEL_VDDREF_gc   /* Vdd (Vcc) be ADC reference */
               | 0 << ADC_SAMPCAP_bp;   /* Sample Capacitance Selection: disabled */

  float Vcc_value = 0;                /* measured Vcc value */

  ADC0.CTRLA = 1 << ADC_ENABLE_bp     /* ADC Enable: enabled */
               | 1 << ADC_FREERUN_bp  /* ADC Free run mode: enabled */
               | ADC_RESSEL_10BIT_gc; /* 10-bit mode */

  ADC0.COMMAND |= 1;                  // start running ADC
  do {
    Vcc_value = ( 0x400 * 1.1 ) / ADC0.RES /* calculate the Vcc value */;
  } while (ADC0.INTFLAGS);

  ADC0.COMMAND |= 0;                  // stop running ADC

  // restore the values
//  VREF.CTRLA = bacVREF;
//  ADC0.MUXPOS = bacMUXPOS;
//  ADC0.CTRLC = bacCTRLC;
//  ADC0.CTRLA = bacCTRLA;

  return Vcc_value;
}

The code works if I use it in a loop by itself, but if after calling the function, I use an analogRead(), the value comes back -1. If I uncomment what I thought would save and restore the settings used in the function for setting ADC0, the function does not work correctly any more.

Am doing the save/restore incorrectly? Any help would be appreciated.

ADC0.COMMAND |= 0;  // no effect

Perhaps using bit masks is better as it only changes the required bits in the register ...

ADC0.CTRLA |= ADC_ENABLE_bm;   // start running ADC
ADC0.CTRLA &= ~ADC_ENABLE_bm;  // stop running ADC

dlloyd,

thanks for the pointers, still getting used to the bit manipulation.

Sadly, I had bigger problems, hard to take an analog reading on a digital pin. As I started to review the code and other examples I looked back at the pin maps and really read it this time and saw that the pin I was trying to read was digital only.

wiping the egg off my face and getting back to it.

Are you using megaTinyCore?

Let me know how you get on with it, especially if that's what you're using. I would love to add an example of doing that to my docs for the core (so far I've been up to my eyeballs in the millis timing code - 1.1.19 is going to have mad fixes for timing related issues).

As an aside, the "good" ATtiny 1-series ("tinyAVR 1-series parts") - the ones with 16 or 32k of flash - have a second ADC, ADC1! You can take over that one without messing with the configuration of the one that the core uses for analogRead()

Yup using the MTC.

Now working on getting the right registers configured. Something strange is happening where I am getting 2X the Vcc voltage. I will beat it down, it is a good learning experience.

Thanks again for all the work on the MTC.

Er - maybe not...

Okay, I'm doing 1.1.10 release. I have no trouble reading the microcontroller voltage - but that's after I modified analogRead() (and Arduino.h) to support using analogRead() to read internal voltage references and the on-chip DAC and TEMPSENSE, so that I could write a sensible test for this that didn't require external hardware.

My guess is that your code was expecting analogRead to pass anything you gave it for pin straight through to the MUXPOS register, when that's not what analogRead did (it sanity-checks the pin you give it).

#define RESULTCOUNT 4
int16_t results[RESULTCOUNT];
int32_t sum;
int16_t average;
  
void setup() {
  // put your setup code here, to run once:
  delay(1000);
  Serial.begin(57600);
  Serial.println("testAnalogReference");
  analogWrite(PIN_A6,127); //Output 50% of max voltage from DAC, DAC VREF = 0.55 V, so this is 0.275 V
  Serial.println("Initial state of ADC registers: ");
  printRegisters();
}

void loop() {
  if(!runTest(INTERNAL0V55,ADC_DAC0,"0.55V",508)) Serial.println("PASS");
  if(!runTest(INTERNAL1V1,ADC_DAC0,"1.1V",254)) Serial.println("PASS");
  if(!runTest(INTERNAL1V5,ADC_DAC0,"1.5V",186)) Serial.println("PASS");
  if(!runTest(INTERNAL2V5,ADC_DAC0,"2.5V",112)) Serial.println("PASS");
  if(!runTest(INTERNAL4V34,ADC_DAC0,"4.34V",64)) Serial.println("PASS");
  int16_t vscale=runTest(VDD,ADC_DAC0,"Vcc (5.0V)");
  Serial.print("Voltage is: ");
  Serial.print(average*89);
  Serial.println("mV");
  VREF.CTRLA=VREF_ADC0REFSEL_0V55_gc;
  if(!runTest(VDD,ADC_INTREF,"Vcc (5.0V) Internal Ref 0.55V",(113L*vscale)/56)) Serial.println("PASS");
  VREF.CTRLA=VREF_ADC0REFSEL_1V1_gc;
  if(!runTest(VDD,ADC_INTREF,"Vcc (5.0V) Internal Ref 1.1V",(225L*vscale)/56)) Serial.println("PASS");
  VREF.CTRLA=VREF_ADC0REFSEL_1V5_gc;
  if(!runTest(VDD,ADC_INTREF,"Vcc (5.0V) Internal Ref 1.5V",(307L*vscale)/56)) Serial.println("PASS");
  VREF.CTRLA=VREF_ADC0REFSEL_2V5_gc;
  if(!runTest(VDD,ADC_INTREF,"Vcc (5.0V) Internal Ref 2.5V",(512L*vscale)/56)) Serial.println("PASS");
  VREF.CTRLA=VREF_ADC0REFSEL_4V34_gc;
  if(!runTest(VDD,ADC_INTREF,"Vcc (5.0V) Internal Ref 4.34V",(888L*vscale)/56)) Serial.println("PASS");
  delay(10000);
}
int16_t runTest(uint8_t reference, uint8_t pin, char voltageastext[]){
  clearResults();
  Serial.print("Now testing pin 0x");
  showHex(pin);
  Serial.print(" with VREF=");
  Serial.println(voltageastext);
  analogReference(reference);
  for (byte x = 0;x<RESULTCOUNT;x++){
    results[x]=analogRead(pin);
  }
  printRegisters();
  for (byte x = 0;x<RESULTCOUNT;x++){
    sum+=results[x];
  }
  Serial.print("Average: ");
  average=sum/RESULTCOUNT;
  Serial.println(average);
  return average;
}

int8_t runTest(uint8_t reference, uint8_t pin, char voltageastext[], int16_t expected){
  runTest(reference, pin, voltageastext);
  Serial.print("Expected: ");
  Serial.println(expected);
  int16_t diff = expected-average;
  if (diff < 0){
    diff-=2*diff;
  }
  if (diff<8){
    return 0;
  }
  return 1;
}

void clearResults() {
  for (byte x = 0;x<RESULTCOUNT;x++){
    results[x]=-1;
  }
  sum=0;
  average=0;
}
void printRegisters(){
  Serial.print("ADC0.MUXPOS: ");
  showHex(ADC0.MUXPOS);
  Serial.print("  ADC0.CTRLC: ");
  showHex(ADC0.CTRLC);
  Serial.print("  VREF.CTRLA: ");
  showHex(VREF.CTRLA);
  Serial.println();
}

void showHex (const byte b) {
  char buf [3] = { ((b >> 4) & 0x0F) | '0', (b & 0x0F) | '0', 0};
  if (buf [0] > '9')
    buf [0] += 7;
  if (buf [1] > '9')
    buf [1] += 7;
  Serial.print(buf);
}

Thanks for the help on this - love the core and these uC, cheap and very featured.