-
Notifications
You must be signed in to change notification settings - Fork 3
K30 CO2 Sensor (SE 0018)
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.
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.
-
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.
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 with some angle
Back view with some angle
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.
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.
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.
- Install Arduino IDE in our Loom team GitHub Wiki Page
- 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.
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]}\
]\
}"
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.
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.
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.
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.
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.
-
eGreenhouse for the sensor package
- Check out their GitHub wiki page
- Project Planning
- Prototyping
- Synthesis
- Lab Testing
- Field Testing
- Finalization/Production
- Poster/Presentation
- Publication
- CRES
- Dendrometer
- Djinn
- eDNA Sampler
- eGreenhouse
- Evaporometer
- FloDar
- HyperRail
- Hypnos
- Isotopic Sampler
- Lilypad
- Loom
- Micro-Aggregating Sewer Sampler
- Mooraca
- OPEnSampler
- Pied Piper
- Rag Guard
- Rain Gauge Calibrator
- RainSavor
- RFID Moisture
- Sap Flow Meter
- SitkaNet
- Slide Sentinel
- Smart Rock
- Spool
- WeatherChimes
- Weed Warden
- Wisp
- Archived Project Blogs