[SOLVED] Freeze after 20-90 minutes when using serial communication

Hi Community !

I am working on this project mentioned below since more than four weeks, now and still wasn't able to find a solution nor even a logic reason for the occasional (and seemingly non-deterministic) freezes that occur when I use (both hardware and software) serial communication on my Duemilanove (ATmega328) board...
I have the Ladyada Logger Shield as well as some external periphery (battery controller with serial interface (see below for details), small switching relay and three simple voltage dividers for analog inputs) attached which is required to perform the measuring and logging.
Programming is done with Arduino IDE 1.0.4.

What the following (rather lengthy, unfortunately as I did not want to cut / reduce anything) code actually does is this (from top to bottom of code):
You can also download it as a single file attached to this post (as it was far too large to fit in the 9,500 characters limit, I had to split to over multiple posts, unfortunately.

  • Read stored simple values (2x four characters) from SD card during initialization (used to re-define counters lost e.g. during a board reset)
  • Control flow of program by RTC of the Logger Shield (helps to greatly simplify / reduce function calls so CPU will hopefully not get overloaded)
  • "Toggle" between usage of software and hardware serial port every second (in order to avoid simultaneous usage of both interfaces as I read somewhere that this might cause stability problems - probably nonsense)
  • Read serial data from my solar battery monitor every even second (battery management system = BMS - it's a Victron Energy BMV-600S device - see here for details about it's serial communcation protocol: http://www.victronenergy.de/upload/documents/BMV%20Text%20Protocol.pdf)
  • Process those data read from the BMS and "extract" the important values I want to log each hour from it
  • Check for incoming "control characters" (e.g. via the Serial Monitor of the Arduino IDE or a Java tool I want to write as soon as board is stable) meant to drive the small relay (which enables or disables solar power by triggering a bigger latching relay)
  • Check if SD card is initialized and if not, perform that (I want to be able to remove and replug the SD card during operation - which works without any problems as long as serial communication is disabled, so I doubt that this is a cause of problems)
  • Count "power impulses" = 1 Wh of electric work (incoming through a simple voltage divider on analog input 0 by a power meter featuring a S0 output) - works like a charm (but requires the use of an interrupt due to unpredictable occurrences)
  • Log counted power impulses as well as current readings of the BMS once every hour (as well as cumulated hourly power impulse counts once every day)
  • Check status (enabled / disabled) of solar power circuit (second voltage divider on analog input 3) as a feedback for the board to know whether the latching relay has done its job correctly
  • Send a set of data to the USB / host PC every odd second
  • Check charge status of the solar battery (third voltage divider on analog input 1) and enable or disable power circuit accordingly (using the small relay again which then triggers that latching relay mentioned before)

EnergyLogger.ino (25.4 KB)

#include <SD.h>
#include <Wire.h>
#include <RTClib.h>
#include <SoftwareSerial.h>


/**********************************************
 ** Initialize variables required by program **
 **********************************************/
// GENERAL
boolean bSDCardInitialized = false;
long nBoardOnlineTimer = 0;

// POWER IMPULSE COUNTING
RTC_DS1307 RTC;
DateTime now;
DateTime oLogTimestamp;
int nLastSecond = 0;
int nLastHour = 0;
int nLastDay = 0;
volatile boolean bImpulseOccurred = 0;
float fPowerImpulsesDuringThisHour = 0;
float fPowerImpulsesDuringThisDay = 0;
char nSDCardBackupPowerImpulses[4];
int nPositionIndex = 0;

// SOLAR BATTERY CONTROLLING
int nSolarSystemCheckSecurityHighVoltage = 0;
int nSolarSystemCheckSecurityLowVoltage = 0;
int nWaitCyclesUntilSolarRelayReset = 2;
boolean bSolarRelayActive = false;

// BMS SERIAL DATA COMMUNICATION
// Set Pin 7 as serial RX and Pin 8 as serial TX to receive BMS data
SoftwareSerial BMS(7, 8);
char cBMSSerialChar;
boolean bPropertyNext = false;
boolean bValueNext = false;
String sPropertyBuffer = "";
char aValueBuffer[10] = "         ";
int iValueBufferIndex = 0;
int nCycles = 0;
float fVolts = 0;
float fAmps = 0;
float fSOC = 0;
float fTotalAh = 0;
float fDaysSinceLastSync = 0;

// EXTERNAL INTERACTION
// (solar power controlling)
char cIncomingSerialChar = ' ';
char cExternalControl = ' ';
boolean bSolarCircuitState = false;


/**********************************
 ** Perform board initialization **
 ** (runs only once board reset) **
 **********************************/
void setup(void)
{
  // GENERAL
  // Initialize (Arduino hardware) Serial
  // port with 19200 baud
  Serial.begin(19200);
  // Set Pin 10 of Arduino board as output
  // to write to SD card
  pinMode(10, OUTPUT);
  // Initialize ATMEL board
  Wire.begin();
  // Set pin 3 & 5 as output to drive LEDs
  // pin 3 = green LED / pin 5 = red LED
  pinMode(3, OUTPUT);
  pinMode(5, OUTPUT);
  // Set pin 6 as input with pullup resistor active
  // to detect presence of SD card
  pinMode(6, INPUT_PULLUP);
  // Check if SD card is present
  // (pin 6 = LOW equals to SD card present)
  // and blink red LED as warning if not
  if (!digitalRead(6))
  {
    // Initialize SD card
    // (connected to pin 10 of Arduino)
    SD.begin(10);
    bSDCardInitialized = true;
  }
  else
    blinkWarningLED();
  
  // POWER IMPULSE COUNTING
  // Get current time from RTC chip and initialize
  // corresponding variables
  now = RTC.now();
  oLogTimestamp = now;
  nLastSecond = now.second();
  nLastHour = now.hour();
  nLastDay = now.day();
  //Setup interrupt for impulse counting routine
  attachInterrupt(0, powerImpluseCounting, RISING);
  // Get stored hour impulse counter values
  // from data on SD card
  char aFilenameImpulseCounterHour[] = "POWER/IMPLASTH.TXT";
  // Open file if SD card is initialized
  // and read hour impulses count from it
  if (bSDCardInitialized)
  {
    // Set green LED on
    digitalWrite(3, HIGH);
    File oSDCardBackupPowerImpulses = SD.open(aFilenameImpulseCounterHour, FILE_READ);
    nPositionIndex = 0;
    while (oSDCardBackupPowerImpulses.available() > 0)
    {
      nSDCardBackupPowerImpulses[nPositionIndex] = oSDCardBackupPowerImpulses.read();
      nPositionIndex++;
    }
    // Close file
    oSDCardBackupPowerImpulses.close();
    // Convert read data into float number required later
    fPowerImpulsesDuringThisHour = atof(nSDCardBackupPowerImpulses);
    // Set green LED off
    digitalWrite(3, LOW);
  }
  // Get stored day impulse counter values
  // from data on SD card
  char aFilenameImpulseCounterDay[] = "POWER/IMPLASTD.TXT";
  // Open file if SD card is initialized
  // and read day impule count from it
  if (bSDCardInitialized)
  {
    // Set green LED on
    digitalWrite(3, HIGH);
    File oSDCardBackupPowerImpulses = SD.open(aFilenameImpulseCounterDay, FILE_READ);
    nPositionIndex = 0;
    while (oSDCardBackupPowerImpulses.available() > 0)
    {
      nSDCardBackupPowerImpulses[nPositionIndex] = oSDCardBackupPowerImpulses.read();
      nPositionIndex++;
    }
    // Close file
    oSDCardBackupPowerImpulses.close();
    // Convert read data into float number required later
    fPowerImpulsesDuringThisDay = atof(nSDCardBackupPowerImpulses);
    // Set green LED off
    digitalWrite(3, LOW);
  }
  
  // SOLAR BATTERY CONTROLLING
  // Set Pin 4 of Arduino board as output to trigger
  // battery current switching relay
  pinMode(4, OUTPUT);
  
  // BMS SERIAL DATA COMMUNICATION
  // Initialize BMS (software emulated) Serial port
  // with 19200 baud
  BMS.begin(19200);
}
/*****************************
 ** Main program            **
 ** (runs in infinite loop) **
 *****************************/
void loop(void)
{
  // Get current time from RTC chip
  now = RTC.now();
  
  // Do following tasks once every second
  if (now.second() != nLastSecond)
  {
    nLastSecond = now.second();
    
    // Read serial data provided by BMS
    // every odd second and disable interface
    // afterwards (to avoid interference
    // with hardware serial communication)
    if (nLastSecond % 2 == 1)
    {
      readBMSData();
      // Disable BMS serial data communication
      BMS.end();
    }

    
    // Read incoming serial data over USB line
    // every even second and disable interface
    // afterwards (to avoid interference
    // with software serial communication)
    if (nLastSecond % 2 == 0)
    {
      while (Serial.available() > 0)
      {
        cIncomingSerialChar = Serial.read();
        // Set flags according to external control request
        // via serial data (toggels solar power circuit):
        // 0 = SOFT ON (keeps Arduino battery control active)
        // 1 = SOFT OFF (keeps Arduino battery control active)
        // y = HARD ON (ATTENTION ! Disables Arduino battery control until cleared !)
        // n = HARD OFF (ATTENTION ! Disables Arduino battery control until cleared !)
        // c = CLEAR external control request (deactivates hard on / off flags)
        if (cIncomingSerialChar == '0' ||
            cIncomingSerialChar == '1' ||
            cIncomingSerialChar == 'y' ||
            cIncomingSerialChar == 'n' ||
            cIncomingSerialChar == 'c')
              cExternalControl = cIncomingSerialChar;
      }
      // Disable BMS & host serial data communication
      Serial.end();
    }
    
    // Increase board online timer
    // (simple counter to track time since last reset)
    nBoardOnlineTimer++;
    
    // Check if SD card is present
    if (!digitalRead(6))
    {
      // Check if SD card is NOT initialized
      if (!bSDCardInitialized)
      {
        // Initialize SD card
        SD.begin(10);
        bSDCardInitialized = true;
        // Set warning LED off
        digitalWrite(5, LOW);
      }
    }
    else
    {
      bSDCardInitialized = false;
      // Blink red LED
      if (nLastSecond % 2 == 0)
        digitalWrite(5, HIGH);
      else
        digitalWrite(5, LOW);
    }
    
    // If a power impulse occurred during last second
    // (eliminates most errors caused by external interferences
    // as a boolean variable can only be set to true once even
    // if multiple triggering events should occur due to bouncing)
    if (bImpulseOccurred)
    {
      // Increment impulse counter
      fPowerImpulsesDuringThisHour++;
      // Backup current value of current hour counter
      // (in order to restore its value during initialization after a board reset)
      noInterrupts();
      backupPowerImplusesOfHour();
      interrupts();
    }
    // Reset impulse occurrence flag
    bImpulseOccurred = false;
    
    // If a new hour has begun
    if(now.hour() != nLastHour)
    {
      // Set time object for logging to
      // current time (for next log cycle)
      oLogTimestamp = now;
      
      nLastHour = now.hour();
      
      // Log power impulse count of last hour
      // as well as current BMS dataset
      noInterrupts();
      logPowerImplusesOfHour();
      logBMSValues();
      interrupts();
      
      // If a new day has begun
      // (log impulses of last day to seperate file)
      if(now.day() != nLastDay)
      {
        noInterrupts();
        logPowerImplusesOfDay();
        interrupts();
        
        nLastDay = now.day();
      }
    }
    
    // Retrieve solar power curcuit state by measuring
    // corresponding input pin (maximum value is 1024
    // which means that whole reference voltage of
    // 5 volts is applied)
    int nSolarCircuitVoltage = analogRead(3);
    if (nSolarCircuitVoltage > 500)
      bSolarCircuitState = true;
    else
      bSolarCircuitState = false;
    
    // Re-enable BMS serial data communication
    // only every EVEN second (to avoid interference
    // with hardware serial communication)
    if (nLastSecond % 2 == 0)
      BMS.begin(19200);
    
    // Re-enable serial data over USB line
    // only every ODD second (to avoid interference
    // with hardware serial communication)
    if (nLastSecond % 2 == 1)
    {
      Serial.begin(19200);
      // Send current BMS data to serial output (USB / host PC)
      sendBMSData();
    }
    
    // Check status of solar battery in order to trigger
    // relay of supply circuit
    checkSolarBatteryStatus();
  }
}
/****************************
 ** POWER IMPULSE COUNTING **
 ** (interrupt function)   **
 ****************************/
void powerImpluseCounting()
{
  // Retrieve signal voltage applied to
  // impulse input (maximum value is 1024
  // which means that whole reference
  // voltage (5 volts) is applied)
  int impulseVoltage = analogRead(0);
  // If an impulse comes in (voltage applied
  // to input is higher than about 80% of
  // reference voltage; software threshold)
  if(impulseVoltage > 800)
  {
    // Set impulse occurence flag
    // ("software debouncing")
    bImpulseOccurred = true;
  }
}


/************************************************
 ** BACKUP COUNTED IMPULSES OF HOUR TO SD CARD **
 ***********************************************/
void backupPowerImplusesOfHour()
{
  // Write last hour impulse count to SD card
  // (prevent data loss in case of board reset)
  char filenameImpulseCounterHour[] = "POWER/IMPLASTH.TXT";
  // Define data buffer for formatting impulse count
  char aFormatConversionBuffer[4];
  
  // Open file if SD card is initalized
  // and write current hour impulse count to it
  if (bSDCardInitialized)
  {
    // Set green LED on
    digitalWrite(3, HIGH);
    
    File impulsesLastHour = SD.open(filenameImpulseCounterHour, FILE_WRITE);
    // Seek to start of file in order
    // to overwrite all data in it
    impulsesLastHour.seek(0);
    // Write formatted string (four characters) to file
    // making sure to fully overwrite any exiting number)
    impulsesLastHour.print(dtostrf(fPowerImpulsesDuringThisHour, 4, 0, aFormatConversionBuffer));
    impulsesLastHour.close();
    
    // Set green LED off
    digitalWrite(3, LOW);
  }
}


/***********************************
 ** LOG POWER IMPLUSES OF AN HOUR **
 **********************************/
void logPowerImplusesOfHour()
{
  // Output impluses counted within last
  // hour including date to serial monitor
  // (not required for regular operation)
  /*
  Serial.print("Impulses during last hour: ");
  Serial.print(fPowerImpulsesDuringThisHour);
  Serial.print("  --  Log timestamp: ");
  Serial.print(oLogTimestamp.year(), DEC);
  Serial.print("/");
  Serial.print(oLogTimestamp.month(), DEC);
  Serial.print("/");
  Serial.print(oLogTimestamp.day(), DEC);
  Serial.print(" ");
  Serial.print(oLogTimestamp.hour(), DEC);
  Serial.print(":");
  Serial.print(oLogTimestamp.minute(), DEC);
  Serial.print(":");
  Serial.println(oLogTimestamp.second(), DEC);
  */
  
  // Generate filename dynamically so
  // each month a new file gets created
  // (e.g. log of May 2013 = LOG13_05.CSV)
  char filenameLogMonth[] = "POWER/LOG00_00.CSV";
  filenameLogMonth[9] = (oLogTimestamp.year() - 2000)/10 + '0';
  filenameLogMonth[10] = oLogTimestamp.year()%10 + '0';
  filenameLogMonth[12] = oLogTimestamp.month()/10 + '0';
  filenameLogMonth[13] = oLogTimestamp.month()%10 + '0';
  
  // Open file if SD card is initalized
  // and write date/time as well as kWh counted
  // during last hour to it
  if (bSDCardInitialized)
  {
    // Set green LED on
    digitalWrite(3, HIGH);
    
    File logfileHours = SD.open(filenameLogMonth, FILE_WRITE);
    
    logfileHours.print(oLogTimestamp.year(), DEC);
    logfileHours.print("/");
    logfileHours.print(oLogTimestamp.month(), DEC);
    logfileHours.print("/");
    logfileHours.print(oLogTimestamp.day(), DEC);
    logfileHours.print(" ");
    logfileHours.print(oLogTimestamp.hour(), DEC);
    logfileHours.print(":");
    logfileHours.print(oLogTimestamp.minute(), DEC);
    logfileHours.print(":");
    logfileHours.print(oLogTimestamp.second(), DEC);
    logfileHours.print(", ");
    logfileHours.print(fPowerImpulsesDuringThisHour/1000, 3);
    // Finish dataset with newline
    // command and close the file
    // (writes buffer to SD card)
    logfileHours.println();
    logfileHours.close();
    
    // Add impulses of last hour
    // to day impulse counter
    fPowerImpulsesDuringThisDay = fPowerImpulsesDuringThisDay + fPowerImpulsesDuringThisHour;
    // Reset impulses of current hour counter
    // (to start counting impulses of new hour)
    fPowerImpulsesDuringThisHour = 0;
    // Backup current hour counter with value 0
    // (in order to clear cumulated value set by interrupts)
    backupPowerImplusesOfHour();
    
    // Set green LED off
    digitalWrite(3, LOW);
  }
  // Backup current value of current day counter
  // (in order to restore its value during setup after a board reset)
  backupPowerImpulsesOfDay();
}


/***********************************************
 ** BACKUP COUNTED IMPULSES OF DAY TO SD CARD **
 **********************************************/
void backupPowerImpulsesOfDay()
{
  // Write last day impulse count to SD card
  // (prevent data loss in case of board reset)
  char filenameImpulseCounterDay[] = "POWER/IMPLASTD.TXT";
  // Define data buffer for formatting impulse count
  char aFormatConversionBuffer[4];
  
  // Open file if SD card is initalized
  // and write current day impulse count to it
  if (bSDCardInitialized)
  {
    // Set green LED on
    digitalWrite(3, HIGH);
    
    File impulsesLastDay = SD.open(filenameImpulseCounterDay, FILE_WRITE);
    // Seek to start of file in order
    // to overwrite all data in it
    impulsesLastDay.seek(0);
    // Write formatted string (four characters) to file
    // making sure to fully overwrite any exiting number)
    impulsesLastDay.print(dtostrf(fPowerImpulsesDuringThisDay, 4, 0, aFormatConversionBuffer));
    impulsesLastDay.close();
    
    // Set green LED off
    digitalWrite(3, LOW);
  }
}


/*********************************
 ** LOG POWER IMPLUSES OF A DAY **
 ********************************/
void logPowerImplusesOfDay()
{
  // Output impluses counted within last
  // day including date to serial monitor
  // (not required for regular operation)
  /*
  Serial.print("Impulses during last day: ");
  Serial.print(fPowerImpulsesDuringThisDay);
  Serial.print("  --  Log timestamp: ");
  Serial.print(oLogTimestamp.year(), DEC);
  Serial.print("/");
  Serial.print(oLogTimestamp.month(), DEC);
  Serial.print("/");
  Serial.println(oLogTimestamp.day(), DEC);
  */
  
  // Generate filename dynamically
  // so each year a new file gets
  // created
  // (e.g. log of 2013 = ARCHIVE3.CSV)
  char filenamePowerLogYear[] = "POWER/ARCHIVE0.CSV";
  filenamePowerLogYear[13] = oLogTimestamp.year()%10 + '0';
  
  // Open file if SD card is initalized
  // and write date as well as kWh counted
  // during last day to it
  if (bSDCardInitialized)
  {
    // Set green LED on
    digitalWrite(3, HIGH);
    
    File logfileDays = SD.open(filenamePowerLogYear, FILE_WRITE);
    
    logfileDays.print(oLogTimestamp.year(), DEC);
    logfileDays.print("/");
    logfileDays.print(oLogTimestamp.month(), DEC);
    logfileDays.print("/");
    logfileDays.print(oLogTimestamp.day(), DEC);
    logfileDays.print(", ");
    logfileDays.print(fPowerImpulsesDuringThisDay/1000, 3);
    // Finish dataset with newline
    // command and close the file
    // (writes buffer to SD card)
    logfileDays.println();
    logfileDays.close();
    
    // Reset impulses of current day counter
    // (to start counting impulses of new day)
    fPowerImpulsesDuringThisDay = 0;
    // Backup current day counter with value 0
    // (in order to clear cumulated value set by hourly LOGGING routine)
    backupPowerImpulsesOfDay();
    
    // Set green LED off
    digitalWrite(3, LOW);
  }
}
/*******************************
 ** SOLAR BATTERY CONTROLLING **
 ******************************/
void checkSolarBatteryStatus()
{
  // Retrieve battery state by measuring
  // corresponding input pin (maximum
  // value is 1024 which means that whole
  // reference voltage of 5 volts is applied)
  int nBatteryVoltage = analogRead(1);
  // Output battery voltage,
  // state of switching relay on
  // controlling pin (4) as well as
  // actual time to serial monitor
  // (not required for regular operation)
  /*
  Serial.print("Current time: ");
  Serial.print(now.year(), DEC);
  Serial.print(now.month(), DEC);
  Serial.print(now.day(), DEC);
  Serial.print(now.hour(), DEC);
  Serial.print(now.minute(), DEC);
  Serial.print(now.second(), DEC);
  Serial.print(" / ");
  Serial.print("Battery voltage: ");
  Serial.print(nBatteryVoltage, DEC);
  Serial.print(" -- Solar circuit voltage: ");
  Serial.print(nSolarCircuitVoltage, DEC);
  Serial.print(" -- Relay state: ");
  Serial.println((bSolarCircuitState ? "ON" : "OFF"));
  */
  
  // If battery is fully charged,
  // solar system circuit is currently OFF
  // (650 counts equals to 13.73 V)
  // and no external control commands were applied
  if (!bSolarCircuitState && cExternalControl != 'n' && (nBatteryVoltage > 650 || cExternalControl == '1' || cExternalControl == 'y'))
  {
    // Increase counter if not triggered externally
    // (to avoid unwanted triggering by short interferences)
    nSolarSystemCheckSecurityHighVoltage++;
    if (nSolarSystemCheckSecurityHighVoltage > 10 || cExternalControl == '1' || cExternalControl == 'y')
    {
      // Trigger current switching relay
      // to turn solar system circuit ON
      digitalWrite(4, HIGH);
      // Setup variables for a wait loop (see below)
      // to allow for switching relay operation
      bSolarRelayActive = true;
      nWaitCyclesUntilSolarRelayReset = 2;
    }
  }
  else
  {
    // Reset counter which avoids triggering via 
    // temporary external interferences
    nSolarSystemCheckSecurityHighVoltage = 0;
  }
  
  // If battery is depleted,
  // solar system circuit is currently ON
  // (613 counts equals to 12.94 V)
  // and no external control commands were applied
  if(bSolarCircuitState && cExternalControl != 'y' && (nBatteryVoltage < 613 || cExternalControl == '0' || cExternalControl == 'n'))
  {
    // Increase counter if not triggered externally
    // (to avoid unwanted triggering by short interferences)
    nSolarSystemCheckSecurityLowVoltage++;
    if (nSolarSystemCheckSecurityLowVoltage > 10 || cExternalControl == '0' || cExternalControl == 'n')
    {
      // Trigger current switching relay
      // to turn solar system circuit OFF
      digitalWrite(4, HIGH);
      // Setup variables for a wait loop (see below)
      // to allow for switching relay operation
      bSolarRelayActive = true;
      nWaitCyclesUntilSolarRelayReset = 2;
    }
  }
  else
  {
    // Reset counter which avoids triggering via 
    // temporary external interferences
    nSolarSystemCheckSecurityLowVoltage = 0;
  }
  
  // Decrement relay activation wait counter
  // (as whole function is called only once a second
  // this means 2 seconds of relay activity)
  if (bSolarRelayActive && nWaitCyclesUntilSolarRelayReset > 0)
  {
    nWaitCyclesUntilSolarRelayReset--;
  }
  
  // Deactivate solar circuit switching relay
  if (bSolarRelayActive && nWaitCyclesUntilSolarRelayReset == 0)
  {
    digitalWrite(4, LOW);
    bSolarRelayActive = false;
  }
  
  // Reset external control request flag
  // in case of SOFT toggling or clearing request
  if (cExternalControl != 'y' && cExternalControl != 'n')
  {
    cExternalControl = ' ';
  }
}


/******************************
 ** BMS SERIAL COMMUNICATION **
 ******************************/
void readBMSData()
{ 
  while (BMS.available() > 0)
  {
    // Read data from battery monitoring system
    cBMSSerialChar = BMS.read();
    
    // If carriage return character received
    // and value of a certain property is next
    // data block to expect
    if (cBMSSerialChar == '\r' && bValueNext)
    {
      // Store battery information provided by
      // BMS in floats corresponding to properties
      if (sPropertyBuffer == "V")
        fVolts = atof(aValueBuffer) / 1000;
      else if (sPropertyBuffer == "I")
        fAmps = atof(aValueBuffer) / 1000;
      else if (sPropertyBuffer == "SOC")
        fSOC = atof(aValueBuffer) / 10;
      else if (sPropertyBuffer == "H4")
        nCycles = atoi(aValueBuffer);
      else if (sPropertyBuffer == "H6")
        fTotalAh = atof(aValueBuffer) / 1000;
      else if (sPropertyBuffer == "H9")
        fDaysSinceLastSync = atof(aValueBuffer) / 86400;
      
      // Clear buffer values
      sPropertyBuffer = "";
      for (int i = 0; i <= 8; i++)
        aValueBuffer[i] = ' ';
      iValueBufferIndex = 0;
    }
    // In case of a newline character
    // next data block will contain a property
    else if (cBMSSerialChar == '\n')
    {
      bPropertyNext = true;
      bValueNext = false;
    }
    // In case of a tabulator character
    // next data block will contain a value
    // (of the property transmitted before)
    else if (cBMSSerialChar == '\t' && bPropertyNext)
    {
      bPropertyNext = false;
      bValueNext = true;
    }
    // Store incoming property characters
    // in corresponding string
    else if (bPropertyNext)
    {
      sPropertyBuffer.concat(cBMSSerialChar);
    }
    // Store incoming value characters
    // in corresponding char array
    // (in order to be able to transform it
    // to a floating point number by 'atof')
    else if (bValueNext)
    {
      aValueBuffer[iValueBufferIndex] = cBMSSerialChar;
      iValueBufferIndex++;
    }
  }
}


/************************************
 ** SEND BMS DATA TO SERIAL OUTPUT **
 ***********************************/
void sendBMSData()
{
  // Output board online timer
  // (time since last reset)
  Serial.print(nBoardOnlineTimer);
  Serial.print(";");
  // Output all battery data
  Serial.print(fVolts,3);
  Serial.print(";");
  Serial.print(fAmps,3);
  Serial.print(";");
  Serial.print(fSOC,1);
  Serial.print(";");
  Serial.print(nCycles);
  Serial.print(";");
  Serial.print(fTotalAh,3);
  Serial.print(";");
  Serial.print(fDaysSinceLastSync,2);
  Serial.print(";");
  // Append current state of solar power circuit
  // as well as external control flag
  Serial.print(bSolarCircuitState);
  Serial.print(";");
  Serial.println(cExternalControl);
}


/*********************************
 ** LOG BMS VALUES (EVERY HOUR) **
 ********************************/
void logBMSValues()
{
  // Output important BMS values
  // to serial monitor
  // (not required for regular operation)
  /*
  Serial.print(fVolts,3);
  Serial.print(" V - ");
  Serial.print(fAmps,3);
  Serial.print(" A - ");
  Serial.print(fSOC,1);
  Serial.print(" % - Cycles: ");
  Serial.print(nCycles);
  Serial.print(" - Discharge: ");
  Serial.print(fTotalAh,3);
  Serial.print(" - Last sync: ");
  Serial.print(fDaysSinceLastSync,2);
  Serial.print(" - Time: ");
  Serial.print(oLogTimestamp.year(), DEC);
  Serial.print("/");
  Serial.print(oLogTimestamp.month(), DEC);
  Serial.print("/");
  Serial.print(oLogTimestamp.day(), DEC);
  Serial.print(" ");
  Serial.print(oLogTimestamp.hour(), DEC);
  Serial.print(":");
  Serial.print(oLogTimestamp.minute(), DEC);
  Serial.print(":");
  Serial.println(oLogTimestamp.second(), DEC);
  */

  // Generate filename dynamically
  // so each year a new file gets
  // created
  // (e.g. log of 2013 = ARCHIVE3.CSV)
  char filenameBMSLogYear[] = "BMS/ARCHIVE0.CSV";
  filenameBMSLogYear[11] = oLogTimestamp.year()%10 + '0';
  
  // Open file if SD card is present
  // and write date as well as the current 
  // BMS dataset (6 variables) to it
  if (bSDCardInitialized)
  {
    // Set green LED on
    digitalWrite(3, HIGH);
    
    File logfileBMS = SD.open(filenameBMSLogYear, FILE_WRITE);
    
    logfileBMS.print(oLogTimestamp.year(), DEC);
    logfileBMS.print("/");
    logfileBMS.print(oLogTimestamp.month(), DEC);
    logfileBMS.print("/");
    logfileBMS.print(oLogTimestamp.day(), DEC);
    logfileBMS.print(" ");
    logfileBMS.print(oLogTimestamp.hour(), DEC);
    logfileBMS.print(":");
    logfileBMS.print(oLogTimestamp.minute(), DEC);
    logfileBMS.print(":");
    logfileBMS.print(oLogTimestamp.second(), DEC);
    logfileBMS.print(";");
    logfileBMS.print(fVolts,3);
    logfileBMS.print(";");
    logfileBMS.print(fAmps,3);
    logfileBMS.print(";");
    logfileBMS.print(fSOC,1);
    logfileBMS.print(";");
    logfileBMS.print(nCycles);
    logfileBMS.print(";");
    logfileBMS.print(fTotalAh,3);
    logfileBMS.print(";");
    logfileBMS.print(fDaysSinceLastSync,2);
    // Finish dataset with newline
    // command and close the file
    // (writes buffer to SD card)
    logfileBMS.println();
    logfileBMS.close();
    
    // Set green LED off
    digitalWrite(3, LOW);
  }
}
/********************************
 ** BLINK RED LED FOR WARNINGS **
 ********************************/
void blinkWarningLED()
{
  for (int i = 0; i < 20; i++)
  {
    digitalWrite(5, HIGH);
    delay(100);
    digitalWrite(5, LOW);
    delay(100);
    i++;
  }
}

I also hope the excessive comments I inserted make things more clear.

The code compiles and works correctly for about 20 to 90 minutes (all log files are written correctly and serial output is perfectly fine when I watch it in Serial Monitor of IDE) so I think it's a memory issue (?) which causes the board to freeze after some time.
Hence, I already tried one of those code snippets giving feedback about the available SRAM (the small 'freeRam' function from here: Arduino Playground - AvailableMemory) which shows 430 bytes of free SRAM right after a reset and suddenly goes wild (giving values of 10,000 and more or even negative values) after about 20-40 minutes. Interestingly, even though those wrong free SRAM values are mentioned the board continues normal operation until it freezes out of the sudden after another half an hour or so.
After a reset, everything is just fine again for the period of time mentioned before (20-90 minutes). I already tried various different approaches to read, process and output the BMS data within the last days / weeks but the one shown in the code above is the cleanest way I came up with, in my opinion.

What should be mentioned is that there are no freezes as soon as I comment out all lines dealing with the software serial input (= reading data from the BMS) and hardware serial output (= writing processed data to USB via FDTI chip).
I also increased the buffer of the software serial port by "patching" SoftwareSerial.h: I increased the RX buffer to 256 bytes (#define _SS_MAX_RX_BUFF 256) from its original 64 in order for it to be able to take a full BMS dataset packet. By doing so, I was able to get the BMS reading function (readBMSData) from the main loop into the if clause "running" just once every second (if (now.second() != nLastSecond)).
Naturally, this takes even more SRAM but I only did so after my first approach of leaving readBMSData in the main loop did also give me problems (logging to SD card not working properly) - so leaving the software serial buffer as it is seems not to solve my problem or at least leaves a new one, unfortunately.

Furthermore, I already tried powering the board via USB (leave it connected all the time) or by an external DC/DC supply with USB disconnected (and hence no data read from Serial.write) - still the same results (as expected because current draw is lower than 50 mA during operation).

So I kindly ask the experienced people here if anybody can spot any particular problem in my code or give me any advice what could lead to these mysterious freezes despite the obviously working general setup...

Any help is greatly appreciated - and please feel free to ask any question that might arise from my rather complex code.

FNW

That sounds like memory corruption. I see you're using the String class, which is a known cause of heap corruption. I suggest you get rid of that and just use ordinary c-strings (null-terminated char arrays).

I gather that the 1.0.4 IDE has a newer AVR-LIBC with a fixed memory allocator. Which version of Arduino are you using?
Does using 1.0.4 fix things?

@PeterH:
Thanks for your proposal. I started the BMS data processing routine with two char arrays but switched to a String for storing properties for convenience reasons (it's simpler to check for a certain property when using strings than when having to fiddle with char arrays there). But if strings (objects) are known to cause problems, I will naturally give a fully char array-based processing routine a try.
But just one simple question regarding erasure of a null-terminated char array: What is the "official" way to clear / reset such an array (= get rid of any previous content like I currently do with sPropertyBuffer = "") with let's say 8 characters ?

Example:

char aPropertyBuffer[9] = "ABCDEFGH"; //using automatic null-termination, so one index more required

// Clear char array content
for (int i = 0; i <= 7; i++)
  aPropertyBuffer[i] = ' ';

Questions:

  • Is it correct to only erase the first 8 positions (0 <= i <= 7) of such an array or do I need to erase all 9 in order to completely clear all content ?
  • Is using a space character in the for loop okay for wiping the char array or is there a better way to "destroy" old content in it ?

@gardner
Thanks for this interesting background information. Unfortunately, I am already using the latest version of the IDE (which is the 1.0.4 mentioned by you) so this is no easy option anymore... Nevertheless, I added this obviously important info right to my initial message so everybody starting to read my thread will know directly.

Try to "isolate" the issue. Do split the code into smaller chunks with certain functionality, and try to trigger the issue somehow, or just observe until the issue occurs..

@pito
I already considered this because the code currently in here is too complex. As written above - I assume to have tracked the issue down to serial communication (reading + processing BMS data and writing the corresponding output to serial output / USB) because my sketch works without any problems (tested for > 24h) if I simply comment out the corresponding few lines of code triggering these functions.
But I will try to build a much simpler sketch containing only those serial communication functions without any measuring and logging in order to find out if the freezes are really only caused by this...

Thanks for your thought - I will keep you posted !

FNW2013:
What is the "official" way to clear / reset such an array (= get rid of any previous content like I currently do with sPropertyBuffer = "") with let's say 8 characters ?

Just write a null (zero) to the first character in the array.

If you are adding to the string one character at a time you might also be using a variable recording the current length of the string - in that case, you would need to zero the length variable as well.

You do not have a test anywhere that iValueBufferIndex does not overstep the bounds of aValueBuffer[]. A bit of noise on the serial line, or a dropped would have you off clobbering lord knows what. I would suggest something like:

    // Store incoming value characters
    // in corresponding char array
    // (in order to be able to transform it
    // to a floating point number by 'atof')
    else if (bValueNext)
    {
      if (iValueBufferIndex >= (sizeof(aValueBuffer) - 1)) {
           // the character is not going to fit -- what do we do?
           // the easy thing is just to carry on waiting for the <NL>.
           // Better would be entering an error state where we just look for
           // the next <NL> and remember not to try to parse this
           // bad string. 
      } else { 
           aValueBuffer[iValueBufferIndex] = cBMSSerialChar;
           iValueBufferIndex++;
      }
    }

@PeterH
I just changed the BMS reading routine to only use char arrays (get rid of the only use of the String class).
Nevertheless, I was unable to clear its values after each run by simply putting a zero in the first position (e.g. aValueBuffer[0] = 0). Doing so just gave me weird numbers (probably leftovers from the last run) for the values (e.g. 1316.3754 instead of just 13.164 volts). Hence, I decided to use a simple for loop to wipe contents of all char arrays each time they got filled (which leads to correct readings).

@gardner
Hey, this is a really good point ! Probably (and hopefully) that's already the really simple solution as it makes perfect sense that an error caused by wrong interpretation of incoming BMS data can lead to a buffer overflow which finally results in a freeze when it happens at the "right" spot in SRAM...
As I don't need to take much care about error handling because I only log one single set of values per hour, I went for the slightly simpler approach and just continue when aValueBuffer or aPropertyBuffer is full.
Thanks a lot for pointing me to that particular issue - here's the "optimized" routine:

void readBMSData()
{ 
  while (BMS.available() > 0)
  {
    // Read data from battery monitoring system
    cBMSSerialChar = BMS.read();
    
    // If carriage return character received
    // and value of a certain property is next
    // data block to expect
    if (cBMSSerialChar == '\r' && bValueNext)
    {
      // Store battery information provided by
      // BMS in floats corresponding to properties
      if (aPropertyBuffer[0] == 'V')
        fVolts = atof(aValueBuffer) / 1000;
      else if (aPropertyBuffer[0] == 'I')
        fAmps = atof(aValueBuffer) / 1000;
      else if (aPropertyBuffer[0] == 'S' && aPropertyBuffer[1] == 'O' && aPropertyBuffer[2] == 'C')
        fSOC = atof(aValueBuffer) / 10;
      else if (aPropertyBuffer[0] == 'H' && aPropertyBuffer[1] == '4')
        nCycles = atoi(aValueBuffer);
      else if (aPropertyBuffer[0] == 'H' && aPropertyBuffer[1] == '6')
        fTotalAh = atof(aValueBuffer) / 1000;
      else if (aPropertyBuffer[0] == 'H' && aPropertyBuffer[1] == '9')
        fDaysSinceLastSync = atof(aValueBuffer) / 86400;
      
      // Clear buffer values
      // (both buffers are of the same size so it doesn't
      // matter on which to call sizeof)
      for (byte x = 0; x < sizeof(aValueBuffer); x++)
      {
        aValueBuffer[x] = ' ';
        aPropertyBuffer[x] = ' ';
      }
      bValueBufferIndex = 0;
      bPropertyBufferIndex = 0;
    }
    // In case of a newline character
    // next data block will contain a property
    else if (cBMSSerialChar == '\n')
    {
      bPropertyNext = true;
      bValueNext = false;
    }
    // In case of a tabulator character
    // next data block will contain a value
    // (of the property transmitted before)
    else if (cBMSSerialChar == '\t' && bPropertyNext)
    {
      bPropertyNext = false;
      bValueNext = true;
    }
    // Store incoming property characters
    // in corresponding string
    else if (bPropertyNext)
    {
      // Check if property index is still within bounds
      if (bPropertyBufferIndex <= (sizeof(aPropertyBuffer) - 1))
      {
        aPropertyBuffer[bPropertyBufferIndex] = cBMSSerialChar;
        bPropertyBufferIndex++;
      }
    }
    // Store incoming value characters
    // in corresponding char array
    // (in order to be able to transform it
    // to a floating point number by 'atof')
    else if (bValueNext)
    {
      // Check if value index is still within bounds
      if (bValueBufferIndex <= (sizeof(aValueBuffer) - 1))
      {
        aValueBuffer[bValueBufferIndex] = cBMSSerialChar;
        bValueBufferIndex++;
      }
    }
  }
}

I just uploaded the whole sketch including all functionality to the board again and will give it a try overnight. If I am lucky, it will keep running which would be a very good signal (as it never made it for more than about 90 minutes before)...
Stay tuned - I will keep you posted.

Thanks again to everybody for helping me ! What would the Arduino community be without such highly motivated and competent contributors...

As I hoped, it seems like gardner's assumption hit the bull's eye: Since I added the simple buffer overflow prevention for aValueBuffer and aPropertyBuffer no more freezes occured !
The board runs fully stable for more than 14 hours, now performing all tasks without a single problem !

To be sure, I also included the SRAM info code mentioned before and it didn't mess up anymore but constanty gives a value of 482 bytes free. Although that's not too much for an ATmega328-based board, I hope it won't cause any trouble in the long run as the value never changed until now and the tasks it has to perform are quite static with barely any user interaction.

If there aren't any reboots or freezes within the next 48 hours, I will mark this topic as solved.

Finally, I want to thank everybody involved in this discussion !
I guess I would never have come to the idea of a simple buffer overflow being the reason for those occasional freezes...