…about Linux, electronics, digital photography, whatever…
Icône RSS Icône Accueil
  • Maximizing ADC range for decent battery voltage monitoring (with minimum parts)

    Posté le 29th mai 2009 leucos 4 commentaires

    Tracking circuit’s battery voltage can be a nice addition to your project. Knowing what is the battery state can help designing a better, more clever circuit, and take appropriate actions depending on the battery state.

    Voltage reference

    In some project, I wanted to use my µcontroller ADC to get battery voltage. The first problem you encounter is : how can I measure on my ADC the circuit voltage since the top value of the ADC is the circuit voltage itself ? Yes, indeed, using the ADC this way you’ll always end up reading the maximum value (256 for 8 bits ones, 1024 for 10 bits ones, …), since the circuit voltage is the reference voltage.

    This can be solved most of the time. For instance, in atmel microcontrollers, you just have to use the internal reference (most of the time 1.1v or 2.56v) by changing the ADMUX register bits. Atmel AVRs also let you provide your own reference at the AREF pin. The datasheet gives plenty of details about this.

    When using Arduino, you can achieve the same ADMUX control by using the analogReference(ref) command where ref can be one of :

    • DEFAULT: use the circuit’s supply as reference (5 or 3.3 volts depending on the supply)
    • INTERNAL: use the built-in reference 1.1 volts on the ATmega168 and 2.56 volts on the ATmega8.
    • EXTERNAL: the voltage applied to the AREF pin is used as the reference.

    This being said, we can start working, but the real challenges lies ahead.

    Gain

    Simple voltage divider

    Simple voltage divider

    Now that we have our reference, how can we measure that battery (let’s say LiPo) voltage ? Let’s suppose the chosen reference is 1.1v. We can not compare the battery voltage directly. LiPo can go as high as 4.25v, much higher than our reference. So we have to scale it down. We can use a voltage divider (a pair of resistors) to make this easily. By doing so, we are applying a gain < 1, to make the battery voltage range seen by the ADC fit below our voltage reference.

    Now, what resistors values should we choose ?

    We know that :

    V={R_1/{R_1+R_2}}*V_bat

    Since we want V to be 1.1v (same as our voltage reference) when battery is full (thus 4.25v), the equation can be written as :

    1.1={R_1/{R_1+R_2}}*4.25 doubleleftright R_1={1.1/3.15}R_2

    Ok, we have our R1/R2 ratio. Since we don’t want this circuit to waste energy (after all, we’re measuring our own battery), we want R1 and R2 to be as high as reasonably possible so current flowing will be negligeable.

    Let’s say we take 3MΩ for R2, we can use a 1MΩ resistor for R1.

    Great, we now lowered the voltage in our circuit, so when the battery is full (4.25v), we read almost exactly the value given by our voltage reference (1.1 in this case), which is the max value that our ADC can report.

    But wait, what happens when the battery is depleted ? Depleted batteries never show 0v. LiPos for instance, are depleted at 2.7v. What will 2.7v show in the ADC in our situation ? Remember :

    V={R_1/{R_1+R_2}}*V_bat (we’re basically dividing the battery voltage by 4 with the values choosen for the resistors)

    Thus, when our LiPo is empty, we’ll read 0.675v, and since :

    ADC_value={V*2^bits}/V_ref

    So our 10bits ADC will tell us some number around 628. So, unfortunately, in this configuration, the only range used by our 10bits ADC is [628-1024], approximately 60% of it’s range. This is pretty bad. Now that we know how to make the voltage range fit below our voltage reference, it could be cool to extend this to the full range of our ADC. We now want our ADC to say 0 when battery is depleted, and not 628.

    Offsetting

    As you may have guessed, we can achieve that by offsetting. Offsetting means adjusting the range by using an offset value. And in our case, we just want to lower measured signal by the minimum value the battery can have : 2.7v.

    Ok, but how can we do that ?

    Offsetting the signal with a simple diode

    Offsetting the signal with a simple diode

    Well, everybody out there probaby know this way to go already. But being self taught in electronics, I just didn’t had the iden in the first place. I probably not very good at Googling too, so, I just searched for a way.

    My initial idea was to use a rectifier diode. Let’s say we arrange the circuit on the left, with a diode between Vbat and R2. Voltage between the diode will be V_bat - V_f. This is fine, things are starting to take place. Yes we want to drop some voltage thru rectifiers until Vf is 2.7v (the minimum value we are interested in).

    Unfortunately, this is not practical. First, Vf varies greatly according to the current in he circuit. Take a diode datasheet, find the Vf vs. I graph, you’ll get the idea. The other problem is to get the Vf we want. It varies, but it is usualy pretty low. This means that we would have to chain several diodes to get the desired effect, but might get surprised in the real achieved Vf, since it depends on diode bin, current, the way we read the graphs, etc…

    Not very practical. After spending an hour browsing datasheets to find the holy grail having the good Vf, I slammed my head on thedesk : « Just use a Zener stupid n00b!« . Yes, Zener, that was the solution.

    Instead of chaining diodes, I just had to put a Zener at the same place, reverse biased, with Vr being the lowest value I needed to measure in my circuit !

    But there is still a problem : we are interested in the Zener’s have a reverse breakdown volatge value. But this voltage (usually noted Vr in their datasheets) is given for a certain current. If we use MegaOhm-style resistors, it simply won’t work : you’ll end up with a Vr completely different (around 0.7v, the classic N-P junction Vf with the Zener I tried). Again, we have to open the Zener’s datasheet, and look for the test current.

    For instance BZX79 series have an Iztest current of 5mA. This means that we will have to make 5mA flow thru this branch to have the reverse breakdown voltage we are looking for.On the capture below, we can see the ideal situation. Yellow is Vbat, green is Vbat-Vz and purple is Vz (Vz is calculated by the scope, note the different scale).

    Voltages in seen in circuit (yellow is Vbat, green is Vbat-Vz, purple is Vz)

    Voltages in seen in circuit (yellow is Vbat, green is Vbat-Vz, purple is Vz)

    But the needed current for the Zener to work is pretty high, given we are running on batteries, and we want to be energy conservative. So, how can we do ?

    After some more thoughts, the solution came : instead of plugging this circuitry to ground, we’ll plug it to a microcontroller pin. When we want to measure the battery voltage (which is something we son’t have to do very often), we just put the pin in OUTPUT mode, and set it to 0v (low). Despite the name, when a pin is in OUTPUT mode, it will sink current if it’s low. The rest of the time, the pin will be set to INPUT mode (Hi-z). It’s as easy as this.

    Final circuit for battery monitoring

    Final circuit for battery monitoring

    The circuit now looks like this. With our ADC, we are measuring voltage V, between the « first » resistor and the reverse biased Zener.

    How can we choose the component values to maximize ADC range now ? We’ll try to find this out with generic terms, so it can be applied to other power sources.

    First, some facts. The maximum value that will be read by the ADC will be Vref when supply voltage is VbatMax – Vz.
    The minimum value read will be VbatMin-Vz, and should be equal to 0.

    We already can deduce how we have to choose Vz (and thus, the Zener) :

    V_min=0 doubleleftright {R_1/{R_1+R_2}}*(V_bat-V_z) = 0 doubleleftright V_bat = V_z

    Ok, the right Zener Vz should be equal to the minimum reading we’re interested in, just what we guessed above.

    Now, let’s try to find the usual relationship between R1 and R2 in our voltage divider. As stated in the above facts, the maximum value that will be read by the ADC is VbatMax – Vz. Ideally, in the middle of our voltage divider, this value should be exactly equal (scaled) to Vref (let’s call it Vmax).

    Thus, we can lay down the usual voltage divider equation stuff :

    V_max = V_ref doubleleftright {R_1/{R_1+R_2}}*(V_batMax-V_z) = V_ref

    doubleleftright R_2={R_1(V_batMax-V_z-V_ref)}/V_ref

    Since all the « Vsomething » are known values, we do have a relashionship between R1 and R2 to create our divider. The values used do not matter, only the ratio… err wait. Let’s not forgot about the Zener’s Iztest ! Remember that to get the reverse beakdown voltage we need, we have a constraint : we have to make the Iztest  current flow thru the circuit. So we need to scale R1 and R2 appropriately. Of course, since the battery voltage varies (why would we need to measure it if it wouldn’t ?), establishing a current based on the resistors values is an approximation. When the battery voltage will be low, the current will low too, and our measurement won’t be as accurate as with a full battery.

    We have several ways to handle this : we can just ignore this, and assume some average voltage. This would be probably fine for LiPos, since most of the time, they hang around 3.7v. We could get the lowest voltage we want to measure, and take it as a base to calculate resistor values to achieve Iztest. But since this voltage is zero, we won’t go far. The best option is probably to take the microcontroller datasheet, and use the maximum sink current (let’s call it Isink, watch for typos :) ) when Vbat is at maximum. This will suck the battery a bit during the ADC sampling, but again, we don’t need to check battery voltage every millisecond. Of course, we need Isink to be higher than Iztest. If Isink > Iztest, no problem as long as we don’t go over the Zener’s power rating. If Isink < Iztest, dump that crappy microcontroller, turn computer off, and have a walk.

    Ok, let’s use this and calculate resistor values to get a correct current, and a good resistor ratio.

    I_sink = {V_batMax-V_z}/{R_1+R_2} doubleleftright R_1+R_2 = (V_batMax - Vz)/I_sink

    Since VbatMax, Vz and Isink are known values, we know what R1+R2 is. And since we have a relashionship between R1 and R2, we have all we need to get R1 and R2 values. Let’s try it out.

    Pencil and paper test

    Let’s try a real life example. We want to measure our LiPo voltage. So we’ll use these values :

    V_batMax = 4.25v
    V_batMin=2.7v
    V_ref=1.1v
    I_sink = 20mA

    We already know that : V_z = V_batMin=2.7v

    Let’s find the R1+R2 sum first : R_1+R_2 = (V_batMax - V_z)/I_sink = 1.55/0.020 = 77.5Omega

    Ok, now, we can use the R1-R2 relashionship : R_2={R_1(V_batMax-V_z-V_ref)}/V_ref = {R_1*0.45}/1.1 = R_1*0.4091

    So, reusing the R1+R2 equation above, R_1+R_1*0.4091 = 77.5 doubleleftright R_1 = 77.5/1.4091 = 55Omega (we’ll use the closest standard value 56Ω).

    Finally, R_2 = 22.5Omega (we’ll use the closest standard value 22Ω).

    Converting ADC reading to real voltage

    Now that we’ve been this far, let’s not forget what we came here for : get the battery voltage. we have to convert the ADC value back to some human reading.

    A n bits ADC reads the voltage at V, and converts it linearly (supposedly) into a value between 0 (meaning 0v in our case) and 2^n-1 (meaning Vref in our case).

    So to convert for an ADC value to battery voltage, we can use :

    ADC/2^n= V/V_ref doubleleftright ADC= {2^n*V}/V_ref = {2^n/V_ref}*{{R_1*(V_bat-V_z)}/{R_1+R_2}}

    Finally : V_bat = {ADC*(R_1+R_2)*V_ref}/{2^n*R_1}+V_z

    Cheat sheet (a.k.a. fast forward mode)

    Known stuff :

    • Maximum voltage to be measured : V_batMax
    • Minimum voltage to be measured : V_batMin
    • Voltage reference : V_ref
    • Maximum sink current for µcontroller : I_sink

    Stuff to calculate :

    • Zener’s required reverse breakdown voltage :

    V_z = V_batMin

    • Resistor value R1 (low resistor) :

    R_1 = {(V_batMax - V_z)/I_sink}-R_2

    • Resistor value R2 (high resistor) :

    R_2={R_1(V_batMax-V_z-V_ref)}/V_ref

    • n bits ADC to battery voltage conversion :

    V_bat = {ADC*(R_1+R_2)*V_ref}/{2^n*R_1}+V_z

    Now, I need to test this on the breadboard. Share your experiments if you use the above stuff. And doublecheck the math :)

    PDF version