The Golden Rule of Mains Electrical Arduino Projects
When browsing electrical Arduino projects online, you will inevitably find tutorials wiring analog current transformers directly to microcontroller ADC pins or using non-isolated op-amp circuits to measure mains voltage. Do not do this. A single ground loop, a misplaced burden resistor, or a voltage spike will bridge 120V/240V AC directly into your 5V logic, instantly bricking your board and creating a severe shock hazard.
The direct answer for safe, reliable AC mains monitoring is to use a galvanically isolated UART sensor. Specifically, the Peacefair PZEM-004T V3.0 module. It measures AC voltage (80-260V), current (up to 100A via split-core CT), active power, power factor, and cumulative energy. It handles the dangerous analog-to-digital conversion and Modbus RTU math internally, passing only safe 5V TTL serial data to your microcontroller.
Sensor Selection Decision Tree
Not every project requires a full AC power analysis. Use this decision matrix to select the right sensor for your specific electrical Arduino project, terminating in the exact part you need to order.
| Measurement Need | Sensor Module | Interface | Pros & Cons | Verdict |
|---|---|---|---|---|
| DC Current/Voltage (up to 26V, 3.2A) | INA219 Breakout | I2C | High precision, easy wiring. Useless for AC mains. | Pick for 12V/24V solar or battery projects. |
| AC Current Only (relative, no voltage) | SCT-013-000 (Analog) | Analog ADC | Cheap ($3). Requires external burden resistor, biasing circuit, and EmonLib calibration. | Pick only if you are on a strict budget and only need to detect if a load is ON/OFF. |
| AC Voltage, Current, Power, PF, Energy (Mains) | PZEM-004T V3.0 | UART (Modbus) | Galvanic isolation, true RMS, no external biasing needed. Slightly larger footprint. | DEFAULT PICK: The definitive choice for safe, comprehensive AC mains monitoring. |
Parts List and Wiring Pinout
This build targets the Arduino Nano V3.0 (ATmega328P, 5V/16MHz). We use the Nano for its compact breadboard-friendly footprint and native 5V logic, which matches the PZEM's TTL output without needing a logic level shifter.
Bill of Materials (BOM)
- Microcontroller: Arduino Nano V3.0 (ATmega328P chip, not the cheaper ATmega168 variant) — ~$6.00
- Sensor: Peacefair PZEM-004T V3.0 Module with 100A split-core Current Transformer (CT) — ~$14.00
- Wiring (Logic): 22 AWG solid core hookup wire (4 colors) — ~$2.00
- Wiring (Mains): 14 AWG THHN stranded wire (rated for 600V) for the AC load path — ~$1.50/ft
- Enclosure: Non-conductive ABS or polycarbonate project box (minimum 3x4x2 inches) to prevent accidental contact with AC terminals.
Pin Mapping Table
The PZEM-004T V3 uses hardware UART internally but communicates via a 4-pin TTL header. We will use the Arduino's SoftwareSerial library so we can keep the hardware UART (pins 0 and 1) free for debugging via the USB Serial Monitor.
| Arduino Nano Pin | PZEM-004T V3 Pin | Wire Color (Suggested) | Notes |
|---|---|---|---|
| D10 (RX) | TX | Green | SoftwareSerial RX listens to PZEM transmit. |
| D11 (TX) | RX | Yellow | SoftwareSerial TX sends commands to PZEM. |
| 5V | 5V | Red | Powers the PZEM optocouplers and logic IC. |
| GND | GND | Black | Common logic ground. Do NOT connect to AC earth ground. |
Step-by-Step Assembly and Safety Checks
- Prepare the Logic Wiring: Solder or breadboard the 4 TTL wires between the Nano and the PZEM module. Double-check that TX goes to RX and RX goes to TX. Swapping these is the #1 cause of failure.
- Mount the CT Sensor: Clip the split-core CT around only one of the AC mains wires (either Line or Neutral, never both). If you clamp both, the magnetic fields cancel out and the sensor will read 0A. Ensure the arrow on the CT points toward the load.
- Wire the AC Terminals: Using 14 AWG THHN wire, connect your AC source to the PZEM's AC input terminal block, and your load to the output side (if measuring inline) or just wire the voltage sense terminals in parallel with your load. Torque the 5.08mm pitch terminal screws firmly; loose mains connections cause arcing and fires.
- The Isolation Check: Before applying mains power, use your multimeter in continuity mode. Place one probe on the Arduino GND pin and the other on the PZEM's AC terminal screw. It must read OL (Open Loop). If it beeps, you have a short and applying power will be lethal.
- Energize and Test: Plug the Arduino into your PC via USB. Open the Serial Monitor. Only after the code is running and verifying communication should you turn on the AC breaker.
Complete Compilable Firmware
This code requires the PZEM004Tv30 library. Install it via the Arduino Library Manager (search for 'PZEM004Tv30' by mandaragod). The firmware includes explicit pin definitions, non-blocking delays, and robust error handling to prevent the serial monitor from flooding with NaN (Not a Number) errors during sensor brownouts.
#include <SoftwareSerial.h>
#include <PZEM004Tv30.h>
// Pin Definitions
#define PZEM_RX_PIN 10
#define PZEM_TX_PIN 11
#define SERIAL_BAUD 115200
#define PZEM_BAUD 9600
// Initialize SoftwareSerial and PZEM object
SoftwareSerial pzemSW(PZEM_RX_PIN, PZEM_TX_PIN);
PZEM004Tv30 pzem(pzemSW);
unsigned long lastRead = 0;
const unsigned long readInterval = 1000; // Read every 1 second
void setup() {
Serial.begin(SERIAL_BAUD);
pzemSW.begin(PZEM_BAUD);
Serial.println("PZEM-004T V3 AC Monitor Initializing...");
Serial.println("Ensure PZEM RX/TX are crossed and 5V/GND are connected.");
// Set the custom shunt address (optional, defaults to 0xF8)
// pzem.setAddress(0xF8);
}
void loop() {
if (millis() - lastRead >= readInterval) {
lastRead = millis();
float voltage = pzem.voltage();
float current = pzem.current();
float power = pzem.power();
float energy = pzem.energy();
float pf = pzem.pf();
float freq = pzem.frequency();
// Error Handling: Check for NaN (Not a Number) which indicates comm failure
if (isnan(voltage) || isnan(current)) {
Serial.println("[ERROR] PZEM reading is NaN. Check wiring and CT clamp.");
return; // Skip printing bad data
}
// Print formatted data to Serial Monitor
Serial.print("V: "); Serial.print(voltage, 1); Serial.print("V | ");
Serial.print("I: "); Serial.print(current, 3); Serial.print("A | ");
Serial.print("P: "); Serial.print(power, 1); Serial.print("W | ");
Serial.print("PF: "); Serial.print(pf, 2); Serial.print(" | ");
Serial.print("Freq: "); Serial.print(freq, 1); Serial.print("Hz | ");
Serial.print("E: "); Serial.print(energy, 3); Serial.println("kWh");
}
}
Debugging: When the Serial Monitor Throws Errors
Embedded hardware rarely works perfectly on the first power-up. If your serial monitor is spitting out errors, follow this ranked troubleshooting path.
- TX/RX Swap: 90% of UART failures are crossed wires. Nano D10 (RX) must go to PZEM TX. Nano D11 (TX) must go to PZEM RX.
- Baud Rate Mismatch: The PZEM V3 is hardware-locked to 9600 baud for its Modbus interface. If you initialized
pzemSW.begin(115200), it will fail. - CT Core Gap: If voltage reads correctly but current reads 0.00A, check the split-core CT. If the two halves aren't snapping completely shut, or if you clamped both Line and Neutral, the magnetic flux cancels out.
Exact Error Strings and Causes
| Exact Error String | Ranked Causes | Fix |
|---|---|---|
[ERROR] PZEM reading is NaN |
1. TX/RX swapped. 2. PZEM 5V pin not receiving power. 3. Baud rate incorrect. |
Swap yellow/green wires. Measure 5V pin with multimeter (should be 4.8V-5.2V). Verify pzemSW.begin(9600). |
[PZEM] CRC error (in debug) |
1. Electrical noise on UART lines. 2. Long unshielded TTL wires. |
Keep TTL wires under 6 inches. Route them away from the AC mains wires. Add a 100nF decoupling capacitor across the PZEM 5V/GND pins. |
Current reads 0.00A under load |
1. CT clamped over both wires. 2. Load is below 0.02A threshold. |
Clamp CT over Line or Neutral only. Plug in a known high-draw load (e.g., 1500W space heater) to test. |
Extending or Simplifying the Build
Once you have the baseline serial output working, you need to decide how to deploy this into the real world. Here is how to scale the project based on your end goal.
How to Simplify (The 'Just Current' Approach)
If you only need to know if an appliance is running (e.g., a well pump or a dust collector) and don't care about true power factor or cumulative kWh, ditch the PZEM. Switch to an INA219 for DC circuits, or a basic SCT-013-030 (which has a built-in burden resistor and outputs 0-1V AC). Wire the SCT-013 to Analog Pin A0, add a 2.5V DC bias using two 10kΩ resistors, and use the EmonLib library. It cuts the BOM cost to under $4, but sacrifices voltage and energy logging.
How to Extend (The IoT Dashboard Approach)
For a permanent home energy monitor, an Arduino Nano tethered to a laptop via USB is impractical.
The Upgrade Path:
1. Swap the Arduino Nano for an ESP32-DevKitC V4 ($7).
2. Change SoftwareSerial to the ESP32's hardware HardwareSerial (UART2 on pins 16/17) for rock-solid Modbus timing.
3. Integrate the PubSubClient library to publish the JSON-formatted PZEM data over MQTT to a local Mosquitto broker.
4. Pipe the MQTT topics into Home Assistant or Grafana for long-term historical logging and automated alerts (e.g., 'Trigger smart plug shutoff if Power Factor drops below 0.7').
By standardizing on isolated UART sensors like the PZEM-004T V3, you eliminate the lethal risks inherent in DIY mains monitoring while gaining professional-grade telemetry data for your electrical Arduino projects.






