I had seen the AC and DC energy meter units from Chinese manufacturer PeaceFair. They looked interesting to me and were very low cost, so I ordered some DC and AC energy meters with RS485 Modbus communication. I want to read the data from them for a remote battery monitoring project. This post contains my notes on communicating with them. There were some hurdles, so hopefully this info helps you with your project.

Read more: Arduino and PeaceFair AC/DC Energy Meters

<TLDR> You must have power on both measurement and RS485 sides of the sensor. You must use a CRC using Modbus16. You need to use 9600 Baud, 8 bit, No Parity and 2 Stop bits. If you use the Arduino hardware serial then ensure you set this. The SoftwareSerial Arduino library does not allow you to do this. I used CustomSoftwareSerial which does.</TLDR>

The unit I am testing here is the DC PZEM-003. This is to measure 0-300V DC and also 0-10A with an internal shunt. You can get versions with external shunts, but I only need to read DC voltages. The techniques here should work with any of the RS485 energy meters. My unit came with a USB to RS485 converter (you need to buy the right ‘pack’ from the suppliers).

Initially I tested this with the USB to RS485 converter in my PC and I used RealTerm serial terminal program. I really like RealTerm as it gives me all the options to test out different port configurations and send data as HEX with CRC checks. I would always check that I can read data from my sensor with this first. The settings used in RealTerm (or any terminal program) are:

  • Port Options: Baud Rate 9600, 8 Data Bits, Parity is None, 2 Stop bits
  • Port Options: Also check the “RS485-rts” Hardware Flow Control
  • Port Options: Connect to the correct port (this is allocated by your computer)
  • Display Options: check “HEX[Space]” and “Half Duplex”

These devices use Modbus commands to request data from the devices. You request data using the ID of the device, along with information on which register you would like to read and how many registers you would like to have returned. You also need to add a CRC (Cyclic Redundancy Check) I also used an online CRC checker program, here. This allowed me to double check the CRC values, which use the Modbus16 polynomial for a 16 bit CRC value. When you are sending data to the DC sensor you need to follow the data sheet for these items. In this example I’m looking to just receive the DC Voltage. I started with the default device address of 1 (or 0x01 in HEX). Reading the datasheet for the unit I can see that to request the voltage we need to request to read 1 register (2 bytes) from the register 0x0000, which holds the voltage value:

<Device Address> <Read Register Command> <Start register address(2 bytes)> <Number of registers to read (2 bytes)> <CRC check High byte> <CRC Check Low Byte>
<0x01> <0x04> <0x00 0x00> <0x00 0x01> <CRC HH> <CRC LL>

I used the “+crc” checkbox on the Send tab of RealTerm. From the drop down menu you also need to use “Modbus16” as the CRC calculation method. I entered in “0x01 0x04 0x00 0x00 0x00 0x01” and sent it and…. no reply!

So you MUST have both the DC input voltage available AND have 5V available on the RS485 connection. This is because the unit is opto-isolated. To get data we need to have power on the input side to power the data collection circuitry AND we need 5V on the RS485 side to provide power for the opto-coupler output. This was my mistake. Without both of these you will not get any data returned. So with power applied, success! I sent the green HEX and got the yellow HEX data back (in this screenshot I asked for 8 registers, hence data request is different to that above).

Great! – So now I know the data coming back is working. I also converted the voltage register value with an applied 12V DC. The data comes back in the form 10000 = 100.00V (so in 10’s of mV). Converting my reading I got a value of 12.16V.

The next step is to talk to these sensors from my ATMega328 project (using an 8MHz clock). This could easily be an Arduino Uno or Nano or similar Arduino-type product. I loaded code using the Arduino IDE. There were some particular constraints I had to follow:

  • The project power supply is only 3.3V.
  • I needed to convert the serial data to RS485.
  • I could not use the hardware serial port – I had to use a software configured port.

So to solve the power supply I used a small DC-DC step up converter (search for “MT3608”) to give me 5V. (Note: I have since tested with 3.3V on the RS485 side and it seems to work YMMV).

To solve the serial to RS485 conversion I used a TTL to Serial converter module (unsurprisingly!). I have used a few of these units recently (available from lots of places) and I love them (compared to ones where you have to control the flow with an additional data pin). These have automatic port flow control and also can work at 3.3V or 5V. This solved my communication requirement.

The last thing was to read the data from a software serial port. After quite a lot of head-scratching I have found a few things:

  • With more that one device on the RS485 cables, you MUST use a software serial library that allows you to have (8N2) 8 bits, No parity and 2 stop bits. The “SoftwareSerial” library built into Arduino does not allow you to configure this. It uses the more standard 8N1. So without the additional stop bit the units get confused when talking to multiple sensors (annoyingly, the “SoftwareSerial works fine with just one sensor). I found the “CustomSoftwareSerial” library that allows you to set 8N2. Follow the examples within the library for information. This is a relatively old library but worked fine when the zip was added to my project. The Software Serial library could probably be adjusted to do 2 stop bits.
  • You must wait at least 500mS for the data to be returned from the sensor. So send a request then wait for at least 500mS before reading the soft serial port. This could also be due to my application, but I found I could not reduce this delay and get reliable information.
  • You will need a CRC checker library to double check the data returned. I use the “CRC” library from Rob Tillaart – this has worked really well for all my CRC needs!

So I managed to get everything running to read two DC sensor units at regular intervals. I am reading every 60 seconds and then averaging that data. Here is my test rig running to my prototype monitoring device.

Here is some basic arduino code that should allow you to take to the DC sensors:

#include <CustomSoftwareSerial.h>   // Use this for custom software serial
CustomSoftwareSerial* customSerial; // Declare serial

#define SOFT_SERIAL_RX    4   
#define SOFT_SERIAL_TX    5

uint8_t data_rs485[20];           // a byte array to hold incoming data

const uint8_t request_dc_v_1[] = {0x01, 0x04, 0x00, 0x00, 0x00, 0x01, 0x31, 0xCA};  // Read voltage from address 0x01
const uint8_t request_dc_v_2[] = {0x02, 0x04, 0x00, 0x00, 0x00, 0x01, 0x31, 0xF9};  // Read voltage from address 0x02

void setup() 
{
  // initialize serial communication:
  Serial.begin(SERIAL_BAUD);

  // Set the data rate for the SoftwareSerial port
  // For peacefair equipment: 8 bit, No parity, 2 Stop bits 

  customSerial = new CustomSoftwareSerial(SOFT_SERIAL_RX, SOFT_SERIAL_TX); // rx, tx
  customSerial->begin(SOFT_SERIAL_BAUD, CSERIAL_8N2);         // Baud rate: 9600, configuration: CSERIAL_8N2
}
void loop()
{
   customSerial->write(request_dc_v_1, sizeof(request_dc_v_1));
   // Clear the data array:
   for (int x = 0; x < data_dc_length; x++)
   {
       data_rs485[x] = 0;
   }
   // MUST BE ABOUT 500mS short delay -  wait on serial to return the data.
   for (int x = 0; x < 50; x++)
   {
       delay(10);
   }
   uint8_t n = 0;

   // Wait for data back - if no data then this is empty
   while (customSerial->available())
    {
       // get the new byte:
       data_rs485[n] = (uint8_t)customSerial->read();
       n++;
    }

    // Display data:
    Serial.print(F("DATA:"));
    for (int x = 0; x < n; x++)
    {
      Serial.print(':');
      Serial.print(data_rs485[x], HEX);
    }
    Serial.println();

    delay(3000);   // Wait and then repeat
}