Interfacing with TWO k30 CO2 sensors

Hi, this is my first post so please excuse any errors I might be making here or in my code.

As a small part of a far larger system what I’m trying to do is read the CO2 ppm levels from to k30 sensors from CO2meter.com. That website handily provides an example of interfacing with a single CO2 sensor using the kSeries.h library that they provide. (Unfortunately, despite several emails, their customer service has been unable to provide a helpful answer to my specific issue.)

The Arduino interface example they show is the 5th note down on this webpage, AN126: http://www.co2meter.com/pages/indoor-air-quality-links

They also provide the library there as well. This all works perfectly with a single sensor. My problem is that, being a total Arduino novice, I have absolutely no knowledge of how to alter the example code (or the kSeries library itself) to allow two separate getco2 commands to poll to separate sensors.

Here’s the example code:

/* 

 Reports values from a K-series sensor back to the computer 

 written by Jason Berger 

 Co2Meter.com 

*/ 

#include "kSeries.h" //include kSeries Library 

kSeries K_30(12,13); //Initialize a kSeries Sensor with pin 12 as Rx and 13 as Tx 

void setup() 

{ 

 Serial.begin(9600); //start a serial port to communicate with the computer 

 Serial.println("Serial Up!"); 

} 

void loop() 

{ 

 double co2 = K_30.getCO2('p'); //returns co2 value in ppm ('p') or percent ('%') 

 Serial.print("Co2 ppm = "); 

 Serial.println(co2); //print value 

 delay(60000); //wait 1 minute 

}

And here’s my code, altered to write the values to a LCD (hopefully):

/* 

 Tries to report values from TWO K-series sensors to an LCD 

 altered by James 

*/ 

#include "kSeries.h" //include   kSeries Library 
#include <LiquidCrystal.h>

kSeries K_30a(12,13); //Initialize a kSeries Sensor with pin 12 as Rx1 and 13 as Tx1 
kSeries K_30b(8,9); //Initialize a kSeries Sensor with pin 9 as Rx2 and 8 as Tx2 
LiquidCrystal lcd(7, 8, 9, 10, 11, 12); // initialize the library with the numbers of the interface pins

void setup() 

{ 
  // set up the LCD's number of columns and rows: 
  lcd.begin(16, 2);
  lcd.print("Starting up");
  
} 

void loop() 

{ 

 double co2a = K_30a.getCO2('p'); //returns co2 value in ppm ('p') or percent ('%') 
 double co2b = K_30b.getCO2('p');

  lcd.setCursor(0, 1);
  lcd.print("CO2-in:");
  lcd.setCursor(1, 1); 
  lcd.print("CO2-out:"); 

 lcd.setCursor(0, 8);
 lcd.print(co2a);
 lcd.setCursor(1, 9);
 lcd.print(co2b);
 

 delay(30000); //wait 30 seconds 

}

For now I’ve tried to initialize the two different sensors, calling them kSeries k_30a and kSeries k_30b. Obviously they need different pins, which I’ve altered, but from there I have essentially no idea how to make this work. What I need is to be able to fill the two different double variables that I’m calling co2a and co2b. From there, printing that data to the LCD should be fairly easy… right?

NOTE: I realize that the pins overlap between the lcd and the sensors, all the numbers are just placeholders for now while I figure out the code… The LCD will use an Adafruit LCD backpack so it won’t need as many pins.

So, bottom line, based on the kSeries library and the two sensors that I have, what do I need to change to get this to work?

Any help at all would really be appreciated, thanks!

James

Without looking at the library, your method of declaring and reading from the two sensors looks ok. Given your acknowledged issue with the pin number reuse though it isn't going to talk to the LCD properly. I'd suggest that you prove out your ability to read the sensors using serial.print and move on to the LCD when that piece is working.

Thanks for the reply. Switching to serial.print will be my first step once I get back to the lab. The question is, does the library allow reads from two sensors. Do they need different addresses assigned to them in the library, or maybe even onboard the sensors? Unfortunately I can't really understand what's going on in the library to answer my own questions...

James_M:
The question is, does the library allow reads from two sensors. Do they need different addresses assigned to them in the library, or maybe even onboard the sensors?

As you haven’t provided a link to the library code, it’s impossible for us to say. I had a quick browse of their web site, but didn’t find it.

James,
I just sent you an email regarding this, but I figured I would post here in case anyone else runs into this problem. The library was written which a single sensor in mind. it opens up a software serial port so to use multiple instances of it would require you to switch between them. However these sensors use a modbus protocol so they can be addressed, so you can talk to several of them on a single Serialport . The default address is 104 (0x68), so change one of the sensors adresses and then you can read from them independently. The following code has a function that will change the address of the attached sensor to 0x34. connect ONLY ONE sensor to the arduino and uncomment the line “//changeAddress();”, then run the program. then comment out or delete that line and upload it again. now you can attach both sensors to the same Tx/Rx (12,13) and it will read from both of them.

/*
  Basic Arduino example for K-Series sensor
  Created by Jason Berger
  
  *edited to use 2 sensors on the same line*
  Co2meter.com  
*/


#include "SoftwareSerial.h"


byte addressA = 0x68;
byte addressB = 0x34;	//change to address


SoftwareSerial K_30_Serial(12,13);  //Sets up a virtual serial port
                                    //Using pin 12 for Rx and pin 13 for Tx


byte readCO2_a[] = {addressA, 0X44, 0X00, 0X08, 0X02, 0X9F, 0X25};  //Command packet to read Co2 (see app note)
byte readCO2_b[] = {addressB, 0X44, 0X00, 0X08, 0X02, 0X9F, 0X25};
byte response[] = {0,0,0,0,0,0,0};  //create an array to store the response

unsigned long valCO2_A;
unsigned long valCO2_B;

//multiplier for value. default is 1. set to 3 for K-30 3% and 10 for K-33 ICB
int valMultiplier = 1;

void setup() 
{
  // put your setup code here, to run once:
  Serial.begin(9600);         //Opens the main serial port to communicate with the computer
  K_30_Serial.begin(9600);    //Opens the virtual serial port with a baud of 9600
  
  
  
  //Uncomment the following line to and run to change address of connected sensor to 0x34;
  //This should only be done with one sensor attached
  //changeAddress();
}

void loop() 
{
  sendRequest(readCO2_a);			//send request to A and store response
  valCO2_A = getValue(response);	//parse response and store as valCO2_A
  
  sendRequest(readCO2_b);			//send request to B and store response
  valCO2_B = getValue(response);	//parse response and store as valCO2_A
  
  Serial.print("Co2[A] ppm = ");
  Serial.println(valCO2_A);
   Serial.print("Co2[B] ppm = ");
  Serial.println(valCO2_B);
  delay(2000);
  
}

void sendRequest(byte packet[])
{
  while(!K_30_Serial.available())  //keep sending request until we start to get a response
  {
    K_30_Serial.write(readCO2,7);
    delay(50);
  }
  
  int timeout=0;  //set a timeoute counter
  while(K_30_Serial.available() < 7 ) //Wait to get a 7 byte response
  {
    timeout++;  
    if(timeout > 10)    //if it takes to long there was probably an error
      {
        while(K_30_Serial.available())  //flush whatever we have
          K_30_Serial.read();
          
          break;                        //exit and try again
      }
      delay(50);
  }
  
  for (int i=0; i < 7; i++)
  {
    response[i] = K_30_Serial.read();
  }  
}

unsigned long getValue(byte packet[])
{
    int high = packet[3];                        //high byte for value is 4th byte in packet in the packet
    int low = packet[4];                         //low byte for value is 5th byte in the packet

  
    unsigned long val = high*256 + low;                //Combine high byte and low byte with this formula to get value
    return val* valMultiplier;
}

void changeAddress()
{

	//The adress is stored in the eeprom at address 0x00 and RAM address 0x20
	//if it is changed in eeprom it will automatically be loaded into ram after a power cycle
	//but we will write it manually so we dont have to powercycle
	//
	//if desired adress is changed then the CRC (last 2 bytes) must be recalculated also
	//
	byte changeEEPROM[] = {0xFE,0x43,0x00,0x00,0x1,0x34,0x50,0x4d};
	byte changeRAM[] =  {0xFE,0x41,0x00,0x20,0x1,0x34,0x28,0x47};
	
	K_30_Serial.write(changeEEPROM,8);	//Send each one 3 times just to ensure it goes through
	delay(100);
	K_30_Serial.write(changeEEPROM,8);
	delay(100);
	K_30_Serial.write(changeEEPROM,8);
	delay(100);
	K_30_Serial.write(changeRAM,8);
	delay(100);
	K_30_Serial.write(changeRAM,8);
	delay(100);
	K_30_Serial.write(changeRAM,8);
	delay(100)
}

-Jason B

Also a side note:

There is software available on our website called Gaslab that allows you to change the Address of the sensors About GasLab® Data Logging Software | CO2Meter.com

It requires you to connect to the sensor via an FTDI cable, however if you don't have one of these you can connect the arduinos RESET pin to GND and use its onboard FTDI chip by connecting the sensor to pins 0 and 1. Just remember that the "Tx" and "Rx" markings on the arduino refer to the atmega pins, so when referring to the host computer they are switched. So the connections are:

**Arduino ** K-30
5v 5v
GND GND
0 Tx
1 Rx

I hope this helps anyone who is looking to do a similar project. please feel free to email me at JasonB@co2meter.com

Thanks,
-Jason B

1 Like

Wow, thanks for the prompt and thorough reply. I haven't had a chance to test it yet but it looks like I should be able to manage it. I'll post back if I have any problems. Thanks again!

PS: I didn't get an email from you Jason, just a heads up.

Thanks again for the help but when I verify the code there's a tiny error and it's beyond me to fix...

Line 67 (ish) in the sendRequest function calls the readCO2 variable every time. In the updated code that variable doesn't exist, it's replaced with readCO2_a and readCO2_b.

    K_30_Serial.write(readCO2,7);

My hypothesis is that the the sendRequest function needs to use whatever variable was sent to it when it is invoked in the main loop function. In this case that line would use either readCO2_a or readCO2_b. I just don't know how to make that happen. Or my hypothesis could be completely wrong and something else needs to happen.

So, anyone out there who wouldn't mind showing me what to fix?

PS: DC42, here's the link to the library (direct link to zip file): http://www.co2meters.com/Documentation/AppNotes/AN126-K3x-sensor-arduino-uart.zip

I’ve looked at that library, and it should have no problems supporting multiple sensors. Your code in the original post looks correct to me, apart from the pin number clashes. Have you tried it?

Hi dc42, as much as it logically follows that the kSeries library should work with two sensors, I've found that it does not. I did test it (with a corrected version of the code I first posted) and it displays one sensors value correctly and another's value as -203.00 which essentially means that it is not functioning properly. I've double checked the wiring and pin assignments so as far as I can tell it's a code issue. Not to mention that this solution really eats into the total number of pins on the Arduino, especially with a LCD display added and the final product will require me to be very pin frugal.

So, this leads me back to the question I asked this morning about line 67 in the serial port code... Any solutions? I'm convinced that it's just a small oversight in Jason_B's excellent solution, it's just one that I'm unable to correct.

Hi James,

I don't understand why the original code (with pin numbers corrected) didn't work, assuming both sensors were set to the default address. But I understand that you want to use the same Tx and Rx pins for both of them, to reduce the pin count. Sharing the Tx pins should be no problem, however sharing the Rx pins will only work if they have been designed to work with wire-and or wire-or and you add an appropriate pullup or pulldown resistor. Otherwise, you will need to add an external hardware gate (possibly just 2 diodes and a resistor). The K30 datasheet says that the hardware spec is in document "“ModBus on CO2 Engine K30", but I was unable to find that document.

I believe that in line 67, you should replace "readCO2" by "packet".

Sorry for the long delay, it’s taken me some time to get back to this.

At this point I’m really stumped. Neither of the above options (either altering the k.Series library code like I proposed in my first post OR changing the address using the code that Jason posted) work for me. Using the corrected version of my code from the first post one sensor always reads at around -290 and the other one reads correctly. This remains true even if I switch which sensor is connected to which pins, so I know it’s not the sensor or pin configuration.

In Jason’s code the change address part runs fine with one sensor attached. Then, with the chance address part removed and both sensors attached it doesn’t get past the sendRequest(readCO2_a); line. I assume that this is because it can’t contact sensor A so it get stuck on the loop in the sendRequest section. All I changed in this code was the first line of that section to packet as per dc42’s suggestion.

The fact that Jason’s code didn’t work made me suspicious that the address change didn’t really do it’s job when I ran it. The only way to test this that I could think of was to separately try both sensors using the example code (written for a single sensor at the default address). Since that code did read the CO2 values from each sensor (separately of course) I take that to mean that the changeAddress part did not work.

I’m at a loss, at this point, for what to do next. The changeAddress code is way beyond me so I wouldn’t know where to start diagnosing that. The only other option is to use the GasLab software like Jason suggested, but that seems a bit daunting and I’m not even sure that that will solve the problem (what if it’s not the addresses that are the issue?)

So, any suggestions?

  1. What do you do to the sensor to make it respond at a different address? Is there some sort of address configuration switch on the sensor? If so, have you checked that you can talk to a single sensor when it is configured for the alternate address?

  2. As I said before, I think you will have real problems using the same Rx pin for both sensors unless you use some sort of gate.

The procedure for changing the address involves running some code to change the address information stored in the sensor EEPROM and RAM. Jason posted the code (in this thread, above)to do this using the Arduino but I'm not sure if I've gotten it to work.

What would I have to change in the code to use two different Rx pins, right now the softwareSerial library get initialized like this:

SoftwareSerial K_30_Serial(12,13); //Sets up a virtual serial port
                                                        //Using pin 12 for Rx and pin 13 for Tx

Would I add a second line right after that read like this?

SoftwareSerial K_30_Serial(11,13);

At this point I'm wondering if it wouldn't be easier to sidestep the whole problem and adapt the TwoPortRecieve Arduino example to poll each sensor separately, then perhaps it wouldn't matter that the sensor addresses are the same?

1 Like

James_M:
The procedure for changing the address involves running some code to change the address information stored in the sensor EEPROM and RAM. Jason posted the code (in this thread, above)to do this using the Arduino but I'm not sure if I've gotten it to work.

The first thing you need to do is to get the second sensor working, by itself, at the alternate address. Otherwise, you won't be able to share any transmit or receive pins.

James_M:
What would I have to change in the code to use two different Rx pins, right now the softwareSerial library get initialized like this:

SoftwareSerial K_30_Serial(12,13); //Sets up a virtual serial port

//Using pin 12 for Rx and pin 13 for Tx




Would I add a second line right after that read like this?



SoftwareSerial K_30_Serial(11,13);

James_M:
At this point I'm wondering if it wouldn't be easier to sidestep the whole problem and adapt the TwoPortRecieve Arduino example to poll each sensor separately, then perhaps it wouldn't matter that the sensor addresses are the same?

If you used 4 pins in total (2 for each sensor), then it wouldn't matter if the sensor addresses are the same. You might be able to share the receive pin using this arrangement, although I still suspect you will need some sort of hardware gate.

Once you have set up one of the sensors to have an alternate address, they can share the same Tx and Rx lines. For instance say you have 2 sensors, Sensor_A is at address 0x68, and Sensor_B is at 0x34. you would connect both sensors to the same 2 pins on the arduino (12 and 13 in the example) and when you send a read request with the address 0x68, only Sensor_A will reply, and when you send a request to 0x34 only Sensor_B will reply.

So you only need to initialize one SoftwareSerial.

If you wanted to run it on two separate Softwareserial ports, you could initialize two and use a total of 4 pins. However when using multiple SoftwareSerials you must switch between them manually using the .listen() function. this page explains how to do this: http://arduino.cc/en/Reference/SoftwareSerialListen

Hope this helps.

I never did manage to get the address change thing to work correctly. I’m sure I was doing something wrong but I have no clue what. I did, however, get the two sensors playing nice by using the TwoPortReceive method outlined here http://arduino.cc/en/Tutorial/TwoPortReceive

So far my code checks one sensor, writes it to the LCD then checks the other and displays that on the second LCD line. Next up, PWM a 24 LED array via a TIP120 based on the CO2 levels. Code to date is below (I know it’s pretty ungainly, I’m figuring this stuff out as I go along).

/*
  Software serial multple serial test
 
 Receives from the two software serial ports, 
 sends to the hardware serial port. 
 
 */

#include <SoftwareSerial.h>
// software serial #1: TX = digital pin 10, RX = digital pin 11
SoftwareSerial portOne(12,13);
#include "Wire.h"
#include "LiquidCrystal.h"

// Connect via i2c, default address #0 (A0-A2 not jumpered)
LiquidCrystal lcd(0);

// software serial #2: TX = digital pin 8, RX = digital pin 9
// on the Mega, use other pins instead, since 8 and 9 don't work on the Mega
SoftwareSerial portTwo(8,9);

byte readCO2[] = {0xFE, 0X44, 0X00, 0X08, 0X02, 0X9F, 0X25}; //Command packet to read Co2 (see app note) 

byte response[] = {0,0,0,0,0,0,0}; //create an array to store the response 

//multiplier for value. default is 1. set to 3 for K-30 3% and 10 for K-33 ICB 

int valMultiplier = 1; 

void setup()
{
 // set up the LCD's number of rows and columns: 
  lcd.begin(16, 2);
  lcd.print("Hello!"); 
  delay(5000);
  lcd.setCursor(0, 0);
  lcd.print("CO2-in:");
  lcd.setCursor(0, 1); 
  lcd.print("CO2-out:");    
  
 // Open serial communications and wait for port to open:
  Serial.begin(9600);


  // Start each software serial port
  portOne.begin(9600);
  portTwo.begin(9600);
}

void loop()
{
  // By default, the last intialized port is listening.
  // when you want to listen on a port, explicitly select it:
  portOne.listen();
  sendRequestA(readCO2); 
  unsigned long valCO2A = getValueA(response); 
  Serial.print("Co2 A = "); 
  Serial.println(valCO2A); 
  lcd.setCursor(8, 0);
  
  for (int i = 0; i < 16; ++i)
{
  lcd.write(' ');
}
  lcd.setCursor(8, 0);
  lcd.print(valCO2A);  
  delay(5000);

 

  // blank line to separate data from the two ports:
  Serial.println();

  // Now listen on the second port
  portTwo.listen();
  sendRequestB(readCO2); 
  unsigned long valCO2B = getValueB(response); 
  Serial.print("Co2 B = "); 
  Serial.println(valCO2B);
  lcd.setCursor(9, 1);
  
  for (int i = 0; i < 16; ++i)
{
  lcd.write(' ');
}  
  lcd.setCursor(9, 1);
  lcd.print(valCO2B);
  delay(5000);


  // blank line to separate data from the two ports:
  Serial.println();
}














//*************************************************************************
void sendRequestA(byte packet[]) 
{ 
 while(!portOne.available()) //keep sending request until we start to get a response 
 { 
 portOne.write(readCO2,7); 

 delay(50); 

 } 
 int timeout=0; //set a timeoute counter 
 while(portOne.available() < 7 ) //Wait to get a 7 byte response 
 { 
 timeout++; 
 if(timeout > 10) //if it takes to long there was probably an error 
 { 
 while(portOne.available()) //flush whatever we have 
 portOne.read(); 
 break; //exit and try again 
 } 
 delay(50); 
 } 
 for (int i=0; i < 7; i++) 
 { 
 response[i] = portOne.read(); 
 } 
} 


unsigned long getValueA(byte packet[]) 
{ 
 int high = packet[3]; //high byte for value is 4th byte in packet in the packet 
 int low = packet[4]; //low byte for value is 5th byte in the packet 
 unsigned long val = high*256 + low; //Combine high byte and low byte with this formula to get value 
 return val* valMultiplier; 

}



//*************************************************************************
void sendRequestB(byte packet[]) 
{ 
 while(!portTwo.available()) //keep sending request until we start to get a response 
 { 
 portTwo.write(readCO2,7); 

 delay(50); 

 } 
 int timeout=0; //set a timeoute counter 
 while(portTwo.available() < 7 ) //Wait to get a 7 byte response 
 { 
 timeout++; 
 if(timeout > 10) //if it takes to long there was probably an error 
 { 
 while(portTwo.available()) //flush whatever we have 
 portTwo.read(); 
 break; //exit and try again 
 } 

 delay(50); 
 
 } 
 for (int i=0; i < 7; i++) 
 {
 response[i] = portTwo.read(); 
 } 

} 


unsigned long getValueB(byte packet[]) 
{ 
 int high = packet[3]; //high byte for value is 4th byte in packet in the packet 
 int low = packet[4]; //low byte for value is 5th byte in the packet 
 unsigned long val = high*256 + low; //Combine high byte and low byte with this formula to get value 
 return val* valMultiplier; 

}

Jason, i Did a test with one sensor using the same code posted here, i have connected the sensor to the arduino using pins 12 and 13 to the UART conectors on the sensor. Please correct me if I am wrong but the sensor address must be needed only if I use the IC2 connection. The sensor address should not matter for the UART connection. My thinking is correct.

To use the sensor address the connection used must be to the IC2 arduino pins that are not the common digital pins 1 trough 13.

So if I use the UART connection I don't need to change one sensor address.

What is the best option? UART or IC2. I was thinking to use the IC2 connection because I need the arduino Digital Pins for other sensors.

I didn't find (at least yest) a sample using the IC2 connections on co2meter site. Can you point how I do the IC2 connection and programming with arduino?

Regards

Hi, Gents.

I just get starting with K30 CO2 sensor, following https://www.openhomeautomation.net/wireless-co2-sensor-arduino/, yet always a negative value -203.

I also tried http://cdn.shopify.com/s/files/1/0019/5952/files/Senseair-Arduino.pdf?1264294173, but always get Checksum failed / Communication failure, that means still a negative value.

Any idea gents...

Thanks.

Can you print the serial data to the serial monitor? It is not clear if you have a wiring problem or a bad sensor. You need to look at the raw data to see if it is sending anything.