Monitor 12v battery

@Kuto, have you got it working already?

If not I’d have some suggestion to make.
I must admit I’ve only skimmed over previous posts, but one thing sprang to my attention.
You do this const float ratioV = (120000 + 33000) / 33000; and assume it works out to 4.636363.
But have you actually tested this?
Since all your number literals are of integer type, your result might be of integer 4 which gets stored in your float.

Try this instead

const float ratioV = (120000.0 + 33000.0) / 33000.0;

Just a guess without having tried it, but out of old C experience, this does happen :wink:


map() might be viable in some cases, but it also only uses integer math, so if you need fractions, you’d either need to scale your values by x10, x100, x1000 or do your own float version like this

float mapfloat(floar val, float in_min, float in_max, float out_min, float out_max)
{
  if(in_max == in_min)  // avoid div/zero
    return 0;
  else
    return (val - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
2 Likes

Doesn’t the Particle give an ADC of 0 to 4095? Therefore all division should be by 4096. .02% error may throw some sensitive stuff off!

Great info @ScruffR.
IMO, if you only need accuracy of the voltage to within 1 volt, the calculation of resistors values will be ok.
If you desire accuracy to within 1/10 of a volt, then you should expect to calibrate it using a good VOM, and map().
map() is as @ScruffR says based on integer math. Our initial reading of A0 is coming in as an integer.So stick with integer until it is necessary to switch.

If you want down to 1/10 volt accuracy, you can map the output as decivolts rather than volts.
If you want down to 1/100 volt accuracy, you can map the output as centivolts rather than volts.
If you then later want to display the results for someone not aware of your scale, then convert it to float and divide so it can be displayed as volts.

Just my thoughts on the map() function.
Thanks, Jack

Thank You Guys for the help. scruffr I tried you suggestion, but still show a bit off. About the map() I still trying to understand how it work and how do I need to write it on to make it work. )-:

@Kuto, It is a bit confusing when you first look at it. [it is documented at][1]
[1]: http://docs.particle.io/photon/firmware/#math-map

Basicly, set your voltage to about your lowest expected reading, Get the value from the volt meter, and get the value from the reading on A0 (we can call these V_low and A0_low), these are related.

Then turn the voltage up to about the highest you expect, and get the readings, V_high and A0_high. These are related.

I normally use a reading of 1/10 of a volt for V_low and V_high (if it is 6.8 volts, I enter 68 being an integer). decivolts.

Then in your code, inside loop(), something like this:

int rawVal = analogRead(0);
  decivolt = map(rawVal, A0_low,A0_high , V_low, V_high); 

Once you have it calibrated, you should get a value in decivolt pretty accurate for 1/10 of a volt. (ie, 11.7 volts, would be 117).
It is a bit confusing, I can see. Ask again if you have more questions.

map() takes a set of numbers, and maps them to a new set of numbers, like: convert 1 - 100, to 4 - 8, so if the input number was 50, the output number would be 6.

BTW: a small cap on the analogue input would be a good idea ( 0.01 uF maybe).

1 Like

This what I did I went to the lowest that Im going to measure and the highest I going to measure. So I got this.
V_298 and A0_.362 Lowest
V_1502 and A0_1.455 Highest

So in the code do I write it like this?

void setup()
{
    Serial.begin(9600); 
}

void loop() 
   {
int rawVal = analogRead(0);
int decivolt = map(rawVal, 362,1455 , 298, 1592);
Serial.print(" Voltage ");
Serial.print( decivolt);      
delay(3000);
    }
Please use this format to insert code in discourse

``` <--- insert this

//paste code here

``` <-- insert this

That looks good to me @Kuto. I see no problem with that.
Good luck. Let us know how it works.
I do notice you say 1502 one place, and then 1592 in another. That just a typo?

@Kuto, I’ll have another look into it ASAP

I have some suggestions too.

Because you are using relatively high resistor values, it’s probably too high voor the ADC to properly convert (it ends up too low). In the case of ATmega AVR’s (Arduinos) for example, the data sheet advices you to use less than 10kΩ of resistance on analog inputs when measuring. You can solve this either by lowering R1 (and R2 with it), or by putting a voltage following opamp between the divider and the analog input.

It’s also maybe a good idea to switch wifi off and add some delay before measuring the voltage. Because the more power you draw, the higher the voltage drop will be. Which means that you might be measuring a couple of millivolts less than the actual charge. This is probably negligible from your point of view though, so might not bother doing that.

Another thing you might want to do, is measure the resistors with your multimeter and use the measured values in your code. It’s all about the ratio, so measuring them will give you some more accuracy for sure. You don’t have to bother having 1% resistors in that case either.

Oh and if you’re going to go the route of adding an opamp in your circuit, you might consider using a voltage reference as well. At home I have a solar charger hooked up to a battery and I measure the charge using a 10 volt reference and a differential amplifier circuit. This measures the difference between the 10 volt reference and the battery. Which results in 0-4(max), when fully charged. Again, this will give you a nice increase in accuracy.

1 Like

@Kuto, you did get some good advice from @zoef__ and @Jack in some of the previous posts, which should bring you closer to your desired result.

Some addition from my side (might be already in the posts I only skimmed over :blush:).
You may also like to measure the actual 3V3 output of your Core and set this value as reference in your code - instead of 3300mV (in your original code), since this will be closer to the internal ADC ref than 3.3V.
The next thing instead of just lowering your resistor values, would be to calculate what the best combination of resistors might be for your purpose, to use the full range of 0..4095 /0V..~3.3V and use the closest standard value you can get your hands on.
Your maximum analogRead of 1455 does leave more than half of the ADCs capabilities unused.
And applying an amp for your measureing will definetly help too.


While this might sound logical, for the code to which this is applied, it's not applicable.
Since the integer value is used to scale up a 3.3V reading to 12V the loss of 0.636363 in the scaling factor results in a difference of 2.1V (3.3V * 4 = 13.2 compared to 3.3V * 4.636363 = 15.3] (or worse, if 12V corresponds 1.6V instead of 3.3V, due to the choice of resistors used).

So please always remember to double check info you get (even from Elite's - we do suffer brain farts from time to time, too ;-)) and for others, please do think twice, before pushing out of the shelf advice, if it actually is applicable in this special case.
E.g. "looks good" but not drawing attention to the fact that the ADC is not put to its best use, is only half-helpful :confused:


Thanks @Jack for rewording my suggestion to scale your values by x10, x100, x1000

This does definetly make things clearer :wink:

1 Like

@steelydev, sorry I missed your post at first, but no.
There is a more elaborate post about this on this forum somewhere, but I just can’t find it at the mo’.

But if you do the maths for the two extremes

Min-Value (0):
3.3 * 0 / 4095 = 0.00000000

Max-Value (4095):
3.3 * 4095 / 4095 = 3.30000000

But if you do the last one according to your suggestion, you won’t get back your original 3.3V

Yup. I get it.

1 Like

@ScruffR, I am not clear on your suggestion.

  1. Use the calculation method.
  2. Use VOM calibrate and the map() method.
  3. Some other method?
    I suspect each one has good and bad aspects.

BTW:Just a word of warning in case it wasn’t mentioned earlier. If R2 becomes disconnected, there is a risk of damaging your processor.

Thanks, Jack

@Jack, and I don’t quite get what’s not clear for you there :confused:

But the main point is if @Kuto understands what was meant?
Since he’s already put some thoughts into the “tedious but transparent calculation” method (vs. hidden map() with int maths), I rather tried to offer support from there, than “confuse” him more than necessary.
So, @Kuto have we confused you even more now, or are you good with us :wink:



Off topic:
map() is actually nothing else than “calculation method”.
In the given case, it just goes together with a different set of “anchor points” (0…3.3 plus voltage devider constant vs. measured min/max ADC value/voltage).
map() just hides the maths from the user and due to the hidden integer nature the given results might not always line up with what you’d expect when checking with a DMM and doing the maths on paper.

1 Like

Thank You Guys for th support I had to go out of town, but ill try your recommendations to see if I can make it work.
Thank You, again :wink:

3 Likes

I moved 6 posts to a new topic: Q: Does the length of an integer datatype impact the precision of calculation?

Thank You Guys for the support.
I got it working. So far Im happy with the reading.

1 Like

@Kuto, great that you got it working for you.

It may help others with similar needs, if you would share how you have it setup (hardware, firmware), and your resulting readings.

Thanks, and good luck.

Im using the code that @kennethlimcp Elite provided me. The only thing I changed was I bought new Precision resistors from Digikey and first measure the actuals resistance and put it on the code. After that it stared giving me almost a perfect reading because sometimes the reading would give me a little off by more or less by 100 millivolts, but Im happy with that. (-:
Thank You,

const float voltsPerBit = 3.3 / 4095;  // Calculate volts per bit of ADC reading
const float ratioV = (120000 + 33000) / 33000;  //Calculates to 4.636363

unsigned long old_time = millis();

void setup()
{
    Serial.begin(9600); 
}

void loop() 
{
    if(millis() - old_time >= 3000){
        int Vin = analogRead(A0);
        float rawVolts = Vin * voltsPerBit;  //Calculate voltage at A0 input
        float batteryVolts = rawVolts * ratioV;
        Serial.print("Voltage ");
        Serial.print(batteryVolts);
        
        old_time = millis();
    }
}

(ScruffR: I’ve reformatted your code block for you ;-))

1 Like

@Kuto, great that it is working now. I was just curious,
what did your VOM measure the two resistors in the voltage divider at ? The script does not seem to indicate the new values.