Different ADC results with different CPU clockspeeds (Attiny84a)

A little ago someone asked a question on Github about the different analogRead() values he obtained when compiling at different CPU clock speeds. I found it an interesting question and did some tests, which confirmed his findings.

I tried if there was a difference between using analogRead() or using the ADC directly, with or without ADC prescaling. Even when I prescale the ADC to always use a 125KHz ADC clock I get different results.

The 1.1V internal reference is used and I then used a potmeter to set a voltage just under 1.1V on pin PA3 and measure that at 1MHz, 4MHz and 8MHz.

Apart from a little noise, that I averaged out, I get constant and consistent different ADC values. I have no more idea's what could cause this.

Test sketch:

/* Possible values to compute a shifting average in order to smoothen */
#define AVG_WITH_1_VALUE        0
#define AVG_WITH_2_VALUES       1
#define AVG_WITH_4_VALUES       2
#define AVG_WITH_8_VALUES       3
#define AVG_WITH_16_VALUES      4

#define AVERAGE_LEVEL          AVG_WITH_8_VALUES  /* Choose here the average level among the above listed values */
/* Higher is the average level, more the system is stable (jitter suppression), but lesser is the reaction */

/* Macro for average */
#define AVERAGE(ValueToAverage,LastReceivedValue,AverageLevelInPowerOf2)  ValueToAverage=(((ValueToAverage)*((1<<(AverageLevelInPowerOf2))-1)+(LastReceivedValue))/(1<<(AverageLevelInPowerOf2)))
// AVERAGE(  xxxxx ,  yyyyy  , AVERAGE_LEVEL) ;

uint16_t adc_average;

void setup()
{
ADMUX = _BV(REFS1) | _BV(MUX0) | _BV(MUX1);; //internal 1.1V reference and PA3 channel
#if (F_CPU == 8000000)
ADCSRA = _BV(ADPS2) | _BV(ADPS1); // Set ADC prescaler to 64 125KHz ADC clock at 8MHz CPU
#elif (F_CPU == 4000000)
ADCSRA = _BV(ADPS2) | _BV(ADPS0); // Set ADC prescaler to 32 125KHz ADC clock at 4MHz CPU
#elif (F_CPU == 1000000)
ADCSRA = _BV(ADPS1) | _BV(ADPS0); // Set ADC prescaler to 8  125KHz ADC clock at 1MHz CPU
#else
#ifndef F_CPU
#error "F_CPU not defined"
#else
#error "F_CPU defined as an unsupported value for ADC prescaler test"
#endif
#endif
ADCSRA |= _BV(ADEN); // Enable ADC 
delay(5);// allow internal reference to settle.
Serial.begin(9600);
adc_average = analogReadDirect(); // initail value for averaging
}
void loop()
{
AVERAGE(adc_average, analogReadDirect(), AVERAGE_LEVEL);
Serial.print("CPU speed = ");
Serial.print(F_CPU/1000000);
Serial.print(" MHz, ADC value: ");
Serial.println(adc_average);
delay(500);
}

uint16_t analogReadDirect(){
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA,ADSC)); // measuring, wait until conversion complete
  return ADC;  
}

my rather consistent results:

CPU speed = 1 MHz, ADC value: 992
CPU speed = 1 MHz, ADC value: 992
CPU speed = 1 MHz, ADC value: 992
CPU speed = 1 MHz, ADC value: 992
CPU speed = 1 MHz, ADC value: 992
CPU speed = 1 MHz, ADC value: 992
CPU speed = 1 MHz, ADC value: 992
CPU speed = 1 MHz, ADC value: 992
CPU speed = 1 MHz, ADC value: 992
CPU speed = 1 MHz, ADC value: 992
CPU speed = 1 MHz, ADC value: 992
CPU speed = 1 MHz, ADC value: 992
CPU speed = 1 MHz, ADC value: 992
CPU speed = 1 MHz, ADC value: 992
CPU speed = 1 MHz, ADC value: 992
CPU speed = 1 MHz, ADC value: 992
CPU speed = 4 MHz, ADC value: 1012
CPU speed = 4 MHz, ADC value: 1012
CPU speed = 4 MHz, ADC value: 1012
CPU speed = 4 MHz, ADC value: 1012
CPU speed = 4 MHz, ADC value: 1011
CPU speed = 4 MHz, ADC value: 1011
CPU speed = 4 MHz, ADC value: 1011
CPU speed = 4 MHz, ADC value: 1011
CPU speed = 4 MHz, ADC value: 1011
CPU speed = 4 MHz, ADC value: 1011
CPU speed = 4 MHz, ADC value: 1011
CPU speed = 4 MHz, ADC value: 1011
CPU speed = 4 MHz, ADC value: 1011
CPU speed = 4 MHz, ADC value: 1011
CPU speed = 4 MHz, ADC value: 1011
CPU speed = 4 MHz, ADC value: 1011
CPU speed = 4 MHz, ADC value: 1011
CPU speed = 4 MHz, ADC value: 1011
CPU speed = 4 MHz, ADC value: 1011
CPU speed = 8 MHz, ADC value: 1004
CPU speed = 8 MHz, ADC value: 1004
CPU speed = 8 MHz, ADC value: 1004
CPU speed = 8 MHz, ADC value: 1004
CPU speed = 8 MHz, ADC value: 1004
CPU speed = 8 MHz, ADC value: 1004
CPU speed = 8 MHz, ADC value: 1004
CPU speed = 8 MHz, ADC value: 1004
CPU speed = 8 MHz, ADC value: 1004
CPU speed = 8 MHz, ADC value: 1004
CPU speed = 8 MHz, ADC value: 1003

Any idea's ?

You can edit your code with added delays and power supply filtering considerations:

#define AVG_WITH_1_VALUE        0
#define AVG_WITH_2_VALUES       1
#define AVG_WITH_4_VALUES       2
#define AVG_WITH_8_VALUES       3
#define AVG_WITH_16_VALUES      4

#define AVERAGE_LEVEL          AVG_WITH_8_VALUES
#define AVERAGE(ValueToAverage,LastReceivedValue,AverageLevelInPowerOf2)  ValueToAverage=(((ValueToAverage)*((1<<(AverageLevelInPowerOf2))-1)+(LastReceivedValue))/(1<<(AverageLevelInPowerOf2)))

uint16_t adc_average;

void setup() {
  ADMUX = _BV(REFS1) | _BV(MUX0) | _BV(MUX1); // internal 1.1V reference and PA3 channel

  #if (F_CPU == 8000000)
  ADCSRA = _BV(ADPS2) | _BV(ADPS1); // Set ADC prescaler to 64 for 125KHz ADC clock at 8MHz CPU
  #elif (F_CPU == 4000000)
  ADCSRA = _BV(ADPS2) | _BV(ADPS0); // Set ADC prescaler to 32 for 125KHz ADC clock at 4MHz CPU
  #elif (F_CPU == 1000000)
  ADCSRA = _BV(ADPS1) | _BV(ADPS0); // Set ADC prescaler to 8 for 125KHz ADC clock at 1MHz CPU
  #else
  #ifndef F_CPU
  #error "F_CPU not defined"
  #else
  #error "F_CPU defined as an unsupported value for ADC prescaler test"
  #endif
  #endif

  ADCSRA |= _BV(ADEN); // Enable ADC 
  delay(5); // Allow internal reference to settle

  Serial.begin(9600);

  adc_average = analogReadDirect(); // Initial value for averaging
}

void loop() {
  AVERAGE(adc_average, analogReadDirect(), AVERAGE_LEVEL);
  Serial.print("CPU speed = ");
  Serial.print(F_CPU / 1000000);
  Serial.print(" MHz, ADC value: ");
  Serial.println(adc_average);
  delay(500);
}

uint16_t analogReadDirect() {
  delayMicroseconds(10); // Additional settling time
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA, ADSC)); // Wait until conversion complete
  return ADC;
}

My guess is that at different clock speeds, different areas of the crystal heat up differently and maybe that's what causes these slight variations. It seems to me that it is not some kind of drama. The difference between the smallest and largest value is 2%, i.e. there is a +/- 1% deviation which seems normal to me.

I do not understand how delays and power supply filtering would bring the ADC results closer together for the different CPU clock-speeds. The fluctuations of the ADC measurements within a given CPU clock speed are minimal.

It's no drama at all, but I find it interesting to understand what is causing this.

According the datasheet the absolute accuracy should be + or - 2 LSB (least significant bit). I interpret that as + or - 4 decimal points.

image

FYI I am not using a crystal. The chip is running on its internal 8MHz oscillator, prescaling the system clock by 1, 2 or 8.

The internal reference also has a (not great) accuracy, probably good for another couple of bits.

Your comment sparked an idea to check if the clock speed has an influence on the internal voltage reference.
I took an Attiny85, as they have the option to select the internal 2.56V reference and present that voltage on the AREF (PB0) pin.

With VCC @ 5V I measure 2.49V at 1MHz CPU clock and 2.35 at 8MHz CPU clock.

If the same happens with the 1.1V reference, which is not available on a pin to measure directly, that may explain my findings.

That is certainly unexpected, and I wonder why. There is no obvious reason that the clock speed should have any effect on the voltage reference.

But if so, there are single IC (3 pin) voltage references that could be used as external ADC references.

Yes, that was my next thing to test. Do the whole ADC experiment over again, but with the TL431 that I used in the past after someone on this forum suggested it.