How to measure battery level with ESP32 microcontroller
Autonomous devices like URU Key require periodic measurement of the battery voltage. I am going to use very simple schematics and very simple algorithm to understand how much juice my battery has at the moment.
There is a great thread about measurement circuitry and a switchable voltage divider on StackOverflow. Following the thread I have created the following circuitry for the battery measurement:
The schematics uses two MOSFETs to enable measurement circuitry on demand and prevent leaking the power trough the resistors.
Another option I have found was discussed at this thread. Guys offered to use NTJD1155L combined MOSFETs to save space on the PCB. The corresponding schematics looks as follows:
The positive battery terminal is connected to the line VBAT
, the ADC of ESP32 is connected to the BAT_ADC
line and the line BAT_ADC_EN
is used to enable and disable the measurement circuitry.
Let’s write the code which reads the value from ADC and converts it to the battery level in the range from 0 to 100%.
In my device I have used resistors of 100K and 10K, the ones I have at the moment:
#define R2 100
#define R3 10
The output voltage of the resistor divider is calculated as Vout = (Vin * R3) / (R2 + R3)
.
#define VOLTAGE_OUT(Vin) (((Vin) * R3) / (R2 + R3))
As a very rough estimation, we can take the battery voltage of 4.2V as 100% and the voltage of 3.3V as 0%. I will convert them to millivolts to avoid floating-point calculations.
#define VOLTAGE_MAX 4200
#define VOLTAGE_MIN 3300
The reference voltage of ESP32 ADC is 1100mV. Let’s define it:
#define ADC_REFERENCE 1100
The value returned from ADC working in 12-bit mode will be in range 0 to 4095. Then we can convert the voltage to ADC value using the following formula:
#define VOLTAGE_TO_ADC(in) ((ADC_REFERENCE * (in)) / 4096)
So, the minimal and maximal values of battery voltage converted by a resistor divider and returned from ADC are:
#define BATTERY_MAX_ADC VOLTAGE_TO_ADC(VOLTAGE_OUT(VOLTAGE_MAX))
#define BATTERY_MIN_ADC VOLTAGE_TO_ADC(VOLTAGE_OUT(VOLTAGE_MIN))
Retrieving value from ADC is pretty straightforward and well described in official Espressif documentation. Let’s say we have the value from ADC in the variable called adc
. Then the battery level calculation will look as follows:
int calc_battery_percentage(int adc)
{
int battery_percentage = 100 * (adc - BATTERY_MIN_ADC) / (BATTERY_MAX_ADC - BATTERY_MIN_ADC);
if (battery_percentage < 0)
battery_percentage = 0;
if (battery_percentage > 100)
battery_percentage = 100;
return battery_percentage;
}
To start measurement we have to enable a high level on the GPIO port connected to the BAT_ADC_EN
line. Then the current battery level is read from ADC and converted to the percentage value. After the measurement is done, the GPIO port should be set to a low level to avoid battery drain through the divider.
The URU Key device does not have any means to display the battery level except for three LEDs. I am going to use an effect like blinking on red one to indicate that battery level is near to critical. As well the BLE protocol defines special endpoint to return current battery level percentage. I am implementing it as well.
References
- Datasheet on NTJD1155L - NTJD1155L-D.PDF