[solved] Problem reading CO2 concentration from COZIR sensor

I am trying to build an incubator that can control CO2 concentration at 5% by volume. Detailed instructions are available here:

https://www.pellinglab.net/single-post/diy/DIY-CO2-Incubator-Bioreactor-for-Mammalian-Cell-Culture

Project specification:
MCU: Arduino Mega 2560 R3
CO2 sensor: COZIR SprintIR-WX-20 0-20% CO2 sensor (Data sheet attached in the attachment and HERE)

I tried to test the sensor with the test code provided by the author:

#include "cozir.h"
#include "SoftwareSerial.h"

#define RxPin 15
#define TxPin 14

SoftwareSerial nss(RxPin, TxPin); // Tx, Rx from the sensor to Pins 15, 14 on Arduino
COZIR czr(nss);

float c, reading = 0;
float multiplier = 10; 
                           
void setup()
{
  Serial.begin(9600);
  czr.SetOperatingMode(CZR_POLLING);
  delay(100);
}

void loop()
{
  c = czr.CO2(); // read the sensor, values output as ppm/10
  reading = c*multiplier; // convert ppm reading to ppm
  Serial.print("CO2 Content: ");
  Serial.print(reading);
  Serial.println("ppm");
  Serial.println();
  delay(50);  
}

This code is using this library:

Here is the circuit:

You may not be able to see it clearly, there are only 4 connections:
Sensor Arduino
GND-------->GND
Vcc--------->3v3
Rx---------->14 (TX3)
Tx---------->15 (RX3)

The problem is that when I tried out the sensor, no matter how much CO2 I was blowing on its face (from a gas tank), it only reads 20ppm. I thought there may be some problem with the Cozir library, so I went online and found another test code which doesn't use the library

#include <SoftwareSerial.h>

#define Rx 15
#define Tx 14

SoftwareSerial mySerial(Rx, Tx); // RX, TX pins on Ardunio

int co2 = 0;
double multiplier = 10;// 1 for 2% =20000 PPM, 10 for 20% = 200,000 PPM
uint8_t buffer[25];
uint8_t ind = 0;
uint8_t index = 0;

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

void setup() {
  Serial.begin(9600);
  Serial.print("\n\n");
  Serial.println("             AN128 Ardunio to Cozir CO2 Sensor - Demonstration code 11/29/2017\n\n");

  pinMode(Rx, INPUT);
  pinMode(Tx, OUTPUT);
  mySerial.begin(9600); // Start serial communications with sensor
  //mySerial.println("K 0\r\n");  // Set Command mode
  mySerial.print("M 6\r\n"); // send Mode for Z and z outputs
  // "Z xxxxx z xxxxx" (CO2 filtered and unfiltered)

  mySerial.print("K 1\r\n");  // set streaming mode
}

void loop() {
  fill_buffer();  // function call that reacds CO2 sensor and fills buffer
  Serial.print("Buffer contains: ");
  for (int j = 0; j < ind; j++)Serial.print(buffer[j], HEX);
  index = 0;
  format_output();
  Serial.print(" Raw PPM        ");

  index = 8;  // In ASCII buffer, filtered value is offset from raw by 8 bytes
  format_output();
  Serial.println(" Filtered PPM\n\n");
}

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
    Serial.println(mySerial.available());
    if (mySerial.available()) {
      buffer[ind] = mySerial.read();
      ind++;
      Serial.print(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
}

int format_output(void) { // 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;
  Serial.print("\n CO2 = ");
  Serial.print(co2 * multiplier, 0);
  // Serial.print(" PPM,");
  //    Serial.print("\n");
  delay(200);
}

I still didn't get it to work, so I inserted the line "Serial.println(ind);" in the function fill_buffer(). On the serial monitor, ind is ALWAYS 0. That means NOTHING is in the serial receive buffer. I read in other threads that declaring the pinMode of Rx and Tx as OUTPUT and INPUT respectively may solve the problem, I tried it and it didn't work, now I am running out of idea. Could someone give me some advice on this issue?

SprintIR_-Apollo.PDF (789 KB)

uno ?? pins 2/3 and code refers to pins 14/15 (analog pins A0,A1 on the uno)

If you have a MEGA, you have 4 hardware serials. These are preferred above software serial.

Have you tried the test sketches of the library?

(Yes, I co-developed the library but do not have such sensor)

knut_ny:
uno ?? pins 2/3 and code refers to pins 14/15 (analog pins A0,A1 on the uno)

I am using Arduino Mega so I was using pins 14/15, which are Tx3 and Rx3

robtillaart:
If you have a MEGA, you have 4 hardware serials. These are preferred above software serial.

Have you tried the test sketches of the library?

(Yes, I co-developed the library but do not have such sensor)

I tried it. Nothing appear on the serial monitor, not even Serial.println("Setup")
Perhaps it is my Arduino board that is flawed?

and a minimal sketch like below does that print over serial?

void setup() 
{
  Serial.begin(9600);
  Serial.print("hello world");
}

void loop() 
{
}

robtillaart:
If you have a MEGA, you have 4 hardware serials. These are preferred above software serial.

Have you tried the test sketches of the library?

(Yes, I co-developed the library but do not have such sensor)

Thank you very much for the advice on changing the software serial to hardware serial. I am relatively new to Arduino and it took me quite some time to sort out how to properly use hardware serial on Arduino mega. I modified your library to utilize the 3 hardware serial on the mega, and guess what, it works! In case anyone would like to use mega and COZIR SprintIR sensor together, I attached a copy of the files I modified. A short summary of the things I changed is shown below:

cozir.h
Not much change here, just changed the class constructor

...

class COZIR
{
  public:
 COZIR(int port);
 
 void SetOperatingMode(uint8_t mode);

...

cozir.cpp
Same as header, only change the constructor definition

...
//
// CONSTRUCTOR
//
COZIR::COZIR(int port)
{
  if (port == 1) Serial1.begin(9600);
  else if (port == 2) Serial2.begin(9600);
  else if (port == 3) Serial3.begin(9600);
}

...

Test code body

/*
/////////////////////////////////////////////////////////////////////////
Simple program to read from a COZIR SprintIR CO2 sensor and report over serial.
Sensor powered by Arduino 5V. Tx/Rx on Arduino Pins 2/3.
CO2 Meter (SprintIR 0-20% GC-0017):
http://www.co2meter.com/collections/co2-sensors/products/sprintir-100-percent-co2-sensor
Also need the latest COZIR library, version 1.0 used for this code.
Online forum has links to library and lots of information:
http://forum.arduino.cc/index.php?topic=91467.0
/////////////////////////////////////////////////////////////////////////
*/

#include "cozir.h"

COZIR sensor(1); 
/* For mega, put port number in the bracket
 * 1 = pin 18, 19 on Arduino
 * 2 = pin 16, 17 on Arduino
 * 3 = pin 14, 16 on Arduino
 * Serial port 0 is reserved for serial monitor
*/

float c, reading = 0;
float multiplier = 10; 
                           
void setup()
{
  Serial.begin(9600);
  sensor.SetOperatingMode(CZR_POLLING);
  delay(100);
}

void loop()
{
  c = sensor.CO2(); // read the sensor, values output as ppm
  reading = c*multiplier; // convert ppm reading to percentage
  Serial.print("CO2 Content: ");
  Serial.print(reading);
  Serial.println("ppm");
  Serial.println();
  delay(50);  
}

Now that it works, I am wondering why SoftwareSerial does not work? I obviously do not have great styling in writing library, would you consider an update to this library so that it allows people to use hardware serial?

CO2_test_hardware_serial.zip (4.02 KB)

Thanks for your feedback,

as the constructor is COZIR(Stream *) the following should have worked too

COZIR czr(&Serial1);

and in setup()

Serial1.begin(9600);

but your proposal makes me think that the constructor could do the begin() call

ser.begin(9600);

Hi,

I tried using " CO2_test_hardware_serial.zip", but the problem persists. I keep getting 20 ppm in my serial monitor. Any suggestions?

ExplodeMilk,

I think you use the Operating mode K 0 which stops the conversion. Use K 2 mode (POLL MODE) then you get a reading after every request
Otto