Flow Totalizer save to SD Card and recall

Hello all,

I'm slowly learning Arduino, trying different sensors with the aim of combining them into a Reverse Osmosis control system. I'm coding for a Uno but eventually moving to a Mega for more outputs when all sensors and outputs are connected and coded to one Control System.

So far I've managed to set up a flow sensor (YF‐ S201) to count flow and total volume(volume). These are float values. This prints to a 16x2 screen. The float value saves to a SD card in a text file when the total changes.

I've used a data.File.println(volume); or
print(volume); print(",");

to the SD card to save as a line or comma separated within the txt file.

My struggle is how to recall this total in the event of a power cut or restart. I'm thinking i want to take the float value saved to the SD card and add it to setup.

total = some code to recall the SD card info and ensure its readable as a float value this should give a starting point for the sensor to then add on top off.

I have hunted through google for anyone doing something similar to what im doing but cant find anything. Does anyone have an idea of whether this is possible and how?

are you adding at the end of the file or just replacing the content of the file with a new value?

Say the value is constantly added at the end of the file.
The algorithm for retrieving the value would be as you would do yourself.

  • go to the end of the file
  • walk your way back to find the start of the last line (if you used println()) or the last separator (',').
  • read a float from the character after the separator

Hi i can do either add to the end or replace. whichever would have the best outcome. I would like to data log with time all changes but that could be written to a seperate txt file.

I don't know the code to do these steps ... the good news it sound like what im trying to do will work

  • go to the end of the file
  • walk your way back to find the start of the last line (if you used println()) or the last separator (',').
  • read a float from the character after the separator

Thank you for your help

the SD - Arduino Reference offers all what you need to move around the file

  • size() will tell you how long the file is
  • seek() will let you go to a specific position in the file
  • read() will let you read a byte

Hi ,
Post the code writes and reads from the SD that you've done so far.

When posting code, remember to use </> tags.

ArduinoForum

#include <SoftwareSerial.h>
#include <LiquidCrystal_I2C.h>
#include <SPI.h>
#include <SD.h>

SoftwareSerial mySerial(7, 6); // RX, TX
unsigned char data[4] = {};
float distance;
float initialDistance = 120;
float offset;
float surfaceArea = 8525;
float heightOfWater;
float tankLvl; // sensor level in water tank
float tankLvlCheck; // old sensor level value in water tank
bool tankLvlChanged; // yes or no if match


volatile int flow_frequency; // Measures flow sensor pulses
// Calculated litres/hour
float totalizer = 0.0, l_minute;
float totalizerCheck;
bool totalizerChanged;
unsigned char flowsensor = 2; // Sensor Input
const int chipSelect = 4;
unsigned long currentTime;
unsigned long cloopTime;
float frequency = 1.1056;

LiquidCrystal_I2C lcd(0x27, 16, 2); // set the LCD address to 0x27 for a 16 chars and 2 line display
void flow () // Interrupt function
{
  flow_frequency++;
}

void setup()
{
  pinMode(flowsensor, INPUT);
  digitalWrite(flowsensor, HIGH); // Optional Internal Pull-Up
  Serial.begin(57600);
  mySerial.begin(9600);
  lcd.init();
  lcd.backlight();
  attachInterrupt(digitalPinToInterrupt(flowsensor), flow, RISING); // Setup Interrupt
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Wtr Flow/Tnk Lvl");
  lcd.setCursor(0, 1);
  lcd.print("Circuit Digest");
  currentTime = millis();
  cloopTime = currentTime;
  Serial.print("Initializing SD card...");

  // 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:
    while (1);
  }
  Serial.println("card initialized.");
}
void loop()
{
  currentTime = millis();
  // Every second, calculate and print litres/hour
  if (currentTime >= (cloopTime + 1000))
  {
    cloopTime = currentTime; // Updates cloopTime
    if (flow_frequency != 0) {
      // Pulse frequency (Hz) = 7.5Q, Q is flow rate in L/min.
      l_minute = ((flow_frequency / 7.5) * frequency); // (Pulse frequency x 60 min) / 7.5Q = flowrate in L/hour
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Rate");
      lcd.print(l_minute);
      lcd.print(" L/M");
      l_minute = l_minute / 60;
      lcd.setCursor(8, 0);
     totalizer = totalizer + l_minute;
      lcd.print("Vol");
      lcd.print(totalizer);
      lcd.print("L");
      flow_frequency = 0; // Reset Counter
      Serial.print(l_minute, DEC); // Print litres/hour
      Serial.println(" L/Sec");
      lcd.setCursor(0, 1);
      lcd.print(tankLvl);
      lcd.print("Litres");
    }
    else {
      Serial.println(" flow rate = 0 ");
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Rate:");
      lcd.print( flow_frequency );
      lcd.print(" L/M");
      lcd.setCursor(8, 0);
      lcd.print("Vol:");
      lcd.print(totalizer);
      lcd.print("L");
      lcd.setCursor(0, 1);
      lcd.print(tankLvl);
      lcd.print("Litres");
    }
  }


  do {
    for (int i = 0; i < 4; i++)
    {
      data[i] = mySerial.read();
    }
  } while (mySerial.read() == 0xff);

  mySerial.flush();

  if (data[0] == 0xff)
  {
    int sum;
    sum = (data[0] + data[1] + data[2]) & 0x00FF;
    if (sum == data[3])
    {
      distance = (data[1] << 8) + data[2];
      if (distance > 30)
      {
        Serial.print("distance=");
        Serial.print(distance / 10);
        Serial.println("cm");
        distance = (distance / 10) - 3;
        delay(2000);
        Serial.println(distance);
        heightOfWater = initialDistance - distance;
        tankLvl = (heightOfWater * surfaceArea) / 1000;
        Serial.println(heightOfWater);
        delay(2000);
        Serial.print("volume=");
        Serial.println(tankLvl);

        lcd.setCursor(0, 1);
        lcd.print(tankLvl);
        lcd.print("Litres");

      } else
      {
        Serial.println("Below the lower limit");
      }
    } else Serial.println("ERROR");
  }
  {
    delay(100);
  }
  if (tankLvl != tankLvlCheck)
  { tankLvlChanged = true;
    tankLvlCheck = tankLvl;}
    else if (tankLvl == tankLvlCheck){
    tankLvlChanged = false;}
  

  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  File dataFile = SD.open("lvlChng.txt", FILE_WRITE);

  // if the file is available, write to it:
  if (dataFile && tankLvlChanged == true) {
    dataFile.println(tankLvl);
    dataFile.close();
    // print to the serial port too:
    Serial.print("Data Saved");
  }
  else {
    dataFile.close();
  }


if (totalizer != totalizerCheck)
{ totalizerChanged == true;
  totalizerCheck = totalizer;
}
else if (totalizer == totalizerCheck){
    totalizerChanged = false;}
    
// open the file. note that only one file can be open at a time,
// so you have to close this one before opening another.
dataFile = SD.open("flowTot.txt", FILE_WRITE);

// if the file is available, write to it:
if (dataFile && totalizerChanged == true) {
  dataFile.println(totalizer);
  dataFile.close();
  // print to the serial port too:
  Serial.print("Data Saved");
}
else {
  dataFile.close();
}

}

I also need to create functions with the instructions for those functions inside to neaten up and make it easier to read. Something i need to work out as last time i tried it caused the code to not work correctly.

Hi peeps. I'm quite new to this and still struggling to achieve what i set out to do. While I understand the principle behind J-M-L Jacksons advice. I do not know the code i need to implement to float recall from the SD card.

Give it a try and post your best attempt

Ok so far in my attempt i have created a text file that deletes itself and then saves the new data. So it will always contain only the most recent total

  void loop()
SD.remove("TotTemp.txt");

  File dataFileMem = SD.open("TotTemp.txt", FILE_WRITE);
  // if the file is available, write to it:
  if (dataFileMem  == true) {
    dataFileMem.println(totalizer);
    dataFileMem.close();
    // print to the serial port too:
    Serial.print("Tot Temp Data Saved");
  }
  else {
    dataFileMem.close();
  }

My next step is to read that and turn the characters into a float value. I have attempted this by copying some code online and adapting but i dont think its right. this code goes at the end of void setup()

// TEST READ SD FILE TO FLOAT


{ SD.open("TotTemp.txt", FILE_WRITE);
 //dataFileMem.seek(0);
if (dataFileMem.available())
{
  Serial.print("SD reaad and Available");
  char aChar = dataFileMem.read();
  if (aChar != '\n' && aChar != '\r')
  {
    fileContents[index++] = aChar;
    fileContents[index] = '\0'; // NULL terminate the array
  }
  else // the character is CR or LF
  {
    Serial.print("fileContents: [");
    Serial.print(fileContents);
    Serial.println("]");
    if (strlen(fileContents) > 0)
    {
      float aVal = atof(fileContents);
      aVal = totalizer;
      // Do something with aVal
    }

    fileContents[0] = '\0';
    index = 0;
 dataFileMem.close(); 
  }
}

if you want to read the file, it's probably logic to open it with FILE_READ rather than FILE_WRITE

you call SD.open but you don't have a variable initialised to play with the file. Look at the previous code, you were doing File dataFileMem = SD.open("TotTemp.txt", FILE_WRITE); and then you could use dataFileMem to access the file.

assuming dataFileMem is initialized, when you do

if (dataFileMem.available()) {
  Serial.print("SD reaad and Available");
  char aChar = dataFileMem.read();
  if (aChar != '\n' && aChar != '\r')
  {
    fileContents[index++] = aChar;
    fileContents[index] = '\0'; // NULL terminate the array
  }

you only read ONE char from the file. that's not enough to represent your float. You could use a while / for loop to go over each character in file until you reach the end of the line and store them into the fileContents array which then you can indeed (after adding the trailing '\0' as you do) convert to a float. strtod() is better than atof() as it will let you catch errors

Thank you for your input. I had tried the code as was. Tried changing and removing things. forgot to put back

File dataFileMem =

I think I also changed the while to an if somewhere along the line but that's not understanding the code. Thank you for explaining that it runs through until it reaches the end of the line.

Do i need to specifically add the \0 into the write section of code?

ive also just tried changing atof() to strtod() and im getting a new error...

15_4_Ultrasonic___Flow_____SD_save:100:41: error: too few arguments to function 'double strtod(const char*, char**)'
float aVal = strtod(fileContents);
^
In file included from C:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino/Arduino.h:23:0,
from sketch\15_4_Ultrasonic___Flow_____SD_save.ino.cpp:1:
c:\program files (x86)\arduino\hardware\tools\avr\avr\include\stdlib.h:350:15: note: declared here
extern double strtod(const char __nptr, char __endptr);
^~~~~~
exit status 1
too few arguments to function 'double strtod(const char
, char
)'

So after lots of searching online i came across a GitHub by a chap called Norman Dunbar.

GitHub - NormanDunbar/SDFileHandler: Better SD Card File handler for Arduino.

This came with some samples so i worked it out fairly easily. It now recalls my data and might be useful for someone looking to do similar.

I now have to work out some method to stop removing and re writing the file when it powers down so as to reduce the risk of corrupting the SD card.

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