Documentation

PZEM-004T AC Energy Meter & Power Monitor Module with Current Transformer | ShillehTek Product Manual
Documentation / PZEM-004T AC Energy Meter & Power Monitor Module with Current Transformer | ShillehTek Product Manual

PZEM-004T AC Energy Meter & Power Monitor Module with Current Transformer | ShillehTek Product Manual

pzem-004t-ac-energy-meter-current-transformershillehtek

Overview

The PZEM-004T is the module that turns "I wonder what that appliance costs to run" into hard numbers. Clamp its split-core current transformer around a live wire, give the board a voltage reference from the same circuit, and it measures everything that matters on a single-phase AC line: voltage, current, active power, energy (kWh), frequency, and power factor — all computed on-board to metering-grade accuracy and served over a simple TTL UART using Modbus-RTU at 9600 baud.

The design keeps your microcontroller a safe distance from the mains: the measurement side and the communication side are galvanically separated by optocouplers, and the current sensing itself is a clip-on CT that never touches copper. Your Arduino, ESP32, Pi, or Pico only ever sees four low-voltage wires — 5V, GND, TX, RX — while the module does its work across the isolation barrier. The 100A CT version covers everything from a phone charger to a whole subpanel.

Classic builds: whole-house energy dashboards, per-appliance cost logging, solar/generator monitoring, detecting when the washing machine finishes (power drops), and Home Assistant integrations. One rule stands above all of them: the screw-terminal side carries lethal mains voltage. Wire it de-energized, insulate it in an enclosure, and treat that end of the board with the respect you'd give any breaker panel.

At a Glance

Measures
V, A, W, kWh, Hz, PF
Voltage Range
AC 80 - 260V
Current Range
0 - 100A (external CT)
Interface
TTL UART, Modbus-RTU
Isolation
Optocoupled UART
Logic Supply
5V (3.3V-friendly signals)

Specifications

Parameter Value
Model PZEM-004T V3.0, 100A variant with split-core CT
Voltage Measurement 80 - 260V AC, resolution 0.1V (±0.5%)
Current Measurement 0 - 100A via CT, starting current 0.02A (±0.5%)
Power 0 - 23 kW active power, resolution 0.1W
Energy 0 - 9999.99 kWh, stored through power loss, resettable by command
Frequency / PF 45 - 65 Hz (±0.5%) / power factor 0.00 - 1.00
Interface TTL UART 9600 8N1, Modbus-RTU protocol, default address 0xF8
Isolation Optocoupler-isolated UART between mains side and logic side
Interface Power 5V on the 4-pin connector (~10 mA)
Terminals L, N (voltage sense) + 2x CT secondary
Alarm Programmable over-power alarm threshold

Pinout Diagram

Mains side (screw terminal): L and N sense the line voltage; the other two screws take the CT's secondary wires. The CT itself clamps around the LIVE conductor only — never around both wires of a cord, or the fields cancel and current reads zero. Logic side (4-pin connector): 5V, RX, TX, GND to your microcontroller, crossed as usual (module TX to your RX).

PZEM-004T AC energy meter wiring diagram showing CT clamp, L N terminals and UART connections to a microcontroller

Wiring Guide

Danger — mains voltage: The L/N terminals connect directly to wall power. Kill the breaker before touching anything, double-insulate the terminal side, and never run this open on a bench where fingers or probes can wander. If you have not wired mains before, have someone qualified check your work.

Arduino Wiring (Uno: SoftwareSerial / Mega: Serial3)

PZEM Pin Arduino Pin Details
5V 5V Interface power
GND GND
TX D10 (Uno) / RX3 (Mega) Module transmits
RX D11 (Uno) / TX3 (Mega) Module receives
L / N / CT Mains + CT clamp CT around LIVE wire only

ESP32 Wiring (hardware UART2)

PZEM Pin ESP32 Pin Details
5V VIN (5V) Interface wants 5V
GND GND
TX GPIO 16 (RX2) Opto output is 3.3V-safe in practice
RX GPIO 17 (TX2) 3.3V drive works through the opto
Note: The optocoupled interface makes the V3 boards happy with 3.3V logic on TX/RX as long as the 5V pin is fed with real 5V. This is the standard ESP32/ESPHome hookup.

Raspberry Pi Wiring (UART or USB-TTL)

PZEM Pin Pi Pin Details
5V Pin 2 (5V)
GND Pin 6 (GND)
TX Pin 10 (GPIO 15, RXD)
RX Pin 8 (GPIO 14, TXD)
Tip: Enable the UART in raspi-config (login shell OFF, hardware ON) so /dev/serial0 exists — or skip GPIO entirely and use a $3 USB-TTL adapter as /dev/ttyUSB0.

Raspberry Pi Pico Wiring (UART0)

PZEM Pin Pico Pin Details
5V VBUS (pin 40) USB 5V
GND GND (pin 38)
TX GP1 (UART0 RX)
RX GP0 (UART0 TX)

Code Examples

The Arduino-family examples use the PZEM004Tv30 library (Library Manager). The Pi and Pico examples speak Modbus-RTU directly — no library needed beyond pyserial on the Pi.

Arduino

pzem_arduino.ino
// PZEM-004T V3 - Arduino Example
// TX->D10, RX->D11 (Uno, SoftwareSerial) | Library: "PZEM004Tv30"

#include <PZEM004Tv30.h>
#include <SoftwareSerial.h>

SoftwareSerial pzemSerial(10, 11);   // RX, TX
PZEM004Tv30 pzem(pzemSerial);

void setup() {
  Serial.begin(115200);
  Serial.println("PZEM-004T energy monitor");
}

void loop() {
  float voltage = pzem.voltage();
  float current = pzem.current();
  float power   = pzem.power();
  float energy  = pzem.energy();
  float freq    = pzem.frequency();
  float pf      = pzem.pf();

  if (isnan(voltage)) {
    Serial.println("No response - check wiring and mains presence");
  } else {
    Serial.print(voltage);  Serial.print(" V | ");
    Serial.print(current);  Serial.print(" A | ");
    Serial.print(power);    Serial.print(" W | ");
    Serial.print(energy, 3);Serial.print(" kWh | ");
    Serial.print(freq);     Serial.print(" Hz | PF ");
    Serial.println(pf);
  }
  delay(1000);
}

ESP32 (Arduino IDE)

pzem_esp32.ino
// PZEM-004T V3 - ESP32 Example (hardware UART2)
// TX->GPIO16, RX->GPIO17 | Library: "PZEM004Tv30"

#include <PZEM004Tv30.h>

PZEM004Tv30 pzem(Serial2, 16, 17);   // UART2, RX=16, TX=17

void setup() {
  Serial.begin(115200);
}

void loop() {
  float v = pzem.voltage();
  if (isnan(v)) {
    Serial.println("No response from PZEM");
  } 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(1000);

  // pzem.resetEnergy();   // uncomment once to zero the kWh counter
}

Raspberry Pi (Python, Modbus-RTU)

pzem_rpi.py
#!/usr/bin/env python3
# PZEM-004T V3 - Raspberry Pi Example (raw Modbus-RTU)
# TX->GPIO15, RX->GPIO14 (or use /dev/ttyUSB0)
# Install: pip3 install pyserial

import serial, struct, time

def crc16(data):
    crc = 0xFFFF
    for byte in data:
        crc ^= byte
        for _ in range(8):
            if crc & 1:
                crc = (crc >> 1) ^ 0xA001
            else:
                crc >>= 1
    return crc

port = serial.Serial("/dev/serial0", 9600, timeout=1)

# Read 10 input registers from address 0xF8
request = bytes([0xF8, 0x04, 0x00, 0x00, 0x00, 0x0A])
request += struct.pack("<H", crc16(request))

while True:
    port.write(request)
    resp = port.read(25)
    if len(resp) == 25:
        regs = struct.unpack(">10H", resp[3:23])
        voltage = regs[0] / 10
        current = (regs[1] + (regs[2] << 16)) / 1000
        power   = (regs[3] + (regs[4] << 16)) / 10
        energy  = (regs[5] + (regs[6] << 16)) / 1000
        freq    = regs[7] / 10
        pf      = regs[8] / 100
        print(f"{voltage:.1f} V  {current:.3f} A  {power:.1f} W  "
              f"{energy:.3f} kWh  {freq:.1f} Hz  PF {pf:.2f}")
    else:
        print("No/short response - check wiring and mains")
    time.sleep(1)

Raspberry Pi Pico (MicroPython)

pzem_pico.py
# PZEM-004T V3 - Pico MicroPython Example (raw Modbus-RTU)
# TX->GP1, RX->GP0, 5V->VBUS

from machine import UART, Pin
import struct, time

uart = UART(0, baudrate=9600, tx=Pin(0), rx=Pin(1), timeout=300)

def crc16(data):
    crc = 0xFFFF
    for byte in data:
        crc ^= byte
        for _ in range(8):
            crc = (crc >> 1) ^ 0xA001 if crc & 1 else crc >> 1
    return crc

req = bytes([0xF8, 0x04, 0x00, 0x00, 0x00, 0x0A])
req += struct.pack("<H", crc16(req))

while True:
    uart.write(req)
    time.sleep_ms(200)
    resp = uart.read()
    if resp and len(resp) >= 25:
        regs = struct.unpack(">10H", resp[3:23])
        voltage = regs[0] / 10
        current = (regs[1] + (regs[2] << 16)) / 1000
        power   = (regs[3] + (regs[4] << 16)) / 10
        energy  = (regs[5] + (regs[6] << 16)) / 1000
        print("{:.1f} V  {:.3f} A  {:.1f} W  {:.3f} kWh".format(
            voltage, current, power, energy))
    else:
        print("No response - check wiring and mains")
    time.sleep(1)

Frequently Asked Questions

Everything reads NaN / no response. What's wrong?
Three usual causes, in order: TX/RX not crossed (module TX must reach your RX); the 5V pin not actually at 5V (the opto interface needs it even when your logic is 3.3V); and no mains on L/N — the measurement side is powered by the line itself, so with the breaker off the module can't answer measurements. Wire it, energize it safely, and the readings appear.
Voltage reads fine but current is always 0.00 A.
The CT is clamped around the whole cord instead of one conductor — live and neutral currents cancel exactly. Open the clamp and put it around the LIVE wire only (split a short extension cord's outer jacket, or clamp inside a panel on one conductor). Also confirm the CT plugs into the two CT screws and snaps fully closed — an air gap in the core reads low.
How do I reset the kWh counter?
Energy accumulates in nonvolatile storage and survives power cycles by design. Reset it with the library call (pzem.resetEnergy() in PZEM004Tv30) or the Modbus reset command (function 0x42). Many dashboards never reset it and instead log deltas — that way an accidental power cycle can't lose your month's total.
Is it safe to have my microcontroller connected while mains is live?
That's the point of the design: the UART crosses an optocoupler barrier, so the logic connector carries no galvanic connection to mains. The dangers are physical, not electrical-through-the-cable: exposed L/N screws, loose strands, and uninsulated CT terminals. Box the module, strain-relieve the mains wires, and keep the low-voltage side's wiring away from the terminal side.
Can it measure DC, or 3-phase, or two circuits?
No DC — the CT and metering IC are AC-only (for DC use a shunt or Hall module like the ACS712). For 3-phase, use three PZEMs (one per phase) with different Modbus addresses on a shared bus, or a purpose-built 3-phase meter. Multiple single-phase circuits work the same way: each module gets its own address via the library's setAddress() and they share one UART.
How accurate is it really?
Spec is 0.5% class for V/A/W — and in practice units track a utility meter within a percent or two once the CT is seated properly. The weak spots: very small loads (below the 20 mA starting current read as zero), waveform-mangling loads (cheap dimmers) where PF drops, and clamping the CT off-center. For appliance-level cost tracking it's more than accurate enough.
Does it work with Home Assistant / ESPHome?
Yes — ESPHome has a native pzem004t sensor platform: an ESP32 wired exactly as in the ESP32 tab plus five lines of YAML gets you live V/A/W/kWh entities in Home Assistant. Tasmota supports it too. It's one of the most popular DIY energy-monitoring stacks, and this module is its standard hardware.

Related Tutorials