ADC use to measure battery level / Vin level

  • Hi,
    I want to measure and report the Vin tension (that will be the battery level). So I monted a resistor bridge : 220kOhm between GND & P16 and 1MOhm between P16 & Vin to make Vin attenuation. I plug the USB : I have Vin at 4.66V and P16 at 0.76V.
    I run the code:

    import machine
    adc = machine.ADC()             # create an ADC object
    apin ='P16')   # create an analog pin on P16
    val = apin()                    # read an analog value

    and run and get :
    "Tension 4095"

    I have not understood the ADC use may be...
    1/ I expected 0.76x1000 = 760 Why the difference?
    2/ how I use the code adcchannel.voltage() in my code please ?
    Remark : If I connect P16 to GND, and run, I get 0 (so normal).
    thank you

  • @johncaipa The maximum range of the ADC when using the 11db attenuation is 0-~3V. A Li-Battery has a voltage of up to 4.2V. So you need a voltage divider. And then it does not matter, whether the range at its output is 0-1V or 0-3.3V. So take a resistor pair of 1MOhm(to battery) and 220kOhm, dividing by 5.4545, and use the basic range. Add a smal capacitor at the ADC input to GND to have a low impedance for the moment of sampling. That divider will draw a constant current of 2.5µA. If that is too much, you'll need a P-Mosfet to switch of the divider when not needed.

  • @robert-hh What do you recommend to get a aprox. voltage of a battery connected to Vin without using the expansion board? A voltage divider?

  • @prx The basic range of the ADC is 0-about 1 V as input voltage, which returns a numeric range of 0-4095. That's what is returned with apin.value(). The ESP32 has a switchable attenuator, by which the voltage range can be changed, up to 0-about 3.3 V.
    The built-in ADC of the ESP32 is very bad. It is noisy and nonlinear, and there is some variation between the devices. So the lower value is not 0V but about 0.05V, and the upper value also varies, and there is an substantial temperature drift. Effectively, the resolution is 8-9 bits.
    The Pycom API contains some serve functions. First of all to deal with the upper level variation, caused by the varying internal Vref, consisting of adc.vref_to_pin() which connects the internal Vref to an external Pin, so you can determine the value with an voltage meter. And then you can call adc.vref() to store that value in the API data.
    This value will then in combination with the attenuator setting be used by adcchannel.voltage() or adcchannel.value_to_voltage(value) to convert a raw ADC value into a voltage reading.
    The newer Pycom devices (WiPy3, LoPy4, FiPy) with a Rev1 ESP32 chip are told to be calibrated for a lower non-linearity, but that does not reduce the noise.
    If you need a precise ADC, use an external one.

  • @robert-hh
    Hi thank you for your explanation, in fact if you read ADC doc only, you can see : "Create an analog pin.
    pin is a keyword-only string argument. Valid pins are P13 to P20"
    and its example is on P16 !!
    And you forget the expansion board with the divider on P16. Sorry because I scanned all the pins between P13 to 20 !
    I use the expansion board as a dev kit , not for final production: I will have my own simplified board, so I forget all the features provided on it.

    You are so implicated and full of knowledge that: yes, I had thought you were from the supplier ! ;-)

    But I still miss something : the rule between the value and the voltage : is it 1.1V for 4095 ? or 1V or ???
    so 2000 gives 2000/4095 x 1.1 = 0.53V ?

  • @prx P16 is working fine and not different form other ADC pins. But if you plug you device on an expansion board, it will allow you to read the battery voltage from the battery conector, because the expansion board connects the battery to P16 with voltage divider with a factor or 56k/(56k+116k) = 0.3256. If you battery has 4.2V then you get 1.38V at P16.
    If you connect Vin Via an 1Mohm /220k resistor to P16, this collides with the resistors on the expansion board. Try to use another pin.
    I did never test P19 or P20. They may behave different because they are also used to connect a crystal or 32 kHz oscillator for the real time clock.
    I also never had difficulties in understanding the documentation about the ADC. or Please read them careful and make yourself familiar with the operation of an ADC.
    Note: I'm not working for Pycom.

  • @robert-hh hi,
    I do not understand what you explain: where that is documented more deeply please?
    Do you reply to my post dated Sept 30 mentioning a bug on P16 ?
    Some other questions are not answered:
    -- Test on P19 & 20 to be made : are they compliant with ADC ?
    -- what is the rule (very poor documentation on that aspect) between the value returned (ex : 2045) and the tension in V ???
    Do you mean basically that P16 is different from the other pins ?
    Definitely the documentation is lacking.
    thanks by advance .
    best regards

  • @prx P16 is connected on expansion board 3 via a voltage divider 115k/56k to the battery input.

    The basic range of all ADC inputs is 0-1v for a value range of 0-4095. You can change the input voltage range with the atten setting of 3dB, 6dB and 11dB up to a range of 0-3.3V

  • @robert-hh
    I am still on Sipy.
    So: I tested all pins on ADC function : :P16 does NOT work: seems to have a pull-up connected in the firmware : test the tension on P16 disconnected: you get 4095 and 1.2V !!, P14;15;17 & 18 work : still a bug similar to SPI one ?? never been tested that Sipy? :-( can you correct please the firmware ?
    -- Test on P19 & 20 to be made : are they compliant with ADC ?
    -- what is the rule (very poor documentation on that aspect) between the value returned (ex : 2045) and the tension in V ???
    Thanks a lot

  • @prx You did not tell which Pycom device you are using, but besides that the data you have is inconsistent. A voltage divider of 220k/1M should have a voltage of about 0.8 V, assuming a Vin of 4.6V and an internal Impedance of the meters of 10 M. O.64 and 0.56 sound more like a Vin of 3.3V, or bad connections in your test set-up.

    adcchannel.voltage() returns reasonable values only for the input at the device. Any external circuitry has to be considered by your code. Therefore it is less useful in your case.

  • @robert-hh Thanks for your answer : it is not documented what you tell me , interesting ! (--> documentation should be improved on this subject)

    So I remake the same experience this evening without changing anything but at the beginning I unplug P16: + I measure with the oscilloscope + voltmeter: I get:

    • without pluging P16 (at 220KOhm resistor): oscillo=0.64V, voltmeter =0.56v

    • P16 plugged : oscillo=0.96V, voltmeter =0.89v and Tension 3781 ! Vin is the same in any case.

    • tension is not the same as this morning, I do not explain why

    • tension is not exact may be on my voltmeter

    • mainly : the tension plugged is different of the one unplugged :Why ? : is there any pull-up resistor in P16 ?

    In return:

    • Thank you for your ideas.
    • Can you please explain me how I should use the code adcchannel.voltage() in my code please ?

  • @prx The raw range of the adc is 0-4095, at a input voltage of roughly 0-1V. Os with 0.76 V you should get a reading in the 3100 range. 4095 means overflow of the ADC.
    If I make the same set-up here with a Lopy4, I get:
    Vp6 = 0,83
    Tension 3271
    Which is in the expected order.

Pycom on Twitter