1

I am using Teensy 3.2 to develop an air quality monitoring system. The principle is quite easy. Get the data from the sensors, (PM 2.5 ,CO2) , Send it back to Teensy to calculate one minute average, Record the average and store it on a microSD card. So far, I tested all sensors and Teensy individually; they all worked perfectly. However, when I was constructing the main loop. After calling getPM(), the loop just stopped working. I suspect it might be the data rate sending back to Teensy from those sensors are too fast compare to the loop's executing speed. But I am not sure what's the fix.

The pm sensor uses UART and the CO2/VOC uses I2C protocal

Here is my code :

#include <SoftwareSerial.h>
//Serial connection for PM / Wifi
#include <SPI.h> 
#include <SD.h> 
#include <stdint.h> 
#include <SD_t3.h>
  // SPI for Micro SD card
#include "RTClib.h"
  // For RTC 
#include <SparkFunBME280.h> 
#include <SparkFunCCS811.h> 
#define CCS811_ADDR 0x5B //Default I2C Address for CCS811 sensor

const int LCDTx = 10;
const int WIFITx = 8;
const int WIFIRx = 7;
const int PMRX = 0;
const int PMTx = 1;
const char * buffer = "teensy.txt";
File myFile;
String writeString;
String year, month, day, second, hour, minute;

//PM Sensors variable
uint16_t pm10 = 0;
uint16_t pm25 = 0;
uint16_t pm100 = 0;
uint16_t tpm10 = 0;
uint16_t tpm25 = 0;
uint16_t tpm100 = 0;

uint16_t tpm10Sum;
uint16_t tpm25Sum;
uint16_t tpm100Sum;
uint16_t pm10Sum;
uint16_t pm25Sum;
uint16_t pm100Sum;
int PMerrors = 0;
uint8_t buf[24];

uint16_t TPM01ValueAvg;
uint16_t TPM2_5ValueAvg;
uint16_t TPM10ValueAvg;
uint16_t PM01ValueAvg;
uint16_t PM2_5ValueAvg;
uint16_t PM10ValueAvg;

//Temperature and Humidity variable

float tempC = 0;
float tempCSum;
float tempCAvg;

float humidity = 0;
float humiditySum;
float humidityAvg;

// CO2 variable 

uint16_t CO2con = 0;
uint16_t CO2conSum;
uint16_t CO2conAvg;

// VOC variable

uint16_t VOCcon = 0;
uint16_t VOCconSum;
uint16_t VOCconAvg;

uint16_t Height = 0;

RTC_DS3231 rtc;
CCS811 myCCS811(CCS811_ADDR);
BME280 myBME280;

SoftwareSerial PMSerial(0, 1); // PM for RX, TX
SoftwareSerial WIFISerial(7, 8); // WIFI for RX , TX
SoftwareSerial LCD = SoftwareSerial(255, LCDTx);

void setup() {
  Serial.begin(9600); // For Serial monitor 
  PMSerial.begin(9600); // For PM sensor
  WIFISerial.begin(9600); // For wifi
  SPI.begin();
  Wire.begin();

  // LCD start up
  pinMode(LCDTx, OUTPUT);
  digitalWrite(LCDTx, HIGH);
  LCD.begin(9600);
  delay(1000);
  LCD.write(17);

  // RTC start up 

  if (!rtc.begin()) {
    Serial.println("Can't fine RTC");
    //Clear screen
    LCD.write(12);
    delay(5);
    //Print out statement, display for 2 seconds
    LCD.print("Can't find RTC");
    LCD.write(13);
    delay(2000);
    //Clear screen
    LCD.write(12);
    while (1);
  } else {
    Serial.println("RTC initialized successfully");
    LCD.write(12);
    delay(5);
    LCD.print("RTC initialized successfully");
    LCD.write(13);
    delay(2000);
    LCD.write(12);
  }
  DateTime now = rtc.now(); // Catch the time on RTC for now
  DateTime PCTime = DateTime(__DATE__, __TIME__); // Catch the time on PC for now

  // If any discrepencies , update with the time on PC 
  // Manually change this code when the timezone is different uncomment the rtc.adjust(DateTime(__DATE__, __TIME__));
  // Upload it again to Arduino and check if the time is correct
  // Comment out rtc.adjust(DateTime(__DATE__, __TIME__)); lastly, Upload the entire code again
  if (now.unixtime() < PCTime.unixtime()) {
    rtc.adjust(DateTime(__DATE__, __TIME__));
  }
  rtc.begin();

  // Set SS / CS pin as 15 , SD.Begin(XX)-> XX is also SS pin number
  pinMode(15, OUTPUT);
  digitalWrite(15, HIGH);

  // SD card reader start up (Due to the bad reader, max clock speed can only be 72 MHz)

  if (SD.begin(15) == false) {
    Serial.println("SD card didn't initialized");
    LCD.write(12);
    delay(5);
    LCD.print("SD card didn't initialized");
    LCD.write(13);
    delay(2000);
    LCD.write(12);
    delay(5);
  } else {
    Serial.println("SD card initialized successfully");
    LCD.write(12);
    delay(5);
    LCD.print("SD card initialized sucessfully");
    LCD.write(13);
    delay(2000);
    LCD.write(12);
    delay(5);
  }

  //This begins the CCS811 sensor and prints error status of .begin()
  CCS811Core::status returnCode = myCCS811.begin();
  Serial.print("CCS811 begin exited with: ");
  //Pass the error code to a function to print the results
  printDriverError(returnCode);
  Serial.println();

  //For I2C, enable the following and disable the SPI section
  myBME280.settings.commInterface = I2C_MODE;
  myBME280.settings.I2CAddress = 0x77;

  //Initialize BME280
  //For I2C, enable the following and disable the SPI section
  myBME280.settings.commInterface = I2C_MODE;
  myBME280.settings.I2CAddress = 0x77;
  myBME280.settings.runMode = 3; //Normal mode
  myBME280.settings.tStandby = 0;
  myBME280.settings.filter = 4;
  myBME280.settings.tempOverSample = 5;
  myBME280.settings.pressOverSample = 5;
  myBME280.settings.humidOverSample = 5;

  //Calling .begin() causes the settings to be loaded
  delay(10); //Make sure sensor had enough time to turn on. BME280 requires 2ms to start up.
  myBME280.begin();

}

//Timer for SD logging
int sampleSize = 0;
elapsedMillis sinceStartup;

void loop() {

  sampleSize++;



  Serial.println("1");
  elapsedMillis SampleTime;
  Serial.println(SampleTime);
 // getTHCV();
 // getPM();




  if (sinceStartup > 60000) {
    Calcavg();
    DateTime now = rtc.now();
    year = String(now.year(), DEC);
    //Convert from Now.year() long to Decimal String object
    month = String(now.month(), DEC);
    day = String(now.day(), DEC);
    hour = String(now.hour(), DEC);
    minute = String(now.minute(), DEC);
    second = String(now.second(), DEC);
    writeString = year + "/" + month + "/" + day + " " + hour + ":" + minute + ":" + second + " ";
    sdLog(buffer, writeString + TPM2_5ValueAvg + " " + PM2_5ValueAvg + " " + humidityAvg + " " + CO2conAvg + " " + VOCconAvg);
    Serial.println(writeString + TPM2_5ValueAvg + " " + PM2_5ValueAvg + " " + humidityAvg + " " + CO2conAvg + " " + VOCconAvg);
    Reset();
  }else{

  Serial.println(CO2con);  
  }

}

void getTHCV() {

  if (myCCS811.dataAvailable()) {
    //Calling this function updates the global tVOC and eCO2 variables
    Serial.println("HelloWorld !");

    myCCS811.readAlgorithmResults();
    //printInfoSerial fetches the values of tVOC and eCO2
    CO2con = myCCS811.getCO2();
    VOCcon = myCCS811.getTVOC();
    tempC = myBME280.readTempC();
    Height = myBME280.readFloatAltitudeMeters();
    humidity = myBME280.readFloatHumidity();

    //This sends the temperature data to the CCS811
    myCCS811.setEnvironmentalData(humidity, tempC);

    //humiditySum += humidity;
    //tempCSum += tempC;
    //CO2conSum += CO2con;
    //VOCconSum += VOCcon;

  } else if (myCCS811.checkForStatusError()) {
    //If the CCS811 found an internal error, print it.
    printSensorError();
      Serial.println("HelloWorld !");

  }

}

void getPM() {
  delay(400);
  int idx = 0;
  memset(buf, 0, 24);
  while (Serial1.available()) {
    buf[idx++] = Serial1.read();
  }

  if (buf[0] == 0x42 && buf[1] == 0x4d) {

    pm25 = (buf[12] << 8) | buf[13];
    pm10 = (buf[10] << 8) | buf[11];
    pm100 = (buf[14] << 8) | buf[15];
    tpm10 = (buf[4] << 8) | buf[5];
    tpm25 = (buf[6] << 8) | buf[7];
    tpm100 = (buf[8] << 8) | buf[9];

    if (checkValue(buf, 24)) {
      tpm10Sum += tpm10;
      tpm25Sum += tpm25;
      tpm100Sum += tpm100;
      pm10Sum += pm10;
      pm25Sum += pm25;
      pm100Sum += pm100;
    }
  }
}

void Calcavg() {

  TPM01ValueAvg = tpm10Sum / sampleSize;
  TPM2_5ValueAvg = tpm25Sum / sampleSize;
  TPM10ValueAvg = tpm100Sum / sampleSize;
  PM01ValueAvg = pm10Sum / sampleSize;
  PM2_5ValueAvg = pm25Sum / sampleSize;
  PM10ValueAvg = pm100Sum / sampleSize;

  CO2conAvg = CO2conSum / sampleSize;
  tempCAvg = tempCSum / sampleSize;
  humidityAvg = humiditySum / sampleSize;
  VOCconAvg = VOCconSum / sampleSize;

}

void Reset() {
  CO2conSum = 0;
  tempCSum = 0;
  humiditySum = 0;

  CO2conAvg = 0;
  tempCAvg = 0;
  humidityAvg = 0;

  sampleSize = 0;

  tpm10Sum = 0;
  tpm25Sum = 0;
  tpm100Sum = 0;
  pm10Sum = 0;
  pm25Sum = 0;
  pm100Sum = 0;

  TPM01ValueAvg = 0;
  TPM2_5ValueAvg = 0;
  TPM10ValueAvg = 0;
  PM01ValueAvg = 0;
  PM2_5ValueAvg = 0;
  PM10ValueAvg = 0;
  sinceStartup = 0;

}

void sdLog(const char * fileName, String stringToWrite) {
  File myFile = SD.open(fileName, FILE_WRITE);
  // if the file opened okay, write to it:
  if (myFile) {
    Serial.print("Writing to ");
    Serial.print(fileName);
    Serial.print("...");
    myFile.println(stringToWrite);
    // close the file:
    myFile.close();
    Serial.println("done.");
    digitalWrite(13, HIGH);
    delay(300);
    digitalWrite(13, LOW);
    delay(300);
  } else {
    // if the file didn't open, print an error:
    Serial.print("error opening ");
    Serial.println(fileName);
  }
}
void printDriverError(CCS811Core::status errorCode) {
  switch (errorCode) {
  case CCS811Core::SENSOR_SUCCESS:
    Serial.print("SUCCESS");
    break;
  case CCS811Core::SENSOR_ID_ERROR:
    Serial.print("ID_ERROR");
    break;
  case CCS811Core::SENSOR_I2C_ERROR:
    Serial.print("I2C_ERROR");
    break;
  case CCS811Core::SENSOR_INTERNAL_ERROR:
    Serial.print("INTERNAL_ERROR");
    break;
  case CCS811Core::SENSOR_GENERIC_ERROR:
    Serial.print("GENERIC_ERROR");
    break;
  default:
    Serial.print("Unspecified error.");
  }
}

void printSensorError() {
  uint8_t error = myCCS811.getErrorRegister();

  if (error == 0xFF) //comm error
  {
    Serial.println("Failed to get ERROR_ID register.");
  } else {
    Serial.print("Error: ");
    if (error & 1 << 5) Serial.print("HeaterSupply");
    if (error & 1 << 4) Serial.print("HeaterFault");
    if (error & 1 << 3) Serial.print("MaxResistance");
    if (error & 1 << 2) Serial.print("MeasModeInvalid");
    if (error & 1 << 1) Serial.print("ReadRegInvalid");
    if (error & 1 << 0) Serial.print("MsgInvalid");
    Serial.println();
  }
}

// ***Checksum for PM values ***//
int checkValue(uint8_t thebuf[24], int leng) {
  char receiveflag = 0;
  int receiveSum = 0;
  int i = 0;

  for (i = 0; i < leng; i++) {
    receiveSum = receiveSum + thebuf[i];
  }

  if (receiveSum == ((thebuf[leng - 2] << 8) + thebuf[leng - 1] + thebuf[leng - 2] + thebuf[leng - 1])) //checksum the serial data
  {
    receiveSum = 0;
    receiveflag = 1;
  }
  return receiveflag;
}        
  • Are you returning anything such as a value after you call getPM() ? In getTHCV() I see that you are printing stuff in the Serial monitor. – Ivam Jul 04 '18 at 03:32
  • so I tried to print out the PM values or VOC/ CO2 values in the serial monitor, but nothing came out. If I uncomment the part where the main loop is trying to execute getPM() and getTHCV(), it works. I am not sure where exactly is the bug. – thomas101rcx Jul 04 '18 at 12:16
  • so if you uncomment the getPM() and getTHCV() parts then the code will loop through only once and then stop? That is what I'm understanding here – Ivam Jul 04 '18 at 14:50
  • 1
    Nope. It will continue to execute and print out 1. As indicated in the code. The if statement will take over and tries to execute. – thomas101rcx Jul 05 '18 at 00:40
  • @yofavpotato any thoughts ? – thomas101rcx Jul 06 '18 at 00:31
  • I looked at it for a while but I couldn’t really find a bug in your code sorry :/. Perhaps you can ask of Arduino Stack Exchange – Ivam Jul 06 '18 at 00:50

1 Answers1

0

The OP has surely resolved this problem by now, but did not provide the solution. I will explain one obvious problem in this code in hopes that it may help a future visitor.

Here is the start of the offending routine:

void getPM() {
  delay(400);
  int idx = 0;
  memset(buf, 0, 24);
  while (Serial1.available()) {
    buf[idx++] = Serial1.read();
  }
  ...}

In routine getPM(), characters received are written into a 24-character buffer using an index that keeps incrementing as long as Serial1.available() is true.

The first problem is that if the sensor is sending data quickly enough, Serial1.available() could be true forever, in which case the function will never return.

The more serious problem is that the sensor may send more than 24 characters between loops. Even if the sensor sends data very slowly, more than 24 characters might have arrived before the first pass through the loop. If 25 or more characters have arrived before getPM() is called, the first 24 bytes will be written into the character buffer and the remaining bytes will be written into memory at locations that don't belong to the buffer, likely causing a program crash.

It is poor practice to make assumptions about how much data will be received and how quickly; instead of blindly incrementing a buffer index, the upper and lower limits should be set explicitly to match the buffer size.

Craig.Feied
  • 2,617
  • 2
  • 16
  • 25