Leonardo(32u4) internal reference 2.56V: wonder why?

Ever wondered why the Leonardo ( 32u4, ATmega32U4 ) has an internal reference of 2.56 volts? Not 2V, not 3V?

The 10bit sampling of analogRead() gives you 1024 levels: 0 to 1023

Incidentally, 2560mV / 1024 = 2.5, exactly. This means that if you multiply the analogRead() sample value by 2.5 you get your analog voltage in millivolts.

But wait! There is more…

Say:
v = alalogRead(pin)
mV = 2.5 * v
same as
mV = 2v + 0.5v
same as
mV = 2
v + v/2

Maybe you didn’t know of the neat low level trick, arithmetic shift, where:
shift left 1 on a number << multiplies it by 2
shift right 1 on a number >> divides it by 2

So, instead of multiplications you can do this to convert your sample to millivolts:
v = alalogRead(pin)
mV = v << 1 + v >> 1

No multiplication, no floating point arithmetic.

PS. The cool thing is, the compiler knows about arithmetic shifts so you can optimise the conversion simply by writing:
mV = 2*v + v/2
and it will all be taken care of so long as no one involved is a float. Easier to read than shifts.

Read more about Arithmetic Shift:

You are not clear if you mention a floating point calculation or an integer calculation.
If optimized code is needed that is very fast, some arithmetic optimizations can be useful. But I would not recommend it.

For normal readable code, a floating point calculation using 2.56 is best.
And the internal voltage reference is never exactly 2.56, it might be 2.52 or 2.63. That would be easy to correct in the code if a normal floating point calculation is used.

Thanks for sharing the idea - even if it raises some questions - discussion is good!

Note that the analog measurement itself takes about 120 microSeconds so the math will probably not be the problem.

Hey all, I just thought this was an interesting conclusion. Does it actually help anyone? No idea. Helps me crystalise things in my head anyway XD

@caltoa you are right! Idid not mention that the variables "v" and "mV" need to be integer/long to benefit from this. Also, how can you measure the absolute internal voltage reference so you can correct for it in code?

@robtillaart 120us?? Wow! I have to keep that in mind.

Thanks for the feedback guys!

aris00:
@robtillaart 120us?? Wow! I have to keep that in mind.

just a litlle test script to show the timing, there is a small loop overhead of course

volatile int x = 0;

void setup() 
{
  Serial.begin(115200);
  Serial.println("Start ");
  
  uint32_t start = micros();
  for (int i = 0; i < 1000; ++i)
  {
    x = analogRead(A0);
  }
  uint32_t stop = micros();
  
  Serial.println(stop - start);
}

void loop() 
{
}

output: 112096 => 112 usec per analogRead => max 8928 reads per second (with no time to do anything else :wink:

@robtillaart That's a handy experimental result. Will note that. Thanks!

While we are talking timing and analog inputs, it is worth mentioning what I read elsewhere in the forum: Switching analogReference() takes about 5.5ms : "this delay is because of the 100nF cap on the AREF pin" says @wayneft

Things might have changed in the library. I read some day that analogReference is kind of fake, and it is set during the next analogRead. I'm too lazy to read the source code now :astonished:

Not exactly fake, just weird... The analogReference function does not perform the switch, it only writes an internal variable. Then at the beginning of analogRead, the register of the AVR is written.

I'm not quite sure how the ADMUX register is designed in 32U4 yet though. Arduino code (look for "wiring_analog.c") is not very well documented wrt Leonardo unfortunately.