PZEM-004T library for UNO R4 (works on R3/Nano/Mega too)

Hey folks,

I just published a library for the PZEM-004T V4.0 energy monitor. The main reason I made this is because none of the existing libraries work with the new UNO R4 boards - they all use Serial1 differently and most just crash.

What it does: Reads voltage, current, power, energy, frequency, and power factor from PZEM-004T using Modbus RTU. Also lets you reset the energy counter and change the device address if needed.

Why it might be useful:

  • Actually works on UNO R4 (Minima and WiFi versions)

  • Also works on R3, Nano, Mega with the same code

  • Library auto-detects which board you're using

  • Includes proper error handling with specific error codes

  • Has a batch read function that's way faster than reading parameters individually

Installation: Library Manager -> search "PZEM004Tv40_R4"

Or grab it from GitHub: https://github.com/bharanidharanrangaraj/PZEM004Tv40_R4

Basic example (For UNO R4 series):

#include <PZEM004Tv40_R4.h>

PZEM004Tv40_R4 pzem(&Serial1);  // R4 uses Serial1

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

void loop() {
  if (pzem.readAll()) {
    Serial.print("Voltage: ");
    Serial.print(pzem.getVoltage());
    Serial.println(" V");
    
    Serial.print("Power: ");
    Serial.print(pzem.getPower());
    Serial.println(" W");
  }
  delay(1000);
}

For R3/Nano, you'd use SoftwareSerial instead - there's an example in the library that shows both.

It includes Basic reading example, Individual parameter reading

I've been using this for a couple months now in my home energy monitoring setup and it's been solid. Tested on R4 WiFi, R4 Minima, regular R3 and Nano Series.

If you find bugs or have suggestions, open an issue on GitHub or let me know here. Pull requests welcome if you want to add features.

Thanks and Regards
Bharani Dharan Rangaraj

Thanks for sharing, appreciated!

At first sight I see no bugs, just two performance tips:

multiplication is faster than division

_current = iRaw / 1000.0;

==>

_current = iRaw * 1e-3;

delay

More important is to investigate how to remove the calls to delay() as these are blocking.
Async calls might help.

Thanks robtillaart for the feedback! Good catch on the multiplication. I'll update those divisions to use multiplication instead.

For the delay() calls, I'll add a non-blocking example using millis() in the next update. Want to keep the basic examples simple, but definitely agree async would be better for real applications.

Appreciate you taking the time to review! :slight_smile:

You're welcome.

some minor remarks

receiveResponse() is now a blocking call.
with some rework you can make it async too, make the internal buffer static

You could replace unsigned long and long with "exact types" uint32_t and int32_t

I keep in several of my libraries an uint32_t for a lastRead() function,
this allows for easy polling by the user, e.g.

if (millis() - PZEM.lastRead() >= 10000)  //  time to read again - cache outdated