LoPy ADCs



  • Hi Guys,

    Has anyone has any success getting the ADCs to work? I thought I'd share some things here which may be helpful for others. I also have some questions that someone may be able to help with.

    • I assume that GND pin on the board is the reference 0V for the ADC. (Can someone confirm this?)
    • By default, It looks like the ADC is set up to operate from 0-1V (from experimentation)
    • Even though the specs say 12 bit, it looks like it is currently implemented at 10 bit (Max reading 1023)
    from machine import ADC
    adc = ADC(0)
    ram_pressure = adc.channel(pin='P13')
    ram_pressure.value()
    

    Given the above, everything works as expected - except for the fact that given a constant voltage applied to an ADC pin, I get big variations if I poll for the value. For example, Given a constant 357mV applied between GND and P13, I get ADC values anywhere between 293 and 312. I'm not sure if something is wrong with my board, or I'm missing something - but any help here would be appreciated. So far the ADCs don't seem very useful for real world applications.

    I believe the ADC on the ESP32 is highly configurable so the initial implementation on the LoPy may not represent it's full capabilities just yet, but at present seems very inaccurate...

    Please let me know if anyone else has tested this, along with your results, or any suggestions.

    Cheers,
    Nick



  • Some LoPy specs and performance that it would be useful to know, theoretically and measured :

    • flash memory available for MicroPython user;
    • LoRa speed and range in real user cases;
    • Bluetooth speed and range in real user cases.


  • @acheesehead said in LoPy ADCs:

    The attenuation of 4 does not work. 0 to 3 do.

    Yes, ADC_ATTEN_MAX is just there as a constant for internal use of the C code (like comparing if the attenuation parameter is right). 12 dB is the highest achievable attenuation. Hope this clarifies the point. :)



  • @abilio This is great news! thanks!



  • The attenuation of 4 does not work. 0 to 3 do.

    apin=adc.channel(pin='P13', attn=4)
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    ValueError: invalid argument(s) value

    @brotherdust said in LoPy ADCs:

    @rcolistete I was working on setting up battery monitoring today. I have a 3.7v Li-Ion battery (nominal 4v). The voltage divider built in to the expansion board brings this down to ~1.4v for the ADC on P16. By default, the attenuation on an ADC pin is 0. So, when above 1v, the ADC just reads 1023. Kinda useless, right?

    Your mention of selectable ADC ranges the ESP32 offers pointed me in the right direction. I found an "undocumented feature" that Pycom put in there, but hasn't documented yet.

    Here's the relevant section from the source: https://github.com/pycom/pycom-micropython/blob/master/esp32/mods/analog.h#L56

    typedef enum {
        ADC_ATTEN_0DB = 0,
        ADC_ATTEN_3DB, // 1
        ADC_ATTEN_6DB, // 2
        ADC_ATTEN_12DB, // 3
        ADC_ATTEN_MAX, // 4
    } adc_atten_t;
    

    So, to set this up:

    from machine import ADC
    adc = ADC()
    batt = adc.channel(attn=1, pin='P16')
    batt.value()
    

    3dB attenuation should be sufficient for our purposes. The readings have the same variability that @rcolistete noticed. I adjusted his function for 3dB attenuation (rounded to 1400mV).

    import machine
    numADCreadings = const(100)
    def ADCloopMeanStdDev():
        adc = machine.ADC(0)
        adcread = adc.channel(attn=1, pin='P16')
        samplesADC = [0.0]*numADCreadings; meanADC = 0.0
        i = 0
        while (i < numADCreadings):
            adcint = adcread()
            samplesADC[i] = adcint
            meanADC += adcint
            i += 1
        meanADC /= numADCreadings
        varianceADC = 0.0
        for adcint in samplesADC:
            varianceADC += (adcint - meanADC)**2
        varianceADC /= (numADCreadings - 1)
        print("%u ADC readings :\n%s" %(numADCreadings, str(samplesADC)))
        print("Mean of ADC readings (0-1023) = %15.13f" % meanADC)
        print("Mean of ADC readings (0-1400 mV) = %15.13f" % (meanADC*1400/1024))
        print("Variance of ADC readings = %15.13f" % varianceADC)
        print("10**6*Variance/(Mean**2) of ADC readings = %15.13f" % ((varianceADC*10**6)//(meanADC**2)))
    

    And the output:

    >>> ADCloopMeanStdDev()
    100 ADC readings :
    [1013, 1015, 1012, 1015, 1013, 1014, 1015, 1017, 1019, 1017, 1015, 1013, 1021, 1013, 1018, 1017, 1015, 1015, 1012, 1017, 1013, 1012, 1013, 1013, 1015, 1020, 1013, 1017, 1013, 1012, 1014, 1018, 1015, 1019, 1020, 1017, 1015, 1016, 1017, 1015, 1007, 1014, 1017, 1017, 1015, 1012, 1014, 1015, 1012, 1012, 1015, 1020, 1018, 1019, 1012, 1019, 1017, 1013, 1017, 1014, 1015, 1012, 1003, 1015, 1013, 1015, 1012, 1015, 1013, 1019, 1015, 1015, 1015, 1012, 1016, 1013, 1018, 1014, 1015, 1017, 1021, 1017, 1014, 1015, 1017, 1013, 1018, 1019, 1013, 1015, 1014, 1019, 1015, 1020, 1011, 1013, 1013, 1017, 1013, 1013]
    Mean of ADC readings (0-1023) = 1015.0399208068847
    Mean of ADC readings (0-1400 mV) = 1387.7500343322753
    Variance of ADC readings = 8.0589838027954
    10**6*Variance/(Mean**2) of ADC readings = 7.0000000000000
    

    At time of measurement, my Fluke 179 MM read 1.367V. So, pretty close, I'd say!

    Hopefully this has been helpful! Now, if I may ask a question in return: given these measurements, how can I get a reasonably accurate idea of remaining battery capacity? I'm sure there are libraries out there that do this already, but wanted to see if any of you had some suggestions first!

    Thanks!



  • @abilio
    this is really good news :)
    ADC is really important for us



  • @manu, sorry for the silence, after your post I went into checking what could be done to get 12 bit reads.

    The good news is that it seems to be working now. Tomorrow there will be a release, that includes the fix to the issue we found :)



  • @abilio
    Regarding the fluctuation of the ADC values, is there a reason behind it and will it be fixed?



  • @abilio Do you have an update on the 10bits - 12bits problem. We have a project were the 12bits ADC is mandatory.
    Where does the problem come from ?
    Do you have an idea when the problem might be solved ?

    Thanks for the update.



  • @abilio said in LoPy ADCs:

    Hi @Nick, 10 bit ADC is a known limitation at the moment. We're working with the ESP32 manufacturer on enabling 12 bits plus the internal attenuation (will enable measurements of up to 4.4V). As soon as this changes we will release a firmware upgrade. I'm going to modify the documentation to include this as a note in the ADC section. Thanks.

    @abilio, may I make some suggestions on the next iteration of the expansion board (2.1?) (as well as the PySense and PyTrack)?

    1. Add a hard switch in-line with the battery (either a slider or latching), or change the board layout slightly and leave the traces so it can be added after-market.
    2. Add something like a BQ27421-G1 for battery monitoring functions. This would be MUCH better than monitoring it through ADC as all the engineering (good engineering, I might add) has already been done. Why re-invent the wheel? =)


  • Hi @Nick, 10 bit ADC is a known limitation at the moment. We're working with the ESP32 manufacturer on enabling 12 bits plus the internal attenuation (will enable measurements of up to 4.4V). As soon as this changes we will release a firmware upgrade. I'm going to modify the documentation to include this as a note in the ADC section. Thanks.



  • Thanks for all the replies.
    After much more testing I have reverted back to a pyboard for the particular project I was working on, losing 2 bits of resolution is a pretty big limitation, so hopefully this gets sorted at some point. Thanks @brotherdust for pointing out about the attentuation settings, that will be helpful in the future.

    Also in regards to @Jiemde initial suggestion, that the ref for the ADC is probably the power supply on the board - I think this is probably the answer to the fluctuating value. When I come back to the lopy I will experiment with some capacitors on the powersupply to the board and run the ADCloopMeanStdDev() function and compare results to the ones already posted here.

    Cheers



  • @brotherdust said in LoPy ADCs:

    Hopefully this has been helpful! Now, if I may ask a question in return: given these measurements, how can I get a reasonably accurate idea of remaining battery capacity? I'm sure there are libraries out there that do this already, but wanted to see if any of you had some suggestions first!

    Usually they are measuring the characteristics discharging battery from fully charge to zero (OK, it is not zero but to a minimum allowed voltage) and using collected data for estimation. It is a seamless process, but e.g. on Android there are so called battery calibrators too for users.



  • @rcolistete I was working on setting up battery monitoring today. I have a 3.7v Li-Ion battery (nominal 4v). The voltage divider built in to the expansion board brings this down to ~1.4v for the ADC on P16. By default, the attenuation on an ADC pin is 0. So, when above 1v, the ADC just reads 1023. Kinda useless, right?

    Your mention of selectable ADC ranges the ESP32 offers pointed me in the right direction. I found an "undocumented feature" that Pycom put in there, but hasn't documented yet.

    Here's the relevant section from the source: https://github.com/pycom/pycom-micropython/blob/master/esp32/mods/analog.h#L56

    typedef enum {
        ADC_ATTEN_0DB = 0,
        ADC_ATTEN_3DB, // 1
        ADC_ATTEN_6DB, // 2
        ADC_ATTEN_12DB, // 3
        ADC_ATTEN_MAX, // 4
    } adc_atten_t;
    

    So, to set this up:

    from machine import ADC
    adc = ADC()
    batt = adc.channel(attn=1, pin='P16')
    batt.value()
    

    3dB attenuation should be sufficient for our purposes. The readings have the same variability that @rcolistete noticed. I adjusted his function for 3dB attenuation (rounded to 1400mV).

    import machine
    numADCreadings = const(100)
    def ADCloopMeanStdDev():
        adc = machine.ADC(0)
        adcread = adc.channel(attn=1, pin='P16')
        samplesADC = [0.0]*numADCreadings; meanADC = 0.0
        i = 0
        while (i < numADCreadings):
            adcint = adcread()
            samplesADC[i] = adcint
            meanADC += adcint
            i += 1
        meanADC /= numADCreadings
        varianceADC = 0.0
        for adcint in samplesADC:
            varianceADC += (adcint - meanADC)**2
        varianceADC /= (numADCreadings - 1)
        print("%u ADC readings :\n%s" %(numADCreadings, str(samplesADC)))
        print("Mean of ADC readings (0-1023) = %15.13f" % meanADC)
        print("Mean of ADC readings (0-1400 mV) = %15.13f" % (meanADC*1400/1024))
        print("Variance of ADC readings = %15.13f" % varianceADC)
        print("10**6*Variance/(Mean**2) of ADC readings = %15.13f" % ((varianceADC*10**6)//(meanADC**2)))
    

    And the output:

    >>> ADCloopMeanStdDev()
    100 ADC readings :
    [1013, 1015, 1012, 1015, 1013, 1014, 1015, 1017, 1019, 1017, 1015, 1013, 1021, 1013, 1018, 1017, 1015, 1015, 1012, 1017, 1013, 1012, 1013, 1013, 1015, 1020, 1013, 1017, 1013, 1012, 1014, 1018, 1015, 1019, 1020, 1017, 1015, 1016, 1017, 1015, 1007, 1014, 1017, 1017, 1015, 1012, 1014, 1015, 1012, 1012, 1015, 1020, 1018, 1019, 1012, 1019, 1017, 1013, 1017, 1014, 1015, 1012, 1003, 1015, 1013, 1015, 1012, 1015, 1013, 1019, 1015, 1015, 1015, 1012, 1016, 1013, 1018, 1014, 1015, 1017, 1021, 1017, 1014, 1015, 1017, 1013, 1018, 1019, 1013, 1015, 1014, 1019, 1015, 1020, 1011, 1013, 1013, 1017, 1013, 1013]
    Mean of ADC readings (0-1023) = 1015.0399208068847
    Mean of ADC readings (0-1400 mV) = 1387.7500343322753
    Variance of ADC readings = 8.0589838027954
    10**6*Variance/(Mean**2) of ADC readings = 7.0000000000000
    

    At time of measurement, my Fluke 179 MM read 1.367V. So, pretty close, I'd say!

    Hopefully this has been helpful! Now, if I may ask a question in return: given these measurements, how can I get a reasonably accurate idea of remaining battery capacity? I'm sure there are libraries out there that do this already, but wanted to see if any of you had some suggestions first!

    Thanks!



  • I confim by testing that currently firmware (0.9.6.b1) implements the ADC with range 0.0-1.0V and 10 bits (0-1023 values). While ESP32 specs shows 12 bits and selectable ranges 0-1V, 0-1.4V, 0-2V, or 0-4V. So I think is some Pycom choice to implement this simplified configuration in these beta releases.

    About the ADC noise, I have tested this script on WiPy 2 (LoPy should be the same) :

    import machine
    import math
    numADCreadings = const(100)
    def ADCloopMeanStdDev():
        adc = machine.ADC(0)
        adcread = adc.channel(pin='P13')
        samplesADC = [0.0]*numADCreadings; meanADC = 0.0
        i = 0
        while (i < numADCreadings):
            adcint = adcread()
            samplesADC[i] = adcint
            meanADC += adcint
            i += 1
        meanADC /= numADCreadings
        varianceADC = 0.0
        for adcint in samplesADC:
            varianceADC += (adcint - meanADC)**2
        varianceADC /= (numADCreadings - 1)
        print("%u ADC readings :\n%s" %(numADCreadings, str(samplesADC)))
        print("Mean of ADC readings (0-1023) = %15.13f" % meanADC)
        print("Mean of ADC readings (0-1000 mV) = %15.13f" % (meanADC*1000/1024))
        print("Variance of ADC readings = %15.13f" % varianceADC)
        print("10**6*Variance/(Mean**2) of ADC readings = %15.13f" % ((varianceADC*10**6)//(meanADC**2)))
        print("Standard deviation of ADC readings = %15.13f" % math.sqrt(varianceADC))
    

    A typical run, reading 3.3V (from MCU board connected to USB cable) with voltage divider (82K + 33K resistors), is :

    >>> ADCloopMeanStdDev()
    100 ADC readings :
    [908, 905, 903, 912, 907, 906, 908, 908, 909, 908, 911, 907, 906, 910, 907, 907, 903, 905, 909, 905, 908, 908, 909, 908, 911, 905, 907, 905, 901, 901, 908, 905, 913, 904, 906, 908, 911, 908, 911, 911, 910, 907, 906, 907, 908, 913, 906, 929, 868, 911, 915, 940, 916, 909, 905, 909, 904, 907, 920, 901, 909, 912, 903, 909, 915, 911, 915, 904, 911, 947, 913, 910, 849, 912, 913, 912, 908, 900, 905, 908, 901, 906, 911, 908, 907, 902, 911, 905, 907, 907, 911, 915, 904, 909, 909, 909, 907, 906, 913, 905]
    Mean of ADC readings (0-1023) = 908.0200195312500
    Mean of ADC readings (0-1000 mV) = 886.7383003234863
    Variance of ADC readings = 94.4035530090332
    10**6*Variance/(Mean**2) of ADC readings = 113.9999985694885
    Standard deviation of ADC readings = 9.7161493301392
    

    But putting a 100nF capacitor between the ADC pin and GND, the noise is decresead a lot :

    >>> ADCloopMeanStdDev()
    100 ADC readings :
    [909, 908, 906, 909, 908, 911, 909, 908, 907, 908, 908, 910, 908, 913, 909, 908, 909, 907, 909, 909, 908, 907, 909, 909, 909, 909, 909, 908, 909, 911, 895, 907, 908, 908, 909, 906, 905, 908, 913, 907, 911, 907, 909, 909, 908, 908, 908, 909, 908, 909, 908, 908, 908, 908, 908, 907, 908, 904, 908, 906, 909, 908, 907, 908, 913, 911, 909, 910, 908, 907, 909, 907, 909, 908, 911, 906, 911, 908, 908, 908, 911, 908, 914, 910, 908, 906, 911, 905, 908, 906, 908, 906, 908, 908, 907, 908, 908, 909, 908, 908]
    Mean of ADC readings (0-1023) = 908.2599639892578
    Mean of ADC readings (0-1000 mV) = 886.9726181030273
    Variance of ADC readings = 4.5781769752502
    10**6*Variance/(Mean**2) of ADC readings = 5.0000000000000
    Standard deviation of ADC readings = 2.1396675109863
    

    By comparison :

    • WiPy 1.0 ADC (12 bits, 0-1.467V) without capacitor gives "10^6*Variance/(Mean^2)" between 8-50, with capacitor < 1;
    • WiPy 2 ADC (10 bits, 0-1V), 52-189 and 3-7, respectively;
    • Pyboard v1.1 ADC (12 bits, 0-3.3V), 22-65 and 3-4, respectively;
    • WeMos D1 Mini/ESP8266 ADC (10 bits, 0-3.2V), 0-1 and 0, respectively.

    So WiPy 2/LoPy ADC currently has more noise (with or without capacitor) than WiPy 1, Pyboard and WeMos D1 Mini/ESP8266.

    For WiPy and its MCU, CC3200, there is Texas Instruments documentation about the internal capacitance of CC3200 ADC, recommended configuration of external capacitor, etc.

    For ESP32 I've not yet seen any similar documentation.

    PS.: improved the above code a little.



  • Hi jiemde,

    Thanks for the reply. I have just now connected a battery to the ADC instead of a power supply. (Perhaps the power supply I was using to connect to the ADC may have had some noise). I'm now testing the ADC using a flat AAA battery that is around 550mV. When I read the ADC value now, I'm getting anywhere between 490 and 520, which is a similar fluctuation to before.

    Can anyone confirm if I'm right in assuming the GND on the board is the 0V ref for the ADC?

    I will try to set up an oscilloscope tomorrow and see if anything strange is happening...

    Nick


  • Pybytes Beta

    Hi Nick,
    As an electronic engineer, I immediately think that it can be the alimentation of the board itself, because for an Analog to digital conversion a référence is setting from the power supply, so if the ripple on this power can be the result of the fluctuating value .
    Could you mesure this ripple ?

    Best regards

    jiemde


Log in to reply
 

Pycom on Twitter