Skip to content

K30 CO2 Sensor (SE 0018)

Kenneth Kang edited this page Sep 23, 2020 · 26 revisions

Table of Contents

Overview


K30

The K30 1% CO2 sensor is intended to be built into different host devices that require CO2 monitoring indoors. This sensor is perfect for residential, commercial industrial HVAC or IAQ applications, simple to install that it is pre-calibrated to be ready-to-use, flexible analog and digital outputs, maintenance-free in normal applications, and low cost with significantly low power uses.

Pinouts


K30

The K30 sensor can be used as several different types of sensors, but in Loom, we use this sensor as UART with Serial Sensor. Because of that, we use four pinouts: G0, G+, TxD, and RxD. The four dotted in black are the pin location in the K30 sensor.

  • Power Pins:

    • G+ and G0: these are the power pins. Their absolute maximum ratings are 5.5 to 14V, stabilized to within 10%. However, the preferred operating range is between 5V to 9V. Note that these pins are unprotected against reverse connections.
  • UART Logic Pins:

    • TxD: This pin transmits the data of the CO2 measurements. This pin must be connected to RxD on the other side that the board can receive the data values. You can find this pin commonly in the UART sensor with the Serial Sensor setup.

    • RxD: This pin receives the data from the board. This pin must be connected to TxD on the other side that the board can send data input or request to start to measure. You can find this pin commonly in the UART sensor with the Serial Sensor setup.

Assembly


IMG-0232

As you see above, there is a male pin header and the K30 sensor. You only need 4 header pins to connect with the K30 sensor. Just break it that it only has 4 pins that connected to each other.

K30

In the layout, make sure you solder those 4 male pins into those four black dots. For soldering, we use iron solder and the soldering equipment. Once that is done, it should look like this below.

front view

Front view with some angle

back view

Back view with some angle

Wiring


For wiring, we will be connecting to the MO Feather board from Adafruit. If you use a different board, please take look at the Arduino UART Interface to K30 Sensor in the reference section.

M0 Feather Board with LoRa Radio

The image above is the MO Feather board from Adafruit. You can find more information about the MO board in the reference sections.

The above image is the layout of the MO feather board. The following list must be connected to the K30 sensor in order to power and use the K30 CO2 sensor. The wiring can be using jumper wires or other ways, but it must be connected to each other.

  • MO --> Sensor
  • USB --> G+
  • GND --> G0
  • D13 --> TxD
  • D11 --> RxD

Note: D13 and D11 are labeled as 13 and 11 in the image above.

Once you wire them correctly, you will get the following lights on the K30 sensor.

K30 Lighting up

Loom Coding with Arduino


We use Arduino IDE and OPEnS individually developed language, Loom, for programming other sensors. Before we go in-depth the code for the K30 CO2 sensor, we need to install Arduino and Loom.

  1. Install Arduino IDE in our Loom team GitHub Wiki Page
  2. Install Loom in our Loom team GitHub Wiki Page

Once those steps are done, then we are able to move on to the next step.

K30

As you see the image above, you find the example code from there, by going to files, Examples, Loom, Sensors, and K30 or copy the codes below. The first code will be the K30.ino and the second code is the config.h file for the K30.ino file. These two files must be in the same folder that the name must be K30.

// K30.ino file

#include <Loom.h>

#include "wiring_private.h"

//Include Configuration
const char* json_config =
#include "config.h"
;

// Set enabled modules
LoomFactory<
  Enable::Internet::Disabled,                                   
  Enable::Sensors::Enabled,                                     
  Enable::Radios::Enabled,                                      
  Enable::Actuators::Disabled,                                  
  Enable::Max::Disabled                                         
> ModuleFactory{};

LoomManager Loom{ &ModuleFactory };

// Create Serial SERCOM for K30 Sensor: RX pin 13, TX pin 11
Uart Serial2 = Uart(&sercom1, 13, 11, SERCOM_RX_PAD_1, UART_TX_PAD_0);

void setup() {
  
  Serial2.begin(9600);
  
  Loom.begin_serial(true);
  Loom.parse_config(json_config);
  Loom.print_config();

  //Assign pins 13 & 11 SERCOM functionality
  pinPeripheral(11, PIO_SERCOM);
  pinPeripheral(13, PIO_SERCOM);
  
  Loom.K30().set_serial(&Serial2);

  LPrintln("\n ** Setup Complete ** ");

  warmUpTimer();

}

void loop() {  

  Loom.measure(); // Sample attached sensors
  Loom.package(); // Format data for display and SD
  Loom.display_data(); // display printed JSON formatted data on serial monitor
  Loom.SDCARD().log(); // Loggin K30 Data value into SDCard
  Loom.pause();
}

void SERCOM1_Handler(){ // This function is require for the K30 Serial Sensor because of UART Type
  Serial2.IrqHandler();
}

void warmUpTimer(){ // This function is a timer to warm up the K30 sensor to get accurate measurements
  
  LPrintln("\n ** Set up 6 minutes Warm Up time to get accurate measurements ** ");

  for(int timePassed = 1; timePassed < 7; timePassed++){ // By pausing Loom, it will not measure CO2 value for 6 minutes
    Loom.pause(60000); // The max is only 1 min for pause, we loop it for 6 times to make it 6 minutes
    LPrint(timePassed); // Knowing the User that how many minutes have been passed
    LPrint(" minute(s) passed!");
    LPrint("\n");
  }
  
  LPrintln("\n ** Ready to Measure ** ");
}
//config.h
"{\
  'general':{'name':'K30', 'instance':1,'interval':5000},\
  'components':[\
    {'name':'K30','params':'default'},\
    {'name':'SD','params':[true,1000,10,'K30_',true]}\
  ]\
}"

K30

Once you have the code in the Arduino IDE, then connect the Micro USB cable to the board and the computer. Then go to tool and Port to make sure that you connected to the right MO board.

K30 K30

After the board is connected to the right port, then click upload and wait until it deploys the code into the program. Once you get a message from the IDE like CPU Reset in red, it is complete without any error.

K30 K30

Then go to the Serial Monitor. There are two ways to go into the Serial Monitor by going to Tools, Serial Monitor, or click the top right corner where there is a magnifying glass shape.

K30 K30

If you get this box with measurements without any error message, then you have done it successfully!

If there is an error, please send an issue request in the Loom Team GitHub Issue Page.

Note: There is a 6 minute warm-up time in order to get accurate measurements.

Without Loom Coding with Arduino


Most of the information will be based on the Arduino UART Interface to K30 Sensor in the reference section.

In order to run the sensor, you need 4 different dependencies that are related to the K30 sensor. You need to add these four files to the libraries of Arduino. You can find them by going Documents, Arduino, libraries. In that folder, create a new folder called K_Series and add the following 4 codes into that folder. Or you can download the code from the Arduino UART Interface to K30 Sensor in the reference section.

// This file name will be SoftwareSerial.cpp
/*
SoftwareSerial.cpp (formerly NewSoftSerial.cpp) - 
Multi-instance software serial library for Arduino/Wiring
-- Interrupt-driven receive and other improvements by ladyada
   (http://ladyada.net)
-- Tuning, circular buffer, derivation from class Print/Stream,
   multi-instance support, porting to 8MHz processors,
   various optimizations, PROGMEM delay tables, inverse logic and 
   direct port writing by Mikal Hart (http://www.arduiniana.org)
-- Pin change interrupt macros by Paul Stoffregen (http://www.pjrc.com)
-- 20MHz processor support by Garrett Mace (http://www.macetech.com)
-- ATmega1280/2560 support by Brett Hagman (http://www.roguerobotics.com/)

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

The latest version of this library can always be found at
http://arduiniana.org.
*/

// When set, _DEBUG co-opts pins 11 and 13 for debugging with an
// oscilloscope or logic analyzer.  Beware: it also slightly modifies
// the bit times, so don't rely on it too much at high baud rates
#define _DEBUG 0
#define _DEBUG_PIN1 11
#define _DEBUG_PIN2 13
// 
// Includes
// 
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include "Arduino.h"
#include "SoftwareSerial.h"
//
// Lookup table
//
typedef struct _DELAY_TABLE
{
  long baud;
  unsigned short rx_delay_centering;
  unsigned short rx_delay_intrabit;
  unsigned short rx_delay_stopbit;
  unsigned short tx_delay;
} DELAY_TABLE;

#if F_CPU == 16000000

static const DELAY_TABLE PROGMEM table[] = 
{
  //  baud    rxcenter   rxintra    rxstop    tx
  { 115200,   1,         17,        17,       12,    },
  { 57600,    10,        37,        37,       33,    },
  { 38400,    25,        57,        57,       54,    },
  { 31250,    31,        70,        70,       68,    },
  { 28800,    34,        77,        77,       74,    },
  { 19200,    54,        117,       117,      114,   },
  { 14400,    74,        156,       156,      153,   },
  { 9600,     114,       236,       236,      233,   },
  { 4800,     233,       474,       474,      471,   },
  { 2400,     471,       950,       950,      947,   },
  { 1200,     947,       1902,      1902,     1899,  },
  { 300,      3804,      7617,      7617,     7614,  },
};

const int XMIT_START_ADJUSTMENT = 5;

#elif F_CPU == 8000000

static const DELAY_TABLE table[] PROGMEM = 
{
  //  baud    rxcenter    rxintra    rxstop  tx
  { 115200,   1,          5,         5,      3,      },
  { 57600,    1,          15,        15,     13,     },
  { 38400,    2,          25,        26,     23,     },
  { 31250,    7,          32,        33,     29,     },
  { 28800,    11,         35,        35,     32,     },
  { 19200,    20,         55,        55,     52,     },
  { 14400,    30,         75,        75,     72,     },
  { 9600,     50,         114,       114,    112,    },
  { 4800,     110,        233,       233,    230,    },
  { 2400,     229,        472,       472,    469,    },
  { 1200,     467,        948,       948,    945,    },
  { 300,      1895,       3805,      3805,   3802,   },
};

const int XMIT_START_ADJUSTMENT = 4;

#elif F_CPU == 20000000

// 20MHz support courtesy of the good people at macegr.com.
// Thanks, Garrett!

static const DELAY_TABLE PROGMEM table[] =
{
  //  baud    rxcenter    rxintra    rxstop  tx
  { 115200,   3,          21,        21,     18,     },
  { 57600,    20,         43,        43,     41,     },
  { 38400,    37,         73,        73,     70,     },
  { 31250,    45,         89,        89,     88,     },
  { 28800,    46,         98,        98,     95,     },
  { 19200,    71,         148,       148,    145,    },
  { 14400,    96,         197,       197,    194,    },
  { 9600,     146,        297,       297,    294,    },
  { 4800,     296,        595,       595,    592,    },
  { 2400,     592,        1189,      1189,   1186,   },
  { 1200,     1187,       2379,      2379,   2376,   },
  { 300,      4759,       9523,      9523,   9520,   },
};

const int XMIT_START_ADJUSTMENT = 6;

#else

#error This version of SoftwareSerial supports only 20, 16 and 8MHz processors

#endif

//
// Statics
//
SoftwareSerial *SoftwareSerial::active_object = 0;
char SoftwareSerial::_receive_buffer[_SS_MAX_RX_BUFF]; 
volatile uint8_t SoftwareSerial::_receive_buffer_tail = 0;
volatile uint8_t SoftwareSerial::_receive_buffer_head = 0;

//
// Debugging
//
// This function generates a brief pulse
// for debugging or measuring on an oscilloscope.
inline void DebugPulse(uint8_t pin, uint8_t count)
{
#if _DEBUG
  volatile uint8_t *pport = portOutputRegister(digitalPinToPort(pin));

  uint8_t val = *pport;
  while (count--)
  {
    *pport = val | digitalPinToBitMask(pin);
    *pport = val;
  }
#endif
}

//
// Private methods
//

/* static */ 
inline void SoftwareSerial::tunedDelay(uint16_t delay) { 
  uint8_t tmp=0;

  asm volatile("sbiw    %0, 0x01 \n\t"
    "ldi %1, 0xFF \n\t"
    "cpi %A0, 0xFF \n\t"
    "cpc %B0, %1 \n\t"
    "brne .-10 \n\t"
    : "+r" (delay), "+a" (tmp)
    : "0" (delay)
    );
}

// This function sets the current object as the "listening"
// one and returns true if it replaces another 
bool SoftwareSerial::listen()
{
  if (active_object != this)
  {
    _buffer_overflow = false;
    uint8_t oldSREG = SREG;
    cli();
    _receive_buffer_head = _receive_buffer_tail = 0;
    active_object = this;
    SREG = oldSREG;
    return true;
  }

  return false;
}

//
// The receive routine called by the interrupt handler
//
void SoftwareSerial::recv()
{

#if GCC_VERSION < 40302
// Work-around for avr-gcc 4.3.0 OSX version bug
// Preserve the registers that the compiler misses
// (courtesy of Arduino forum user *etracer*)
  asm volatile(
    "push r18 \n\t"
    "push r19 \n\t"
    "push r20 \n\t"
    "push r21 \n\t"
    "push r22 \n\t"
    "push r23 \n\t"
    "push r26 \n\t"
    "push r27 \n\t"
    ::);
#endif  

  uint8_t d = 0;

  // If RX line is high, then we don't see any start bit
  // so interrupt is probably not for us
  if (_inverse_logic ? rx_pin_read() : !rx_pin_read())
  {
    // Wait approximately 1/2 of a bit width to "center" the sample
    tunedDelay(_rx_delay_centering);
    DebugPulse(_DEBUG_PIN2, 1);

    // Read each of the 8 bits
    for (uint8_t i=0x1; i; i <<= 1)
    {
      tunedDelay(_rx_delay_intrabit);
      DebugPulse(_DEBUG_PIN2, 1);
      uint8_t noti = ~i;
      if (rx_pin_read())
        d |= i;
      else // else clause added to ensure function timing is ~balanced
        d &= noti;
    }

    // skip the stop bit
    tunedDelay(_rx_delay_stopbit);
    DebugPulse(_DEBUG_PIN2, 1);

    if (_inverse_logic)
      d = ~d;

    // if buffer full, set the overflow flag and return
    if ((_receive_buffer_tail + 1) % _SS_MAX_RX_BUFF != _receive_buffer_head) 
    {
      // save new data in buffer: tail points to where byte goes
      _receive_buffer[_receive_buffer_tail] = d; // save new byte
      _receive_buffer_tail = (_receive_buffer_tail + 1) % _SS_MAX_RX_BUFF;
    } 
    else 
    {
#if _DEBUG // for scope: pulse pin as overflow indictator
      DebugPulse(_DEBUG_PIN1, 1);
#endif
      _buffer_overflow = true;
    }
  }

#if GCC_VERSION < 40302
// Work-around for avr-gcc 4.3.0 OSX version bug
// Restore the registers that the compiler misses
  asm volatile(
    "pop r27 \n\t"
    "pop r26 \n\t"
    "pop r23 \n\t"
    "pop r22 \n\t"
    "pop r21 \n\t"
    "pop r20 \n\t"
    "pop r19 \n\t"
    "pop r18 \n\t"
    ::);
#endif
}

void SoftwareSerial::tx_pin_write(uint8_t pin_state)
{
  if (pin_state == LOW)
    *_transmitPortRegister &= ~_transmitBitMask;
  else
    *_transmitPortRegister |= _transmitBitMask;
}

uint8_t SoftwareSerial::rx_pin_read()
{
  return *_receivePortRegister & _receiveBitMask;
}

//
// Interrupt handling
//

/* static */
inline void SoftwareSerial::handle_interrupt()
{
  if (active_object)
  {
    active_object->recv();
  }
}

#if defined(PCINT0_vect)
ISR(PCINT0_vect)
{
  SoftwareSerial::handle_interrupt();
}
#endif

#if defined(PCINT1_vect)
ISR(PCINT1_vect)
{
  SoftwareSerial::handle_interrupt();
}
#endif

#if defined(PCINT2_vect)
ISR(PCINT2_vect)
{
  SoftwareSerial::handle_interrupt();
}
#endif

#if defined(PCINT3_vect)
ISR(PCINT3_vect)
{
  SoftwareSerial::handle_interrupt();
}
#endif

//
// Constructor
//
SoftwareSerial::SoftwareSerial(uint8_t receivePin, uint8_t transmitPin, bool inverse_logic /* = false */) : 
  _rx_delay_centering(0),
  _rx_delay_intrabit(0),
  _rx_delay_stopbit(0),
  _tx_delay(0),
  _buffer_overflow(false),
  _inverse_logic(inverse_logic)
{
  setTX(transmitPin);
  setRX(receivePin);
}

//
// Destructor
//
SoftwareSerial::~SoftwareSerial()
{
  end();
}

void SoftwareSerial::setTX(uint8_t tx)
{
  pinMode(tx, OUTPUT);
  digitalWrite(tx, HIGH);
  _transmitBitMask = digitalPinToBitMask(tx);
  uint8_t port = digitalPinToPort(tx);
  _transmitPortRegister = portOutputRegister(port);
}

void SoftwareSerial::setRX(uint8_t rx)
{
  pinMode(rx, INPUT);
  if (!_inverse_logic)
    digitalWrite(rx, HIGH);  // pullup for normal logic!
  _receivePin = rx;
  _receiveBitMask = digitalPinToBitMask(rx);
  uint8_t port = digitalPinToPort(rx);
  _receivePortRegister = portInputRegister(port);
}

//
// Public methods
//

void SoftwareSerial::begin(long speed)
{
  _rx_delay_centering = _rx_delay_intrabit = _rx_delay_stopbit = _tx_delay = 0;

  for (unsigned i=0; i<sizeof(table)/sizeof(table[0]); ++i)
  {
    long baud = pgm_read_dword(&table[i].baud);
    if (baud == speed)
    {
      _rx_delay_centering = pgm_read_word(&table[i].rx_delay_centering);
      _rx_delay_intrabit = pgm_read_word(&table[i].rx_delay_intrabit);
      _rx_delay_stopbit = pgm_read_word(&table[i].rx_delay_stopbit);
      _tx_delay = pgm_read_word(&table[i].tx_delay);
      break;
    }
  }

  // Set up RX interrupts, but only if we have a valid RX baud rate
  if (_rx_delay_stopbit)
  {
    if (digitalPinToPCICR(_receivePin))
    {
      *digitalPinToPCICR(_receivePin) |= _BV(digitalPinToPCICRbit(_receivePin));
      *digitalPinToPCMSK(_receivePin) |= _BV(digitalPinToPCMSKbit(_receivePin));
    }
    tunedDelay(_tx_delay); // if we were low this establishes the end
  }

#if _DEBUG
  pinMode(_DEBUG_PIN1, OUTPUT);
  pinMode(_DEBUG_PIN2, OUTPUT);
#endif

  listen();
}

void SoftwareSerial::end()
{
  if (digitalPinToPCMSK(_receivePin))
    *digitalPinToPCMSK(_receivePin) &= ~_BV(digitalPinToPCMSKbit(_receivePin));
}


// Read data from buffer
int SoftwareSerial::read()
{
  if (!isListening())
    return -1;

  // Empty buffer?
  if (_receive_buffer_head == _receive_buffer_tail)
    return -1;

  // Read from "head"
  uint8_t d = _receive_buffer[_receive_buffer_head]; // grab next byte
  _receive_buffer_head = (_receive_buffer_head + 1) % _SS_MAX_RX_BUFF;
  return d;
}

int SoftwareSerial::available()
{
  if (!isListening())
    return 0;

  return (_receive_buffer_tail + _SS_MAX_RX_BUFF - _receive_buffer_head) % _SS_MAX_RX_BUFF;
}

size_t SoftwareSerial::write(uint8_t b)
{
  if (_tx_delay == 0) {
    setWriteError();
    return 0;
  }

  uint8_t oldSREG = SREG;
  cli();  // turn off interrupts for a clean txmit

  // Write the start bit
  tx_pin_write(_inverse_logic ? HIGH : LOW);
  tunedDelay(_tx_delay + XMIT_START_ADJUSTMENT);

  // Write each of the 8 bits
  if (_inverse_logic)
  {
    for (byte mask = 0x01; mask; mask <<= 1)
    {
      if (b & mask) // choose bit
        tx_pin_write(LOW); // send 1
      else
        tx_pin_write(HIGH); // send 0
    
      tunedDelay(_tx_delay);
    }

    tx_pin_write(LOW); // restore pin to natural state
  }
  else
  {
    for (byte mask = 0x01; mask; mask <<= 1)
    {
      if (b & mask) // choose bit
        tx_pin_write(HIGH); // send 1
      else
        tx_pin_write(LOW); // send 0
    
      tunedDelay(_tx_delay);
    }

    tx_pin_write(HIGH); // restore pin to natural state
  }

  SREG = oldSREG; // turn interrupts back on
  tunedDelay(_tx_delay);
  
  return 1;
}

void SoftwareSerial::flush()
{
  if (!isListening())
    return;

  uint8_t oldSREG = SREG;
  cli();
  _receive_buffer_head = _receive_buffer_tail = 0;
  SREG = oldSREG;
}

int SoftwareSerial::peek()
{
  if (!isListening())
    return -1;

  // Empty buffer?
  if (_receive_buffer_head == _receive_buffer_tail)
    return -1;

  // Read from "head"
  return _receive_buffer[_receive_buffer_head];
}
// This file name will be SoftwareSerial.h
/*
SoftwareSerial.h (formerly NewSoftSerial.h) - 
Multi-instance software serial library for Arduino/Wiring
-- Interrupt-driven receive and other improvements by ladyada
   (http://ladyada.net)
-- Tuning, circular buffer, derivation from class Print/Stream,
   multi-instance support, porting to 8MHz processors,
   various optimizations, PROGMEM delay tables, inverse logic and 
   direct port writing by Mikal Hart (http://www.arduiniana.org)
-- Pin change interrupt macros by Paul Stoffregen (http://www.pjrc.com)
-- 20MHz processor support by Garrett Mace (http://www.macetech.com)
-- ATmega1280/2560 support by Brett Hagman (http://www.roguerobotics.com/)

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

The latest version of this library can always be found at
http://arduiniana.org.
*/

#ifndef SoftwareSerial_h
#define SoftwareSerial_h

#include <inttypes.h>
#include <Stream.h>

/******************************************************************************
* Definitions
******************************************************************************/

#define _SS_MAX_RX_BUFF 64 // RX buffer size
#ifndef GCC_VERSION
#define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
#endif

class SoftwareSerial : public Stream
{
private:
  // per object data
  uint8_t _receivePin;
  uint8_t _receiveBitMask;
  volatile uint8_t *_receivePortRegister;
  uint8_t _transmitBitMask;
  volatile uint8_t *_transmitPortRegister;

  uint16_t _rx_delay_centering;
  uint16_t _rx_delay_intrabit;
  uint16_t _rx_delay_stopbit;
  uint16_t _tx_delay;

  uint16_t _buffer_overflow:1;
  uint16_t _inverse_logic:1;

  // static data
  static char _receive_buffer[_SS_MAX_RX_BUFF]; 
  static volatile uint8_t _receive_buffer_tail;
  static volatile uint8_t _receive_buffer_head;
  static SoftwareSerial *active_object;

  // private methods
  void recv();
  uint8_t rx_pin_read();
  void tx_pin_write(uint8_t pin_state);
  void setTX(uint8_t transmitPin);
  void setRX(uint8_t receivePin);

  // private static method for timing
  static inline void tunedDelay(uint16_t delay);

public:
  // public methods
  SoftwareSerial(uint8_t receivePin, uint8_t transmitPin, bool inverse_logic = false);
  ~SoftwareSerial();
  void begin(long speed);
  bool listen();
  void end();
  bool isListening() { return this == active_object; }
  bool overflow() { bool ret = _buffer_overflow; _buffer_overflow = false; return ret; }
  int peek();

  virtual size_t write(uint8_t byte);
  virtual int read();
  virtual int available();
  virtual void flush();
  
  using Print::write;

  // public only for easy access by interrupt handlers
  static inline void handle_interrupt();
};

// Arduino 0012 workaround
#undef int
#undef char
#undef long
#undef byte
#undef float
#undef abs
#undef round

#endif

// This file name will be K_Series.cpp
/*
	K_Series.cpp -Library for interfacing with a K-series sensor
	Created by Jason Berger
	for CO2METER.com
	OCT-12-2012

*/

//#include "Arduino.h"
#include "kSeries.h"
#include "SoftwareSerial.h" 	//Virtual Serial library

//SoftwareSerial* _Serial;

//commands

byte cmd_read_CO2[] = {0xFE, 0X44, 0X00, 0X08, 0X02, 0X9F, 0X25};	//type [0]
byte cmd_read_Temp[] = {0xFE, 0X44, 0X00, 0X12, 0X02, 0X94, 0X45};	//type [1]
byte cmd_read_RH[] = {0xFE, 0x44, 0x00, 0x14, 0x02, 0x97, 0xE5 };	//type [2]
byte cmd_init[] = {0xFE, 0X41, 0X00, 0X60, 0X01, 0X35, 0XE8, 0x53}; //type [3]

kSeries :: kSeries(uint8_t Rx,uint8_t Tx)
{
	this->_Serial = new SoftwareSerial(Rx,Tx);
	this->_Serial->begin(9600);
	//chkSensorType();
}

int kSeries :: cmdInit()
{
	return sendRequest(3,4,0);
}
double kSeries :: getCO2(char format)
{
	double co2 = sendRequest(0,7,3);
	if(format == '%')
		co2 /= 10000;
		
	return co2;
}
double kSeries :: getTemp(char unit)
{
	double temp = sendRequest(1,7,3);
	temp/=100;
	if ((unit =='f') || (unit == 'F'))
		temp = (temp * 9 / 5) + 32;
	
	return temp;
}
double kSeries :: getRH()
{
	double RH = sendRequest(2,7,3);
	RH/=100;
	return RH;
}
void kSeries :: chkSensorType()
{
	chkASCII();
	chkK33();
}

void kSeries::chkK33()
{
	int temp = sendRequest(1,7,3);
	if(temp > -255)
		_K33 = true;
	else
		_K33 = false;
	
}

void kSeries :: chkASCII()
{
	int timeout=0;
	while(this->_Serial->available() == 0)
	{
		if(timeout > 100)
		{
			_ASCII =false;
			break;
		}
		timeout++;
		delay(25);
	}	
	if (timeout < 200)
		_ASCII = true;
	
}

int kSeries :: sendRequest(int reqType, int respSize, int respInd)
{
	long Val=-255;
	int cmdTimeout =0;
	while(this->_Serial->available() == 0)	//send read command in a loop until we see a response
    { 
		switch(reqType)
		{
			case 0:
				this->_Serial->write(cmd_read_CO2,7);
				break;
			case 1:
				this->_Serial->write(cmd_read_Temp,7);
				break;
			case 2:
				this->_Serial->write(cmd_read_RH,7);
				break;
			case 3:
				this->_Serial->write(cmd_init,8);
				break;
			default:
				return -256;
				break;
		}
		cmdTimeout++;
		if(cmdTimeout > 20)
			return -203;
      wait(130);					//give some time after each request for a response
    }
	
	int timeOut=0;	//initialize a timeout counter
	
	while(this->_Serial->available() < 7)	//loop through until we have are 7-byte response
	{ 
		if(timeOut > 40);			//after 40 loops, break out and try again
			break;
		timeOut++;
		delay(5);
	}
	
	if(this->_Serial->available() == 7)		//if we have our 7-byte response get value
			 Val = getResp(respSize,respInd);
	else							//if we dont i.e. our request timed out
	{
		Val = -300;
		while(this->_Serial->available() > 0)	//loop through and flush any bytes we did receiver so they dont throw the next packet off track
		{
				Val++;
				this->_Serial->read();
		}	
	}
	return Val;
}

long kSeries :: getResp(int size, int strt)
{
	byte packet[size];
    for(int i=0; i<size; i++)
    {
		packet[i] = this->_Serial->read();                   //create array from packet
    }
	
    int high = packet[strt];                        //high byte for value is 4th byte in packet in the packet
    int low = packet[strt+1];                         //low byte for value is 5th byte in the packet

  
    unsigned long val = high*256 + low;                //combine high byte and low byte
    return val;
}

void kSeries :: wait(int ms)
{
	long start = millis();
	long tmp = millis();
	while ((tmp - start) < ms)
	{
		tmp=millis();
	}
}
// This file name will be K_Series.h
/*
	K_Series.h -Library for interfacing with a K-series sensor
	Created by Jason Berger
	for CO2METER.com
	OCT-12-2012

*/


#if ARDUINO >= 100
 #include "Arduino.h"
#else
 #include "WProgram.h"
#endif

#ifndef kSeries_h
#define kSeries_h

#include <SoftwareSerial.h> 	//Virtual Serial library

class kSeries
{
  public:
    kSeries(uint8_t Rx, uint8_t Tx);
    double getCO2(char format);
	double getTemp(char unit);
	double getRH();
	bool _K33;
	bool _ASCII;
	int cmdInit();
  private:
	SoftwareSerial* _Serial;
	void chkSensorType();
	void chkASCII();
	void chkK33();
	int sendRequest(int reqType, int respSize, int respInd);
	long getResp(int size, int strt);	
	void wait(int ms);
};

#endif

Once that is complete, create a new sketch in Arduino called K30_Basics.ino and copy the code below.

/*
  Basic Arduino example for K-Series sensor
  Created by Jason Berger
  Co2meter.com  
*/

#include "SoftwareSerial.h"

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


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() 
{
  // 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
}

void loop() 
{
  sendRequest(readCO2);
  unsigned long valCO2 = getValue(response);
  Serial.print("Co2 ppm = ");
  Serial.println(valCO2);
  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;
}

Make sure you connect the board type is either Uno or MEGA by going to Tools, and Boards: Arduino Uno or Arduino MEGA. This method will only work with Uno, MEGA, and MEGA2560 boards.

Note: We are not using the M0 board in this case, which is different than the previous method.

Once that is complete, then upload the code and you will be ready to use the K30 Sensor.

If there any questions about this method, please send an email to CO2Meter staff members. They are the one who creates this method with the credits.

Which Project Use This Sensor


Reference


Clone this wiki locally