Creating a start-stop iterative loop with the character entered from the Arduino serial monitor

Hi guys, I'm trying to make a data logger. I've managed to partially make a datalogger now but my problem is that I can't start and end the recording based on a character read from the serial monitor. Any character is ok but I can't figure out how to assign it to a specific character. When the character "A" is entered, it will start recording and when the character "B" is entered, it will end the recording, but after the recording is finished, I also want the loop to return to the beginning so that the recording can be started again.
I'm trying to create a project, but after coming to a certain place, I was left without a solution, I'm still trying to read and solve the problem, if anyone can help me, I would be very happy.
I'm sorry I added the code sample as below because I'm a new member.

Every opinion given is respected.

/*
   Simple data logger.
*/
#include <SPI.h>
#include "SdFat.h"
#include "RTClib.h"
#include "HX711.h"//XFW-HX711 amplifier 80Hz


#define calibration_factor -7090.0 //This value is obtained using the SparkFun_HX711_Calibration sketch

int LOADCELL_DOUT_PIN = 7;
int LOADCELL_SCK_PIN = 6;

int LOADCELL_DOUT_PIN_1 = 5;
int LOADCELL_SCK_PIN_1 = 4;

int LOADCELL_DOUT_PIN_2 = 3;
int LOADCELL_SCK_PIN_2 = 2;
HX711 scale;
HX711 scale1;
HX711 scale2;



RTC_DS1307 RTC;//using RTClib
DateTime now;



const uint8_t chipSelect = 10;// SD chip select pin.  Be sure to disable any other SPI devices such as Enet.

// Interval between data records in milliseconds.
// The interval must be greater than the maximum SD write latency plus the
// time to acquire and write data to the SD to avoid overrun errors.
// Run the bench example to check the quality of your SD card.

const uint32_t SAMPLE_INTERVAL_MS = 1;//time

// Log file base name.  Must be six characters or less

#define FILE_BASE_NAME "LOGGG"
//------------------------------------------------------------------------------
// File system object.=

SdFat sd;

SdFile file;// Log file.

uint32_t logTime;

//==============================================================================
// User functions.  Edit writeHeader() and logData() for your requirements.=


int Date_count = 1;
int Time_count = 1;
int Load_Cell1 = 1;
int Load_Cell2 = 1;
int Load_Cell3 = 1;
//------------------------------------------------------------------------------

void load_cell () {

  Serial.print("Reading: ");
  Serial.print(scale.get_units(), 1); //scale.get_units() returns a float
  // Serial.print(" kg"); //You can change this to kg but you'll need to refactor the calibration_factor
  Serial.print("\t");
  Serial.print(scale1.get_units(), 1);
  Serial.print("\t");
  Serial.print(scale2.get_units(), 1);
  Serial.print(" kg");
  Serial.println();

}
void writeHeader() {

  file.print(F("Time(us)"));
  for (int c = 0; c < Load_Cell1; c++) {
    file.print(F(",lCell"));
    //file.print(c, DEC);
  } for (int d = 0; d < Load_Cell2; d++) {
    file.print(F(",2Cell"));
    //file.print(d, DEC);
  } for (int e = 0; e < Load_Cell3; e++) {
    file.print(F(",3Cell"));
    //file.print(e, DEC);
  }
  for (int a = 0; a < Date_count; a++) {
    now = RTC.now();
    file.print(F(",date:"));
    file.print(now.day(), DEC);
    file.print('/');
    file.print(now.month(), DEC);
    file.print('/');
    file.print(now.year(), DEC);

  } for (int b = 0; b < Time_count; b++) {
    file.print(F(",Time(second)"));

  }
  file.println();
}
//------------------------------------------------------------------------------
// Log a data record.

void logData() {
  // Read all channels to avoid SD write latency between readings.

  file.print(logTime);// Write ADC data to CSV record.
  for (int c = 0; c < Load_Cell1; c++) {
    file.write(',');
    file.print(scale.get_units());
  } for (int d = 0; d < Load_Cell2; d++) {
    file.write(',');
    file.print(scale1.get_units());
  } for (int e = 0; e < Load_Cell3; e++) {
    file.write(',');
    file.print(scale2.get_units());//file.print(scale2.get_units(),DEC);
  }
  for (int a = 0; a < Date_count; a++) {
    file.write(',');
    //file.print("test");
  } for (int b = 0; b < Time_count; b++) {
    file.write(',');
    file.print(now.hour(), DEC);
    file.print(":");
    file.print(now.minute(), DEC);
    file.print(":");
    file.print(now.second(), DEC);
    file.print(":");
    file.print(micros(), DEC);
  }
  file.println();

}
//==============================================================================
// Error messages stored in flash.

#define error(msg) sd.errorHalt(F(msg))
//------------------------------------------------------------------------------
void setup() {
  Serial.begin(9600);// Wait for USB Serial


  scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
  scale1.begin(LOADCELL_DOUT_PIN_1, LOADCELL_SCK_PIN_1);
  scale2.begin(LOADCELL_DOUT_PIN_2, LOADCELL_SCK_PIN_2);

  scale.set_scale(calibration_factor); //This value is obtained by using the SparkFun_HX711_Calibration sketch
  scale.tare(); //Assuming there is no weight on the scale at start up, reset the scale to 0

  scale1.set_scale(calibration_factor); //This value is obtained by using the SparkFun_HX711_Calibration sketch
  scale1.tare(); //Assuming there is no weight on the scale at start up, reset the scale to 0

  scale2.set_scale(calibration_factor); //This value is obtained by using the SparkFun_HX711_Calibration sketch
  scale2.tare(); //Assuming there is no weight on the scale at start up, reset the scale to 0


  RTC.begin();
  const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;
  char fileName[13] = FILE_BASE_NAME "00.csv";

  while (!Serial) {
    SysCall::yield();
  }
  delay(100);

  Serial.println(F("enter any character to start"));
  while (!Serial.available()) {
    load_cell ();
    SysCall::yield();
  }

  // Initialize at the highest speed supported by the board that is.  not over 50 MHz. Try a lower speed if SPI errors occur.
  if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
    sd.initErrorHalt();
  }

  // Find an unused file name.

  if (BASE_NAME_SIZE > 7) {
    error("FILE_BASE_NAME too long");
  }
  while (sd.exists(fileName)) {
    if (fileName[BASE_NAME_SIZE + 1] != '9') {
      fileName[BASE_NAME_SIZE + 1]++;
    } else if (fileName[BASE_NAME_SIZE] != '9') {
      fileName[BASE_NAME_SIZE + 1] = '0';
      fileName[BASE_NAME_SIZE]++;
    } else {
      error("Could not create file name");
    }
  }
  if (!file.open(fileName, O_WRONLY | O_CREAT | O_EXCL)) {
    error("File.open");
  }

  do {
    delay(100);

  } while (Serial.available() && Serial.read() >= 0);

  Serial.print(F("Recording: "));
  Serial.println(fileName);
  Serial.println(F("enter any character to stop"));

  // Write data header.//

  writeHeader();

  // Start on a multiple of the sample interval.

  //logTime = millis()/(1UL*SAMPLE_INTERVAL_MS) + 1;
  //logTime *= 1UL*SAMPLE_INTERVAL_MS;

}
//------------------------------------------------------------------------------
void loop() {

  logTime += SAMPLE_INTERVAL_MS;

  logData();

  // Force data to SD and update the directory entry to avoid data loss.

  if (!file.sync() || file.getWriteError()) {
    error("error write");
  }

  if (Serial.available()) {
    file.close();

    Serial.println(F("done"));
    SysCall::halt();
  }
}

Consider something like this in the loop.

bool acquisitionMode = false;
void loop() {
  int r = Serial.read(); // -1 when nothing
  switch ( r ) {
    case 'G':  acquisitionMode = true;  break;  // G for Go
    case 'S':  acquisitionMode = false; break;  // S for Stop
    default: break;  // ignore everything else
  }
  
  if (acquisitionMode) {
    // do something that is non blocking 
    // like acquiring a sample from time to time
    // using millis or your RTC to know when it’s time to do it 
    // Don’t use delay()
  }

}
1 Like

J-M-L)Jackson
I'm a little new to this, could you please explain a little bit about this?

The first part of the loop is just listening to the serial port. If you send S or G it sets the Boolean variable to true or false accordingly

If this variable is false (it’s false by default) you never enter the following if() statement and so the loop() loops and you go back checking the serial port.

If this variable is true (ie you typed G), you enter the if() statement, perform one sample log for example and exit the if.

The loop() loops and if you did not enter anything the variable is still true and thus you can perform the next acquisition / log.

This continues until you type S, then the Boolean variable changes and the acquisition is stopped (until you type G again)

1 Like

I have no idea how to adapt this in the code I posted, how to read info from void setup after each record is finished?
G will start recording after one letter
Recording will be stopped with the letter S.
If I want to do it again, how will it start again when the letter G is written as a serial monitor.

to see what's going on, try this code and open the Serial Monitor at 115200 bauds and type G to start and S to stop.

bool acquisitionMode = false;
unsigned long lastTime;
const unsigned long acquisitionPeriod_ms = 2000; // 2 seconds in ms

void setup() {
  Serial.begin(115200);
}

void loop() {
  int r = Serial.read(); // -1 when nothing
  switch ( r ) {
    case 'G':  acquisitionMode = true;  Serial.println("START"); break;  // G for Go
    case 'S':  acquisitionMode = false; Serial.println("STOP");  break;  // S for Stop
    default: break;  // ignore everything else
  }

  if (acquisitionMode) {
    if (millis() - lastTime >= acquisitionPeriod_ms) {
      lastTime = millis();
      Serial.print("Acquisition: ");
      Serial.println(lastTime); // whatever just something that will be changing
    }
  }
}

what you want to do is replace

      Serial.print("Acquisition: ");
      Serial.println(lastTime); // whatever just something that will be changing

by a real acquisition and log.

The period here is set to an acquisition every 2 seconds, you could of course change that to whatever makes sense by modifying the constant acquisitionPeriod_ms

1 Like

@parworker You have to read the Serial port in the loop() and start and stop in the loop(). There is no other way. Because of that you need a global 'bool' variable that tells when the acquisition is running. Use the examples by J-M-L. You have to re-organize your sketch.

void setup()
{
  initialize everything.
  just initialize, no waiting for serial input
}

void loop()
{
  acquisitionMode is true at the moment ?
    then take a sample and add it to the file

  Start ?
    then open a new file and write header, acquistionMode = true

  Stop ?
    close the file, acquistionMode = false
}

I'm still working on the method suggested by JML, but I'm really having trouble with how to do this. Because the name of the recording file needs to be changed every time the recording starts, and the code has to go through the void setup at the start of recording.

every time you start the process, you go through where I print "START" ➜ change the file name there in the switch/case

bool acquisitionMode = false;
unsigned long lastTime;
const unsigned long acquisitionPeriod_ms = 2000; // 2 seconds in ms

void setup() {
  Serial.begin(115200);
}

void loop() {
  int r = Serial.read(); // -1 when nothing
  switch ( r ) {
    case 'G':
      acquisitionMode = true;
      Serial.println("START");
      // BUILD NEW FILE NAME
      // (POSSIBLY OPEN FILE FOR WRITING. SOME RIKS THERE)
      break;  // G for Go

    case 'S':
      acquisitionMode = false;
      Serial.println("STOP");
      // CLOSE FILE IF IT WAS OPEN ONCE WHEN STARTING
      break;  // S for Stop

    default:
      break;  // ignore everything else
  }

  if (acquisitionMode) {
    if (millis() - lastTime >= acquisitionPeriod_ms) {
      lastTime = millis();
      Serial.print("Acquisition: ");
      Serial.println(lastTime); // whatever just something that will be changing
    }
  }
}

Thanks for your help, I'll have to work hard to implement the method you suggested :)because I think I need to change the whole code.

That's OK. It happens often in software development.

See that as an opportunity to get a better structure and learn from your version 1.0 code and you can still reuse part of it I'm sure :slight_smile:

try to create functions, that will make the code more readable

How do you think I should create a code? I don't have a problem to improve myself a little more, but with examples, my progress is faster. I have a problem writing multiple strings of code. Try to create functions that will make the code more readable. What do you mean by that, could you please explain a little more about this?

like write functions for

  • buildNewFileName();
  • acquireData();
  • saveData();
bool acquisitionMode = false;
unsigned long lastTime;
const unsigned long acquisitionPeriod_ms = 2000; // 2 seconds in ms

void setup() {
  Serial.begin(115200);
}

void loop() {
  int r = Serial.read(); // -1 when nothing
  switch ( r ) {
    case 'G':
      acquisitionMode = true;
      Serial.println("START");
      buildNewFileName();
      break;  // G for Go

    case 'S':
      acquisitionMode = false;
      Serial.println("STOP");
      break;  // S for Stop

    default:
      break;  // ignore everything else
  }

  if (acquisitionMode) {
    if (millis() - lastTime >= acquisitionPeriod_ms) {
      acquireData();
      saveData();
      lastTime = millis();
    }
  }
}

Thanks for now, but I'm confused right now :slight_smile: Since the piece of code is a bit large, I'm trying to understand where to write what.

Yes, there is work to do :slight_smile:
you put in each function what is specific to the function (your code already has a number of functions)

Do you mean you want to start a new file, or just turn on and off logging to the one true file?

An extra case in @J-M-L 's scheme might be

 case 'X' :
    file.close();

    Serial.println(F("done"));
    SysCall::halt();     // dunno what this does but

    while (true);        // will make _everything_ stop

If it is multiple files, then another case and (character "command") would be to make the new file ready for the next logging, I which, um, case you wouldn't want to while(true) away the future.

a7

I don't understand exactly what you mean alto777
Could you please explain a little more?

Opening and closing a file takes a bit of time but it's not significant if the period of logging is low

it's probably a good idea to open and close the file for each data log to avoid loosing data in case of power failure (the data is not always written to the SD, it might stay in the cache for some time before being flushed) or at the minimum call flush().

It's okay for a power outage because the circuit runs on the battery. But the user has to have a choice between constantly recording and not recording. Therefore, there needs to be a start and end command every time.
but anyway i am modifying the code here and there to generate these commands :slight_smile:

the battery could be dead at some point :slight_smile:
opening and closing the file is possible with "constantly recording".

Writing to an SD is slow anyway and I assume you don't need to record your load cells 100 times per second anyway, right ?