The PZEM-004T v3.0 is a complete energy meter on a small board: it measures mains voltage, current (through its CT), power, energy, frequency and power factor, and sends the numbers over a 9600-baud TTL serial line. It is the standard part for home energy monitors and solar-export meters in India.
On the ESP32 DevKit V1, use the second hardware UART: RX2 (GPIO16) and TX2 (GPIO17), passed explicitly because core 3.x moved the defaults. The PZEM’s TX is a 5 V signal, so it goes through a 1 kΩ / 2 kΩ divider.
Everything on the L, N and CT side is 230 V and electrician-only. Only the four TTL pins are wired on the bench.
Wiring
| ESP32 DevKit V1 | PZEM-004T v3.0 AC energy meter (100 A with CT) | Wire | Notes |
|---|---|---|---|
| — | L | — | To Mains live — 230 V, electrician only (not drawn) |
| — | N | — | To Mains neutral — 230 V, electrician only (not drawn) |
| — | CT1 | — | To Current-transformer lead (the CT clamps around the live wire only) (not drawn) |
| — | CT2 | — | To Current-transformer lead (other wire) (not drawn) |
| VIN | 5V | red | Powers the opto-isolated TTL side (5 V while the ESP32 runs from USB). |
| GND | GND | black | TTL-side ground — isolated from mains by the optocouplers. |
| RX2 | TX | yellow | Via 1 kΩ / 2 kΩ divider — PZEM TX idles at 5 V. 1 kΩ from TX to RX2 (GPIO16), 2 kΩ from RX2 to GND → about 3.3 V. |
| TX2 | RX | green | GPIO17. 3.3 V from the ESP32 drives the PZEM’s opto input directly. |
TTL side 5 V from your board; measuring side powered from the 230 V it measures part, 3.3 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
- L, N and the CT are a 230 V installation. An electrician should wire L/N from an MCB- and RCCB-protected circuit and pass only the live wire through the CT, with the module in a closed box. You wire the four TTL pins, before mains is connected.
- The PZEM’s meter chip is powered from the mains side. With L/N unconnected it never replies, even though the TTL LEDs may light — so a bench test without mains always shows “No reply”.
- Arduino-ESP32 3.x moved the default Serial2 pins to RX 4 / TX 25. The library constructor takes the pins, so the sketch passes GPIO16 and GPIO17 (printed RX2 / TX2) explicitly.
- Many v3.0 boards also work with 3.3 V on the 5V pin (then no divider is needed), but not all of them — 5 V plus the divider works on every board.
- Energy (kWh) is stored inside the PZEM and survives power cuts.
pzem.resetEnergy()clears it. - The CT must clamp the live wire alone. Around a cable holding live and neutral, the currents cancel and it reads zero.
Code
// PZEM-004T v3.0 energy meter on an ESP32 DevKit V1 over Serial2 (Modbus-RTU, 9600 baud).
// Low-voltage side: 5V → VIN, GND → GND, PZEM TX → 1 kΩ/2 kΩ divider → RX2 (GPIO16), PZEM RX ← TX2 (GPIO17).
// L/N and the CT clamp are on 230 V — electrician only. The meter answers only while mains is connected.
#include <PZEM004Tv30.h>
constexpr uint8_t RX_PIN = 16, TX_PIN = 17;
// Name the pins: arduino-esp32 3.x would otherwise put Serial2 on RX 4 / TX 25.
PZEM004Tv30 pzem(Serial2, RX_PIN, TX_PIN);
void setup() {
Serial.begin(115200);
Serial.println("PZEM-004T v3.0 on Serial2 (RX2/TX2)");
}
void loop() {
float v = pzem.voltage();
if (isnan(v)) {
Serial.println("No reply — is 230 V connected to L/N? Check TX/RX and the divider.");
} else {
Serial.printf("%.1f V %.3f A %.1f W %.3f kWh %.1f Hz PF %.2f\n", v, pzem.current(), pzem.power(),
pzem.energy(), pzem.frequency(), pzem.pf());
}
delay(2000);
}// PZEM-004T v3.0 energy meter on an ESP32 DevKit V1 over Serial2 (Modbus-RTU, 9600 baud).
// Low-voltage side: 5V → VIN, GND → GND, PZEM TX → 1 kΩ/2 kΩ divider → RX2 (GPIO16), PZEM RX ← TX2 (GPIO17).
// L/N and the CT clamp are on 230 V — electrician only. The meter answers only while mains is connected.
#include <PZEM004Tv30.h>
constexpr uint8_t RX_PIN = 16, TX_PIN = 17;
// Name the pins: arduino-esp32 3.x would otherwise put Serial2 on RX 4 / TX 25.
PZEM004Tv30 pzem(Serial2, RX_PIN, TX_PIN);
void setup() {
Serial.begin(115200);
Serial.println("PZEM-004T v3.0 on Serial2 (RX2/TX2)");
}
void loop() {
float v = pzem.voltage();
if (isnan(v)) {
Serial.println("No reply — is 230 V connected to L/N? Check TX/RX and the divider.");
} else {
Serial.printf("%.1f V %.3f A %.1f W %.3f kWh %.1f Hz PF %.2f\n", v, pzem.current(), pzem.power(),
pzem.energy(), pzem.frequency(), pzem.pf());
}
delay(2000);
}Library: PZEM004Tv30 (Jakub Mandula) — compiled with PZEM004Tv30 1.2.1.
Expected serial output
PZEM-004T v3.0 on Serial2 (RX2/TX2)
231.4 V 0.412 A 61.8 W 12.347 kWh 50.0 Hz PF 0.65
230.9 V 0.415 A 62.1 W 12.347 kWh 50.0 Hz PF 0.65PZEM-004T v3.0 on Serial2 (RX2/TX2) 231.4 V 0.412 A 61.8 W 12.347 kWh 50.0 Hz PF 0.65 230.9 V 0.415 A 62.1 W 12.347 kWh 50.0 Hz PF 0.65
Common errors
“No reply” on every reading
Mains not connected to L/N (the meter chip needs it), TX/RX swapped, or the divider wired the wrong way round (1 kΩ on the PZEM side, 2 kΩ to GND). Also check the board is a v3.0 — the old v1/v2 use a different protocol.
Voltage is right but current reads 0
The CT isn’t around the live wire alone, the load is off, or the CT leads aren’t tight in CT1/CT2. Ask the electrician to check — don’t open the box with mains on.
Readings work, then stop after Wi-Fi starts
Not a Wi-Fi problem — UART is unaffected. Look for a loose divider resistor or a shared GND that is missing.