We all know the basic voltmeter, ammeter, and wattmeter—the three essential devices for measuring values in any electronic project or circuit. Measuring voltage and current with a multimeter is a great method, but one of the biggest challenges I face when testing circuits is measuring power supply efficiency. Therefore, today we’ll address this by building an Arduino and ESP32-based power meter that measures input voltage, input current, output voltage, and output current. This allows it to simultaneously measure input and output power, and using these values, we can easily measure efficiency.
Building a digital meter not only reduces costs but also provides room for upgrades and improvements. Because we’re using an ESP32 to build this project, we can easily add IoT capabilities to this meter and log data via the web. Let’s get started.
Materials required for ESP32 power meter
The image below shows the materials used to build the circuit. It is composed of very common components.

I have also listed the following components and the quantities required. If you are building the circuit yourself, it is highly recommended to use all materials from the list below.
- ● ESP32 Development Board-1
- ● 128X64 OLED-1
- ● ACS712-20 IC-2
- ● DC power plug-1
- ● 100uF capacitor – 2
- ● 104pF capacitor – 2
- ● 102pF capacitor – 2
- ● 10K, 1%-4
- ● 68K, 1%-2
- ● 6.8K, 1%-2
Power meter based on Arduino and ESP32 – Circuit diagram
The schematic of a power meter based on Arduino and ESP32 is shown below. Creating this circuit is very simple, using common components.

The circuit operation is very simple. We will measure the voltage and current in this project in a unique way. We measure the input and output voltage and current simultaneously, so we can see the circuit’s efficiency in real time. This is very convenient for certain projects. The working principle of these circuits is described below.
ACS712 Current Sensor

As shown in the diagram above, we use the ACS712 current sensor to measure current. This is a very interesting IC because it uses the Hall effect to measure current. We are using the 20A model, specifically the ACS712-20.
The ACS712 datasheet recommends normal operation within a voltage range of 4.5-5.5V. When using the ESP32 to measure current, it can only withstand 3.3V, which is why I used a voltage divider with two 10K resistors to reduce the output voltage of the ACS712 IC. When no current flows through the IC, it outputs 2.5V, and when current flows through the IC, it decreases or increases the voltage depending on the direction of the current flow.
Voltage divider circuit

To measure the input and output voltages, we have two voltage dividers on the input and output sides of the circuit. The circuit can measure a maximum voltage of 35V, but this can be easily changed by altering the resistance values of the voltage dividers.
voltage regulator

The ESP32, OLED, and ACS712 ICs are powered using a general-purpose LM7805 regulator. While no decoupling capacitors are needed when powering them with a very clean power supply, we used 100uF capacitors at both the input and output to stabilize the ICs.
ESP32 IC and OLED display

We used an ESP32 as the main processor, responsible for all readings, calculations, inputs, and outputs. Additionally, we used a 128×64 OLED display to show these values.
PCB design of a power meter based on Arduino and ESP32
Our PCB design for an efficiency meter based on Arduino and ESP32 is on a single-sided board. Eagle was used to design the PCB, but you can use any design software of your choice. A 2D image of the board design is shown below.

Sufficient grounding wires are used to ensure proper grounding connections between all components. Furthermore, we ensure the correct 5V and 3.3V wiring is used to reduce noise and improve efficiency.
Arduino code
Now that we have a good understanding of the hardware, we can open the Arduino IDE and start writing code. The purpose of this code is to read analog voltages from pins 35 and 33 of the ESP32 open-circuit board. Additionally, we read the voltage from pin 32 and the current value from pin 34. After doing this, we can multiply them to get the input power and output power, and then put them into the efficiency formula to derive the efficiency. Finally, we display it on the LCD screen.
Since we are using a 128×64 OLED display, we need the Adafruit_GFX and Adafruit_SSD1306 libraries to communicate with the display. You can download these from the Arduino’s default board manager; alternatively, you can download and include the libraries from its associated GitHub repository, as shown below.
This content is available to logged-in users only.
هذا المحتوى متاح فقط للمستخدمين المسجلين.
Ce contenu est réservé aux membres connectés.
🔑 Login 📝 RegisterLog in or create a free account to access downloads and exclusive content.
This content is available to logged-in users only.
هذا المحتوى متاح فقط للمستخدمين المسجلين.
Ce contenu est réservé aux membres connectés.
🔑 Login 📝 RegisterLog in or create a free account to access downloads and exclusive content.
As always, we first include all the necessary libraries. Then, we define all the pins and variables, as shown below.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <math.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define INPUT_VOLTAGE_SENSE_PIN 33
#define OUTPUT_VOLTAGE_SENSE_PIN 35
#define INPUT_CURRENT_SENSE_PIN 32
#define OUTPUT_CURRENT_SENSE_PIN 34
double R1_VOLTAGE = 68000; //68K
double R2_VOLTAGE = 6800; // 6.8K
double R1_Current 10000 //10K
double R2_Current 22000 //22K
//We have used 50 as mVperp value; please refer to the documentation for further information
double mVperAmp = 50; // use 100 for 20A Module and 66 for 30A Module
double ACSoffset = /*1130*/ 1136;
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
The SCREEN_WIDTH and SCREEN_HEIGHT definitions are used to define the screen size. Next, we define all the necessary pins through which we will measure voltage and current. Next, we define the resistor values, which are used in the hardware, as can be seen from the schematic. If you don’t have these values, or want to change the range of the meters, you can change these values and the code will work correctly.
When using the ACS712 to measure current, we need the mVperAmp value to calculate the current based on the voltage. When I used the 20A ACS712 module, the datasheet gave a mV/A value of 100. However, because I was using an ESP32 and a voltage divider, I only got half the value (i.e., 50).
ACSoffset is the offset required to calculate the current from the voltage. Since the ACS712 IC is powered by 5V, the offset voltage is 2.5V. However, when using a voltage divider, it drops to 1.25V. If calibration issues arise, these values can be adjusted to compensate the ADC.
Finally, we create a display object using the Adafruit_SSD1306 class and define the function by passing the screen width, height, I2C configuration, and a final -1 parameter. If your display does not have an external reset pin, then the last parameter must be -1.
void setup() {
Serial.begin(115200);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
Serial.println(F("SSD1306 allocation failed"));
for (;;);
}
display.clearDisplay();
display.setRotation(2);
display.setTextSize(1);
delay(100);
}
Next, we’ll introduce the `setup()` section. In this section, we’ll enable serial debugging and check if the I2C display is available using the `begin` method of the display object. We’ll also set the I2C address. Next, we’ll use `clearDisplay()` to clear the display content. Then, we’ll use the `setRotation` function to rotate the display. Next, we’ll set a 100 ms delay for the functions to take effect. After that, we can now continue with the `loop` function. However, before that, we need to discuss two other functions: `return_voltage_value()` and `return_current_value()` .
double return_voltage_value(int pin_no)
{
double tmp = 0;
double ADCVoltage = 0;
double inputVoltage = 0;
double avg = 0;
for (int i = 0; i < 150; i++)
{
tmp = tmp + analogRead(pin_no);
}
avg = tmp / 150;
ADCVoltage = ((avg * 3.3) / (4095)) + 0.138;
inputVoltage = ADCVoltage / (R2_VOLTAGE / (R1_VOLTAGE + R2_VOLTAGE)); // formula for calculating voltage in ie GND
return inputVoltage;
}
The `return_voltage_value()` function measures the voltage entering the ADC, taking the pin number as a parameter. In this function, we first declare several variables: `tmp`, `ADCVoltage`, `inputVoltage`, and `avg`. The `tmp` variable stores a temporary ADC value obtained from the `analogRead()` function, which is then averaged 150 times in a `for` loop, and this value is stored in a variable called `avg`. Then, `ADCVoltage` is calculated according to a given formula, and finally, the input voltage is calculated and the value is returned. The +0.138 value you see is the calibration value I used to calibrate the voltage level.
double return_current_value(int pin_no)
{
double tmp = 0;
double avg = 0;
double ADCVoltage = 0;
double Amps = 0;
for (int z = 0; z < 150; z++)
{
tmp = tmp + analogRead(pin_no);
}
avg = tmp / 150;
ADCVoltage = ((avg / 4095.0) * 3300); // Gets you mV
Amps = ((ADCVoltage - ACSoffset) / mVperAmp);
return Amps;
}
Next, we introduce the `return_current_value()` function. This function also uses the pin number as a parameter. Within this function, we also have four variables: `viz`, `tmp`, `avg`, `ADCVoltage`, and `Amps`. Next, we use the `analogRead()` function to read the pin and average it 150 times, then calculate the ADC voltage using the formula, and finally calculate the current and return that value.
void loop()
{
float input_voltage = abs(return_voltage_value(INPUT_VOLTAGE_SENSE_PIN)) ;
float input_current = abs(return_current_value(INPUT_CURRENT_SENSE_PIN)) ;
float output_voltage = abs(return_voltage_value(OUTPUT_VOLTAGE_SENSE_PIN)) ;
float output_current = abs((return_current_value(OUTPUT_CURRENT_SENSE_PIN))) ;
input_current = input_current - 0.025;
Serial.print("Input Voltage: ");
Serial.print(input_voltage);
Serial.print(" | Input Current: ");
Serial.print(input_current);
Serial.print(" | Output Voltage: ");
Serial.print(output_voltage);
Serial.print(" | Output Current: ");
Serial.println(output_current);
delay(300);
display.clearDisplay();
display.setCursor(0, 0);
display.print("I/PV: ");
display.setCursor(37, 0);
display.print(input_voltage);
display.setCursor(70, 0);
display.print("V");
}
First, we declare and define four float variables. We call the corresponding function, passing the pin number as a parameter, because the ACS712 module can return a negative current value. We use the abs() function from the math library to set the negative value to positive. Next, we print all the values via serial port for debugging. Then, we clear the display, set the cursor, and print the values again.
Testing a power meter based on Arduino and ESP32

You can see my test setup in the image above. I used a 30V transformer as the input and connected the meters to the test board. I used an LM2596-based buck converter board for the load and three 10-ohm resistors in parallel.

As shown in the diagram above, I have connected a multimeter to check the input and output voltages. The transformer produces approximately 32V, and the buck converter outputs 3.95V.

The graph above shows the output current measured by a power meter and a multimeter. The multimeter reading is 0.97 amps, which is slightly off due to nonlinearity in the ACS712 module, but this is acceptable for our purposes.








