Go Down

Topic: Interfacing with TWO k30 CO2 sensors (Read 5180 times) previous topic - next topic

James_M

Mar 21, 2013, 05:32 pm Last Edit: Mar 21, 2013, 05:54 pm by James_M Reason: 1
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:
Code: [Select]
/*

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):

Code: [Select]
/*

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


wildbill

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.

James_M

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...

dc42


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.
Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

Jason_B

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.

Code: [Select]

/*
  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
Co2meter.com

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 http://www.co2meter.com/pages/downloads

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
5v5v
GNDGND
0Tx
1Rx


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

James_M

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.

James_M

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. 

Code: [Select]
    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

dc42

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?
Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

James_M

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.

dc42

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".
Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

James_M

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?

dc42

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.
Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

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.

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

Code: [Select]
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?

Code: [Select]
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?

dc42


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.


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

Code: [Select]
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?

Code: [Select]
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?


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.
Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

Go Up