// PZEM-017 DC Energy Meter with LCD By Solarduino
// Note Summary
// Note : Safety is very important when dealing with electricity. We take no responsibilities while you do it at your own risk.
// Note : This DC Energy Monitoring Code needs PZEM-017 DC Energy Meter to measure values and Arduio Mega / UNO for communication and display.
// Note : This Code monitors DC Voltage, current, Power, and Energy.
// Note : The values shown in LCD Display is refreshed every second.
// Note : The values are calculated internally by energy meter and function of Arduino is only to read the value and for further calculation.
// Note : The first step is need to select shunt value and change the value accordingly. look for line "static uint16_t NewshuntAddr = 0x0000; "
// Note : You need to download and install (modified) Modbus Master library at our website (https://solarduino.com/pzem-014-or-016-ac-energy-meter-with-arduino/ )
// Note : The Core of the code was from EvertDekker.com 2018 which based on the example from http://solar4living.com/pzem-arduino-modbus.htm
// Note : Solarduino only amend necessary code and integrate with LCD Display Shield.
/*/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/////////////*/
/* 1- PZEM-017 DC Energy Meter */
#include <ModbusMaster.h> // Load the (modified) library for modbus communication command codes. Kindly install at our website.
#define MAX485_DE 19 // Define DE Pin to Arduino pin. Connect DE Pin of Max485 converter module to Pin 2 (default) Arduino board
#define MAX485_RE 18 // Define RE Pin to Arduino pin. Connect RE Pin of Max485 converter module to Pin 3 (default) Arduino board
// These DE anr RE pins can be any other Digital Pins to be activated during transmission and reception process.
static uint8_t pzemSlaveAddr = 0x01; // Declare the address of device (meter) in term of 8 bits. You can change to 0x02 etc if you have more than 1 meter.
static uint16_t NewshuntAddr = 0x0003;
ModbusMaster node; /* activate modbus master codes*/
float PZEMVoltage = 0; /* Declare value for DC voltage */
float PZEMCurrent = 0; /* Declare value for DC current*/
float PZEMPower = 0; /* Declare value for DC Power */
float PZEMEnergy = 0; /* Declare value for DC Energy */
unsigned long startMillisPZEM; /* start counting time for LCD Display */
unsigned long currentMillisPZEM; /* current counting time for LCD Display */
const unsigned long periodPZEM = 1000; // refresh every X seconds (in seconds) in LED Display. Default 1000 = 1 second
unsigned long startMillisLCD; /* start counting time for LCD Display */
unsigned long currentMillisLCD; /* current counting time for LCD Display */
const unsigned long periodLCD = 1000; /* refresh every X seconds (in seconds) in LED Display. Default 1000 = 1 second */
int ResetEnergy = 0; /* reset energy function */
unsigned long startMillisEnergy; /* start counting time for LCD Display */
unsigned long currentMillisEnergy; /* current counting time for LCD Display */
const unsigned long periodEnergy = 1000; // refresh every X seconds (in seconds) in LED Display. Default 1000 = 1 second
void setup()
{
/*0 General*/
Serial.begin(9600); /* to display readings in Serial Monitor at 9600 baud rates */
/* 1- PZEM-017 DC Energy Meter */
setShunt(pzemSlaveAddr); // Delete the "//" to set shunt rating (0x01) is the meter address by default
startMillisPZEM = millis(); /* Start counting time for run code */
Serial2.begin(9600, SERIAL_8N2); /* To assign communication port to communicate with meter. with 2 stop bits (refer to manual)*/
node.begin(pzemSlaveAddr, Serial2); /* Define and start the Modbus RTU communication. Communication to specific slave address and which Serial port */
pinMode(MAX485_RE, OUTPUT); /* Define RE Pin as Signal Output for RS485 converter. Output pin means Arduino command the pin signal to go high or low so that signal is received by the converter*/
pinMode(MAX485_DE, OUTPUT); /* Define DE Pin as Signal Output for RS485 converter. Output pin means Arduino command the pin signal to go high or low so that signal is received by the converter*/
digitalWrite(MAX485_RE, 0); /* Arduino create output signal for pin RE as LOW (no output)*/
digitalWrite(MAX485_DE, 0); /* Arduino create output signal for pin DE as LOW (no output)*/
// both pins no output means the converter is in communication signal receiving mode
node.preTransmission(preTransmission); // Callbacks allow us to configure the RS485 transceiver correctly
node.postTransmission(postTransmission);
changeAddress(0XF8, pzemSlaveAddr); // By delete the double slash symbol, the meter address will be set as 0x01.
// By default I allow this code to run every program startup. Will not have effect if you only have 1 meter
delay(1000); /* after everything done, wait for 1 second */
startMillisLCD = millis(); /* Start counting time for display refresh time*/
}
void loop()
{
currentMillisPZEM = millis(); /* count time for program run every second (by default)*/
if (currentMillisPZEM - startMillisPZEM >= periodPZEM) /* for every x seconds, run the codes below*/
{
uint8_t result; /* Declare variable "result" as 8 bits */
result = node.readInputRegisters(0x0000, 6); /* read the 9 registers (information) of the PZEM-014 / 016 starting 0x0000 (voltage information) kindly refer to manual)*/
if (result == 0x00) /* If there is a response */
{
uint32_t tempdouble = 0x00000000; /* Declare variable "tempdouble" as 32 bits with initial value is 0 */
PZEMVoltage = node.getResponseBuffer(0x0000) / 100.0; /* get the 16bit value for the voltage value, divide it by 100 (as per manual) */
// 0x0000 to 0x0008 are the register address of the measurement value
PZEMCurrent = node.getResponseBuffer(0x0001) / 100.0; /* get the 16bit value for the current value, divide it by 100 (as per manual) */
tempdouble = (node.getResponseBuffer(0x0003) << 16) + node.getResponseBuffer(0x0002); /* get the power value. Power value is consists of 2 parts (2 digits of 16 bits in front and 2 digits of 16 bits at the back) and combine them to an unsigned 32bit */
PZEMPower = tempdouble / 10.0; /* Divide the value by 10 to get actual power value (as per manual) */
tempdouble = (node.getResponseBuffer(0x0005) << 16) + node.getResponseBuffer(0x0004); /* get the energy value. Energy value is consists of 2 parts (2 digits of 16 bits in front and 2 digits of 16 bits at the back) and combine them to an unsigned 32bit */
PZEMEnergy = tempdouble;
Serial.print(PZEMVoltage, 1); /* Print Voltage value on Serial Monitor with 1 decimal*/
Serial.print("V ");
Serial.print(PZEMCurrent, 3);
Serial.print("A ");
Serial.print(PZEMPower, 1);
Serial.print("W ");
Serial.print(PZEMEnergy, 0);
Serial.print("Wh ");
Serial.println();
}
else
{
Serial.print("Failed to read modbus ");
Serial.println(result);
}
startMillisPZEM = currentMillisPZEM ; /* Set the starting point again for next counting time */
}
}
void preTransmission() /* transmission program when triggered*/
{
digitalWrite(MAX485_RE, 1); /* put RE Pin to high*/
digitalWrite(MAX485_DE, 1); /* put DE Pin to high*/
delay(1); // When both RE and DE Pin are high, converter is allow to transmit communication
}
void postTransmission() /* Reception program when triggered*/
{
delay(3); // When both RE and DE Pin are low, converter is allow to receive communication
digitalWrite(MAX485_RE, 0); /* put RE Pin to low*/
digitalWrite(MAX485_DE, 0); /* put DE Pin to low*/
}
void setShunt(uint8_t slaveAddr) //Change the slave address of a node
{
static uint8_t SlaveParameter = 0x06; /* Write command code to PZEM */
static uint16_t registerAddress = 0x0003; /* change shunt register address command code */
uint16_t u16CRC = 0xFFFF; /* declare CRC check 16 bits*/
u16CRC = crc16_update(u16CRC, slaveAddr); // Calculate the crc16 over the 6bytes to be send
u16CRC = crc16_update(u16CRC, SlaveParameter);
u16CRC = crc16_update(u16CRC, highByte(registerAddress));
u16CRC = crc16_update(u16CRC, lowByte(registerAddress));
u16CRC = crc16_update(u16CRC, highByte(NewshuntAddr));
u16CRC = crc16_update(u16CRC, lowByte(NewshuntAddr));
Serial.println("Change shunt address");
preTransmission(); /* trigger transmission mode*/
Serial2.write(slaveAddr); /* these whole process code sequence refer to manual*/
Serial2.write(SlaveParameter);
Serial2.write(highByte(registerAddress));
Serial2.write(lowByte(registerAddress));
Serial2.write(highByte(NewshuntAddr));
Serial2.write(lowByte(NewshuntAddr));
Serial2.write(lowByte(u16CRC));
Serial2.write(highByte(u16CRC));
delay(10);
postTransmission(); /* trigger reception mode*/
delay(100);
while (Serial2.available()) /* while receiving signal from Serial2 from meter and converter */
{
Serial.print(char(Serial2.read()), HEX); /* Prints the response and display on Serial Monitor (Serial)*/
Serial.print(" ");
}
}
void changeAddress(uint8_t OldslaveAddr, uint8_t NewslaveAddr) //Change the slave address of a node
{
static uint8_t SlaveParameter = 0x06; /* Write command code to PZEM */
static uint16_t registerAddress = 0x0002; /* Modbus RTU device address command code */
uint16_t u16CRC = 0xFFFF; /* declare CRC check 16 bits*/
u16CRC = crc16_update(u16CRC, OldslaveAddr); // Calculate the crc16 over the 6bytes to be send
u16CRC = crc16_update(u16CRC, SlaveParameter);
u16CRC = crc16_update(u16CRC, highByte(registerAddress));
u16CRC = crc16_update(u16CRC, lowByte(registerAddress));
u16CRC = crc16_update(u16CRC, highByte(NewslaveAddr));
u16CRC = crc16_update(u16CRC, lowByte(NewslaveAddr));
Serial.println("Change Slave Address");
preTransmission(); /* trigger transmission mode*/
Serial2.write(OldslaveAddr); /* these whole process code sequence refer to manual*/
Serial2.write(SlaveParameter);
Serial2.write(highByte(registerAddress));
Serial2.write(lowByte(registerAddress));
Serial2.write(highByte(NewslaveAddr));
Serial2.write(lowByte(NewslaveAddr));
Serial2.write(lowByte(u16CRC));
Serial2.write(highByte(u16CRC));
delay(10);
postTransmission(); /* trigger reception mode*/
delay(100);
while (Serial2.available()) /* while receiving signal from Serial2 from meter and converter */
{
Serial.print(char(Serial2.read()), HEX); /* Prints the response and display on Serial Monitor (Serial)*/
Serial.print(" ");
}
}