CO2 Engine (K33) BLG CO2-Sensor strange Data via I2C

Dear Arduino-Enthusiasts,

My Introduction:
I am relatively new to Arduino and the Community and this is the first problem within a project I was not able to solve on my own within some weeks. I am trying to incroporate the Arduino in an univsersity project, where it will controll some sensors measuring CO2, relative humidity, temperature and pressure during air tight storage of grains. I am an agriculrural / environmental scientist and not a computer engineer, so programming unfortunately is not my background. I apologize if I should mix things up. I am trying to be as correct and precise as possible.
I hope the forum here will provide me the droids....uhm answers I am looking for.

Overview of the problem:
When reading out CO2 concentration via I2C from a Senseair CO2-Engine BLG (similar to CO2 Meter K33 BLG) the two bytes that should contain the data only are (in hex) 7F and FF. They do not change. All in all, I am reading four bytes: Status, Data, Checksum.
Statusbyte and Checksum appear to be okay, the output allways is (Hex): 21|7F|FF|9F .

Detailed problem, setup and code
I am having an Arduino Pro Mini 3.3V / 8 Mhz hooked up via FTDI to my computer.

The Senseair K33 BLG (diffusion, not the one with pump) is connected via I2C-

SDA goes to A4 and SCL to A5. Power supply is 5V and comes from external current (Arduino Uno 5V output).

The code is modified from the one supplied by Andrew Robinson, CO2 Meter <co2meter.com> (Thanks to you guys by the way, here is the original).

I think, most of the code is working. I have modified it for debugging reasons, so it will Serial.print() the buffer bytes that are received via Wire.read().

The Sensor is working, I get rather reliable data for RH and Temperature I have checked with an other sensor. Also, there is a status light within the K33 that blinks twice at the beginning of each measurment.

The wake up cycle seems to be working, the measurment cylce as well. Measurment of CO2, RH and temperature are coded in 3 separate functions.

Now comes the sequence that troubles me. This is the request for CO2 data within function double readCO2()

Wire.beginTransmission(K33); //int K33 == 0x68
  Wire.write(0x22); //Command number 2 (Read Ram, 2 bytes)
  Wire.write(0x00); //Sensor address in ?? EEPROM ?? I have no clue what this one does
  Wire.write(0x08); //Two bytes starting from 0x08 (high byte) and 0x09 (low byte). They contain the CO2 data
  Wire.write(0x2A); //Checksum
Wire.endTransmission(); 
  delay(50);

The addresses are taken from the CO2meter.com example and rechecked with 2 documentations. The draft one from Senseair (pdf via cdn.shopify.com) and the one from CO2meter.com, which is better (pdf via source.hacker.lu)

Afterwards the data from the sensor is read, the bytes are transformed and stored in int CO2_value:

Wire.requestFrom(K33, 4); 
  byte i = 0; 
  byte buffer[4] = {0, 0, 0, 0}; 
  while(Wire.available()) { 
      buffer[i] = Wire.read(); 
      i++; }   
    
  CO2_value = 0; 
  CO2_value |= buffer[1] & 0xFF;
  CO2_value = CO2_value << 8; 
  CO2_value |= buffer[2] & 0xFF;

To be honest, I don't really understand what is happening here. It is bitwise operation. From my understanding, the high byte and the low byte a transformed to an integer, which would be 2 bytes long.

Including the Serial.print() function for debugging and the functions for RH and temperature, this is what the serial monitor will show:

RH  Data 21|B|5A|86
T   Data 21|9|D7|1
CO2 Data 21|7F|FF|9F
RH: 29.06% | Temp: 25.19C | CO2: 32767ppm

I understand, that 32767 is the maximum value of an integer in a Pro Mini board and that this value is achieved from the above posted bitwise operators. Nevertheless, this does not solve my problem with the CO2 data, since the bytes that should contain the values do not change.

I also understand, that 0x7F is the address for "any sensor" and 0xFF the byte being sent for a response from the K33 to this address (see page 22 from the CO2meter.com documentation). What is the point in having these information in the bytes that should contain the CO2 data?

An option might be, that the RAM address I am using is wrong. I also tried (no particular reason, just trial and error) with 0x10 and 0x16, but both do not yield CO2 data. I understand, that the example from CO2meter.com used and Arduino Due which stores 4 byte in an integer, but I don't see any difference that would be making here.

I have no idea, where to continue from here on and would highly appreciate any idea or hint that could help!
For better understanding I'll attach the complete code.

Best regards and thanks for a discussion,
Simon

// Arduino Pro Mini 3.3 V and K33 BLG 
// Modified by Smundo from original version
////////////
// CO2 Meter K?series Example Interface 
// by Andrew Robinson, CO2 Meter <co2meter.com> 
// Talks via I2C to K33?ELG/BLG Sensors for Host?Initiated Data Collection 
// 4.1.2011 
#include <Wire.h> 

int K33 = 0x68;    
// This is the default address of the CO2 sensor, 7bits shifted left. 
void setup() { 
  Serial.begin(9200);     
  Wire.begin (); 
  pinMode(13, OUTPUT);  // We will use this pin as a read?indicator 
  Serial.println("Start"); 
} 
 
/////////////////////////////////////////////////////////////////// 
// Function : void wakeSensor() 
// Executes : Sends wakeup commands to K33 sensors. 
// Note     : THIS COMMAND MUST BE MODIFIED FOR THE SPECIFIC AVR YOU ARE USING 
//            THE REGISTERS ARE HARD?CODED 
///////////////////////////////////////////////////////////////// 
 
void wakeSensor() { 
  // This command serves as a wakeup to the CO2 sensor, for K33?ELG/BLG Sensors Only 
   
  // You'll have the look up the registers for your specific device, but the idea here is simple: 
  // 1. Disabled the I2C engine on the AVR 
  // 2. Set the Data Direction register to output on the SDA line 
  // 3. Toggle the line low for ~1ms to wake the micro up. Enable I2C Engine 
  // 4. Wake a millisecond. 
   
  TWCR &= ~(1<<2); // Disable I2C Engine 
  DDRC |= (1<<4); // Set pin to output mode 
  PORTC &= ~(1<<4); // Pull pin low 
  delay(1); 
  PORTC |= (1<<4); // Pull pin high again 
  TWCR |= (1<<2); // I2C is now enabled 
  delay(1);     
}
 
/////////////////////////////////////////////////////////////////// 
// Function : void initPoll() 
// Executes : Tells sensor to take a measurement. 
// Notes    : A fuller implementation would read the register back and  
//            ensure the flag was set, but in our case we ensure the poll 
//            period is >25s and life is generally good. 
/////////////////////////////////////////////////////////////////// 
void initPoll() { 
  Wire.beginTransmission(K33); 
  Wire.write(0x11); 
  Wire.write(0x00); 
  Wire.write(0x60); 
  Wire.write(0x35); 
  Wire.write(0xA6); 
   
  Wire.endTransmission(); 
  delay(20);  
  Wire.requestFrom(K33, 2); 
    
  byte i = 0; 
  byte buffer[2] = {0, 0}; 
 
  while(Wire.available()) { 
      buffer[i] = Wire.read(); 
      i++; 
  }   
   
}
 
/////////////////////////////////////////////////////////////////// 
// Function : double readRH() 
// Returns  : The current RH Value, ?1 if error has occured 
/////////////////////////////////////////////////////////////////// 
 
double readRH() {
  int RH_value = 0;   // We will store the RH value inside this variable.  
  digitalWrite(13, HIGH);                
  
  ////////////////////////// 
  /* Begin Write Sequence */ 
  ////////////////////////// 
   
  Wire.beginTransmission(K33); 
  Wire.write(0x22); //Command number 2 (Read Ram, 2 bytes)
  Wire.write(0x00); //Sensor address in ?? EEPROM ??
  Wire.write(0x14); //Two bytes starting from 0x14 (high byte) and 0x15 (low byte)
  Wire.write(0x36); //Checksum
   
  Wire.endTransmission(); 
  
  delay(20); 
  
  /////////////////////////   
  /* Begin Read Sequence */ 
  /////////////////////////  
    
  Wire.requestFrom(K33, 4); 
    
  byte i = 0; 
  byte buffer[4] = {0, 0, 0, 0}; 
  
while(Wire.available()) { 
      buffer[i] = Wire.read(); 
      i++; 
  }   
    
  RH_value = 0; 
  RH_value |= buffer[1] & 0xFF;
  RH_value = RH_value << 8; 
  RH_value |= buffer[2] & 0xFF;
  Serial.print("RH  Data ");
  Serial.print(buffer[0], HEX);
  Serial.print("|"); 
  Serial.print(buffer[1], HEX);
  Serial.print("|");  
  Serial.print(buffer[2], HEX);
  Serial.print("|"); 
  Serial.println(buffer[3], HEX); 
  
  byte sum = 0;                              //Checksum Byte 
  sum = buffer[0] + buffer[1] + buffer[2];   //Byte addition utilizes overflow 
   
  if(sum == buffer[3]) { 
      // Success! 
      digitalWrite(13, LOW); 
      delay(10); 
      return ((double)RH_value / (double) 100); 
  }   
  else { 
   // Failure!  
   digitalWrite(13, LOW);
   delay(10);
   return ((double) -1); 
  }   
} 

/////////////////////////////////////////////////////////////////// 
// Function : double readTemp() 
// Returns  : The current Temperature Value, ?1 if error has occured 
/////////////////////////////////////////////////////////////////// 
 
double readTemp() {
  int Temp_value = 0;   // We will store the temperature value inside this variable.  
  digitalWrite(13, HIGH);                
  
  ////////////////////////// 
  /* Begin Write Sequence */ 
  ////////////////////////// 
   
  Wire.beginTransmission(K33); //int K33 == 0x68
  Wire.write(0x22); //Command number 2 (Read Ram, 2 bytes)
  Wire.write(0x00); //Sensor address in ?? EEPROM ??
  Wire.write(0x12); //Two bytes starting from 0x12 (high byte) and 0x13 (low byte)
  Wire.write(0x34); //Checksum
   
  Wire.endTransmission(); 
  
  delay(20); 
  
  /////////////////////////   
  /* Begin Read Sequence */ 
  /////////////////////////  
    
  Wire.requestFrom(K33, 4); 
    
  byte i = 0; 
  byte buffer[4] = {0, 0, 0, 0}; 
  
while(Wire.available()) { 
      buffer[i] = Wire.read(); 
      i++; 
  }   
    
  Temp_value = 0; 
  Temp_value |= buffer[1] & 0xFF;
  Temp_value = Temp_value << 8; 
  Temp_value |= buffer[2] & 0xFF;
  Serial.print("T   Data ");
  Serial.print(buffer[0], HEX);
  Serial.print("|"); 
  Serial.print(buffer[1], HEX);
  Serial.print("|");  
  Serial.print(buffer[2], HEX);
  Serial.print("|"); 
  Serial.println(buffer[3], HEX); 
  
  byte sum = 0;                              //Checksum Byte 
  sum = buffer[0] + buffer[1] + buffer[2];   //Byte addition utilizes overflow 
   
  if(sum == buffer[3]) { 
      // Success! 
      digitalWrite(13, LOW); 
      delay(10); 
      return ((double)Temp_value / (double) 100); 
  }   
  else { 
   // Failure!  
   digitalWrite(13, LOW);
   delay(10);
   return ((double) -1); 
  }   
} 

/////////////////////////////////////////////////////////////////// 
// Function : double readCO2() 
// Returns  : The current CO2 Value, ?1 if error has occured 
/////////////////////////////////////////////////////////////////// 
 
double readCO2() {
  int CO2_value = 0;   // We will store the temperature value inside this variable.  
  digitalWrite(13, HIGH);                
  
  ////////////////////////// 
  /* Begin Write Sequence */ 
  ////////////////////////// 
   
  Wire.beginTransmission(K33); //int K33 == 0x68
  Wire.write(0x22); //Command number 2 (Read Ram, 2 bytes)
  Wire.write(0x00); //Sensor address in ?? EEPROM ??
  Wire.write(0x08); //Two bytes starting from 0x08 (high byte) and 0x09 (low byte). They contain the CO2 data
  Wire.write(0x2A); //Checksum
   
  Wire.endTransmission(); 
  
  delay(50); 
  
  /////////////////////////   
  /* Begin Read Sequence */ 
  /////////////////////////  
    
  Wire.requestFrom(K33, 4); 
    
  byte i = 0; 
  byte buffer[4] = {0, 0, 0, 0}; 
  
while(Wire.available()) { 
      buffer[i] = Wire.read(); 
      i++; 
  }   
    
  CO2_value = 0; 
  CO2_value |= buffer[1] & 0xFF;
  CO2_value = CO2_value << 8; 
  CO2_value |= buffer[2] & 0xFF;
  Serial.print("CO2 Data ");
  Serial.print(buffer[0], HEX);
  Serial.print("|"); 
  Serial.print(buffer[1], HEX);
  Serial.print("|");  
  Serial.print(buffer[2], HEX);
  Serial.print("|"); 
  Serial.println(buffer[3], HEX); 
  
  byte sum = 0;                              //Checksum Byte 
  sum = buffer[0] + buffer[1] + buffer[2];   //Byte addition utilizes overflow 
   
  if(sum == buffer[3]) { 
      // Success! 
      digitalWrite(13, LOW); 
      delay(10); 
      return ((double)CO2_value * (double) 1); 
  }   
  else { 
   // Failure!  
   digitalWrite(13, LOW);
   delay(10);
   return ((double) -1); 
  }   
} 

/////////////////////////////////////////////////////////////////// 
// Function : void loop() 
// Returns  : Read RH and then temperature
/////////////////////////////////////////////////////////////////// 

void loop() { 
  // We keep the sample period >25s or so, else the sensor will start ignoring sample requests. 
  wakeSensor(); 
  initPoll(); 
   
  delay(16000);
  wakeSensor(); 
  double RHValue = readRH();   
 
  delay(20);
  wakeSensor(); 
  double TempValue = readTemp(); 
 
  delay(50);
  wakeSensor(); 
  double CO2Value = readCO2();   
    
  if(RHValue >= 0) { 
        Serial.print("RH: "); 
        Serial.print(RHValue); 
        Serial.print("% | Temp: ");
        Serial.print(TempValue); 
        Serial.print("C | CO2: ");
        Serial.print(CO2Value, 0);
        Serial.println("ppm"); 
        Serial.println();       
  }     
  else { 
        Serial.println(" | Checksum failed / Communication failure"); 
  }     
  delay(9000); 
}

what is probably happening, is that your I2C communication isn't actually working.

michinyon:
what is probably happening, is that your I2C communication isn't actually working.

Blowing on the sensor results in an increase of relative humidity and temperature, so I am pretty positive that communication is established. Is it possible, that the communications is partially not working (only for the CO2 function) ?

Well I2C tends to either work, or not work.

When it is not working, your arduino won't know that it is not working, what will happen is that your attempts to read information from the device will just return rubbish, typically mostly F's which is what was stated in the first post.

Seems like something to go on with, I try to work on that, thanks!

I'm sorry, but it is confusing for me.

The sensor has a 3.3V I2C bus, and you use a 8MHz 3.3V Arduino Pro Mini. So far so good.
Do you use 5V at the 'RAW' pin ? And that 5V is from an Arduino Uno board ? Okay, no problem there.
How do you power the sensor ? Also with 5V ? That is not good. According to the specification of the SenseAir it should be at least 5.5V. Get a power supply for that.

I would like to know if the wires are okay and there is no disturbance.
Can you let these scanners run for about 10 minutes, to see if the result is stable ?
http://playground.arduino.cc/Main/I2cScanner
http://forum.arduino.cc/index.php?topic=197360

I'm not happy with the wake up sequence. It might interfere with the Wire library.
But let's assumet that it is working.
A workaround would be by first setting SDA as output and make it low for 1ms, and after that undo that by setting SDA to input and call Wire.begin() for the first time.

I'm also not happy with the code. The return values of the Wire functions are not checked, so you would not know if an error occurs.

I'm also not happy how the data is read. If nothing is available, the values are already pre-filled with zeros, and they would stay zero. Everything relies on the checksum, which could be valid with everything zero. I think that is bad programming.

Finally, I get to the problem.
Your CO2 values are stuck to : 0x21, 0x7F, 0xFF, 0x9F
The 0x9F is the checksum.
The "0x7F, 0xFF" is the maximum signed integer value. 0x7F is the high byte. The sensor could be broken.
I don't know what that 0x21 is doing there.
This is getting very complicated very fast, it seems that the module uses a software i2c implementation with all kind of weird behaviour.
I think the I2C is actually switched off while the sensor is measuring the CO2.

(While I was typing this and reading the datasheets, 4 others have already replied. I think you have some testing to do. Let us know the result).

Thank you Caltoa, I am trying your suggestions.

For clarification: The pro mini is powered via FTDI, that is 3.3V and the VCC pin.

Yes, the sensor is powered with 5.5V. The 5V form the Uno board only goes to the K33's G+ while the Uno's GND is connected to K33 G0. I can supply a drawing later, hope I'm not doing something incredibly stupid here by having a power supply of the sensor completely independent from the Pro Mini.

You are compelely right, datasheets talk of a minimum of 5.5V, I have no clue how I managed to ignore that. Thanks for pointing that out, I will switch to 9V and report the result.

The I2C Scanner from the playground found a device on 0x68 for every cycle it ran. The other scanner I will have to try out tomorrow, untill now it stops at 0x65 but I didn't spend time on reading yet :slight_smile:
I'll get back tomorrow, many thanks for the help!

Everything works now.

For future reference:

The K33 BLG can not be powered by 5V from Arduino. Nevertheless, it works nicely with 9 or 12 V.

The RH and Temp sensor is situated on the backside of the platine. It is the Sensiron SHT11, which can operate at lower voltage, whichs is why it gave out reasonable data.

I have added a function for calibration while connected to Arduino. Please not, that inclusion of this function only makes sense if it is called intentionally, if you want to have something automatic, use the ABC algorithm already provided in the Sensor.

/////////////////////////////////////////////////////////////////// 
// Function : void calibrate() 
// Executes : Starts a calibration after netx measurement.
/////////////////////////////////////////////////////////////////// 
void calibrate() {
  wakeSensor();
  Wire.beginTransmission(K33);
  Wire.write(0x12);
  Wire.write(0x00); 
  Wire.write(0x42); 
  Wire.write(0x7C);
  Wire.write(0x06);;
  Wire.write(0xD6); 
  Wire.endTransmission(true); 
}

You can easily access the Sensor with the software UIP5 provided by SenseAir (The original manufacturer).

Take a Serial to USB FTDI Adapter and connect to the UART-Terminal. Please check the attached picture for prober wiring. This will provide access to all of the sensors functions.

Hi. I also have a Sensor from SenseAir and am trying to get readings from it with an Arduino Uno board. The sensor is the CO2 Engine K33-LP T/RH. I wired it according to figure 2 on page 4 of this pdf. The Data sheet for the sensor I am using referenced this pdf linked above.

Based on what I read in the posts above, and in the data sheet for the sensor I've connected the G+ and G0 pins to en external power supply and am supplying it with 9.2 V. That leaves the SDA SCL and DVCC leads which are connected to the Arduino (DVCC to 3.3V, SDA to A4, and SCL to A5).

I currently have the code posted by Smundo on Feb 18, 2014, 02:09 pm on the board. The readings in the Comport are all Zeros though; the following is copied and pasted:

RH Data 0|0|0|0
T Data 0|0|0|0
CO2 Data 0|0|0|0
RH: 0.00% | Temp: 0.00C | CO2: 0ppm

I've run a scanner from this page to check the address of the sensor, but the report is consistently "No I2C devices found".

I also tried the code at the end of this post, but it also failed to deliver any readings other than zeros. Does anyone have any idea what could be going wrong?

// CO2 Meter K-series Example Interface
// by Andrew Robinson, CO2 Meter <co2meter.com>
// Talks via I2C to K30/K22/K33/Logger sensors and displays CO2 values
// 12/31/09
#include <Wire.h>
// We will be using the I2C hardware interface on the Arduino in
// combination with the built-in Wire library to interface.
// Arduino analog input 5 - I2C SCL
// Arduino analog input 4 - I2C SDA
/*
 In this example we will do a basic read of the CO2 value and checksum verification.
 For more advanced applications please see the I2C Comm guide.
*/
int co2Addr = 0x68;
// This is the default address of the CO2 sensor, 7bits shifted left.
void setup() {
 Serial.begin(9600);
 Wire.begin ();
 pinMode(13, OUTPUT); // We will use this pin as a read-indicator
 Serial.println("What a wonderful day, to read atmospheric CO2 concentrations!");
}
///////////////////////////////////////////////////////////////////
// Function : int readCO2()
// Returns : CO2 Value upon success, 0 upon checksum failure
// Assumes : - Wire library has been imported successfully.
// - LED is connected to IO pin 13
// - CO2 sensor address is defined in co2_addr
///////////////////////////////////////////////////////////////////
int readCO2()
{
 int co2_value = 0;
 // We will store the CO2 value inside this variable.
 digitalWrite(13, HIGH);
 // On most Arduino platforms this pin is used as an indicator light.

 //////////////////////////
 /* Begin Write Sequence */
 //////////////////////////

 Wire.beginTransmission(co2Addr);
 Wire.write(0x22);
 Wire.write(0x00);
 Wire.write(0x08);
 Wire.write(0x2A);
 
  Wire.endTransmission();

 /////////////////////////
 /* End Write Sequence. */
 /////////////////////////

 /*
 We wait 10ms for the sensor to process our command.
 The sensors's primary duties are to accurately
 measure CO2 values. Waiting 10ms will ensure the
 data is properly written to RAM

 */

 delay(100);

 /////////////////////////
 /* Begin Read Sequence */
 /////////////////////////

 /*
 Since we requested 2 bytes from the sensor we must
 read in 4 bytes. This includes the payload, checksum,
 and command status byte.

 */

 Wire.requestFrom(co2Addr, 4);

 byte i = 0;
 byte buffer[4] = {0, 0, 0, 0};

 /*
 Wire.available() is not nessessary. Implementation is obscure but we leave
 it in here for portability and to future proof our code
  */
 while(Wire.available())
 {
 buffer[i] = Wire.read();
 i++;
 }

 ///////////////////////
 /* End Read Sequence */
 ///////////////////////

 /*
 Using some bitwise manipulation we will shift our buffer
 into an integer for general consumption
 */

 co2_value = 0;
 co2_value |= buffer[1] & 0xFF;
 co2_value = co2_value << 8;
 co2_value |= buffer[2] & 0xFF;


 byte sum = 0; //Checksum Byte
 sum = buffer[0] + buffer[1] + buffer[2]; //Byte addition utilizes overflow

 if(sum == buffer[3])
 {
 // Success!
 digitalWrite(13, LOW);
 return co2_value;
 }
 else
 {
 // Failure!
 /*
 Checksum failure can be due to a number of factors,
 fuzzy electrons, sensor busy, etc.
 */

 digitalWrite(13, LOW);
 return 0;
 }
}
void loop() {

 int co2Value = readCO2();
 if(co2Value > 0)
 {
 Serial.print("CO2 Value: ");
 Serial.println(co2Value);
 }
 else
 {
 Serial.println("Checksum failed / Communication failure");
 }
 delay(2000);
}

Not sure if anyone is checking this, but I got it working... it was just a broken connection in the plug-connection which I soldered to the board; I replaced the broken part and it works fine. The code works great!

I know this is really old, but unfortunately I have this exact same issue and it isn't as easy as a plug connection. I have tried all above codes and wiring schemes to no avail.

I2C scanner does not find the address of the device and the only thing the code ever displays is Checksum Failed/Communication failure. I'm using the K30 version of this sensor and an Arduino Uno (but the code on the website are identical).

A maybe somewhat helpful note is when I apply 3.3V to the DVCC pin I read about 3.27 on A4[SDA] and as high as 3.8 on A5[SCL]. This is just with a cheap multimeter, so I'm not sure how accurate it is, but I thought it was worth a note.

Any help is GREATLY appreciated.

Thanks....
Chase