The PZEM-004T v3.0 is a complete single-phase energy meter on one board. It measures voltage, current, power, energy (kWh), frequency and power factor, and reports them over a serial link. For about ₹900 it is the basis of most DIY home-energy monitors in India.
On the Uno only the TTL side is your wiring: 5V, GND, and the serial lines to SoftwareSerial on D10/D11. The PZEM’s TX is pulled up to whatever feeds its 5V pin, so on a 5 V Uno it connects directly, with no divider. The mains side — L and N, and the current transformer clipped around the live wire — is electrician’s work, inside a closed enclosure.
The sketch uses the PZEM004Tv30 library and prints all six values every 2 seconds. It says clearly when the meter doesn’t answer, which on the bench (with no mains connected) is normal.
Wiring
| Arduino Uno R3 | PZEM-004T v3.0 AC energy meter (100 A with CT) | Wire | Notes |
|---|---|---|---|
| — | L | — | To Mains live, from an MCB-protected circuit (electrician only) (not drawn) |
| — | N | — | To Mains neutral (electrician only) (not drawn) |
| — | CT1 | — | To Current transformer lead 1 (CT clipped around the live wire to the load) (not drawn) |
| — | CT2 | — | To Current transformer lead 2 (not drawn) |
| 5V | 5V | red | Powers the opto-isolated TTL side only. |
| GND | GND | black | |
| D10 | TX | yellow | PZEM TX → Uno SoftwareSerial RX. At 5 V no divider is needed (unlike ESP32/Pico). |
| D11 | RX | green | Uno TX → PZEM RX. |
TTL side 5 V from your board; measuring side powered from the 230 V it measures part, 5 V board. The TTL side is opto-isolated and powered from your board. Its TX line is pulled up to whatever you feed the 5V pin: on ESP32/Pico, keep 5 V and put a 1 kΩ / 2 kΩ divider on PZEM TX → board RX (many v3.0 units also work with 3.3 V on the 5V pin). On Uno connect directly.
Board quirks
- Electrician only for L, N and the CT. The PZEM measures voltage from its own L/N terminals, which carry 230 V, and its board is not touch-safe. Mount it in a closed enclosure on an MCB- and RCCB-protected circuit. The CT clips around the live wire of the load only — never both live and neutral, or it reads zero.
- The TTL side is opto-isolated, so the Uno never touches the mains — as long as you never connect the Uno’s GND to neutral.
- The PZEM only answers when its L/N side is powered from the mains. On USB alone it stays silent: “No reply” on the bench is expected.
- The Uno’s hardware serial is busy with USB, so the PZEM uses SoftwareSerial at 9600 baud, which is reliable at that speed.
- The energy counter (kWh) is stored inside the PZEM and survives power cuts. Reset it with
pzem.resetEnergy()when you start a new month. - Library Manager lists it as PZEM004Tv30 by Jakub Mandula.
Code
// PZEM-004T v3.0 energy meter on an Arduino Uno R3 (Modbus over SoftwareSerial).
// TTL side only: 5V → 5V, GND → GND, PZEM TX → D10 (Uno RX), PZEM RX ← D11 (Uno TX).
// L/N and the CT around the live wire are mains wiring - ELECTRICIAN ONLY.
#include <SoftwareSerial.h>
#include <PZEM004Tv30.h>
SoftwareSerial pzemSerial(10, 11); // RX, TX
PZEM004Tv30 pzem(pzemSerial);
void setup() {
Serial.begin(9600);
Serial.println(F("PZEM-004T v3 on D10/D11"));
}
void loop() {
float v = pzem.voltage();
if (isnan(v)) {
Serial.println(F("No reply - check TX/RX, 5V and that L/N are live"));
delay(2000);
return;
}
Serial.print(F("V "));
Serial.print(v, 1);
Serial.print(F(" A "));
Serial.print(pzem.current(), 3);
Serial.print(F(" W "));
Serial.print(pzem.power(), 1);
Serial.print(F(" kWh "));
Serial.print(pzem.energy(), 3);
Serial.print(F(" Hz "));
Serial.print(pzem.frequency(), 1);
Serial.print(F(" PF "));
Serial.println(pzem.pf(), 2);
delay(2000);
}// PZEM-004T v3.0 energy meter on an Arduino Uno R3 (Modbus over SoftwareSerial).
// TTL side only: 5V → 5V, GND → GND, PZEM TX → D10 (Uno RX), PZEM RX ← D11 (Uno TX).
// L/N and the CT around the live wire are mains wiring - ELECTRICIAN ONLY.
#include <SoftwareSerial.h>
#include <PZEM004Tv30.h>
SoftwareSerial pzemSerial(10, 11); // RX, TX
PZEM004Tv30 pzem(pzemSerial);
void setup() {
Serial.begin(9600);
Serial.println(F("PZEM-004T v3 on D10/D11"));
}
void loop() {
float v = pzem.voltage();
if (isnan(v)) {
Serial.println(F("No reply - check TX/RX, 5V and that L/N are live"));
delay(2000);
return;
}
Serial.print(F("V "));
Serial.print(v, 1);
Serial.print(F(" A "));
Serial.print(pzem.current(), 3);
Serial.print(F(" W "));
Serial.print(pzem.power(), 1);
Serial.print(F(" kWh "));
Serial.print(pzem.energy(), 3);
Serial.print(F(" Hz "));
Serial.print(pzem.frequency(), 1);
Serial.print(F(" PF "));
Serial.println(pzem.pf(), 2);
delay(2000);
}Library: PZEM004Tv30 (Jakub Mandula) — compiled with PZEM004Tv30 1.2.1.
Expected serial output
PZEM-004T v3 on D10/D11
V 231.4 A 0.412 W 81.3 kWh 0.127 Hz 50.0 PF 0.85
V 231.2 A 0.415 W 81.9 kWh 0.127 Hz 50.0 PF 0.86PZEM-004T v3 on D10/D11 V 231.4 A 0.412 W 81.3 kWh 0.127 Hz 50.0 PF 0.85 V 231.2 A 0.415 W 81.9 kWh 0.127 Hz 50.0 PF 0.86
Common errors
“No reply”
TX/RX crossed wrongly (PZEM TX goes to D10), the 5V/GND pair missing, or the L/N side not powered — it must be live to answer.
Voltage correct, current and power 0
The CT isn’t around the load’s live wire, or it encloses both live and neutral (their fields cancel).
Power factor looks wrong for a heater
Resistive loads read close to 1.00; fans, fridges and LED drivers read 0.5–0.9. Very small loads (under 10 W) give noisy PF values.