Measuring ambient air oxygen level via LuminOx Optical Oxygen Sensor

Hello Paul,

thanks a very lot! I was blind to that and I wonder that I didn't got an error message while compiling.
Changed it to long and now its working again, but still have the problem, that every second read goes wrong.

Oxygen 200.20
Temperature 22.10
Pressure 988
Percent 20.27 correct!
Oxygen 200.30
Temperature 22.00
Pressure 98 wrong
Percent 0.00 wrong
Oxygen 198.50
Temperature 23.70
Pressure 988
Percent 20.09 correct
Oxygen 198.40
Temperature 23.80
Pressure 98 wrong
Percent 0.00 wrong
Oxygen 197.60
Temperature 24.90
Pressure 988
Percent 20.00 correct
Oxygen 197.60
Temperature 24.80
Pressure 98 wrong
Percent 0.00 wrong

Any suggestions, maybe?

Thanks
Triaenodon

AHHH,

found another issue maybe:
The sensor is sending and expecting 3.3 V USART. I connected simply to Pin 10 and 11 which work with 5 V. Can that cause the misread? And when, is there a simple tinker solution or do I have to buy a level shifter like e.g. BSS138 to solve that?

Best regards
Triaenodon

Hello,

thanks to your help. It works now very reliable. The poll mode did not work for me.
I had a bug in the function "void recvWithStartEndMarkers()".
Additionally I installed the mentioned level shifter.

That's what my code looks like now:

#include <SoftwareSerial.h>
/*
sensor Pin3(TX) to Arduino Pin 10(RX)
sensor Pin4(RX) to Arduino Pin 11(TX)
*/
SoftwareSerial mySerial(10, 11); // RX, TX

const byte numChars = 41;
char receivedChars[numChars];
char tempChars[numChars];     // temporary array for use when parsing
      
float oxygen = 0.0;  // variables to hold the parsed data
float temperature = 0.0;
int pressure = 0;
float percentage = 0.0;

boolean newData = false;

unsigned long currentMillis = 0;
unsigned long previousReadOxySenMillis = 0;
//unsigned long previousReadDHTMillis = 0;
const long ReadOxySenInterval = 10000;

//============

void setup() {
    Serial.begin(9600);
    mySerial.begin(9600);
}

//============

void loop() {
    currentMillis = millis();  // capture the latest value of millis()
    updateReadOxySen();
}

//============

void recvWithStartEndMarkers() {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = 'O';
    char endMarker = 'e';
    char rc;

    while (mySerial.available() > 0 && newData == false) {
        rc = mySerial.read();

        if (recvInProgress == true) {
            if (rc != endMarker) {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) {
                    ndx = numChars - 1;
                }
            }
            else {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                newData = true;
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
        }
    }
}

//============

void parseData() {      // split the data into its parts

    char * strtokIndx; // this is used by strtok() as an index

    //data from sensor send looks like: "O xxxx.x T yxx.x P xxxx % xxx.xx e xxxx\r\n"
    //e.g. "O 0020.1 T +19.3 P 1013 % 020.16 e 0001\r\n" 

    strtokIndx = strtok(tempChars,"T");      // get the first part - the string
    oxygen = atof(strtokIndx); // copy it to oxygen
    
    strtokIndx = strtok(NULL, "P"); // this continues where the previous call left off
    temperature = atof(strtokIndx);     // convert this part to a float

    strtokIndx = strtok(NULL, "%"); // this continues where the previous call left off
    pressure = atoi(strtokIndx);     // convert this part to an integer

    strtokIndx = strtok(NULL, "e"); // this continues where the previous call left off
    percentage = atof(strtokIndx);     // convert this part to a float
}

//============

void showParsedData() {
    Serial.print("Oxygen ");
    Serial.println(oxygen);
    Serial.print("Temperature ");
    Serial.println(temperature);
    Serial.print("Pressure ");
    Serial.println(pressure);
    Serial.print("Percent ");
    Serial.println(percentage);    
}


void updateReadOxySen() {
    if (currentMillis - previousReadOxySenMillis >= ReadOxySenInterval) {
          // time is up, so make anew read
        recvWithStartEndMarkers();
        if (newData == true) {
            strcpy(tempChars, receivedChars);
                // this temporary copy is necessary to protect the original data
                //   because strtok() used in parseData() replaces the commas with \0
            parseData();
            showParsedData();
            newData = false;
        }           
          // and save the time of change
       previousReadOxySenMillis += ReadOxySenInterval;
    }
}

Hello, it's me again, sorry!

I was to rashly :confused:

In general the above code is working fine, but there must be still a bug inside, I can't find out.
Every second read the value for pressure and percentage is wrong.

Output:
15:29:15.344 -> Oxygen 197.40
15:29:15.377 -> Temperature 22.90
15:29:15.377 -> Pressure 985
15:29:15.411 -> Percent 20.04
15:29:17.337 -> Oxygen 197.40
15:29:17.370 -> Temperature 22.80
15:29:17.370 -> Pressure 98
15:29:17.403 -> Percent 0.00
15:29:19.330 -> Oxygen 197.40
15:29:19.363 -> Temperature 22.80
15:29:19.397 -> Pressure 985
15:29:19.397 -> Percent 20.04
15:29:21.357 -> Oxygen 197.40
15:29:21.357 -> Temperature 22.90
15:29:21.390 -> Pressure 98
15:29:21.390 -> Percent 0.00

Both values are read from the sensor. It looks like the sensor is sending wrong values every second time.
If I try another sensor (I have two of them), it's the same behavior. After a while the wrong readings become randomly.
If I read in a very simple way, the sensors are sending correct data.

Simple way:

#include <SoftwareSerial.h>

SoftwareSerial mySerial(10, 11); // RX, TX

void setup() {
  Serial.begin(9600);
  mySerial.begin(9600);
}

void loop() {
 if (mySerial.available())  {
    Serial.write(mySerial.read());
    }
}

Maybe someone has an idea, where I should search for a solution? I ran out of ideas. :frowning:

Many thanks in advance!
Triaenodon

The '0000' on the end of your data is confusing subsequent attempts to read and parse data. You need to read it and throw it away.

Ok , thank you! How and where would you do that?

If I change "char endMarker = 'e';" to "char endMarker = '\n';" in the function "recvWithStartEndMarkers()" and add

strtokIndx = strtok(NULL, "\n"); // this continues where the previous call left off
errorox = atoi(strtokIndx); // convert this part to an integer

to the function "parseData()", I can't notice any change in the missreadings.

Post your new code.

#include <SoftwareSerial.h>
/*
sensor Pin3 OxySen (TX) to Arduino Pin 10(RX)
sensor Pin4 OxySen (RX) to Arduino Pin 11(TX)
*/
SoftwareSerial mySerial(10, 11); // RX, TX

const byte numChars = 43;
char receivedChars[numChars];
char tempChars[numChars];     // temporary array for use when parsing
      
float oxygen = 0.0;  // variables to hold the parsed data
float tempo = 0.0;
int pressure = 0;
float percentage = 0.0;
int errorox = 0;

boolean newData = false;

unsigned long currentMillis = 0;
unsigned long previousReadOxySenMillis = 0;
const long ReadOxySenInterval = 2000;

//============

void setup() {
    Serial.begin(9600);
    mySerial.begin(9600);
}

//============

void loop() {
    currentMillis = millis();  // capture the latest value of millis()
    updateReadOxySen();
}

//============

void recvWithStartEndMarkers() {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = 'O';
    //char endMarker = 'e';
    char endMarker = '\n';
    char rc;

    while (mySerial.available() > 0 && newData == false) {
        rc = mySerial.read();

        if (recvInProgress == true) {
            if (rc != endMarker) {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) {
                    ndx = numChars - 1;
                }
            }
            else {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                newData = true;
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
        }
    }
}

//============

void parseData() {      // split the data into its parts

    char * strtokIndx; // this is used by strtok() as an index

    //data from sensor send looks like: "O xxxx.x T yxx.x P xxxx % xxx.xx e xxxx\r\n"
    //e.g. "O 0020.1 T +19.3 P 1013 % 020.16 e 0001\r\n" 

    strtokIndx = strtok(tempChars,"T");      // get the first part - the string
    oxygen = atof(strtokIndx); // copy it to oxygen
    
    strtokIndx = strtok(NULL, "P"); // this continues where the previous call left off
    tempo = atof(strtokIndx);     // convert this part to a float

    strtokIndx = strtok(NULL, "%"); // this continues where the previous call left off
    pressure = atoi(strtokIndx);     // convert this part to an integer

    strtokIndx = strtok(NULL, "e"); // this continues where the previous call left off
    percentage = atof(strtokIndx);     // convert this part to a float

    strtokIndx = strtok(NULL, "\n"); // this continues where the previous call left off
    errorox = atoi(strtokIndx);     // convert this part to an integer
    
}

//============

void showParsedData() {
    Serial.print("Oxygen ");
    Serial.println(oxygen);
    Serial.print("TemperatureO ");
    Serial.println(tempo);
    Serial.print("Pressure ");
    Serial.println(pressure);
    Serial.print("Percent ");
    Serial.println(percentage); 
    Serial.print("Error from sensor ");
    Serial.println(errorox);   
}


void updateReadOxySen() {
    if (currentMillis - previousReadOxySenMillis >= ReadOxySenInterval) {
          // time is up, so make anew read
        recvWithStartEndMarkers();
        if (newData == true) {
            strcpy(tempChars, receivedChars);
                // this temporary copy is necessary to protect the original data
                //   because strtok() used in parseData() replaces the commas with \0
            parseData();
            showParsedData();
            newData = false;
        }           
          // and save the time of change
       previousReadOxySenMillis += ReadOxySenInterval;
    }
}

output looks like:

17:58:56.402 -> Oxygen 197.70
17:58:56.436 -> TemperatureO 23.00
17:58:56.436 -> Pressure 988
17:58:56.469 -> Percent 20.01
17:58:56.469 -> Error from sensor 0
17:58:58.428 -> Oxygen 197.70
17:58:58.428 -> TemperatureO 23.00
17:58:58.461 -> Pressure 98
17:58:58.461 -> Percent 0.00
17:58:58.495 -> Error from sensor 0
17:59:00.421 -> Oxygen 197.70
17:59:00.421 -> TemperatureO 23.10
17:59:00.454 -> Pressure 988
17:59:00.454 -> Percent 20.01
17:59:00.488 -> Error from sensor 0
17:59:02.413 -> Oxygen 197.70
17:59:02.413 -> TemperatureO 23.00
17:59:02.481 -> Pressure 98
17:59:02.481 -> Percent 0.00
17:59:02.481 -> Error from sensor 0
17:59:04.406 -> Oxygen 197.70
17:59:04.406 -> TemperatureO 22.90
17:59:04.439 -> Pressure 988
17:59:04.439 -> Percent 20.01
17:59:04.473 -> Error from sensor 0
17:59:06.399 -> Oxygen 197.70
17:59:06.432 -> TemperatureO 23.00
17:59:06.432 -> Pressure 98
17:59:06.465 -> Percent 0.00
17:59:06.465 -> Error from sensor 0
17:59:08.391 -> Oxygen 197.70
17:59:08.425 -> TemperatureO 23.10
17:59:08.425 -> Pressure 988
17:59:08.458 -> Percent 20.01
17:59:08.458 -> Error from sensor 0

That's odd. Can you try printing what you receive before you start using strtok on it?

Also,my apologies- the start char of 'O' should have taken care of the final 0000. My analysis was flawed.

I added
Serial.print(tempChars);
Serial.print(receivedChars);
to the function showParsedData() and it gives readings like this:

18:36:07.804 -> 0198.2 0198.2 T +22.8 P 0989 % 020.04 e 0000
Oxygen 198.20
18:36:07.871 -> TemperatureO 22.80
18:36:07.871 -> Pressure 989
18:36:07.904 -> Percent 20.04
18:36:07.904 -> Error from sensor 0
18:36:09.801 -> 0198.2 0198.2 T +22.8 P 098O 0198.2 T +22.8 P 09Oxygen 198.20
18:36:09.868 -> TemperatureO 22.80
18:36:09.901 -> Pressure 98
18:36:09.901 -> Percent 0.00
18:36:09.901 -> Error from sensor 0
18:36:11.794 -> 0198.1 0198.1 T +22.8 P 0989 % 020.03 e 0000
Oxygen 198.10
18:36:11.861 -> TemperatureO 22.80
18:36:11.894 -> Pressure 989
18:36:11.894 -> Percent 20.03
18:36:11.927 -> Error from sensor 0
18:36:13.787 -> 0198.1 0198.1 T +22.8 P 098O 0198.1 T +22.8 P 09Oxygen 198.10
18:36:13.854 -> TemperatureO 22.80
18:36:13.958 -> Pressure 98
18:36:13.958 -> Percent 0.00
18:36:13.958 -> Error from sensor 0
18:36:15.780 -> 0198.1 0198.1 T +22.7 P 0989 % 020.03 e 0000
Oxygen 198.10
18:36:15.847 -> TemperatureO 22.70
18:36:15.881 -> Pressure 989
18:36:15.881 -> Percent 20.03
18:36:15.913 -> Error from sensor 0
18:36:17.773 -> 0198.1 0198.1 T +22.8 P 098O 0198.1 T +22.8 P 09Oxygen 198.10
18:36:17.840 -> TemperatureO 22.80
18:36:17.873 -> Pressure 98
18:36:17.873 -> Percent 0.00
18:36:17.906 -> Error from sensor 0
18:36:19.799 -> 0198.1 0198.1 T +22.8 P 0990 % 020.01 e 0000
Oxygen 198.10
18:36:19.865 -> TemperatureO 22.80
18:36:19.865 -> Pressure 990
18:36:19.865 -> Percent 20.01
18:36:19.899 -> Error from sensor 0
18:36:21.792 -> 0198.1 0198.1 T +22.8 P 098O 0198.1 T +22.7 P 09Oxygen 198.10
18:36:21.859 -> TemperatureO 22.80
18:36:21.859 -> Pressure 98
18:36:21.892 -> Percent 0.00
18:36:21.892 -> Error from sensor 0

For me it looks like the function "recvWithStartEndMarkers()" does not work proper? It breaks in the middle and continues writing from the beginning?

Try cranking up the baud rate on serial, 115200 perhaps. No need to have it running at the same speed as the sensor. How frequently does the sensor send data?

Hi,

the sensor has a Baudrate of 9600.

Ok, but how often does it send a line of data?

Streaming 1 sample per second.

I'd suggest that you print what you received before you parse it. It's already partially processed by the time you do so now.

Also, it should not be necessary, but replacing this

    else if (rc == startMarker)
    {
      recvInProgress = true;
    }

with this:

    else if (rc == startMarker)
    {
      recvInProgress = true;
      ndx=0;
    }

Might help. If it does though, I think it'll be masking the issue, rather than solving it and you won't get every sample from the sensor.

This is what data looks like when it is just relayed with terminal program from examples (might open different perspective) note; no erroneous data:

Hi Zega, Thanks for your code, I have spent several days trying to parse the string before I found your post.

I found changing the ReadOxySenInterval to 1000 and the Serial Terminal to 115200 gave consistent results.

I then added a 16x2 LCD display to display the results on an test instrument and commented out the Serial Terminal.

/*****************************************************************************

   Arduino program to read LuminOx O2 Sensor into sensorString and display
   on the Serial Monitor, Extract the Oxygen, Temperature and Pressure
   and display on an LCD display.

 ******************************************************************************/

#include <SoftwareSerial.h>
#include <LiquidCrystal.h>

/*
sensor Pin3 OxySen (TX) to Arduino Pin 10(RX)
sensor Pin4 OxySen (RX) to Arduino Pin 9(TX)
*/
SoftwareSerial mySerial(10, 9); // RX, TX
LiquidCrystal lcd(7,6,5,4,3,2); //RS,EN,D4,D3,D2,D1 connections

const byte numChars = 43;
char receivedChars[numChars];
char tempChars[numChars];     // temporary array for use when parsing
     
float oxygen = 0.0;  // variables to hold the parsed data
float tempo = 0.0;
int pressure = 0;
float percentage = 0.0;
int errorox = 0;

boolean newData = false;

unsigned long currentMillis = 0;
unsigned long previousReadOxySenMillis = 0;
const long ReadOxySenInterval = 1000;

//============

void setup() {
    Serial.begin(115200);
    mySerial.begin(9600);
    lcd.begin(16,2);
}

//============

void loop() {
    currentMillis = millis();  // capture the latest value of millis()
    updateReadOxySen();
}

//============

void recvWithStartEndMarkers() {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = 'O';
    char endMarker = '\n';
    char rc;

    while (mySerial.available() > 0 && newData == false) {
        rc = mySerial.read();

        if (recvInProgress == true) {
            if (rc != endMarker) {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) {
                    ndx = numChars - 1;
                }
            }
            else {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                newData = true;
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
        }
    }
}

//============

void parseData() {      // split the data into its parts

    char * strtokIndx; // this is used by strtok() as an index

    //data from sensor send looks like: "O xxxx.x T yxx.x P xxxx % xxx.xx e xxxx\r\n"
    //e.g. "O 0020.1 T +19.3 P 1013 % 020.16 e 0001\r\n"

    strtokIndx = strtok(tempChars,"T");      // get the first part - the string
    oxygen = atof(strtokIndx); // copy it to oxygen
   
    strtokIndx = strtok(NULL, "P"); // this continues where the previous call left off
    tempo = atof(strtokIndx);     // convert this part to a float

    strtokIndx = strtok(NULL, "%"); // this continues where the previous call left off
    pressure = atoi(strtokIndx);     // convert this part to an integer

    strtokIndx = strtok(NULL, "e"); // this continues where the previous call left off
    percentage = atof(strtokIndx);     // convert this part to a float

    strtokIndx = strtok(NULL, "\n"); // this continues where the previous call left off
    errorox = atoi(strtokIndx);     // convert this part to an integer
   
}

//============

void showParsedData() {
/*    Serial.print("Oxygen ");
    Serial.println(oxygen);
    Serial.print("Temperature ");
    Serial.println(tempo);
    Serial.print("Pressure ");
    Serial.println(pressure);
    Serial.print("Percent ");
    Serial.println(percentage);
    Serial.print("Error from sensor ");
    Serial.println(errorox);   
*/

// Print to LCD display
  lcd.setCursor(0,0); //first row
  lcd.print("O2 ");
  lcd.print(oxygen); lcd.print(" ");
  lcd.print(percentage); lcd.print("%");
  lcd.setCursor(0,1); //second row
  lcd.print(tempo); lcd.print((char)223); lcd.print("C ");
  lcd.print(pressure); lcd.print(" hPa ");

}

void updateReadOxySen() {
    if (currentMillis - previousReadOxySenMillis >= ReadOxySenInterval) {
          // time is up, so make anew read
        recvWithStartEndMarkers();
        if (newData == true) {
            strcpy(tempChars, receivedChars);
                // this temporary copy is necessary to protect the original data
                //   because strtok() used in parseData() replaces the commas with \0
            parseData();
            showParsedData();
            newData = false;
        }           
          // and save the time of change
       previousReadOxySenMillis += ReadOxySenInterval;
    }
}