PZEM-004T module: SD write appending "dataString"

Background Info

Uno R3 (w/ DIP-mounted controller)
Arduino IDE 2.3.4
PZEM-004T (v3) sensor module for AC power monitoring
Adafruit RTC + SD-Card shield (RTC is based on the the PCF8523 chip)

Problem

I began with the LCD-display code supplied from Miliohm (sic) guide to the PZEM-004T, which relies on PZEM004Tv30.h in the PZEM004Tv30 library of Jakub Mandula.

I then modified the hardware, adding an Adafruit RTC clock + SD-Card shield. Working methodically, step-wise, I proved that adding the shield did not cause interference with the original code.

I then modified that code from the Miliohm tutorial: I added RTC functionality, and added the Serial.print (but not lcd.print, because the display is always "real time") so that it spits out the real time as Unixtime each time the PZEM module is queried. The serial output looks like this:

Unixtime: 1740396098
Voltage: 121.20V
Current: 0.00A
Power: 0.00W
Energy: 0.000kWh
Frequency: 60.0Hz
Φ: 0.00

I then modified that code, so that I can write the data to an SD card.
I used dataString so that I can write the data in CSV format to the SD card.
I was able to create a dataString that includes all of the named values generated within PZEM004Tv30.h. As things stand now, the Sketch happily writes those values to the SD card, and I can then read them within Excel (etc etc)
However...
For the life of me, I cannot figure out how to add the value of Unixtime to the dataString. Intuitively, this would seem to be a trival matter, but clearly I am not playing with a full deck of cards...
Here is the full code

#include <PZEM004Tv30.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "RTClib.h"
#include <SD.h>


const int chipSelect = 10;
float mark;
RTC_PCF8523 rtc;
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
LiquidCrystal_I2C lcd(0x27, 16, 2);
PZEM004Tv30 pzem(8, 9); // Software Serial pin 8 (RX) & 9 (TX)

String dataString =""; // holds the data to be written to the SD card

File pzemData;
//
//
void setup(){
// Open serial communications
Serial.begin(9600);
Serial.print("Initializing SD card...");
pinMode(chipSelect, OUTPUT);
//
// see if the card is present and can be initialized:
if (!SD.begin(chipSelect)) {
Serial.println("Card failed, or not present");
// don't do anything more:
return;
}
Serial.println("card initialized.");


  // define RTclock functions and set clock, lines 22-78 inclusive
    if (! rtc.begin()) {
        Serial.println("Couldn't find RTC");
        Serial.flush();
        while (1) delay(10);
      }

      if (! rtc.initialized() || rtc.lostPower()) {
        Serial.println("RTC is NOT initialized, let's set the time!");
        // When time needs to be set on a new device, or after a power loss, the
        // following line sets the RTC to the date & time this sketch was compiled
        rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
        // This line sets the RTC with an explicit date & time, for example to set
        // January 21, 2014 at 3am you would call:
        // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
        //
        // Note: allow 2 seconds after inserting battery or applying external power
        // without battery before calling adjust(). This gives the PCF8523's
        // crystal oscillator time to stabilize. If you call adjust() very quickly
        // after the RTC is powered, lostPower() may still return true.
      }

      // When time needs to be re-set on a previously configured device, the
      // following line sets the RTC to the date & time this sketch was compiled
      // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
      // This line sets the RTC with an explicit date & time, for example to set
      // January 21, 2014 at 3am you would call:
      // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));

      // When the RTC was stopped and stays connected to the battery, it has
      // to be restarted by clearing the STOP bit. Let's do this to ensure
      // the RTC is running.
      rtc.start();

      // The PCF8523 can be calibrated for:
      //        - Aging adjustment
      //        - Temperature compensation
      //        - Accuracy tuning
      // The offset mode to use, once every two hours or once every minute.
      // The offset Offset value from -64 to +63. See the Application Note for calculation of offset values.
      // https://www.nxp.com/docs/en/application-note/AN11247.pdf
      // The deviation in parts per million can be calculated over a period of observation. Both the drift (which can be negative)
      // and the observation period must be in seconds. For accuracy the variation should be observed over about 1 week.
      // Note: any previous calibration should cancelled prior to any new observation period.
      // Example - RTC gaining 43 seconds in 1 week
      float drift = 43; // seconds plus or minus over oservation period - set to 0 to cancel previous calibration.
      float period_sec = (7 * 86400);  // total obsevation period in seconds (86400 = seconds in 1 day:  7 days = (7 * 86400) seconds )
      float deviation_ppm = (drift / period_sec * 1000000); //  deviation in parts per million (μs)
      float drift_unit = 4.34; // use with offset mode PCF8523_TwoHours
      // float drift_unit = 4.069; //For corrections every min the drift_unit is 4.069 ppm (use with offset mode PCF8523_OneMinute)
      int offset = round(deviation_ppm / drift_unit);
      // rtc.calibrate(PCF8523_TwoHours, offset); // Un-comment to perform calibration once drift (seconds) and observation period (seconds) are correct
      // rtc.calibrate(PCF8523_TwoHours, 0); // Un-comment to cancel previous calibration

      Serial.print("Offset is "); Serial.println(offset); // Print to control offset
  //end setting of RTclock functions


  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("PZEM Test");
  lcd.setCursor(0, 1);
  lcd.print("by Miliohm.com");
  delay(2000);
  lcd.clear();
}

void loop() {
DateTime now = rtc.now();
  Serial.print("Unixtime: ");
  Serial.print(now.unixtime());

  Serial.println();
  float voltage = pzem.voltage();
  if (voltage != NAN) {
    Serial.print("Voltage: ");
    Serial.print(voltage);
    Serial.println("V");
    lcd.setCursor(0, 0);
    lcd.print("V");
    lcd.print(voltage);
  } else {
    Serial.println("Error reading voltage");
  }
  float current = pzem.current();
  if (current != NAN) {
    Serial.print("Current: ");
    Serial.print(current);
    Serial.println("A");
    lcd.setCursor(0, 1);
    lcd.print("A");
    lcd.print(current);
  } else {
    Serial.println("Error reading current");
  }
  float power = pzem.power();
  if (current != NAN) {
    Serial.print("Power: ");
    Serial.print(power);
    Serial.println("W");
    lcd.setCursor(6, 0);
    lcd.print("W");
    lcd.print(power);
  } else {
    Serial.println("Error reading power");
  }
  float energy = pzem.energy();
  if (current != NAN) {
    Serial.print("Energy: ");
    Serial.print(energy, 3);
    Serial.println("kWh");
    lcd.setCursor(4, 1);
    lcd.print("kWh");
    lcd.print(energy);
  } else {
    Serial.println("Error reading energy");
  }
  float frequency = pzem.frequency();
  if (current != NAN) {
    Serial.print("Frequency: ");
    Serial.print(frequency, 1);
    Serial.println("Hz");
    lcd.setCursor(11, 0);
    lcd.print("f");
    lcd.print(frequency);
  } else {
    Serial.println("Error reading frequency");
  }
  float pf = pzem.pf();
  if (current != NAN) {
    Serial.print("Φ: ");
    Serial.println(pf);
    lcd.setCursor(11,1);
    lcd.print("pF");
    lcd.print(pf);
  } else {
    Serial.println("Error reading power factor");
  }
  Serial.println();

// SD Card writing based on https://rydepier.wordpress.com/2015/08/07/using-an-sd-card-reader-to-store-and-retrieve-data-with-arduino/1740349303Conv
// build the data string
    dataString = String(voltage) + "," + String(current) + "," + String(power) + "," + String(frequency) + "," + String(pf); // convert to CSV
    //saveData(); // save to SD card
    delay(2000); // delay before next write to SD Card, adjust as required
    }
    //
    void saveData(){
    if(SD.exists("data.csv")){              // check the card is still there!
    // now, let's append new data file!
    pzemData = SD.open("data.csv", FILE_WRITE);
    if (pzemData){
    pzemData.println(dataString);
    pzemData.close(); // close the file

    }
    else{
    Serial.println("Error writing to file !");
    }
    }

delay(2000);
}

Any & all helpful suggestions would appreciated.

Why not add it to the string in the same way as the other components?
Add ",",String(unixtime) Before sending to the SD card.

I tried that previously, in many different variations. Every time I do that, I get the error message

Compilation error: 'unixtime' was not declared in this scope

I am unsure of how to interpret "not declared in this scope".
I have tried to define a variable mark=now.unixtime() , and then use + String(mark) + but I keep getting the same error message of ...not decared in this scope
I am missing something fundamental in setting this up. I am in the "fools jump in" stage, after having jumped through the "beginners' luck" stage.

I should note that all of the variables listed in the dataString file are, in fact, defined within PZEM004Tv30.h, not within the Sketch.

I don't want to start mucking around with PZEM004Tv30.h, for obvious reasons. So the question is How do I alter the Sketch so that there is some variable that I can list in dataString which is, in actuality, "Unixtime" ?

That means that the symbol or name was not declared in the function or code block where it is referenced. Variable scope is an extremely important concept in C/C++ programming. It is essential that you read up on the topic and understand it.

For help with compiler error messages, post the code in question, using code tags, and the complete error message, in quotes.

Thanks.
I have uploaded the current version of my code, without ANY attempt to add a date-and-time stamp to the definition of dataString = . The current status of the code is...

  • The code compiles and runs
  • The LCD display performs as expected
  • The SD card is written with the actual values of voltage, current, power...etc, as those are variables are defined in PZEM004Tv30.h

However... my attempts to add any other data to the definition of dataString simply fails.

I have tried to add fixed-value integers, float values, and even text strings to dataString but the compiler fails, always in the same way.

I don't understand whether the failures result because those variables that are successful are defined in the background ⸺ i.e., the *.h portion of the program ⸺ or, whether it is something entirely different. There seems to be controversy in the Forum as to whether to even bother with String / dataString. If I am hitting my head against another brick wall, just say so

#include <PZEM004Tv30.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "RTClib.h"
#include <SD.h>


const int chipSelect = 10;
RTC_PCF8523 rtc;
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
LiquidCrystal_I2C lcd(0x27, 16, 2);
PZEM004Tv30 pzem(8, 9); // Software Serial pin 8 (RX) & 9 (TX)

String dataString =""; // holds the data to be written to the SD card

File pzemData;
//
//
void setup(){
// Open serial communications
Serial.begin(9600);
Serial.print("Initializing SD card...");
pinMode(chipSelect, OUTPUT);
//
// see if the card is present and can be initialized:
if (!SD.begin(chipSelect)) {
Serial.println("Card failed, or not present");
// don't do anything more:
return;
}
Serial.println("card initialized.");


  // define RTclock functions and set clock, lines 22-78 inclusive
    if (! rtc.begin()) {
        Serial.println("Couldn't find RTC");
        Serial.flush();
        while (1) delay(10);
      }

      if (! rtc.initialized() || rtc.lostPower()) {
        Serial.println("RTC is NOT initialized, let's set the time!");
        // When time needs to be set on a new device, or after a power loss, the
        // following line sets the RTC to the date & time this sketch was compiled
        rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
        // This line sets the RTC with an explicit date & time, for example to set
        // January 21, 2014 at 3am you would call:
        // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
        //
        // Note: allow 2 seconds after inserting battery or applying external power
        // without battery before calling adjust(). This gives the PCF8523's
        // crystal oscillator time to stabilize. If you call adjust() very quickly
        // after the RTC is powered, lostPower() may still return true.
      }

      // When time needs to be re-set on a previously configured device, the
      // following line sets the RTC to the date & time this sketch was compiled
      // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
      // This line sets the RTC with an explicit date & time, for example to set
      // January 21, 2014 at 3am you would call:
      // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));

      // When the RTC was stopped and stays connected to the battery, it has
      // to be restarted by clearing the STOP bit. Let's do this to ensure
      // the RTC is running.
      rtc.start();

      // The PCF8523 can be calibrated for:
      //        - Aging adjustment
      //        - Temperature compensation
      //        - Accuracy tuning
      // The offset mode to use, once every two hours or once every minute.
      // The offset Offset value from -64 to +63. See the Application Note for calculation of offset values.
      // https://www.nxp.com/docs/en/application-note/AN11247.pdf
      // The deviation in parts per million can be calculated over a period of observation. Both the drift (which can be negative)
      // and the observation period must be in seconds. For accuracy the variation should be observed over about 1 week.
      // Note: any previous calibration should cancelled prior to any new observation period.
      // Example - RTC gaining 43 seconds in 1 week
      float drift = 43; // seconds plus or minus over oservation period - set to 0 to cancel previous calibration.
      float period_sec = (7 * 86400);  // total obsevation period in seconds (86400 = seconds in 1 day:  7 days = (7 * 86400) seconds )
      float deviation_ppm = (drift / period_sec * 1000000); //  deviation in parts per million (μs)
      float drift_unit = 4.34; // use with offset mode PCF8523_TwoHours
      // float drift_unit = 4.069; //For corrections every min the drift_unit is 4.069 ppm (use with offset mode PCF8523_OneMinute)
      int offset = round(deviation_ppm / drift_unit);
      // rtc.calibrate(PCF8523_TwoHours, offset); // Un-comment to perform calibration once drift (seconds) and observation period (seconds) are correct
      // rtc.calibrate(PCF8523_TwoHours, 0); // Un-comment to cancel previous calibration

      Serial.print("Offset is "); Serial.println(offset); // Print to control offset
  //end setting of RTclock functions


  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("PZEM Test");
  lcd.setCursor(0, 1);
  lcd.print("by Miliohm.com");
  delay(2000);
  lcd.clear();
}

void loop() {
DateTime now = rtc.now();
  Serial.print("Unixtime: ");
  Serial.print(now.unixtime());

  Serial.println();
  float voltage = pzem.voltage();
  if (voltage != NAN) {
    Serial.print("Voltage: ");
    Serial.print(voltage);
    Serial.println("V");
    lcd.setCursor(0, 0);
    lcd.print("V");
    lcd.print(voltage);
  } else {
    Serial.println("Error reading voltage");
  }
  float current = pzem.current();
  if (current != NAN) {
    Serial.print("Current: ");
    Serial.print(current);
    Serial.println("A");
    lcd.setCursor(0, 1);
    lcd.print("A");
    lcd.print(current);
  } else {
    Serial.println("Error reading current");
  }
  float power = pzem.power();
  if (current != NAN) {
    Serial.print("Power: ");
    Serial.print(power);
    Serial.println("W");
    lcd.setCursor(6, 0);
    lcd.print("W");
    lcd.print(power);
  } else {
    Serial.println("Error reading power");
  }
  float energy = pzem.energy();
  if (current != NAN) {
    Serial.print("Energy: ");
    Serial.print(energy, 3);
    Serial.println("kWh");
    lcd.setCursor(4, 1);
    lcd.print("kWh");
    lcd.print(energy);
  } else {
    Serial.println("Error reading energy");
  }
  float frequency = pzem.frequency();
  if (current != NAN) {
    Serial.print("Frequency: ");
    Serial.print(frequency, 1);
    Serial.println("Hz");
    lcd.setCursor(11, 0);
    lcd.print("f");
    lcd.print(frequency);
  } else {
    Serial.println("Error reading frequency");
  }
  float pf = pzem.pf();
  if (current != NAN) {
    Serial.print("Φ: ");
    Serial.println(pf);
    lcd.setCursor(11,1);
    lcd.print("pF");
    lcd.print(pf);
  } else {
    Serial.println("Error reading power factor");
  }
  Serial.println();

// SD Card writing based on https://rydepier.wordpress.com/2015/08/07/using-an-sd-card-reader-to-store-and-retrieve-data-with-arduino/1740349303Conv
// build the data string
    dataString = String(voltage) + "," + String(current) + "," + String(power) + "," + String(frequency) + "," + String(pf); // convert to CSV
    //saveData(); // save to SD card
    delay(2000); // delay before next write to SD Card, adjust as required
    }
    //
    void saveData(){
    if(SD.exists("data.csv")){              // check the card is still there!
    // now, let's append new data file!
    pzemData = SD.open("data.csv", FILE_WRITE);
    if (pzemData){
    pzemData.println(dataString);
    pzemData.close(); // close the file

    }
    else{
    Serial.println("Error writing to file !");
    }
    }

delay(2000);
}

If you can't bring yourself to post the code that leads to an error message, then yes, you are wasting your time on this forum.

On the web there are countless examples of code that logs data to SD cards, including with time stamps.

Most forum members recommend to avoid using Strings, as they are never necessary and lead to numerous problems on MCUs with small amounts of memory.

So I misunderstood what you were asking for. You want me to give you an example of at least one version of my code, with at least one error. I am not embarrassed by making and sharing my errors, it is just that the all the errors I have concocted seem to throw the same error message OR they throw no error message at all. In the latter case (i.e., no compilation or run-time messages), the Sketch fails to write anything at all to the SD card.

To my mind, that suggests that there is something fragile about what I have constructed (which actually does most of what I want it to do, but cannot be tweaked to add anything to the data.String other than the variables defined / generated within PZEM004Tv30.h)

Here is some code that is "exactly" the same as the code posted in the prior Post, except that it includes an integer variable mark which takes the value defined by the current Unixtime. The variable mark is added to the items of data.String. Once that extra element has been added to data.String, no values are written to SD at all. And yet, no error messages are generated during compilation, nor are any error messages generated at run-time.

#include <PZEM004Tv30.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "RTClib.h"
#include <SD.h>


const int chipSelect = 10;
int mark;
RTC_PCF8523 rtc;
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
LiquidCrystal_I2C lcd(0x27, 16, 2);
PZEM004Tv30 pzem(8, 9); // Software Serial pin 8 (RX) & 9 (TX)

String dataString =""; // holds the data to be written to the SD card

File pzemData;
//
//
void setup(){
// Open serial communications
Serial.begin(9600);
Serial.print("Initializing SD card...");
pinMode(chipSelect, OUTPUT);
//
// see if the card is present and can be initialized:
if (!SD.begin(chipSelect)) {
Serial.println("Card failed, or not present");
// don't do anything more:
return;
}
Serial.println("card initialized.");


  // define RTclock functions and set clock, lines 22-78 inclusive
    if (! rtc.begin()) {
        Serial.println("Couldn't find RTC");
        Serial.flush();
        while (1) delay(10);
      }

      if (! rtc.initialized() || rtc.lostPower()) {
        Serial.println("RTC is NOT initialized, let's set the time!");
        // When time needs to be set on a new device, or after a power loss, the
        // following line sets the RTC to the date & time this sketch was compiled
        rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
        // This line sets the RTC with an explicit date & time, for example to set
        // January 21, 2014 at 3am you would call:
        // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
        //
        // Note: allow 2 seconds after inserting battery or applying external power
        // without battery before calling adjust(). This gives the PCF8523's
        // crystal oscillator time to stabilize. If you call adjust() very quickly
        // after the RTC is powered, lostPower() may still return true.
      }

      // When time needs to be re-set on a previously configured device, the
      // following line sets the RTC to the date & time this sketch was compiled
      // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
      // This line sets the RTC with an explicit date & time, for example to set
      // January 21, 2014 at 3am you would call:
      // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));

      // When the RTC was stopped and stays connected to the battery, it has
      // to be restarted by clearing the STOP bit. Let's do this to ensure
      // the RTC is running.
      rtc.start();

      // The PCF8523 can be calibrated for:
      //        - Aging adjustment
      //        - Temperature compensation
      //        - Accuracy tuning
      // The offset mode to use, once every two hours or once every minute.
      // The offset Offset value from -64 to +63. See the Application Note for calculation of offset values.
      // https://www.nxp.com/docs/en/application-note/AN11247.pdf
      // The deviation in parts per million can be calculated over a period of observation. Both the drift (which can be negative)
      // and the observation period must be in seconds. For accuracy the variation should be observed over about 1 week.
      // Note: any previous calibration should cancelled prior to any new observation period.
      // Example - RTC gaining 43 seconds in 1 week
      float drift = 43; // seconds plus or minus over oservation period - set to 0 to cancel previous calibration.
      float period_sec = (7 * 86400);  // total obsevation period in seconds (86400 = seconds in 1 day:  7 days = (7 * 86400) seconds )
      float deviation_ppm = (drift / period_sec * 1000000); //  deviation in parts per million (μs)
      float drift_unit = 4.34; // use with offset mode PCF8523_TwoHours
      // float drift_unit = 4.069; //For corrections every min the drift_unit is 4.069 ppm (use with offset mode PCF8523_OneMinute)
      int offset = round(deviation_ppm / drift_unit);
      // rtc.calibrate(PCF8523_TwoHours, offset); // Un-comment to perform calibration once drift (seconds) and observation period (seconds) are correct
      // rtc.calibrate(PCF8523_TwoHours, 0); // Un-comment to cancel previous calibration

      Serial.print("Offset is "); Serial.println(offset); // Print to control offset
  //end setting of RTclock functions


  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("PZEM Test");
  lcd.setCursor(0, 1);
  lcd.print("by Miliohm.com");
  delay(2000);
  lcd.clear();
}

void loop() {
DateTime now = rtc.now();
  Serial.print("Unixtime: ");
  mark = (now.unixtime());
  Serial.print(now.unixtime());

  Serial.println();
  float voltage = pzem.voltage();
  if (voltage != NAN) {
    Serial.print("Voltage: ");
    Serial.print(voltage);
    Serial.println("V");
    lcd.setCursor(0, 0);
    lcd.print("V");
    lcd.print(voltage);
  } else {
    Serial.println("Error reading voltage");
  }
  float current = pzem.current();
  if (current != NAN) {
    Serial.print("Current: ");
    Serial.print(current);
    Serial.println("A");
    lcd.setCursor(0, 1);
    lcd.print("A");
    lcd.print(current);
  } else {
    Serial.println("Error reading current");
  }
  float power = pzem.power();
  if (current != NAN) {
    Serial.print("Power: ");
    Serial.print(power);
    Serial.println("W");
    lcd.setCursor(6, 0);
    lcd.print("W");
    lcd.print(power);
  } else {
    Serial.println("Error reading power");
  }
  float energy = pzem.energy();
  if (current != NAN) {
    Serial.print("Energy: ");
    Serial.print(energy, 3);
    Serial.println("kWh");
    lcd.setCursor(4, 1);
    lcd.print("kWh");
    lcd.print(energy);
  } else {
    Serial.println("Error reading energy");
  }
  float frequency = pzem.frequency();
  if (current != NAN) {
    Serial.print("Frequency: ");
    Serial.print(frequency, 1);
    Serial.println("Hz");
    lcd.setCursor(11, 0);
    lcd.print("f");
    lcd.print(frequency);
  } else {
    Serial.println("Error reading frequency");
  }
  float pf = pzem.pf();
  if (current != NAN) {
    Serial.print("Φ: ");
    Serial.println(pf);
    lcd.setCursor(11,1);
    lcd.print("pF");
    lcd.print(pf);
  } else {
    Serial.println("Error reading power factor");
  }
  Serial.println();

// SD Card writing based on https://rydepier.wordpress.com/2015/08/07/using-an-sd-card-reader-to-store-and-retrieve-data-with-arduino/1740349303Conv
// build the data string
    dataString = String(mark) + "," + String(voltage) + "," + String(current) + "," + String(power) + "," + String(frequency) + "," + String(pf); // convert to CSV
    //saveData(); // save to SD card
    delay(2000); // delay before next write to SD Card, adjust as required
    }
    //
    void saveData(){
    if(SD.exists("data.csv")){              // check the card is still there!
    // now, let's append new data file!
    pzemData = SD.open("data.csv", FILE_WRITE);
    if (pzemData){
    pzemData.println(dataString);
    pzemData.close(); // close the file

    }
    else{
    Serial.println("Error writing to file !");
    }
    }

delay(2000);
}

Properly formatting helps in all but the most pathological cases

  // build the data string
  dataString = String(mark) + "," + String(voltage) + "," + String(current) + "," + String(power) + "," + String(frequency) + "," + String(pf);  // convert to CSV
  //saveData(); // save to SD card
  delay(2000);  // delay before next write to SD Card, adjust as required
}
//
void saveData() {
  if (SD.exists("data.csv")) {  // check the card is still there!

The call to saveData is commented out (in both the "before" and "after").

If that blunder is not the actual problem, then I'd suggest adding a Serial.println message for when it does save data

    if (pzemData) {
      Serial.print("adding: "); Serial.println(dataString);
      pzemData.println(dataString);

There's also no message if "data.csv" is missing. Work your way backward to have messages at every path to see where the code is going.

Thanks for the helpful comments. I restored saveData by removing the comment-out. I still had problems with adding a date-and-time-stamp variable to the dataString declaration. Taking into consideration the comments about 'strings', generally, as made by jremington and others on the Forum, I decided to double-down and simply change the code to NOT use strings at all.
I can now report that my problems, so far, have been resolved. Here is the code that I am now using, and it runs without any hiccups:

/*
 * RTC and SD code was created by ArduinoGetStarted.com
 *
 * The code dealing with the RTC and SD card is in the public domain. See the ArduinoGetStarted tutorial at:
 *
 * https://arduinogetstarted.com/tutorials/arduino-log-data-with-timestamp-to-sd-card
 *
 * The code for PZEM-004T data retrieval, serial-write, and LCD-screen writing originates within Miliohm's tutorial, see:
 * https://miliohm.com/pzem-sensor-tutorial-read-current-voltage-power-factor-power-frequency-on-mains/
 *
*/

#include <PZEM004Tv30.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "RTClib.h"
#include <SD.h>

#define FILE_NAME "data.csv"

const int chipSelect = 10;
RTC_PCF8523 rtc;
char daysOfTheWeek[7][12] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
LiquidCrystal_I2C lcd(0x27, 16, 2);
PZEM004Tv30 pzem(8, 9);  // Software Serial pin 8 (RX) & 9 (TX)
String dataString = "";  // holds the data to be written to the SD card
File pzemData;
//
//
void setup() {
  // Open serial communications
  Serial.begin(9600);
  if (!rtc.begin()) {
    Serial.println(F("Couldn't find RTC"));
    while (1)
      ;
  }

  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("PZEM-004T v3.0");
  lcd.setCursor(0, 1);
  lcd.print("SD-logger w/RTC");
  delay(2000);
  lcd.clear();

  if (!SD.begin(chipSelect)) {
    Serial.println(F("SD CARD FAILED, OR NOT PRESENT!"));
    lcd.print("SD Card Failure!");
    while (1)
      ;  // don't do anything more:
  }

  Serial.println(F("SD CARD INITIALIZED."));
  Serial.println(F("--------------------")); //prints row of dashes, to delineate text
}

void loop() {
  DateTime now = rtc.now();
  Serial.print("Unixtime: ");
  Serial.print(now.unixtime());

  Serial.println();
  float voltage = pzem.voltage();
  if (voltage != NAN) {
    Serial.print("Voltage: ");
    Serial.print(voltage);
    Serial.println("V");
    lcd.setCursor(0, 0);
    lcd.print("V");
    lcd.print(voltage);
  } else {
    Serial.println("Error reading voltage");
  }
  float current = pzem.current();
  if (current != NAN) {
    Serial.print("Current: ");
    Serial.print(current);
    Serial.println("A");
    lcd.setCursor(0, 1);
    lcd.print("A");
    lcd.print(current);
  } else {
    Serial.println("Error reading current");
  }
  float power = pzem.power();
  if (current != NAN) {
    Serial.print("Power: ");
    Serial.print(power);
    Serial.println("W");
    lcd.setCursor(6, 0);
    lcd.print("W");
    lcd.print(power);
  } else {
    Serial.println("Error reading power");
  }
  float energy = pzem.energy();
  if (current != NAN) {
    Serial.print("Energy: ");
    Serial.print(energy, 3);
    Serial.println("kWh");
    lcd.setCursor(4, 1);
    lcd.print("kWh");
    lcd.print(energy);
  } else {
    Serial.println("Error reading energy");
  }
  float frequency = pzem.frequency();
  if (current != NAN) {
    Serial.print("Frequency: ");
    Serial.print(frequency, 1);
    Serial.println("Hz");
    lcd.setCursor(11, 0);
    lcd.print("f");
    lcd.print(frequency);
  } else {
    Serial.println("Error reading frequency");
  }
  float pf = pzem.pf();
  if (current != NAN) {
    Serial.print("Φ: ");
    Serial.println(pf);
    lcd.setCursor(11, 1);
    lcd.print("pF");
    lcd.print(pf);
  } else {
    Serial.println("Error reading power factor");
  }
  //Serial.println();

  /// open file for writing
  pzemData = SD.open(FILE_NAME, FILE_WRITE);

  if (pzemData) {
    Serial.println(F("(Data logged to SD Card)"));
    Serial.println(F("*****"));

    // write timestamp
    DateTime now = rtc.now();
    pzemData.print(now.unixtime());
    pzemData.print(",");  // delimiter between timestamp and data
    // Optional time-stamp data formats include...
      // pzemData.print(now.year(), DEC);
      // pzemData.print('-');
      // pzemData.print(now.month(), DEC);
      // pzemData.print('-');
      // pzemData.print(now.day(), DEC);
      // pzemData.print(' ');
      // pzemData.print(now.hour(), DEC);
      // pzemData.print(':');
      // pzemData.print(now.minute(), DEC);
      // pzemData.print(':');
      // pzemData.print(now.second(), DEC);

    // read data.....if necessary to do this from within the sketch....retained here for future reference!
      // int analog_1 = analogRead(A0);
      // int analog_2 = analogRead(A1);

    // write data
    //pzemData.print("analog_1 = "); // this example retained here for future reference
    pzemData.print(voltage);
    pzemData.print(",");  // delimiter between data
    pzemData.print(current);
    pzemData.print(",");
    pzemData.print(power);
    pzemData.print(",");
    pzemData.print(energy);
    pzemData.print(",");
    pzemData.print(frequency);
    pzemData.print(",");
    pzemData.print(pf);    // this is the column of data written to the SD card in CSV format
    pzemData.write("\n");  // jump to a new line, to prepare for the next WRITE operation

    pzemData.close();
  } else {
    Serial.print(F("SD Card: error on opening file "));
    Serial.println(FILE_NAME);
  }

  delay(5000);  // delay 5 seconds
}

I have left in a lot of optional lines in the Code "for future reference", as I will probably refer back to this code when creating some future code for other purposes.

Thanks to all who have helped out on my problem.
I hope that there are some who can make use of my datalogger code for the PZEM-004T (v3.0)

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.