4

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);
        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++)
    {
        // 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?

JRE
  • 67,678
  • 8
  • 104
  • 179
  • 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. – ocrdu Aug 31 '20 at 17:13
  • I was using the 5v output on the dev board. I have since learned that this is a bad, bad idea! – Andrew Mills Sep 01 '20 at 16:34
  • It's sibling the SAMC21 is 2.7 - 5.5V tolerant though. – Lundin Sep 04 '20 at 13:23
  • And it is Cortex M0+ so you shouldn't be using floating point. – Lundin Sep 04 '20 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. – Andrew Mills Sep 05 '20 at 17:05

2 Answers2

21

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

TimWescott
  • 44,867
  • 1
  • 41
  • 104
  • Can you add a link to the data sheet where you found that information? I searched for a while and was unable to. – Russell Borogove Sep 01 '20 at 13:05
  • @RussellBorogove Table 37-4 (Vddana max = 3.63V) and Tabel 37-24 (Vref max) – EBlake Sep 01 '20 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. – Andrew Mills Sep 01 '20 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. – TimWescott Sep 01 '20 at 17:03
  • Table 37-24 of the [datasheet](https://ww1.microchip.com/downloads/en/DeviceDoc/SAM_D21_DA1_Family_DataSheet_DS40001882F.pdf) says V_DDANA - 0.6V, not +0.6V. – Russell Borogove Sep 01 '20 at 18:50
  • Thank you for that catch! Editing. I think I missed the minus sign because when there's a diode drop involved at all, it's + the diode drop, not minus -- but that's just a lame excuse. – TimWescott Sep 01 '20 at 18:58
  • You still have +0.6 in your 4th graf. – Russell Borogove Sep 01 '20 at 20:21
  • Thank you for your persistence. Today seems to be a day for crossing 'i's and dotting 't's for me. – TimWescott Sep 01 '20 at 20:29
4

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!

Russell Borogove
  • 1,039
  • 1
  • 9
  • 19
  • @TimWescott says that each step is 3.3/4096 V but Russell says each adc step is 3.3/4095 V. Who's right? – D Duck Sep 01 '20 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? – DKNguyen Sep 01 '20 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. – Russell Borogove Sep 01 '20 at 18:45