Interfacing ESP8266 and Peacefair PZEM-017 with ModBUS RTU

This brief note explains how to connect ESP8266 and Peacefair PZEM-017 to read voltage and current from the sensor over Modbus RTU (the RS485 based serial protocol).

Parts List

  • Wemos D1 (ESP 8266)
  • MAX3485
  • 120 ohm resistor (used 100 ohm for testing)

Connections

Code Example

#include          // https://github.com/emelianov/modbus-esp8266
#include 

// ESP8266 pins
#define D6 12
#define D7 13
#define D8 15

// PZEM-017 Shunt Range
#define PZEM_SHUNT_100A 0x0000
#define PZEM_SHUNT_50A  0x0001
#define PZEM_SHUNT_200A 0x0002
#define PZEM_SHUNT_300A 0x0003

// My settings
#define SERIAL_RX D8
#define SERIAL_TX D7
#define SERIAL_CONTROL D6
#define PZEM_MODBUS_ID 0x01
#define PZEM_SHUNT PZEM_SHUNT_200A

SoftwareSerial swSerial(SERIAL_RX, SERIAL_TX);

ModbusRTU modbus;
int modbusLastStatusCode = -1;

bool modbusStatusCallback(Modbus::ResultCode event, uint16_t transactionId, void* data) {
  // Serial.printf_P("Request result: 0x%02X, Mem: %d\n", event, ESP.getFreeHeap());

  modbusLastStatusCode = uint(event);

  return true;
}

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

  swSerial.begin(9600, SWSERIAL_8N1);
  modbus.begin(&swSerial, SERIAL_CONTROL);

  modbus.client();

  Serial.print("Setting shut value...");

  // The write resulted always 0xE4 (timeout), so no callback here
  modbus.writeHreg(PZEM_MODBUS_ID, 0x0003, PZEM_SHUNT);
  modbus.task();

  Serial.println("OK");

  Serial.println("Setup finished");
}

uint16_t registers[2];
float voltage;
float current;

void loop() {
  if (!modbus.slave()) {
    modbus.readIreg(PZEM_MODBUS_ID, 0, registers, 2, modbusStatusCallback);
    if (modbusLastStatusCode == 0) {
        voltage = registers[0] / 100.0;
        current = registers[1] / 100.0;
        Serial.print(voltage);
        Serial.print("V, ");
        Serial.print(current);
        Serial.println("A");
    }
    else {
        Serial.printf_P("Result code: 0x%02X\n", modbusLastStatusCode);
    }
  }
  modbus.task();
  yield();
}

For PlatformIO users, the platformio.ini:

[platformio]
default_envs =
    esp8266

[env]
lib_deps = 
    https://github.com/emelianov/modbus-esp8266
    
[env:esp8266]
platform = espressif8266
framework = arduino
board = d1
lib_ldf_mode = deep
monitor_speed = 115200
upload_speed = 460800