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:

 * Union for Readings
typedef union u_reading {
    int16_t i;
    uint8_t c[2];
} reading;

 * Read the main battery voltage.
static void read_battery_level()
    // Switch on the Control Pin
    gpio_set_pin_level(ADC_CONTROL, 1);
    // Battery Voltage
    float batt_voltage = 0.0f;
    // Array of Samples
    reading batt_readings[BATTERY_READINGS_COUNT];
    int x;
    // Loop through ad Average the Readings
    for (x = 0; x < BATTERY_READINGS_COUNT; x++)
        // Read the ADC Channel
        adc_sync_read_channel(&ADC_BATTERY, 0, batt_readings[x].c, 2);
    // Counter for the Sum
    uint32_t sum = 0;
    // Loop through and Average the Readings
    for (x = 0; x < BATTERY_READINGS_COUNT; x++)
        // Add the Sum
        sum += batt_readings[x].i;
    // Calculate the Mean Reading
    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
    gpio_set_pin_level(ADC_CONTROL, 0);

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?

  • \$\begingroup\$ 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. \$\endgroup\$ – ocrdu Aug 31 '20 at 17:13
  • \$\begingroup\$ I was using the 5v output on the dev board. I have since learned that this is a bad, bad idea! \$\endgroup\$ – Andrew Mills Sep 1 '20 at 16:34
  • \$\begingroup\$ It's sibling the SAMC21 is 2.7 - 5.5V tolerant though. \$\endgroup\$ – Lundin Sep 4 '20 at 13:23
  • \$\begingroup\$ And it is Cortex M0+ so you shouldn't be using floating point. \$\endgroup\$ – Lundin Sep 4 '20 at 13:27
  • \$\begingroup\$ 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. \$\endgroup\$ – Andrew Mills Sep 5 '20 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
#define ADC_COUNT 4096
#define ADC_LSB_MV (1000.0 * ADC_REF / ADC_COUNT)

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).

  • \$\begingroup\$ Can you add a link to the data sheet where you found that information? I searched for a while and was unable to. \$\endgroup\$ – Russell Borogove Sep 1 '20 at 13:05
  • \$\begingroup\$ @RussellBorogove Table 37-4 (Vddana max = 3.63V) and Tabel 37-24 (Vref max) \$\endgroup\$ – EBlake Sep 1 '20 at 15:29
  • \$\begingroup\$ @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. \$\endgroup\$ – Andrew Mills Sep 1 '20 at 16:33
  • \$\begingroup\$ 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. \$\endgroup\$ – TimWescott Sep 1 '20 at 17:03
  • \$\begingroup\$ Table 37-24 of the datasheet says V_DDANA - 0.6V, not +0.6V. \$\endgroup\$ – Russell Borogove Sep 1 '20 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!

  • \$\begingroup\$ @TimWescott says that each step is 3.3/4096 V but Russell says each adc step is 3.3/4095 V. Who's right? \$\endgroup\$ – D Duck Sep 1 '20 at 17:29
  • \$\begingroup\$ @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? \$\endgroup\$ – DKNguyen Sep 1 '20 at 18:08
  • \$\begingroup\$ 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. \$\endgroup\$ – Russell Borogove Sep 1 '20 at 18:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.