Author Topic: How to "scale" / map voltage divider output?  (Read 2580 times)

0 Members and 1 Guest are viewing this topic.

Offline doublec4Topic starter

  • Regular Contributor
  • *
  • Posts: 119
  • Country: ca
How to "scale" / map voltage divider output?
« on: April 01, 2023, 07:31:52 am »
Hello,

I'm trying to use a simple voltage divider to monitor the battery level on a li-ion battery pack.

My controller (ESP32) can accept 3.3V max analog input. I can use a regular divider to easily calculate the resistor values such that the max output remains 3.3V or less for a fully charged battery at ~13.4V

However the problem is that a "dead" battery is ~9.6V for example. The result is that my voltage divider output range is 2.3 - 3.3V since the ratio of output to input is maintained. How can I scale the output from 0 - 3.3V so that the analog channel on my controller has more resolution to determine the battery percentage? Or am I overthinking this and a range of 1V is enough to get a decent reading?

I've been trying to search for a solution but maybe I am not using the right terms. 

Thank you
 

Offline Kleinstein

  • Super Contributor
  • ***
  • Posts: 14192
  • Country: de
Re: How to "scale" / map voltage divider output?
« Reply #1 on: April 01, 2023, 07:37:59 am »
One can subtract the offset, but this also adds unvertainties. Modern µCs tend to include a 12 or 10 Bit ADC and to improve an that one would need resistors better than some 0.1 %.
When using 1 V of the 3.3 V span one looses less than 2 bits of the theoretical resolution and the remaining 8-10 bits should still be plenty to monitor the charge level. For more one would also look at the temperature, as the voltage also changes with temperature.
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2068
  • Country: br
    • CADT Homepage
Re: How to "scale" / map voltage divider output?
« Reply #2 on: April 01, 2023, 08:48:48 am »
Also one should take advantage of the low required measurement bandwidth and implement some oversampling/noise reduction in the firmware. Often MCU ADCs have considerable noise (more than 1 LSB). Some people halt the CPU while taking ADC samples.

Regards, Dieter
 

Offline Ian.M

  • Super Contributor
  • ***
  • Posts: 12856
Re: How to "scale" / map voltage divider output?
« Reply #3 on: April 01, 2023, 09:02:00 am »
IIRC the ESP32 internal ADC is notorious for its poor accuracy . . .  :horse:
 

Offline MrAl

  • Super Contributor
  • ***
  • Posts: 1437
Re: How to "scale" / map voltage divider output?
« Reply #4 on: April 01, 2023, 07:09:44 pm »
Hello,

I'm trying to use a simple voltage divider to monitor the battery level on a li-ion battery pack.

My controller (ESP32) can accept 3.3V max analog input. I can use a regular divider to easily calculate the resistor values such that the max output remains 3.3V or less for a fully charged battery at ~13.4V

However the problem is that a "dead" battery is ~9.6V for example. The result is that my voltage divider output range is 2.3 - 3.3V since the ratio of output to input is maintained. How can I scale the output from 0 - 3.3V so that the analog channel on my controller has more resolution to determine the battery percentage? Or am I overthinking this and a range of 1V is enough to get a decent reading?

I've been trying to search for a solution but maybe I am not using the right terms. 

Thank you


Hello there,

For myself i tend to like to go to a higher resolution ADC.  You can get an external 15 bit ADC pretty cheap that is already mounted on a small PC board, maybe 3 for $10 USD.

If you dont want to do that then the next idea is to subtract a constant voltage from the reading and then adjust the uC code to account for it.
This means if you want to measure 10 volts with a range of 8 to 12 volts then you use a voltage reference or divider to subtract 8 volts from the voltage, then measure the resulting voltage.
When you subtract 8 from 10 you get just 2 volts.  That means that 2 volts now means you really have 10 volts, and 4 volts means you really now have 12 volts.  If you then read 0 volts that means your actual voltage is 8 volts.  So your range is now from 0 to 4 volts but you are actually measuring from 8 to 12 volts.
Let's say you are measuring from 9.6 to 13.6 volts.  That's a range of 4 volts.  If you multiply 4 by 0.825 you get 3.3 volts.
If you subtract 9.6 volts from the reading you get 0v at the low end, and 4 volts at the high end, and if you multiply that (divider) by 0.825 you get 0v at the low end and 3.3v at the high end.
You do have to actually subtract that 9.6 volts though and you can use a voltage reference diode set up for 9.6 volts.  Alternately you could use an op amp and subtract that way, but you should use a voltage reference diode for the constant voltage reference not a zener.

We could draw up a little circuit if you like.

I found the subtraction method to be interesting because if your battery goes too low you really don't need to know the voltage, you just need to know the undervoltage threshold was reached and that means something is really wrong.  For your example if your battery was down to 3 volts you would not need to know it was at 3.001 volts or 3.000 volts, you already know something is really wrong so it doesn't really help to know the actual voltage.
I am using something like this for a solar project but i like a little extra headroom at the top.  My battery is 12 volts and it can go up to 14.2 or more but i have my ADC set up to go from 0v to 16.384 volts.  I don't really need it that high but dont want to have to bother with losing 2 volts just for better resolution when i intend to move to a 16 bit ADC soon anyway.  The resolution i get right now with just a 10 bit ADC isn't that bad either, it's about 0.016 volts from 0 to 16.384 volts, so don't really need much better actually.
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8172
  • Country: fi
Re: How to "scale" / map voltage divider output?
« Reply #5 on: April 02, 2023, 06:57:54 am »
Voltage is not a perfect indicator of the battery state-of-charge anyway, so ADC resolution seldom is the dominating problem. Just scale in software.

Linearly mapping 3.1V/cell = 0% and 4.15V/cell = 100% produces decent % display, which of course jumps around during heavy load situations etc., and is not linear because cell voltage curve isn't, but never is catastrophically off like more complex fuel gauging algorithms, especially those based on integrating charge (A*s), can be.

Basically for a single cell, percentage = (100*(millivolts-3100)) / (4150-3100).

Software averaging gets rid of noise. Cumulative moving average (basically a simple IIR filter) is easy to implement:
average_cumul = (average_cumul*255 + (latest_sample*256)*1)/256
average = average_cumul/256

This mixes 1 part of new value to 255 parts of current averaged value. If you work with integers as in this example, you scale the input value by 256x so that you don't lose resolution in calculation, then scale back by /256 when accessing the cumulative average. This filter updates every cycle, smoothly. Another option is just to average samples together and update when you have enough of them:
cumul += latest_sample;
cumul_n++;
if(cumul_n == 256)
{
    average = cumul/256;
    cumul = 0;
}
« Last Edit: April 02, 2023, 07:04:55 am by Siwastaja »
 

Offline MrAl

  • Super Contributor
  • ***
  • Posts: 1437
Re: How to "scale" / map voltage divider output?
« Reply #6 on: April 02, 2023, 08:07:47 am »
Voltage is not a perfect indicator of the battery state-of-charge anyway, so ADC resolution seldom is the dominating problem. Just scale in software.

Linearly mapping 3.1V/cell = 0% and 4.15V/cell = 100% produces decent % display, which of course jumps around during heavy load situations etc., and is not linear because cell voltage curve isn't, but never is catastrophically off like more complex fuel gauging algorithms, especially those based on integrating charge (A*s), can be.

Basically for a single cell, percentage = (100*(millivolts-3100)) / (4150-3100).

Software averaging gets rid of noise. Cumulative moving average (basically a simple IIR filter) is easy to implement:
average_cumul = (average_cumul*255 + (latest_sample*256)*1)/256
average = average_cumul/256

This mixes 1 part of new value to 255 parts of current averaged value. If you work with integers as in this example, you scale the input value by 256x so that you don't lose resolution in calculation, then scale back by /256 when accessing the cumulative average. This filter updates every cycle, smoothly. Another option is just to average samples together and update when you have enough of them:
cumul += latest_sample;
cumul_n++;
if(cumul_n == 256)
{
    average = cumul/256;
    cumul = 0;
}


Hi,

Are you sure you did that right?
I use this all the time:
avg=(avg*255+newsample)/256

although i seldom need to average over that many samples, and note that if avg=10 and newsample=10 the result is again avg=10 which is the right value for a perfectly smooth set.

It's true that voltage is not the BEST indicator of state of charge, but over time it can be used effectively.  The idea is to take a lot of samples over a long period of time.  For example, if you charge for 1 hour and you measure 12.0v at 1am and then 11.0v at 2am, that may be standard for that battery, but if you measure 10.0v at 2am you know the charge time or current was not enough.
I do like to measure current too though that is good to know also so you can keep an eye on things.
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8172
  • Country: fi
Re: How to "scale" / map voltage divider output?
« Reply #7 on: April 02, 2023, 08:57:45 am »
I use this all the time:
avg=(avg*255+newsample)/256

This reduces the resolution of integers to 1/256 of the original, so it only works if you have enormous amount of excess resolution:

Try it with original avg=0 and newsample = 100:
avg = (0*255 + 100)/256 = 100/256 = 0
Next cycle, newsample = 100
avg = (0*255 + 100)/256 = 0
And so on; it never gets anywhere.

Solution is to scale up the input values by 256; then the cumulative average value will be scaled up by 256 too, so need to keep that in mind and divide by 256 when using it.

If you have a FPU available, then you can of course simply do
double average;
average = 0.999*average + 0.001*new_sample;
and don't care about numerical range or accuracy, as double has plenty.
« Last Edit: April 02, 2023, 09:01:02 am by Siwastaja »
 

Offline paulca

  • Super Contributor
  • ***
  • Posts: 4046
  • Country: gb
Re: How to "scale" / map voltage divider output?
« Reply #8 on: April 02, 2023, 10:52:24 am »
The digital approach is to use a I2C voltage sensor which supports 0-16V.  That puts the finer details of the analouge stuff into the hands of the people who made the voltage sensor.  So they can do a lot more complicated stuff and make it a lot more accurate than you or I.

INA3221 might be a bit OTT, it has it's 1 channel cousin though.
"What could possibly go wrong?"
Current Open Projects:  STM32F411RE+ESP32+TFT for home IoT (NoT) projects.  Child's advent xmas countdown toy.  Digital audio routing board.
 
The following users thanked this post: tooki

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8172
  • Country: fi
Re: How to "scale" / map voltage divider output?
« Reply #9 on: April 02, 2023, 11:10:05 am »
The digital approach is to use a I2C voltage sensor which supports 0-16V.  That puts the finer details of the analouge stuff into the hands of the people who made the voltage sensor.

OMG, ADC IS a "voltage sensor", and the ADC designers already did think about the "finer details". It's already there, integrated, no need to think about digital communication!

If you are adding an external I2C ADC due to the idea of somehow making it simpler to use, you will have the exact opposite result: adding the complexity of I2C communication of that external chip.

Sure, for better accuracy / features, adding another external chip makes sometimes sense.
 

Offline paulca

  • Super Contributor
  • ***
  • Posts: 4046
  • Country: gb
Re: How to "scale" / map voltage divider output?
« Reply #10 on: April 02, 2023, 02:52:51 pm »
So just wire the battery to the ADC them.
"What could possibly go wrong?"
Current Open Projects:  STM32F411RE+ESP32+TFT for home IoT (NoT) projects.  Child's advent xmas countdown toy.  Digital audio routing board.
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8172
  • Country: fi
Re: How to "scale" / map voltage divider output?
« Reply #11 on: April 02, 2023, 02:53:59 pm »
So just wire the battery to the ADC them.

With a simple voltage divider and a filtering capacitor (e.g. 1µF MLCC), yes.
 

Offline paulca

  • Super Contributor
  • ***
  • Posts: 4046
  • Country: gb
Re: How to "scale" / map voltage divider output?
« Reply #12 on: April 02, 2023, 04:05:34 pm »
Does the ESP32 ADC work from 0V to VRef.  Vref is around 1.1V.  It will accept 3.3 meaning it won't be fried by it, but anything over 1.1V will just read max.
"What could possibly go wrong?"
Current Open Projects:  STM32F411RE+ESP32+TFT for home IoT (NoT) projects.  Child's advent xmas countdown toy.  Digital audio routing board.
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8172
  • Country: fi
Re: How to "scale" / map voltage divider output?
« Reply #13 on: April 02, 2023, 06:00:00 pm »
Read the datasheet. If it has internal Vref=1.1V, then calculate the resistor divider so that maximum input voltage gives 1.1V. If it has an option of using Vcc as reference, and if you have a decent regulator supplying Vcc, you can then use Vcc as reference as well.
 

Offline radiolistener

  • Super Contributor
  • ***
  • Posts: 3348
  • Country: ua
Re: How to "scale" / map voltage divider output?
« Reply #14 on: April 02, 2023, 06:51:09 pm »
10-12 bit ADC is enough to measure battery voltage, scale on voltage divider doesn't matters. Usually there is multiple of battery cells, each is about 4.2 V. So you can easily use required voltage divider to scale it down to desired Vref. Let's assume you scale 5V max to Vref. Usually battery cell has a working range 2.5...4.2 V, which is 4.2-2.5 = 1.7 V working range and 1.7 / 5 = 0.34 full ADC scale. For 10 bit ADC it will be 2^10 * 0.34 = 348 counts per working range, so the working resolution will be about 1.7 / 348 = 0.004885 V which is good enough for battery measurement.

But on the other hand, your voltage divider will consume battery power, which is not good for battery powered device. So, I would suggest to add MOSFET switch to enable voltage divider only during battery voltage measurement.
« Last Edit: April 02, 2023, 06:54:37 pm by radiolistener »
 

Offline Ian.M

  • Super Contributor
  • ***
  • Posts: 12856
Re: How to "scale" / map voltage divider output?
« Reply #15 on: April 02, 2023, 07:43:05 pm »
Rather than high-side switching the battery to potential divider connection, it can be worth adding an ultra-low quiescent current RRIO OPAMP buffer to let you use much much higher value divider resistors so the standing current is comparable to or even significantly less than the battery equivalent self-discharge current, while still maintaining a low source resistance for the ADC input.  That also gives you the opportunity to subtract out an offset voltage to better map the battery's usable voltage range to the ADC's range.   However it does have the disadvantage of requiring very high resistance values making the circuit sensitive to leakage currents resulting from any surface contamination of the PCB.
« Last Edit: April 02, 2023, 07:44:50 pm by Ian.M »
 

Offline MrAl

  • Super Contributor
  • ***
  • Posts: 1437
Re: How to "scale" / map voltage divider output?
« Reply #16 on: April 03, 2023, 06:58:47 am »
I use this all the time:
avg=(avg*255+newsample)/256

This reduces the resolution of integers to 1/256 of the original, so it only works if you have enormous amount of excess resolution:

Try it with original avg=0 and newsample = 100:
avg = (0*255 + 100)/256 = 100/256 = 0
Next cycle, newsample = 100
avg = (0*255 + 100)/256 = 0
And so on; it never gets anywhere.

Solution is to scale up the input values by 256; then the cumulative average value will be scaled up by 256 too, so need to keep that in mind and divide by 256 when using it.

If you have a FPU available, then you can of course simply do
double average;
average = 0.999*average + 0.001*new_sample;
and don't care about numerical range or accuracy, as double has plenty.

Hello again,

I dont think you can do this with integers alone can you?
The formula i gave is a digital low pass filter with time constant about 85 percent of the denominator value.  So if each sample time was 1 second and the denominator was 10, then the time constant is about 8.5 seconds.  Of course you have to use floats or doubles with this kind of calculation.

Let me make sure i understand your formula.  You suggested:
avg2=(255*avg1+256*new)/256
then divide that by 256 again:
Y=avg2/256
with Y being the end result.
Is that right?
If so, what advantage do you see?

There are of course better ways we have not talked about yet either, for example, a digital 2nd order low pass filter.  I'll post that at some point also.

BTW, for startup speed i never start with 0*255 i always start with the very first actual sample.  So if the first sample is  9, i use that even though the average is 10, then after that employ the formula i posted, and of course using floats.  Starting with the very first sample as the average speeds up the startup time even though it may be slightly inaccurate.  As the samples come in after that, it gets better and better.

It's also interesting to let some noise in as long as it is a true random noise source.  That would mean no hardware filter capacitor or perhaps a very very small value like 0.001uf or even 100pf.
« Last Edit: April 03, 2023, 07:01:35 am by MrAl »
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2068
  • Country: br
    • CADT Homepage
Re: How to "scale" / map voltage divider output?
« Reply #17 on: April 03, 2023, 09:44:31 am »
People used to do it with integers and the method is called "fixed point". This is what Siwastaja explained without mentioning the term. Nowadays with floating point support in many MCUs you can as well use floating point if you have the FPU.
The method to improve DAC and ADC specs by adding simulated or real noise is called "dithering".
I think before designing a DSP function engineers should learn some basic terms.

Regards, Dieter
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8172
  • Country: fi
Re: How to "scale" / map voltage divider output?
« Reply #18 on: April 03, 2023, 10:27:19 am »
Let me make sure i understand your formula.  You suggested:
avg2=(255*avg1+256*new)/256
then divide that by 256 again:
Y=avg2/256
with Y being the end result.
Is that right?
If so, what advantage do you see?

Advantages compared to what?

* It works,
* It's computationally efficient, especially on a 32-bit CPU and 16-bit input values as everything fits in native register size.

Compared to your suggestion:
* If your suggestion was based on integers, which you did not specify, the advantage of mine is that it works, unlike yours which is broken,
* If your suggestion was based on floats, which you again did not specify, then my integer solution has a performance advantage, especially compared to MCUs without floating point units, but even if you have FPU, an integer solution is preferable for example in interrupt service handlers to avoid stacking of FP registers.

I have no idea why you think you can't do such calculation with integers alone. Of course you can and that's what most sensible people do with simple microcontrollers without floating point units available.
« Last Edit: April 03, 2023, 10:29:29 am by Siwastaja »
 

Offline paulca

  • Super Contributor
  • ***
  • Posts: 4046
  • Country: gb
"What could possibly go wrong?"
Current Open Projects:  STM32F411RE+ESP32+TFT for home IoT (NoT) projects.  Child's advent xmas countdown toy.  Digital audio routing board.
 

Offline Zero999

  • Super Contributor
  • ***
  • Posts: 19517
  • Country: gb
  • 0999
Re: How to "scale" / map voltage divider output?
« Reply #20 on: April 03, 2023, 10:55:03 am »
A shunt reference or zener diode can be used to subtract a fixed voltage from the input to the potential divider.
 

Offline iMo

  • Super Contributor
  • ***
  • Posts: 4780
  • Country: pm
  • It's important to try new things..
Re: How to "scale" / map voltage divider output?
« Reply #21 on: April 03, 2023, 11:11:35 am »
In case you want to enlarge a smaller voltage range to the full scale of your ADC there is only an "active solution" available - you have to use opamps (it means - to amplify 3.3x your 1Volt range into the 3.3Volt range and shift it down from 0V - 3.3V). That is rather difficult exercise. The best way is either to live with the smaller range, or, use an external ADC (ie. I've been using an ADS1110 16bit 6pin I2C ADC for that exact kind of exercises).
 

Offline MrAl

  • Super Contributor
  • ***
  • Posts: 1437
Re: How to "scale" / map voltage divider output?
« Reply #22 on: April 03, 2023, 02:36:46 pm »
Let me make sure i understand your formula.  You suggested:
avg2=(255*avg1+256*new)/256
then divide that by 256 again:
Y=avg2/256
with Y being the end result.
Is that right?
If so, what advantage do you see?


* It works,
* It's computationally efficient, especially on a 32-bit CPU and 16-bit input values as everything fits in native register size.

Compared to your suggestion:
* If your suggestion was based on integers, which you did not specify, the advantage of mine is that it works, unlike yours which is broken,
* If your suggestion was based on floats, which you again did not specify, then my integer solution has a performance advantage, especially compared to MCUs without floating point units, but even if you have FPU, an integer solution is preferable for example in interrupt service handlers to avoid stacking of FP registers.

I have no idea why you think you can't do such calculation with integers alone. Of course you can and that's what most sensible people do with simple microcontrollers without floating point units available.


Apparently we dont understand each other.  We could talk about integers, floats, doubles and leprechauns all day and night it wont do either of us any good.  I can also see we are not talking about the same register lengths either.  I think you might be talking about rolling your own floats (fixed point) also, which i've done and yes it works.  That's your choice.

Show an example where you are averaging a voltage and whatever count you want to use for that.  For example, a 10v input at startup.

Also, if mine was 'broken' i would not be able to measure any voltages, which i have been doing for the last 10 years or more with uC chips of different types.
« Last Edit: April 03, 2023, 02:39:17 pm by MrAl »
 

Offline MrAl

  • Super Contributor
  • ***
  • Posts: 1437
Re: How to "scale" / map voltage divider output?
« Reply #23 on: April 03, 2023, 02:41:52 pm »
https://diyi0t.com/ina219-tutorial-for-arduino-and-esp/

That's interesting i was just looking at those the other day.  I need to add current measurement to my solar panel telemetry.
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8172
  • Country: fi
Re: How to "scale" / map voltage divider output?
« Reply #24 on: April 03, 2023, 02:42:51 pm »
In case the OP missed the correct answers in this noise of all weird and bad advice and bot-like troll replies,

Or am I overthinking this and a range of 1V is enough to get a decent reading?

You are overthinking it, this range is enough for a decent reading. Neither noise nor any ADC-related such as nonlinearity or lack of resolution are limiting factors in state-of-charge indication accuracy. Therefore, scaling the voltage to fit the full range of ADC input, or using an external more accurate ADC will not help at all, because they reduce errors that are already smaller than the dominating error source.

The only way to really make a better battery gauge is to remove the largest error source, and model the battery characteristics and combine current/voltage/possibly temperature measurements, usually using a battery fuel gauge IC, or with a custom solution. This is significantly more complex, more work, and comes with a risk of making things worse compared to your initial suggestion of using a simple voltage divider.

TLDR, try with the simple voltage divider to see if you are happy with it.
« Last Edit: April 03, 2023, 02:47:09 pm by Siwastaja »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf