CO2 Sensor + I2C Communication Failure

I'm trying to accomplish the simplest reading of a CO2 sensor via I2C and an Arduino Uno.

I have checked and rechecked my circuit. The current setup is as follows:

External power supply connected to CO2 sensor (have tried all sorts of different voltages from 5 to 12). One of the application notes states to power from an external 6 to 9V supply while the website states it can handle from 5.5 to 14 Volts. CO2 sensor lights up and blinks regularly with any voltage applied.

Arduino powered through my laptop.

I have two 4.7K pullup resistors on I2C lines (one on each line), connected to the 5V arduino rail on my breadboard.

I have created a common ground between the power supply and the Arduino.

I have used both these application notes (which are almost identical) with no success.

SENOSOR LINK

Application Note 1

Application Note 2

Troubleshooting steps were as follows:

  1. I have tried multiple different voltages between 5 and 12 (with no success).

  2. I have tried swapping the SCL and SDA in case I had confused them (with no success).

  3. I have tried not using a common ground (with no success).

  4. I have tried removing the pullup resistors (with no success).

  5. I have tried powering the sensor from the Arduino 5V pin even though this is not recommended (with no success).

The exact code I'm using is shown below (copied verbatim from the company's application note). When trying to receive readings, the serial monitor only prints out "Checksum Failed/Communication Failure." Is anything obvious in the code on why this might not be working? Such as outdated code format? I can't for the life of me find the error and I don't have an Oscilliscope or logic analyzer.

// CO2 Meter K-series Example Interface
// Revised by Marv Kausch, 7/2016 at CO2 Meter <co2meter.com>
// Talks via I2C to K30/K33 Sensors and displays CO2 values

#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 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); // address of the Arduino LED indicator
 Serial.println("Application Note AN-102: Interface Arduino to K-30");
}

///////////////////////////////////////////////////////////////////
// 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; // Store the CO2 value inside this variable.
 digitalWrite(13, HIGH); // turn on LED
 
 // 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. */
 /////////////////////////
 
 /*
 Wait 10ms for the sensor to process our command. The sensors's
 primary duties are to accurately measure CO2 values. Waiting 10ms
 ensures the data is properly written to RAM
 */

 delay(10);
 
 /////////////////////////
 /* 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 necessary. 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);
}

Thanks in advance for your help. I really appreciate you taking the time.

Respectfully,
Chase

Have you run the I2CScanner sketch, to confirm that the address is correct?

@PaulS

I have not. I will do this and report back.

@PaulS

I2C_Scanner Code I am using:

// --------------------------------------
// i2c_scanner
//
// Version 1
//    This program (or code that looks like it)
//    can be found in many places.
//    For example on the Arduino.cc forum.
//    The original author is not know.
// Version 2, Juni 2012, Using Arduino 1.0.1
//     Adapted to be as simple as possible by Arduino.cc user Krodal
// Version 3, Feb 26  2013
//    V3 by louarnold
// Version 4, March 3, 2013, Using Arduino 1.0.3
//    by Arduino.cc user Krodal.
//    Changes by louarnold removed.
//    Scanning addresses changed from 0...127 to 1...119,
//    according to the i2c scanner by Nick Gammon
//    http://www.gammon.com.au/forum/?id=10896
// Version 5, March 28, 2013
//    As version 4, but address scans now to 127.
//    A sensor seems to use address 120.
// Version 6, November 27, 2015.
//    Added waiting for the Leonardo serial communication.
//
//
// This sketch tests the standard 7-bit addresses
// Devices with higher bit address might not be seen properly.
//
 
#include <Wire.h>
 
 
void setup()
{
  Wire.begin();
 
  Serial.begin(9600);
  while (!Serial);             // Leonardo: wait for serial monitor
  Serial.println("\nI2C Scanner");
}
 
 
void loop()
{
  byte error, address;
  int nDevices;
 
  Serial.println("Scanning...");
 
  nDevices = 0;
  for(address = 1; address < 127; address++ )
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
 
    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.print(address,HEX);
      Serial.println("  !");
 
      nDevices++;
    }
    else if (error==4)
    {
      Serial.print("Unknown error at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.println(address,HEX);
    }    
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");
 
  delay(5000);           // wait 5 seconds for next scan
}

It always returns "Scanning... No I2C Devices Found"

I found another document giving an in-depth look at how to use I2C should be used with the sensor.

It offers a schematic and discusses the use of the DVCC pin. The DVCC pin is used to apply the approproiate I2C voltage since it differs from how much voltage is needed to power the sensor.

For my situation (with the WEMOS), I figured I could use the 3.3v pin on the Wemos to supply 3.3V to the I2C lines via the DVCC pin. The Wemos is currently being powered from my computer.

I then am powering the sensor from an external power supply (have used voltages from 5 to 12).

It also states that the SCL and SDA lines have internal pull up resistors that come with the sensor, so I don't think I need any.

I have attached a couple images from the document for clarity (my Sensor is the K30).

Once again, all help is greatly appreciated as I just can't get this darn thing to work.

I2CTable.PNG

Allowable Voltages.PNG

I2CSchematic.PNG

I2CSchematic2.PNG

Resistor Values.PNG

The K series CO2 meters use a lot of current when the LED flashes. The WEMOS may not be capable of providing that current. It's better to power it from 5V if that's available.

You must connect the grounds. 2-wire I2C communication always requires a ground wire. (one-wire also requires a ground.)

Until the I2C scanner reports an address, it's useless to play with any other code.

I've always used Serial MODBUS to talk to these sensors. I never tried I2C.

@MorganS

Thanks so much for taking the time to respond.

I also cannot get an Arduino Uno to recognize the I2C address when I run the scanner program. I have been powering the sensor with an external power supply for all of these tests. Usually around 9 volts because that seems to be the number where people don't have any problems.

The sensors also have a DVCC pin (which I mentioned earlier) that is meant to be separate from the sensor power and is only associated with the logic of the I2C side of things... this is the only pin that I was powering by the Wemos in one test (and an Arduino in another test.)

I have tested 3 situations, all of which I supplied 9V to the sensor with an external power supply and had a common ground between everything.

  1. providing 5V to DVCC via an Arduino
  2. providing 3.3V to DVCC via an Arduino
  3. providing 3.3V to DVCC via a WEMOS (ESP8266)

None of the scans found an I2C address.

As a side note, the sensor works fantastic if not using I2C, and instead, using a custom library/code found HERE or another example seen below...

#include "kSeries.h"

// Create K30 instance on pin 6 & 7
kSeries K_30(6,7);

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

void loop()
{
  // Get CO2 value from sensor
  double co2 = K_30.getCO2('p');

  // Print the value on Serial
  Serial.print("Co2 ppm = ");
  Serial.println(co2);

  // Wait 2 seconds
  delay(2000);
}

The problem, however, is that I can't get this code to compile on the WEMOS, only on the Arduino. I'm wondering if it has anything to do with SoftwareSerial not working the same on the ESP8266? [EDIT: It does have to do with SoftwareSerial, specifically avr/interrupt.h]

My project requires the ability to transmit this data wirelessly, therefore my workaround was going to be to have the Arduino take the measurements with the code that works, and then transfer those measurements via I2C to the WEMOS and let the WEMOS then throw this measurement over the airwaves. It's ridiculously tedious when the WEMOS itself should simply be able to read the value via I2C directly from the sensor and do all these things.

Once again, any help is greatly appreciated. I have to imagine someone has gotten this beauty up and running on I2C.

Thanks once again for looking into this as I'm crashing and burning fast right now...

Respectfully,
Chase

UPDATE:

I found this code via the wonderful Igrr [Source: ORIGINAL CODE FOUND HERE

I slimmed it down and tried it... Unfortunately it does not work either. I only get a constant 16616 and some garbage characters. I understand this uses Serial instead of I2C. At this point, I don't care how it gets working... I just need to figure this out.

Any ideas?

void setup() {
  delay(1000);
  Serial.begin(9600);
  delay(1000);
}
  int value = 0;

void loop() {
  delay(5000);

  Serial.println("reading co2 level");
  const uint8_t request[] = {0xFE, 0x44, 0x00, 0x08, 0x02, 0x9F, 0x25};
  Serial.write(request, sizeof(request));
  uint8_t response[8];
  int t = millis();
  for (int i = 0; i < 7; ++i) {
    Serial.println("Entered the for loop");
    while (Serial.available()) {
      response[i] = Serial.read();
      Serial.println('response[i]');
    }
    if (millis() - t > 1000) {
      Serial.println("no response");
      return;
    }
  }

  int co2value = static_cast<int>(response[3]) * 256 + static_cast<int>(response[4]);
  Serial.print("co2:");
  Serial.println(co2value);
}

Thanks,
Chase

So... Large update here. I have also posted this over at GitHub in case anyone wants to follow the action there. Posting here, however, because I believe these images could also be helpful to some members of this community. This is somewhat becoming a sensor issue, so I'm honestly not sure if I should make a separate post? I'll let the moderators decide.

GitHub post link

While the post at GitHub describes all the testing done with the ESP8266 Wemos, in this post I am also including the testing done with an Arduino Uno.

As an interesting note: The company's datasheet claims that internal pull-up resistors exist from the I2C lines to the DVCC. I assumed this meant you shouldn't need any... I think all of these tests show that's absolutely false (test 19, for example, shows no external resistors added and the wave is nowhere near square).

If anyone can get me headed in the right direction with the questions I'm asking below, I'd be extremely grateful.

Thanks,
Chase


Basic Info

Trying to connect to CO2 Sensor K30 via I2C but cannot even get I2C scanner code to find the device address.

Sensor Webpage HERE

Sensor Data Sheet HERE

Sensor I2C Application Note HERE

Example Arduino I2C Code/Application Note provided by the company that does not work HERE

See "Description" section below for everything I have tried...

Hardware
Hardware: Wemos D1
Core Version: ESP8266 Arduino Core Version: 2.0.0_16_08_09 [from esp8266/tools/sdk/version file]
Arduino IDE Version: 1.6.9

Description
No matter what I try I cannot get the K30 sensor to return it's I2C address to the WEMOS. I'm not sure if this is an I2C scanner code issue or an issue with the I2C timing/pulses. I have also tried an Arduino Uno with absolutely no luck.

I would like to actually take CO2 Measurements of course, but the first step is to simply get the device to acknowledge a connection.

The below are images I used trying different combinations of connections and resistor values to pull-up the I2C lines. My hope is that someone will be able to see the problem from these images. The K30 Sensor contains a pin called DVCC that is meant to be used to set the logic level of the I2C side of things.

This pin is supposed to be completely separate from the power provided to the sensor and allow you to work with either 3.3V or 5V logic. Just for clarification, my pull-up resistors go from the I2C lines to this DVCC pin.

I am pretty new to electronics (this is my first time using an Oscilloscope), so please let me know if I can do any more tests or provide any more screenshots that would help diagnose this issue.

All tests were done with the following consistent connections.

  1. Laptop ---> Powering Wemos
  2. External Power Supply set to 9V ---> Powering K30 Sensor
  3. Wemos D1[SCL] --> Sensor SCL
  4. Wemos D2[SDA] --> Sensor SDA
  5. COMMON GROUND

***Volt/div of oscilloscope set at 1.5Volts for all tests using 1x probe.

Each test was then done by varying the connections listed above each image.

TEST1

Wemos 5V Pin ---> DVCC of K30 Pin
I2C Resistor Value ---> NO RESISTOR USED

TEST 2

Wemos 5V Pin ---> DVCC of K30 Pin
I2C Resistor Value ---> 10K ohm

TEST 3
Wemos 5V Pin ---> DVCC of K30 Pin
I2C Resistor Value ---> 4.7 K ohm

TEST 4

Wemos 5V Pin ---> DVCC of K30 Pin
I2C Resistor Value ---> 330 ohm

TEST 5
Wemos 3.3V Pin ---> DVCC of K30 Pin
I2C Resistor Value ---> NO RESISTOR USED

TEST 6
Wemos 3.3V Pin ---> DVCC of K30 Pin
I2C Resistor Value ---> 10 K Ohm

TEST 7
Wemos 3.3V Pin ---> DVCC of K30 Pin
I2C Resistor Value ---> 4.7 K Ohm

TEST 8
Wemos 3.3V Pin ---> DVCC of K30 Pin
I2C Resistor Value ---> 330 Ohm

TEST 9
DVCC of K30 SENSOR NOT CONNECTED TO ANYTHING
I2C Resistor Value ---> NO RESISTOR USED

Conclusions

Of the resistor values tested (No resistor, 10Kohm, 4.7Kohm, and 330ohm) the 330ohm resistor appears to make the most square wave. This also appears to be independent of whether 3V or 5V logic is applied to the DVCC pin on the sensor. Even though these waves appear "the most square" could someone please help me understand how to tell if they are within I2C specs?

Also, although I don't know much about I2C, the difference in wave heights seems very strange to me. I assumed that both lines should operate over about the same range? Is this not correct?

For example, (unless I'm reading something wrong) considering that the volts/div are 1.5V, if you look at TEST #4 :

  1. The top, yellow, channel 1 line [SCL] corresponds to a voltage change of about 2.1 volts
  2. The bottom, blue, channel 2 line [SDA] corresponds to a voltage change of about 3.6 volts

Is this what's supposed to happen? What gives!?

In addition to the above, on all the tests, the yelllow, SCL line appears to be floating way above zero volts when pulled down? I'm assuming this isn't good either? Once again... What gives!?

Finally, I couldn't get the entire transmission to fit in 1 screen capture, so I mashed 3 screen captures together to make an image of what was sent when I ran the below code. I'm not sure yet how to read it, but perhaps someone can derive my communication problem from reading it. I actually not really sure it's the entire communication. I set my scope to trigger on the falling edge and to "single" and this is what it gave me. I'm actually not sure why it stops recording? If someone could clear that up it would be fantastic. Like I said, I'm pretty new to all this. In the meantime, I'm going to try and research I2C myself and see if I can't figure out what's going on.

TEST #10 Entire Transmission Run at 3.3V with 330Ohms I2C Resistor

Any and all help is greatly appreciated. I've spent a few hours putting all this together, so I sincerely hopes the community find it helpful and that the eventual solution helps many other people.

ADDITIONAL ARDUINO UNO TESTING WITH SAME PROCEDURE

TEST 11
Arduino 5V Pin ---> DVCC of K30 Pin
I2C Resistor Value ---> 10K Ohm

TEST 12
Arduino 5V Pin ---> DVCC of K30 Pin
I2C Resistor Value ---> 4.7K Ohm

TEST 13
Arduino 5V Pin ---> DVCC of K30 Pin
I2C Resistor Value ---> 330 Ohm

NEVER TRIGGERED A READING

TEST 14
Arduino 5V Pin ---> DVCC of K30 Pin
I2C Resistor Value ---> NO RESISTOR

TEST 15
Arduino 3.3V Pin ---> DVCC of K30 Pin
I2C Resistor Value ---> 10K Ohm

TEST 16
Arduino 3.3V Pin ---> DVCC of K30 Pin
I2C Resistor Value ---> 4.7K Ohm

TEST 17
Arduino 3.3V Pin ---> DVCC of K30 Pin
I2C Resistor Value ---> 330 Ohm

TEST 18
Arduino 3.3V Pin ---> DVCC of K30 Pin
I2C Resistor Value ---> NO RESISTOR

TEST 19

NOTHING CONNECTED TO DVCC
NO I2C RESISTORS

I2C Scanner Code Used

//  I2C Scanner from Arduino.cc
//  Attribution to Krodal, Nick Gammon, Anonymous
 
#include <Wire.h>
 
void setup()
{
  Wire.begin();
 
  Serial.begin(9600);
  Serial.println("\nI2C Scanner");
}
 
void loop()
{
  byte error, address;
  int nDevices;
 
  Serial.println("Scanning...");
 
  nDevices = 0;
  for(address = 1; address <= 127; address++)
  {
    // The i2c_scanner uses the return value of
    // Wire.endTransmission to see if
    // a device acknowledged the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
 
    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.print(address,HEX);
      Serial.println("  !");
 
      nDevices++;
    }
    else if (error==4)
    {
      Serial.print("Unknown error at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.println(address,HEX);
    }    
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");
 
  delay(5000);           // wait 5 seconds for next scan
}

Don't try to make it square. The more square it is the more high frequency reflections bounce off the other end of the cable and radiate crap throughout the rest of your circuit.

The I2C standard says that 70% of the supply rail is treated as a high and 30% is a low. So the voltage on SDA must have risen to 70% at the receiver when the receiver samples the line to see what value it has.

When does this sample occur? A quick scan of the standard doesn't seem to give a specific time although there's lots of timing specifications depending on fast or normal modes. In practise, when the clock rises to 70% then the SDA will be sampled, so it must have risen to 70% well in advance of the clock, assuming the wires are equivalent.

The real limit on I2C is capacitance. If the wires are long or closely spaced then the capacitance can be high. This then takes a lot of energy to charge and discharge the capacitor. Yes bigger pullups will help but then the microcontroller will exceed its maximum output current. If you were using an Arduino Due then 330ohms will be too much for most of its output pins.

@MorganS

Thanks for the link to the standard. I'll have to dig into that as well as understanding capacitance relating to high frequency reflections.

Just taking what you said however...

The I2C standard says that 70% of the supply rail is treated as a high and 30% is a low.

and looking at this image...

If the clock (the top yellow line) is to be considered LOW and the supply rail is 3.3V then...

3.3V x 0.3 = 0.9V

However, the scope was set at 1.5 volts/division. This means that each tiny tick mark is 1.5V/5 tick marks = 0.3V

The lowest the clock (yellow line) ever gets in the image appears to be about 3.5 tick marks:

3.5 tick marks x .3 V = 1.05V

1.5V > 0.9V

Does this mean that the signal is never actually considered LOW? Could this be my issue?

If you look at the images I provided, in almost all of the Arduino Uno tests, it's actually close to 4.5 or 5 tick marks at low. This would put it well above the standard. (Unless I'm really confused)

Any thoughts? (The Wemos tests look closer to the standard LOW?)

If you were using an Arduino Due then 330ohms will be too much for most of its output pins.

First, I'm not using an Arduino Due, my testing was done on a Wemos and an Arduino Uno. With that said though, is this why you said the above? The Arduino Due specs page states:

Each of the 54 digital pins on the Due can be used as an input or output, using pinMode(), digitalWrite(), and digitalRead() functions. They operate at 3.3 volts. Each pin can provide (source) a current of 3 mA or 15 mA, depending on the pin, or receive (sink) a current of 6 mA or 9 mA, depending on the pin. They also have an internal pull-up resistor (disconnected by default) of 100 KOhm. In addition, some pins have specialized functions:

and if we're considering a 3.3V I2C network...

3.3V / 330Ohms = 10mA

...and 10mA is greater than allowable for the Due.

Thankfully, this is not the case with the Wemos, it supports 12mA I believe.

Once again, thanks. I'm learning a lot and am excited to hopefully fix this problem.

-Chase

See my second post at THIS LINK for an update on the problem....

From what I can tell, the I2C scanner seems to be sending 9 bits and then an ACK bit [For a total of 10 bits]... but I thought there was supposed to be:

7bit address + 1 read/write bit + 1 ackbit = 9 bits per transmission?

I'm very confused...

As can be found buried in various places, the i2c communications with arduino works fine with arduino voltages (5 v, gnd, and sla and slc). The K33 sensor works with 9v in via the battery input. The RH and Temp readings will be provided correctly with 5V.

I suggest power your sensor at battery input and read sensory via i2c or modbus.

Let me know if that works for you.