# This ADC code works, but I don't understand why

I have the following code for reading a battery voltage on the ADC on a microcontroller (Atmel SAM D21 to be precise.) The reference voltage is 3.3V and the ADC is reading at 12 bit resolution:

/**
*
*/
int16_t i;
uint8_t c;

/**
* Read the main battery voltage.
*
*/
{
// Switch on the Control Pin

// Battery Voltage
float batt_voltage = 0.0f;

// Array of Samples
int x;

for (x = 0; x < BATTERY_READINGS_COUNT; x++)
{
delay_us(20);
}

// Counter for the Sum
uint32_t sum = 0;

// Loop through and Average the Readings
for (x = 0; x < BATTERY_READINGS_COUNT; x++)
{
}

batt_voltage = (sum / (float)BATTERY_READINGS_COUNT) * 0.8;

// Set the Battery Level
battery_level.i = (uint16_t)batt_voltage;

// Switch off the Control Pin
}


The code works and gives me a very accurate reading for battery voltage - I've tried it with a pretty accurate power source and multiple voltages and the reading is good every time. When I switch the reference voltage to 5V, it's no longer accurate, unless I remove the * 0.8 multiplier.

I'm still wrapping my head around how ADCs work, and I was wondering if someone could explain what's going on here.

Why does a multipler of 0.8 work for a 3.3V input and a multiplier of 1 work for 5V?

• How do you switch the reference voltage to 5V? I don't think the SAMD21 ADC can handle an (external) reference voltage of 5V, as it is a 3.3V thingy. Aug 31, 2020 at 17:13
• I was using the 5v output on the dev board. I have since learned that this is a bad, bad idea! Sep 1, 2020 at 16:34
• It's sibling the SAMC21 is 2.7 - 5.5V tolerant though. Sep 4, 2020 at 13:23
• And it is Cortex M0+ so you shouldn't be using floating point. Sep 4, 2020 at 13:27
• You can use floating point if you implement it software side. It's a little slower, but works well enough for something like this where we're only taking a reading every minute or so. Sep 5, 2020 at 17:05

Generally the output of an ADC is $$\\frac{v_{in}}{V_{ref}}N\$$, where $$\N\$$ is the number of counts you get from the ADC. So if it's a 12-bit ADC the count is $$\N = 4096\$$. If your reference voltage is $$\3.3\mathrm{V}\$$, then each ADC count represents a voltage increase of $$\\frac{3.3\mathrm{V}}{4096} \simeq 806\mu\mathrm{V}\$$ This is close to multiplying by 0.8 to get millivolts.

It's best to make this calculation explicit in your code. Modern C compilers will let you do this as a series of #defines, or possibly const float expressions and then optimize to the actual value; modern C++ compilers will let you do the same thing with constexpr float expressions, only with better type checking than C #defines.

Something like the following would do, and would eliminate the magic number 0.8 from your code:

#define ADC_REF 3.3     // volts


Giving it a 5V reference voltage is violating the absolute maximum parameters. The data sheet asks you to limit this to VDD - 0.6V. I'm not sure why the -0.6V part, but generally chips have input protection diodes to the highest voltage ($$\\mathrm{V_{DDANA}}\$$ in this case), so the chip is probably (unhappily) yanking the +5V reference down to around 4V, which would give you a multiplier of about 1 -- and do all sorts of strange and possibly bad things to the chip.

For that matter, giving it a 3.3V reference is violating the maximum absolute parameters, too, only not as bad. Table 37-24 in the datasheet lists the maximum reference voltage as $$\\mathrm{V_{DDANA} - 0.6V}\$$. So, properly, if you're using a 3.3V analog supply, you should use no higher than a 2.7V reference (2.5V would be convenient, because there's precision 2.5V references).

• Can you add a link to the data sheet where you found that information? I searched for a while and was unable to. Sep 1, 2020 at 13:05
• @RussellBorogove Table 37-4 (Vddana max = 3.63V) and Tabel 37-24 (Vref max) Sep 1, 2020 at 15:29
• @TimWescott thanks for the detailed explanation! This makes a lot more sense now. Also, I had not noticed about not using a 5v ref voltage. That would explain some weird readings I got using that ref. Sep 1, 2020 at 16:33
• Except on quite special chips, the highest and lowest allowable voltages are on the supply and ground (or negative supply) pins. Reference inputs on ADCs are inputs, not supplies. In general, on an ADC, if you raise any input (including the reference) a diode drop or more above the supply voltage, Bad Things will happen. Sep 1, 2020 at 17:03
• Table 37-24 of the datasheet says V_DDANA - 0.6V, not +0.6V. Sep 1, 2020 at 18:50

The value coming from the DAC is 12-bit, i.e. an integer between 0 and 4095 inclusive. The highest value is seen when the input voltage is equal to the full-scale reference voltage.

3.3V ref, 3.3V in yields 4095; 4095 * 0.8 = 3276, which, if interpreted as mV, is pretty close.

5V ref, 5V in yields 4095; 4095 * 0.8 = 3276. You'll want a multiplier of about 5000/4095 = 1.22 to get an accurate report with a 5V reference -- though as pointed out in comments, this part might not actually support an external reference over 3.3V!

• @TimWescott says that each step is 3.3/4096 V but Russell says each adc step is 3.3/4095 V. Who's right? Sep 1, 2020 at 17:29
• @DDuck TimWescott is correct. If you search this website you will find an explanation on why. Simplest to just think about it as one-bit or two bits and a bar. With one bit you have two pieces, separated by one slice. With 2 bits you have four pieces separate by 3 slices. Ask yourself, is each "count" a slice? Or a piece? Sep 1, 2020 at 18:08
• Each DAC output bin covers a range of 3.3 / 4096 V, but the highest figure you'll get as output from the DAC is 4095. It's analogous to having a two-digit decimal display; there are 100 different values you can show including 00, and the highest value you can show is 99. Sep 1, 2020 at 18:45