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

  • @andreas said in ADC use to measure battery level / Vin level:

    We are getting reasonable readings when using the 1 MΩ / 1 MΩ values as resistor values (matching v3.2)

    @MKO just measured the resistor values and confirmed [1] that it's also 1 MΩ / 1 MΩ for the Pycom Expansion Board v3.1, as supposed.


  • @andreas Thanks for the nice words. And no, I cannot tell anything about the v3.x expansion boards. I do not use them. But if you have a voltage meter (and a v3.1), you can tell the ratio and yourself, by taking the value without a xxPy installed, but a battery attached. Since there is a BAt jumper, you can take that of an measure the resistance between the respective xxPy pin and the jumper. In combination with the ratio you get both values. You can also measure the resistance between the jumper an a) GND and b) the yyPy pin. a) will give you the sum of both resistors, b) the value of the high side resistor.
    And yes: It's a shame that Pycom does not publish technical documentation, even if promised.

  • Dear @robert-hh,

    first things first: Since starting to read the Pycom forum more intensively recently, we wanted to tell you that we appreciate your excellent comments all over the place.

    Our story is that within the Hiveeyes [1] and Bee Observer [2] projects, we are developing a datalogger based on the Pycom FiPy [3].

    Currently, we are dedicating some time to implement the vcc reading correctly for different boards over at [4] and [5], which might also spark your interest.

    We are using the Pycom Expansion Board (v3.1 on my workbench) but also develop custom extension boards, so we compiled a list of representative resistor values matching the respective boards.

    • Pycom Expansion board v3.0: 115 kΩ / 56 kΩ
    • Pycom Expansion board v3.1: Undocumented, probably like v3.2?
    • Pycom Expansion board v3.2: 1 MΩ / 1 MΩ
    • BOB-HAT-V5: 1 MΩ / 470 kΩ or 220 kΩ
    • BOB-SHIELD: 10 MΩ / 2 MΩ
    • Air Quality monitor: 100kΩ / 47 kΩ with 2.5dB attenuation

    (all readings using 6dB attenuation unless otherwise noted)

    As you can see, we are still missing the appropriate values for the Pycom Expansion Board v3.1. So we are humbly asking if you are familiar enough with the different revisions of this piece of hardware to answer our question. We are getting reasonable readings when using the 1 MΩ / 1 MΩ values as resistor values (matching v3.2), but wanted to ask and share our observations anyway, a) to be absolutely sure and b) as a reference for others.

    Thanks already and keep up the spirit.

    With kind regards,


  • @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