How to measure battery voltage with internal adc ESP32
Asked Answered
F

1

12

i'm doing wireless sensor node using (esp32, DHT11, soil moisture and nrf24l01) and i want to add an battery to supply those sensors, also need to measure battery voltage. For the battery, voltage always change to cant use as a Vcc reference, so i find there is an internal reference voltage. Could anyone done with this give me some instruction. Thank you

i'm gonna use LIFEPO4 3.3v normaly (3.6v at max) or 18650 3.7v/4.2v max

Fabulist answered 1/7, 2019 at 9:42 Comment(0)
H
6

According to docs:

The default ADC full-scale voltage is 1.1V. To read higher voltages (up to the pin maximum voltage, usually 3.3V) requires setting >0dB signal attenuation for that ADC channel.

So set it to zero for 1.1v; next, you can read the voltage (in a loop for better accuracy) and then convert it to a valid voltage and find the percentage of battery level.

In the below example, the function would return the percentage of battery level. Remember to edit battery_max and battery_min based on your battery voltage levels. I assumed that you connect the battery to ADC1 channel 0 (GPIO 36).

Also, I recommend you create a resistor divider circuit to reduce the voltage level. If your input power supply drops down, the Arduino will feed directly from Analog input, which is undesirable. Remember that your voltage level should not exceed 3.9v.

#include <driver/adc.h>

float battery_read()
{
    //read battery voltage per %
    long sum = 0;                  // sum of samples taken
    float voltage = 0.0;           // calculated voltage
    float output = 0.0;            // output value
    const float battery_max = 3.6; // maximum voltage of battery
    const float battery_min = 3.3; // minimum voltage of battery before shutdown

    float R1 = 100000.0; // resistance of R1 (100K)
    float R2 = 10000.0;  // resistance of R2 (10K)

    for (int i = 0; i < 500; i++)
    {
        sum += adc1_get_voltage(ADC1_CHANNEL_0);
        delayMicroseconds(1000);
    }
    // calculate the voltage
    voltage = sum / (float)500;
    voltage = (voltage * 1.1) / 4096.0; // for internal 1.1v reference
    // use it with divider circuit 
    // voltage = voltage / (R2/(R1+R2));
    // round value by two DP
    voltage = roundf(voltage * 100) / 100;
    Serial.print("voltage: ");
    Serial.println(voltage, 2);
    output = ((voltage - battery_min) / (battery_max - battery_min)) * 100;
    if (output < 100)
        return output;
    else
        return 100.0f;
}

void setup()
{
    adc1_config_width(ADC_WIDTH_12Bit);
    adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_0db); //set reference voltage to internal
    Serial.begin(9600);
}

void loop()
{
    Serial.print("Battery Level: ");
    Serial.println(battery_read(), 2);
    delay(1000);
}

If you add a divider circuit, you need to change battery_min and battery_max according to the new output of the divider circuit.

Harveyharvie answered 1/7, 2019 at 17:31 Comment(11)
if i use 18650 battery with normally 3.6v and max charged is 4.2v so what divider should i need? 4.2v to 3.3v or 3.6v to 3.3v because if i buy a new one it's would be 3.6v. and if i buy a lifepo4 the battery range is 3,0 to 3.6 which is ok to esp32 so i dont need a divider, is it? And is the voltage = sum/(float)/500 a avarage value to more accuray?Osier
voltage = 1.1 because the full scale is 1.1. so the output the always < 0. could we have a factor voltage = x*voltage ?Osier
@QuangMinhLê Circuit can be something like this. Look at editHarveyharvie
link i found this one, i think the code *1.1 the the problem. the voltage is never exceed 3vOsier
Remember that I set the 1.1 because it uses the default internal 1.1v reference if you use other references you should change it too.Harveyharvie
so the default inter 1.1v : adc = 0 -> 0v , adc = 4096 -> 1.1v. if i want to measure lipo pin 4.2max, then i will set ADC_ATTEN_11db --> 3.9; adc = 4096 -->3.9v. if my battery is 4.2v so i only see 3.9v at monitor, when my battery drop like 3.6v then i will see 3.6v at monitor? am i rightOsier
If your battery gets above 3.9v so you will always get 4096. Add the divider circuit and limit your voltage and you can use the default 1.1v reference.Harveyharvie
last question. so if i use 1.1v reference, so i need 1.1v go into ADC pin, which mean from 4.2v -->27Kohm --> measure here (1.1v) -->10khm -->ground. and if i want to use SET_ATTEN_11db then i applied 3.3v go into adc pin and At 11dB attenuation the maximum voltage is limited by VDD_A, not the full scale voltage. max is 3.3vOsier
Yes, just follow the above link for the circuit and adjust values based on your reference level. I suggest you use the internal 1.1v. Because for 3.3v if your battery becomes low the board would feed on ADC pin which is not desirable and you need to avoid that. E.g use R1=1k, R2=3.3k this would result in a 0.97v for a 4.2v battery level.Harveyharvie
i just do a quick test. i set 11db and voltage = voltage*3.9/4096 . my diagram is +pin --> measure here -->ground. i use vom to measure battery and it show 3.6v. but i always get 4095 from monitorOsier
i test with 3 case: 1)11db, 11bit resolution, no voltage divider, connecting direcly to 3.6v. i always get 2047 <\br> 2)11db, 11 bit, voltage divider, 4.2v -->27k-->mesure here-->10k>>ground. i got 3.66v (seem good)<\br> 3)0db,11bit, voltage divider like 2nd case, i got 2047 too. i dont get the point what is the max scale if i use 11db max scale 3.9. i think i will get adc value <2047 for 3.6. but i always get 2047Osier

© 2022 - 2025 — McMap. All rights reserved.