Xenon crashing on 10th SdFat write

Hello—I am trying to write some CO2 sensor data to an SD card using two peripherals on a Xenon: a UART CO2 sensor (COZIR-LP-5) and a Sparkfun microSD breakout. I’m running deviceOS 1.5.2. The code below does what I want it to (poll the UART CO2 sensor then write result to microSD) roughly ten times but then starts flashing red/SOS. It repeats this cycle indefinitely. I can confirm that data are being written to the microSD card as desired.

The code works properly/indefinitely if I comment out the SdFat parts. It also continues running, but never writes to the SD card, if I comment out just the myFile.close(); call at the end of void loop(). So by all accounts, it appears to be a problem with my use of SdFat.

At the moment, I’m using the Xenon just as a microcontroller/development module, not for its mesh capabilities (and I’m aware it’s been discontinued) but since this appears to be a firmware issue, not a hardware one, I’d greatly appreciate any suggestions from you all to help me locate the cause of the crash! Thanks!

#include "SdFat.h"

int led = D7; // blink to let us know you're alive
bool led_state = HIGH; // starting state

//------------------COZIR LP UART Configuration Details--------------------------------

int co2 =0;
double multiplier = 1;// 1 for 2% =20000 PPM, 10 for 20% = 200,000 PPM

// Verify multiplication factor for COZIR-LP-5;
// put in COMMAND MODE "K 0"  in setup
// Serial1.println("K 0");
// then run in loop()
// Serial1.println(".");
// while(Serial1.available()){
//   Serial.print(Serial1.readString());
// }

uint8_t buffer[25];
uint8_t ind =0;

int fill_buffer(); // function prototypes here
int format_output();

//------------------SD SPI Configuration Details--------------------------------
File myFile;
SdFat sd;
const uint8_t chipSelect = SS;

SYSTEM_MODE(MANUAL);
SYSTEM_THREAD(ENABLED);

void setup() {
  pinMode(led, OUTPUT);

  Serial.begin(9600);
  Serial.print("\n\n");
  Serial.println(" Ardunio to COZIR CO2 Sensor - Demonstration code 2/14/18\n\n");
  Serial1.begin(9600); // Start serial communications with sensor

  // COZIR UART
  //Serial1.println("K 0"); // Set Command mode
  Serial1.println("M 6"); // send Mode for Z and z outputs
  // "Z xxxxx z xxxxx" (CO2 filtered and unfiltered)
  Serial1.println("K 1"); // set streaming mode

  // SD Card
  // Uncomment the following four lines for testing
  // Serial.println("Type any character to start");
  // while (Serial.read() <= 0) {
  //   SysCall::yield();
  // }

  // Initialize SdFat or print a detailed error message and halt
  // Use half speed like the native library.
  // Change to SPI_FULL_SPEED for more performance.
  if (!sd.begin(chipSelect)) {
    sd.initErrorHalt();
  }

  delay(3000);

  // open the file for write at end like the "Native SD library"
  if (!myFile.open("COZIR.txt", O_RDWR | O_CREAT | O_AT_END)) {
    sd.errorHalt("opening test.txt for write failed");
  }

  // if the file opened okay, write to it:
  Serial.print("Writing to test.txt...");
  // myFile.println("testing 1, 2, 3.");
  // myFile.printf("fileSize: %d\n", myFile.fileSize());

  // close the file:
  myFile.close();
  Serial.println("done.");

}

void loop() {
  led_state = !led_state; // toggle
  digitalWrite(led, led_state); // command LED accordingly

  // open the file for write at end
  if (!myFile.open("COZIR.txt", O_RDWR | O_CREAT | O_AT_END)) {
    sd.errorHalt("opening test.txt for write failed");
  }

  fill_buffer(); // function call that reads CO2 sensor and fills buffer

  // Serial.print("Buffer contains: ");
  // for(int j=0; j<ind; j++) Serial.print(buffer[j],HEX);

  long unsigned raw_co2 = format_output(0);
  // Serial.print(" Raw PPM ");

  format_output(8);
  // Serial.println(" Filtered PPM\n\n");

  // if the file opened OK above, write to it and close file
  char data[120];
  snprintf(data, sizeof(data), "%lu,%lu",
            Time.now(), raw_co2);
  Serial.println(data);
  myFile.println(data);
  myFile.close();
  delay(2000);
}

int fill_buffer(void){
  // Fill buffer with sensor ascii data
  ind = 0;

  while(buffer[ind-1] != 0x0A){ // Read sensor and fill buffer up to 0XA = CR
    if(Serial1.available()){
      buffer[ind] = Serial1.read();
      ind++;
    }
  }
  // buffer() now filled with sensor ascii data
  // ind contains the number of characters loaded into buffer up to 0xA = CR
  ind = ind -2; // decrement buffer to exactly match last numerical character
  return ind;
}

long unsigned format_output(uint8_t index){ // read buffer, extract 6 ASCII chars, convert to PPM and print
  co2 = buffer[15-index]-0x30;
  co2 = co2+((buffer[14-index]-0x30)*10);
  co2 +=(buffer[13-index]-0x30)*100;
  co2 +=(buffer[12-index]-0x30)*1000;
  co2 +=(buffer[11-index]-0x30)*10000;
  
  long unsigned final_co2 = co2*multiplier;

  // Serial.print("\n CO2 = ");
  // Serial.print(final_co2);

  return final_co2;
}

I’m going to leave the original post up in case anyone actually knows as I am still interested, but I believe I’ve resolved it with a workaround of putting the sensor in polling mode (instead of streaming, as was the case in the original question). I wonder if the sensor in streaming mode occasionally sent a character accidentally that the SD card couldn’t handle.

If others come looking for COZIR-LP CO2 sensor code here, this is what appears to be working for me at the moment (using @rickkas7’s SD example from here):

#include "SdFat.h"

int led = D7; // blink to let us know you're alive
bool led_state = HIGH; // starting state

//------------------COZIR LP UART Configuration Details--------------------------------

int co2 =0;
double multiplier = 1;// 1 for 2% =20000 PPM, 10 for 20% = 200,000 PPM
String final_co2_str;

uint8_t buffer[25];
uint8_t k = 0;
const uint8_t j = 0; // 0 for raw; 8 for filtered

int format_output();

//------------------SD SPI Configuration Details--------------------------------
const int SD_CHIP_SELECT = SS;
SdFat sd;
unsigned long lastTest = 0;

// Function prototype
void tryMeFirst();

SYSTEM_MODE(MANUAL);
void setup() {
  Serial.begin();
  
  // COZIR UART
  Serial1.begin(9600);
  Serial1.println("M 2"); // send Mode for Z and z outputs
  Serial1.println("K 2"); // set polling mode

  pinMode(led, OUTPUT);
}

void loop() {
  if (millis() - lastTest >= 1000) {
	lastTest = millis();
	tryMeFirst();
  }
}

void tryMeFirst() {
  // Toggle LED
  led_state = !led_state; // toggle
  digitalWrite(led, led_state); // command LED accordingly

  // Start SD stuff
  File myFile;

  // Initialize the library
  if (!sd.begin(SD_CHIP_SELECT, SPI_FULL_SPEED)) {
	Serial.println("failed to open card");
	return;
  }

  // open the file for write at end like the "Native SD library"
  if (!myFile.open("cozir.txt", O_RDWR | O_CREAT | O_AT_END)) {
	Serial.println("opening test.txt for write failed");
	return;
  }

  Serial1.println("z"); // poll
  delay(100);
  while(Serial1.available()){
    final_co2_str = Serial1.readString();
  }

  // if the file opened okay, write to it:
  Serial.print("Writing...");

  myFile.print(millis());
  myFile.print(",");
  myFile.println(final_co2_str);
  myFile.close();

  Serial.print(millis());
  Serial.print(",");
  Serial.println(final_co2_str);
}

It would always be good to also state how many slow blinks follow the SOS signal as that provides a hint about the reason and makes looking for the cause easier.
I suspect you see an SOS+1 (hard fault due to buffer overflow).

In your format_output() function Id rather use atoi()or evensscanf()(after terminating the string with a\0` after reading the data).
You also assume that your received string always matches the expectations about length and format - it’s better practice to actually check (including guarding against overshooting your buffer length - I suspect this is your actual issue :wink: ).
I suppose the speed you are reading allows for the RX buffer to get filled and over time wrap round to overwrite the packet terminator of a not yet read packet resulting in a longer than expected string of data which won’t fit into your buffer.

BTW, int co2 doesn’t need to be a global variable AFAICT.

Thanks a ton, @ScruffR.

I didn’t even know to look for a certain number of blinks after the SOS; that’s a new and valuable detail that I’d missed in the docs—thanks! Yes, one blink is correct, so buffer overflow it is.

That makes a lot of sense. Upon re-reading my original code, I can definitely see how that could be an issue.

Lots of helpful (and educational) ideas here. I have to read up more on the utility of atoi() or sscanf() in this scenario. I greatly appreciate it!

1 Like