PZEM-016 Energy Monitor
I bought some (2pieces to start) PZEM-016 energy measure modules from Ali express. You can get them for around $8, not that expensive at all if you take in consideration what this device all can do.
The idea was to measure in my fuse box all the groups (6) separately and push the data to my Mqtt broker over Wifi with the help of an Esp32 and Arduino.
As always the idea was born quick, but to get it all working together was a bit harder, partial because of faulty (not sure about that) manual, other bugs and even something stupid as a bad power supply. But, never give up and it’s now working fine.
The PZEM-014/016 can measure Ac voltage V, current A, active power W, frequency Hz, power factor pf and used energy Wh. You can read all these values from the PZEM with the help of a Rs-485 Half duplex bus. The protocol they used is the well known modbus protocol.
Note: The Rs485 side is safe, but the rest of the PZEM-014/016 has deadly mains voltage. Please be careful.
The schematic is not that special, if you worked before with Rs485 you will recognize the parts.
As Rs485 line driver I used the SN65HVD75 from Texas Instruments, why? Because that’s the one I had on stock. You can use every Rs485 driver for it, however keep in mind that is should work with Vcc of 3.3V provided in the case by the ESP32 Devkit V1.
The data in and out of the Rs4854 driver are connect to the 3e (uart2) hardware port of the Esp32. The data direction (RE_Not/DE) selectors are connected to 2 gpio of the Esp32. You can connected those pins also together to one gpio.
And now something about the RS485 bus.
It’s important that you do this correct to get it all reliable. A good Rs485 has at the begin and the end of the line a termination resistor of 120Ω. So it doesn’t matter if you have 2 or 20 devices on the bus, you always need 2 termination resistors. Why, to get load on the line and prevent reflection of the signal. Google for it.
You see in the schematic and the picture the termination resistor on the Rs485 driver in the breadboard. In the PZEM is a build in termination resistor that can’t be easy disabled, you need to solder it out.
I have to test if a setup with 6 PZEM will work if you leave all the resistors, will come back to that if all my ordered PZEM are arrived and tested.
For the wiring of the Rs485 bus you need to use twisted cable, this will prevent interfering of the Rs485 signal by external sources. Read this if you want to know more theory.
And then we have the ground wire for the network. it’s not necessary but my experience is that the network preforms not that good with long wires if you don’t use the ground wire. To prevent unwanted ground loop currents I have added a 100Ω resistor.
That was the hardware, let continue with the software. First we have to edit some files.
If you are going to use uart1 or 2 on the esp32 you have to comment out some lines in esp32-hal-uart.c ;
//uart->dev->conf0.txfifo_rst = 1; //uart->dev->conf0.txfifo_rst = 0; //uart->dev->conf0.rxfifo_rst = 1; //uart->dev->conf0.rxfifo_rst = 0;
If you want to use an easy way to switch between PZEM slaves you can edit the ModbusMaster files.
In ModbusMaster.h you add at blanco line 78;
void slaveid(uint8_t);
In ModbusMaster.cpp you add at blanco line 75;
void ModbusMaster::slaveid(uint8_t slave) { _u8MBSlave = slave; }
If you are going to use the ModMaster library attached to this post, there’s no need to change this, it’s already done.
The Arduino code is commented and should be clear enough to get you going.
In the code there’s a function called changeAddress , with this function you can change the slave address of the PZEM. Preferable you do this the best when only one PZEM is connected to the network. If you do something wrong or forgot the old slave address you can use the broadcast address 0xF8 to reset the address to a known number changeAddress(0xF8, 0x01). When using the broadcast address you must have only one slave connected.
The other function resetEnergy will set the energy counter (Wh) back to zero.
One think that’s a bit strange and is (I think) as misprint in the manual and that’s the order of the crc bytes.
The manual talks about Crc-highbyte first and then Crc-lowbyte. However this doesn’t work and with the help of sniffing the data between the PZEM014-Master software provided by Peacefair and the PZEM I found out that the crc order must be Crc-lowbyte first and then the Crc-highbyte. You can calculate very easy modbus crc here online.
Tested it all with an Esp32, but it should also work with an Esp8266 if you use the software uart and probably it works also with the other Arduino boards.
Note 27/7/2020: If the communication fails and you are using the PZEM-016 for the first time you must set the slave address first. You can do this with the tool provided by manufacturer or with the help of de Arduino code below by enable the line changeAddress and change it to changeAddress(0xF8, 0x01)
Download the Arduino Sketch and the modified ModbusMaster library.
/* An Arduino Sketch for reading data from a PZEM-014 or PZEM-016, tested with ESP32 DEVKit 1, Arduino 1.8.5 EvertDekker.com 2018 If you want to use slaveid function to change the slaveid on the fly, you need to modify the ModbusMaster library (Or get the copy from my website) In ModbusMaster.h add at line 78 void slaveid(uint8_t); In ModbusMaster.cpp add at line 75 void ModbusMaster::slaveid(uint8_t slave) { _u8MBSlave = slave; } */ /* If you are using other then uart0 on the ESP32, Comment out in esp32-hal-uart.c the follwing line: //uart->dev->conf0.txfifo_rst = 1; //uart->dev->conf0.txfifo_rst = 0; //uart->dev->conf0.rxfifo_rst = 1; //uart->dev->conf0.rxfifo_rst = 0; Source: https://github.com/4-20ma/ModbusMaster/issues/93 */ #include <ModbusMaster.h> HardwareSerial Pzemserial(2); #define RXD2 16 //Gpio pins Serial2 #define TXD2 17 #define MAX485_DE 19 // We're using a MAX485-compatible RS485 Transceiver. The Data Enable and Receiver Enable pins are hooked up as follows: #define MAX485_RE_NEG 18 ModbusMaster node; static uint8_t pzemSlaveAddr = 0x01; void setup() { Pzemserial.begin(9600, SERIAL_8N1, RXD2, TXD2); // Note the format for setting a serial port is as follows: Serial2.begin(baud-rate, protocol, RX pin, TX pin); Serial.begin(9600); node.begin(pzemSlaveAddr, Pzemserial); //Start the Modbusmaster pinMode(MAX485_RE_NEG, OUTPUT); // Setting up the RS485 transceivers pinMode(MAX485_DE, OUTPUT); digitalWrite(MAX485_RE_NEG, 0); // Init in receive mode digitalWrite(MAX485_DE, 0); node.preTransmission(preTransmission); // Callbacks allow us to configure the RS485 transceiver correctly node.postTransmission(postTransmission); //changeAddress(0x01, 0x02); /* By Uncomment the function in the above line you can change the slave address from one of the nodes, only need to be done ones. Preverable do this only with 1 slave in the network. changeAddress(OldAddress, Newaddress) If you f*ck it up or don't know the new address anymore, you can use the broadcast address 0XF8 as OldAddress to change the slave address. Use this with one slave ONLY in the network. Note: First run of a new PZEM-016 you have to set the slave address first with: changeAddress(0xF8, 0x01)<br /><br /> */ //resetEnergy(0x01); /* By Uncomment the function in the above line you can reset the energy counter (Wh) back to zero from one of the slaves. */ delay(1000); } /* RegAddr Description Resolution 0x0000 Voltage value 1LSB correspond to 0.1V 0x0001 Current value low 16 bits 1LSB correspond to 0.001A 0x0002 Current value high 16 bits 0x0003 Power value low 16 bits 1LSB correspond to 0.1W 0x0004 Power value high 16 bits 0x0005 Energy value low 16 bits 1LSB correspond to 1Wh 0x0006 Energy value high 16 bits 0x0007 Frequency value 1LSB correspond to 0.1Hz 0x0008 Power factor value 1LSB correspond to 0.01 0x0009 Alarm status 0xFFFF is alarm,0x0000is not alarm */ void loop() { uint8_t result; for (pzemSlaveAddr = 1; pzemSlaveAddr < 3; pzemSlaveAddr++) { // Loop all the Pzem sensors node.slaveid(pzemSlaveAddr); //Switch to another slave address. NOTE: You can only use this function is you have modified the ModbusMaster library (Or get the copy from my website) Serial.print("Pzem Slave "); Serial.print(pzemSlaveAddr); Serial.print(": "); result = node.readInputRegisters(0x0000, 9); //read the 9 registers of the PZEM-014 / 016 if (result == node.ku8MBSuccess) { uint32_t tempdouble = 0x00000000; float voltage = node.getResponseBuffer(0x0000) / 10.0; //get the 16bit value for the voltage, divide it by 10 and cast in the float variable tempdouble = (node.getResponseBuffer(0x0002) << 16) + node.getResponseBuffer(0x0001); // Get the 2 16bits registers and combine them to an unsigned 32bit float current = tempdouble / 1000.00; // Divide the unsigned 32bit by 1000 and put in the current float variable tempdouble = (node.getResponseBuffer(0x0004) << 16) + node.getResponseBuffer(0x0003); float power = tempdouble / 10.0; tempdouble = (node.getResponseBuffer(0x0006) << 16) + node.getResponseBuffer(0x0005); float energy = tempdouble; float hz = node.getResponseBuffer(0x0007) / 10.0; float pf = node.getResponseBuffer(0x0008) / 100.00; Serial.print(voltage, 1); // Print Voltage with 1 decimal Serial.print("V "); Serial.print(hz, 1); Serial.print("Hz "); Serial.print(current, 3); Serial.print("A "); Serial.print(power, 1); Serial.print("W "); Serial.print(pf, 2); Serial.print("pf "); Serial.print(energy, 0); Serial.print("Wh "); Serial.println(); } else { Serial.println("Failed to read modbus"); } delay(1000); } } void preTransmission() // Put RS485 Transceiver in transmit mode { digitalWrite(MAX485_RE_NEG, 1); digitalWrite(MAX485_DE, 1); delay(1); } void postTransmission() // Put RS485 Transceiver back in receive mode (default mode) { delay(3); digitalWrite(MAX485_RE_NEG, 0); digitalWrite(MAX485_DE, 0); } void resetEnergy(uint8_t slaveAddr) //Reset the slave's energy counter { uint16_t u16CRC = 0xFFFF; static uint8_t resetCommand = 0x42; u16CRC = crc16_update(u16CRC, slaveAddr); u16CRC = crc16_update(u16CRC, resetCommand); Serial.println("Resetting Energy"); preTransmission(); Pzemserial.write(slaveAddr); Pzemserial.write(resetCommand); Pzemserial.write(lowByte(u16CRC)); Pzemserial.write(highByte(u16CRC)); delay(10); postTransmission(); delay(100); while (Pzemserial.available()) { // Prints the response from the Pzem, do something with it if you like Serial.print(char(Pzemserial.read()), HEX); Serial.print(" "); } } void changeAddress(uint8_t OldslaveAddr, uint8_t NewslaveAddr) //Change the slave address of a node { static uint8_t SlaveParameter = 0x06; static uint16_t registerAddress = 0x0002; // Register address to be changed uint16_t u16CRC = 0xFFFF; 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(); Pzemserial.write(OldslaveAddr); Pzemserial.write(SlaveParameter); Pzemserial.write(highByte(registerAddress)); Pzemserial.write(lowByte(registerAddress)); Pzemserial.write(highByte(NewslaveAddr)); Pzemserial.write(lowByte(NewslaveAddr)); Pzemserial.write(lowByte(u16CRC)); Pzemserial.write(highByte(u16CRC)); delay(10); postTransmission(); delay(100); while (Pzemserial.available()) { // Prints the response from the Pzem, do something with it if you like Serial.print(char(Pzemserial.read()), HEX); Serial.print(" "); } }
29/12/2018 Changed Serial2 to Pzemserial because Serial2 is now a reserved name in arduino ide.
Code in the download and here above is changed.
05/01/2019 The delay in the postTransmission was not long enough and therefore the rs485 transmitter was put back in receive mode to fast. Increased the delay to 3 milliseconds.
Code in the download and here above is changed.
/RE-DE signal (blue) is hold longer low to have time enough to shift out all the bits (yellow).
Thanks for this! I also am planning to use 6 units to measure two split-phase 200A panels (4) as well as a ATS from a generator.
How has chaining 6 worked so far? Also have the units remembered their new addresses after power cycles?
Hi John,
Project is still not finished, so I can’t tell how it’s working with the 6 daisy chained.
The units will remember the address after power cycles, that’s no problem.
Hi… I’m trying to compile the above code for ardunio mega but the compilation error says – no matching function for call to ‘HardwareSerial::HardwareSerial(int)’. Could you help ?
Hi,
The code is designed for an ESP32 as mentioned in the text. The Mega doesn’t use HardwareSerial as in the code, remove this line.
You have to change it to something like Serial1.begin(9600)
I have tried with esp8266 module but now it says – invalid conversion from ‘int’ to ‘SerialMode’ [-fpermissive]. Please Help.
Hi,
The Esp8266 doesn’t have a second hardware serial. Use the software serial instead.
Hi. What is the argument in this line?
Pzemserial(2)
The 2 is for the second hardware serial port.
The Esp32 has 3 hardware serial ports and the argument determine what port you want to use.
Oh I see. If I’m going to use an Arduino UNO, I’ll just delete that line. I will be connecting for PZEM-016s. Have you tried it yet?
Four* PZEMs. Have you tried multiples in daisy chain?
I didn’t try it on an Arduino. I did try it in daisy chain with 5. Works flawless.
how can i use this code in esp idf .
No idea how to use this in IDF, did not do much with IDF.
Hi I’ve just built the monitor on ESP32, using the base code, combined with 4 temperature probes on a One wire system to measure the thermal efficiency of the air source heat pumps in our village school. A couple of key points – some of the Peacefair PZEM-016 units (possibly the older ones) come with an RS485 120ohm resistor built in. You need to check this, as it could affect the perfromance of the wiring. If you pop open the box, it is R10 adjacent to the MAX485 chip. This can be desoldered easily. Also using the RS485 with less than 50m of main cable you probably dont need terminator resistors.
Apologies, I may not have made it clear that the more recent PZEM-016 I bought, DO NOT HAVE the 120 ohm resistor. A good move I think on Peacefairs part.
Good day ! Is this applicable to PZEM 004T ? Thank you
Nop, the 004T uses a different protocol.
Hi Evert,
When I tried to compile your code with NodeMCU, I got the following error:
“error: invalid conversion from ‘int’ to ‘SerialMode’ [-fpermissive] ”
I know your response from previous post is to use software Serial, could you please let me know how to modify your code so it can run on NodeMCU?
Thank you in advance for all your hard work.
Regards,
Key
Hi Key,
This is the modified code with software serial. It compiles with the ide set to Nodemcu 1.0, but I didn’t tested it with hardware.
Change the pins according your hw setup.
Pzem016 with software uart
Hi Evert,
I’m trying to use your “Pzem016 with Software uart” on PZEM-017 along with NodeMCU and with TTL-RS485 and I also made some modification on the digital pin like below:
#define RXD2 13 //Gpio pins SoftwareSerial-NodeMCU D7
#define TXD2 15 //Gpio pins SoftwareSerial-NodeMCU D8
#define MAX485_DE 4 //Gpio pins SoftwareSerial-NodeMCU D2
#define MAX485_RE_NEG 5 //Gpio pins SoftwareSerial-NodeMCU D1
Here is the output on the serial monitor:
Pzem Slave 1: Failed to read modbus
Pzem Slave 2: Failed to read modbus
Pzem Slave 1: Failed to read modbus
Pzem Slave 2: Failed to read modbus
Do you know why it failed to read the modbus?
Could you please help me on this.
Waiting for your response and Thank you in advance for all your works.
Regards,
key
Hi Key,
On Github a new version that I confirmed that it is working with hardware.
Used a NodeMcu 1.0 in the Arduino ide setting.
regards,
Evert
Hi Evert,
I did downloaded your new code from Github and put it on NodeMCU 1.0 but it still can’t read the PZEM-017.
Here is my wiring connection:
NodeMCU-G –> PZEM017-GND –> TTL_RS485-GND
NodeMCU-VV –> PZEM017-5V –> TTL_RS485-VCC
NodeMCU-D4 –> TTL_RS485-RE
NodeMCU-D5 –> TTL_RS485-DE
NodeMCU-D6 –> TTL_RS485-RO
NodeMCU-D7 –> TTL_RS485-DI
TTL_RS485-A –> PZEM017-A
TTL_RS485-B –> PZEM017-B
It still can’t see any data as Serial Monitor show:
Pzem Slave 1: Failed to read modbus
Hi Key,
I have also a ground wire with 100 ohm resistor between PZEM and the Rs485
Here is my wiring connection:
NodeMCU-VV –> PZEM017-5V –> TTL_RS485-VCC <-NOT NEEDED
NodeMCU-D4 –> TTL_RS485-RE
NodeMCU-D5 –> TTL_RS485-DE
NodeMCU-D6 –> TTL_RS485-RO
NodeMCU-D7 –> TTL_RS485-DI
TTL_RS485-A –> PZEM017-A
TTL_RS485-B –> PZEM017-B
TTL_RS485-GND -> 100 ohm -> PZEM017-GND
See the schematic; https://www.evertdekker.com/wp/wp-content/gallery/pzem015/thumbs/thumbs_schematic.png
Did you also modified the ModbusMaster library as described in the post? If yes, are you sure that you have only one copy of the ModbusMaster library on your hdd.
And off course the PZEM have to be connected at the other side to the main system.
Hi Evert,
NodeMCU still failed to read PZEM017 🙁 🙁 🙁
Here is what I did again, reconnect all the wiring according to your schematic above, as shown below:
NodeMCU-D4 –> TTL_RS485-RE
NodeMCU-D5 –> TTL_RS485-DE
NodeMCU-D6 –> TTL_RS485-RO
NodeMCU-D7 –> TTL_RS485-DI
NodeMCU-3V –> TTL_RS485-VCC
20Kohm between NodeMCU-3V & TTL_RS485-RO
TTL_RS485-GND -> NodeMCU-G -> 100 ohm -> PZEM017-GND
TTL_RS485-A –> PZEM017-A
TTL_RS485-B –> PZEM017-B
120Ohm between PZEM017 A & B
I also did deleted my exiting “ModbusMaster-master” from the libraries then add your “ModbusMaster-master” that I download from your Github.
Re-download your latest version from Github (your 3 posted above).
I don’t make any change to either your “ModbusMaster-master” or your INO file, just compile and upload it to NodeMCU and the Serial Monitor still show “Pzem Slave 1: Failed to read modbus”
Here is the link to my setup:
https://ibb.co/ZXFY87D
https://ibb.co/FnDtbtt
Please help and let me know what did I missed or what did I do wrong?
Looks all fine from here.
What chip is on your TTL to RS485 board?
Hi Evert,
The chip is MAX 485, please see pictures below:
Chip: https://ibb.co/J5t0XB7
Amazon link to product: https://www.amazon.com/Max485-Chip-RS-485-Module-Raspberry/dp/B00NIOLNAG/ref=sr_1_3?crid=1YM2L4BQG1ZE3&dchild=1&keywords=ttl+to+rs485&qid=1587155212&sprefix=ttl+to+r%2Caps%2C202&sr=8-3
Note: this is the second time I have to repost this response, hopefully it get through this time.
MAX485 should work with 3.3V, not all rs485 drives working correct with 3.3V that’s why I asked.
It looks to me that everything should be fine now, so it’s now out of my scope to debug for you.
The only way now to debug for you is look with a oscilloscope to see what’s going wrong.
Or connect the PZEM to you computer with the supplied software to see if the PZEM is ok.
Hi Evert,
The PZEM is working fine when connect to the computer, I can see the voltage and energy when using with supplied software.
I’m not an electronic expert, don’t know or don’t have scope to troubleshoot the issue. Do you have any other recommendation or how to troubleshoot PZEM to make it run with Blynk?
The code just failed to read PZEM017 🙁
Hi Evert,
Can this “Pzem016 with software uart” to be use with NodeMCU or it has to be use on ESP32?
If yes then will the connect to be the same for NodeMCU just like ESP32?
Regards,
Key
Hi Evert,
I’m trying to use your code “Pzem016 with software uart” along with and NodeMCU for my PZEM-017. I did made some modification on the code:
#define RXD2 13 //Gpio pins SoftwareSerial-NodeMCU D7
#define TXD2 15 //Gpio pins SoftwareSerial-NodeMCU D8
#define MAX485_DE 4 //Gpio pins SoftwareSerial-NodeMCU D2
#define MAX485_RE_NEG 5 //Gpio pins SoftwareSerial-NodeMCU D1
Here is what I get:
Pzem Slave 2: Failed to read modbus
Pzem Slave 1: Failed to read modbus
Pzem Slave 2: Failed to read modbus
Pzem Slave 1: Failed to read modbus
Pzem Slave 2: Failed to read modbus
Pzem Slave 1: Failed to read modbus
Pzem Slave 2: Failed to read modbus
Pzem Slave 1: Failed to read modbus
What else did I missed?
Hi Evert,
I currently have PZEM-004 v3 running with ESP8266 Mini D1 to monitor AC usage. I would like to connect multiple PZEM-004 together with 1 ESP8266, could you please help me to do that?
I have the code and how to instruction for PZEM-003 posted at Blynk forum here: https://community.blynk.cc/t/pzem-004t-v3-0-and-nodemcu-wemos-mini-running-on-blynk-how-to-procedure/39338
Hi,
Sorry, can’t help. The PZEM-004 has a completely other protocol.
Thank you very much!
I’d figured out that I have to modify original Modbus library to make it work with HW serial. I googled it and had found exactly your project!
So, after minor changes and some hardware issues, it works. I used MAX3485 driver – it is built for 3.3V. I also made quick and dirty web server based on library samples, so I disconnected the ESP from USB. It is powered from PZEM itself and I watch the data over Wi-Fi.
Right now I want to continue and move it to ESP8266. Also I think to turn the MCU into RTU/TCP or RTU/MQTT gate.
Great, nice to hear every is working.
How do you send the resetEnergy to reset the pzem’s energy counter?
Hi,
You have to call the function resetEnergy when you want to reset the counters.
Example to reset the counters of unit 3 resetEnergy(3);
Got it. I was trying to call the function within Serial Monitor (doh!). I missed the part that I needed to uncomment the call within setup() then reupload the code. I wish there is a way to call the function without doing that. Will try to figure that out when I got mine working.
Also, I have to say, thank you for documenting your work and patiently answering our questions. I know most of our questions are “duhh” moments but I appreciate you keep responding. 😀
You shouldn’t need the 100 Ohm resistor in the ground line. To prevent ground loops, tie only one end of the ground bus to an actual ground. Either end can be tied to ground. As long as only one end is grounded, you won’t have a ground loop. The ground line ensures that all of the devices on the RS485 bus are referenced to the same voltage. (ideally, 0 Volts) The 100 Ohm resistor will have a voltage drop across it (although the drop will be fairly small) which will cause at least one of the devices to see a different ground reference.
Compiler ERR
ModbusMaster-master/src/ModbusMaster.cpp:99:33: error: ‘read’ is used uninitialized in this function [-Werror=uninitialized]
_u8ResponseBufferLength = read;
^
cc1plus: some warnings being treated as errors
exit status 1
I’ve tried compiling this with two different boards, selecting various ESP32 board types for each.
Arduino IDE 1.8.13
Linux Mint 18.3
I didn’t see any options in the IDE to ignore warnings, Any suggestions?
btw, Using library ModbusMaster-master at version 2.0.1 in folder: /home/beegee/1-Arduino/libraries/ModbusMaster-master
Complier Warnings -> Err:
libraries/ModbusMaster-master/src/ModbusMaster.cpp:99:33: error: ‘read’ is used uninitialized in this function [-Werror=uninitialized]
_u8ResponseBufferLength = read;
^
cc1plus: some warnings being treated as errors
Using library ModbusMaster-master at version 2.0.1 in folder: /home/beegee/1-Arduino/libraries/ModbusMaster-master
exit status 1
Error compiling for board WEMOS LOLIN32
IDE: 1.8.13
Linux Mint 18.3 Sylvia
Solved:
edit line line 90 to be “uint8_t read=0;”
Hi, Evert. Does the code you attach here work for three pzem 016 meters ????
Please helpme with that question.
Yes it works, see the title of this topic.
Hi Evert, thanks for this information.
I am using this PZEM-016 to monitor the PV-system.
As a matter of facts, when sun has gone and no Power will be generated the PV-converter still consumes 4W. Does this PZEM-016 has ability to judge wether the measured power goes in or out?
Thanks very much for your reply.
-Bart
Hi Bart,
The PZEM-016 can measure the current in both direction, however it doesn’t know in what direction the current flows.
The amps are always positive numbers so you don’t know if the current is going in or out.
// PZEM-014/016 AC 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 AC Energy Monitoring Code needs PZEM-014 or PZEM-016 AC Energy Meter to measure values and Arduio Mega / UNO for communication and display.
// Note : This Code monitors AC Voltage, current, Power, Energy, Frequency, and Power Factor.
// 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 : 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-014/016 AC Energy Meter */
#include // Load the (modified) library for modbus communication command codes. Kindly install at our website.
#define MAX485_DE 2 // Define DE Pin to Arduino pin. Connect DE Pin of Max485 converter module to Pin 2 (default) Arduino board
#define MAX485_RE 3 // 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.
ModbusMaster node; /* activate modbus master codes*/
float PZEMVoltage =0; /* Declare value for AC voltage */
float PZEMCurrent =0; /* Declare value for AC current*/
float PZEMPower =0; /* Declare value for AC Power */
float PZEMEnergy=0; /* Declare value for energy */
float PZEMHz =0; /* Declare value for frequency */
float PZEMPf=0; /* Declare value for power factor */
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
void setup()
{
/* General*/
Serial.begin(9600); /* to display readings in Serial Monitor at 9600 baud rates */
/* 1- PZEM-014/016 AC Energy Meter */
startMillisPZEM = millis(); /* Start counting time for run code */
Serial3.begin(9600); // To assign communication port to communicate with meter.
// By default communicate via Serial3 port: pin 14 (Tx) and pin 15 (Rx)
node.begin(pzemSlaveAddr, Serial3); /* 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, 0x01); // 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
resetEnergy(0x01); // By delete the double slash symbol, the Energy value in the meter is reset.
delay(1000); /* after everything done, wait for 1 second */
}
void loop()
{
/* 1- PZEM-014/016 AC Energy Meter */
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, 9); /* read the 9 registers (information) of the PZEM-014 / 016 starting 0x0000 (voltage information) kindly refer to manual)*/
if (result == node.ku8MBSuccess) /* If there is a response */
{
uint32_t tempdouble = 0x00000000; /* Declare variable “tempdouble” as 32 bits with initial value is 0 */
PZEMVoltage = node.getResponseBuffer(0x0000) / 10.0; /* get the 16bit value for the voltage value, divide it by 10 (as per manual) */
// 0x0000 to 0x0008 are the register address of the measurement value
tempdouble = (node.getResponseBuffer(0x0002) << 16) + node.getResponseBuffer(0x0001); /* get the currnet value. Current 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 */
float PZEMCurrent = tempdouble / 1000.00; /* Divide the value by 1000 to get actual current value (as per manual) */
tempdouble = (node.getResponseBuffer(0x0004) << 16) + node.getResponseBuffer(0x0003); /* 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 */
float PZEMPower = tempdouble / 10.0; /* Divide the value by 10 to get actual power value (as per manual) */
tempdouble = (node.getResponseBuffer(0x0006) << 16) + node.getResponseBuffer(0x0005); /* 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 */
float PZEMEnergy = tempdouble;
float PZEMHz = node.getResponseBuffer(0x0007) / 10.0; /* get the 16bit value for the frequency value, divide it by 10 (as per manual) */
float PZEMPf = node.getResponseBuffer(0x0008) / 100.00; /* get the 16bit value for the power factor value, divide it by 100 (as per manual) */
Serial.print(PZEMVoltage, 1); /* Print Voltage value on Serial Monitor with 1 decimal*/
Serial.print("V ");
Serial.print(PZEMHz, 1);
Serial.print("Hz ");
Serial.print(PZEMCurrent, 3);
Serial.print("A ");
Serial.print(PZEMPower, 1);
Serial.print("W ");
Serial.print(PZEMPf, 2);
Serial.print("pf ");
Serial.print(PZEMEnergy, 0);
Serial.print("Wh ");
Serial.println();
if (pzemSlaveAddr==2) /* just for checking purpose to see whether can read modbus*/
{
Serial.println();
}
}
else
{
Serial.println("Failed to read modbus");
}
startMillisPZEM = currentMillisPZEM ; /* Set the starting point again for next counting time */
}
delay(1000);
}
void preTransmission() /* transmission program when triggered*/
{
/* 1- PZEM-014/016 AC Energy Meter */
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*/
{
/* 1- PZEM-014/016 AC Energy Meter */
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 resetEnergy(uint8_t slaveAddr) //Reset the slave's energy counter
{
/* 1- PZEM-014/016 AC Energy Meter */
uint16_t u16CRC = 0xFFFF; /* declare CRC check 16 bits*/
static uint8_t resetCommand = 0x42; /* reset command code*/
u16CRC = crc16_update(u16CRC, slaveAddr);
u16CRC = crc16_update(u16CRC, resetCommand);
Serial.println("Resetting Energy");
preTransmission(); /* trigger transmission mode*/
Serial3.write(slaveAddr); /* send device address in 8 bit*/
Serial3.write(resetCommand); /* send reset command */
Serial3.write(lowByte(u16CRC)); /* send CRC check code low byte (1st part) */
Serial3.write(highByte(u16CRC)); /* send CRC check code high byte (2nd part) */
delay(10);
postTransmission(); /* trigger reception mode*/
delay(100);
while (Serial3.available()) /* while receiving signal from Serial3 from meter and converter */
{
Serial.print(char(Serial3.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
{
/* 1- PZEM-014/016 AC Energy Meter */
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*/
Serial3.write(OldslaveAddr); /* these whole process code sequence refer to manual*/
Serial3.write(SlaveParameter);
Serial3.write(highByte(registerAddress));
Serial3.write(lowByte(registerAddress));
Serial3.write(highByte(NewslaveAddr));
Serial3.write(lowByte(NewslaveAddr));
Serial3.write(lowByte(u16CRC));
Serial3.write(highByte(u16CRC));
delay(1000);
postTransmission(); /* trigger reception mode*/
delay(5000);
while (Serial3.available()) /* while receiving signal from Serial3 from meter and converter */
{
Serial.print(char(Serial3.read()), HEX); /* Prints the response and display on Serial Monitor (Serial)*/
Serial.print(" ");
}
}
Hey I modified you code and used it in a PZEM 016
but I am not able to get the output in the serial monitor
only random numbers numbers HEX address are being shown
Hi,
Random numbers means most of the time wrong baudrate of the terminal or terminal serial port.
What if you do in your main loop
Serial.println("Hello world");
is that readable in the terminal?