3 years, 4 months ago.

Can anyone help with an I2C read/write script?

Hi - I've been trying to talk to a CO2 sensor (http://www.warf.com/download/3423_7657_1.pdf) over I2C for a couple days and am just banging my head against the wall. Can anyone help? I am just starting down the mbed path, though I've used UART and I2C and SPI drivers successfully in other languages.

When I run the following code, the checksum doesn't give anything other than 0 and I'm getting an acknowledgement of 1 from i2c.write, which I take to mean that it didn't work. I printed out my first few lines from TeraTerm below.

Thank you!

i2c_K30.cpp

#include "mbed.h"
 
// Read CO2 from K30

Serial pc(USBTX, USBRX);  // tx, rx
I2C i2c(p26 , p27); // sda, scl
DigitalOut led1(p17);

char co2Addr = 0x68;      // I2C address
char writeBuff[4] = {0x22, 0x00, 0x08, 0x2A};

int main() {
    pc.baud(9600);
    pc.printf("World, is that you?\n\r");
    led1 = 0;
    i2c.frequency(100000);
    
    while (1) {
        //////////////////////////
        /* Begin Write Sequence */
        ////////////////////////// 
        
        // start and tell it what address K-30 is
        i2c.start(); 
     
        // K-30 commands, from co2meters.com/Documentation/AppNotes/AN102-K30-Sensor-Arduino-I2C.pdf
        int ack = i2c.write(co2Addr, writeBuff, 4, false);
        pc.printf("Acknowledgement: %i\n\r", ack);
        i2c.stop();
        
         /*
        Wait for the sensor to process our command. The sensors's
        primary duties are to accurately measure CO2 values. Waiting 
        ensures the data is properly written to RAM
        */
        
        wait_ms(20);
        
        /////////////////////////
        /* Begin Read Sequence */
        ///////////////////////// 
        i2c.start();
        char readBuff[4];
        i2c.read(co2Addr, readBuff, 4, false);
        i2c.stop();
        
        /*
        Using some bitwise manipulation we will shift our readBuff
         into an integer for general consumption
        */
        char co2_value = 0;
        co2_value |= readBuff[1] & 0xFF;
        co2_value = co2_value << 8;
        co2_value |= readBuff[2] & 0xFF;
        
        char sum = 0; //Checksum Byte
        sum = readBuff[0] + readBuff[1] + readBuff[2]; //Byte addition utilizes overflow
        if (sum == readBuff[3]){
            // Success!
            led1 = !led1;
            pc.printf("CO2 value = %d\n\r", co2_value);
        }
        else{
            /*
            Checksum failure can be due to a number of factors,
            fuzzy electrons, sensor busy, etc.
            */
            return 0;
        } 
    wait(2);    
    }
}

Quote:

World, is that you?

Acknowledgement: 1

CO2 value = 0

Acknowledgement: 1

CO2 value = 0

Acknowledgement: 1

CO2 value = 0

EDIT

Reworked code according to Wim's suggestion and the following now works reasonably well (solid readings!) but hangs after a seemingly arbitrary number of loops, usually a few dozen. Presumably some sort of timing issue. Hangups come in the form of either repeated values or no response at all.

i2c_K30.cpp

#include "mbed.h"
 
// Read CO2 from K30

Serial pc(USBTX, USBRX);  // tx, rx
I2C i2c(p26 , p27); // sda, scl
DigitalOut led1(p17);

char co2Addr = 0xD0;      // I2C address shifted one bit (0x68 on datasheet)
char writeBuff[4] = {0x22, 0x00, 0x08, 0x2A};

int main() {
    pc.baud(9600);
    pc.printf("World, is that you?\n\r");
    led1 = 0;
    i2c.frequency(100000);
    
    while (1) {
        // start and tell it what address K-30 is
        // K-30 commands, from co2meters.com/Documentation/AppNotes/AN102-K30-Sensor-Arduino-I2C.pdf
        int ack = i2c.write(co2Addr, writeBuff, 4, false);

         /*
        Wait for the sensor to process our command. The sensors's
        primary duties are to accurately measure CO2 values. Waiting 
        ensures the data is properly written to RAM
        */
        
        wait_ms(20);
        
        // Read 
        char readBuff[4];
        i2c.read(co2Addr, readBuff, 4, false);
        
        int high = readBuff[1];                        //high byte for value is 4th byte in packet in the packet
        int low = readBuff[2];                         //low byte for value is 5th byte in the packet
        unsigned long CO2 = high*256 + low;                //Combine high byte and low byte with this formula to get value

//        int opStatus = readBuff[0]; // should be 0x21 or 33 for p
        
        char sum = readBuff[0] + readBuff[1] + readBuff[2]; //Byte addition utilizes overflow
        
        if (sum == readBuff[3] & ack == 0){
            // Success!
            led1 = !led1;
            pc.printf("CO2 value = %d\n\r", CO2);
//            "b0 = 0x%02x,\tb1 = 0x%02x,\tb2 = 0x%02x,\tb3 = 0x%02x,\t
//            , readBuff[0], readBuff[1], readBuff[2], readBuff[3], 
        }
    wait(3);    
    }
}

1 Answer

3 years, 4 months ago.

There are a few issues here: the I2C address should be 0xD0 instead of 0x68. The mbed libs use 8bit format for the address and so you should shift left the addres shown in the datasheet. You should also remove the i2c.start and i2c.stop calls as they are already included in the block read and write operations. The additional calls will confuse the slave. Make sure to install external pull up resistors (4k7) on SDA and SCL as the provided resistors on the sensor board of some 56k are too high.

Accepted Answer

Thanks, Wim! You're my new hero. I'm now able to get solid CO2 readings, though the sensor comm regularly hangs after roughly two to three minutes and doesn't pick back up. Any guesses why this might be happening? I'm guessing it may be some sort timing issue, but it's hard to troubleshoot due to its irregularity. Thanks again, this is a huge step forward for me.

posted by Phil Bresnahan 21 Jul 2017

I dont have the device so cant test, but timing could be an issue. The datasheet says it wont respond to I2C when it is performing a measurement. You could try to slow down the communication at bit: reduced I2C clockspeed and longer delays. However that may still mean you get problems, only less frequently. Additional error checking and recovery may be needed in case you need more robustness. Also note that the CO2 value might be negative according to the datasheet so you would need a different conversion method to deal with the signbit. Give the code below a try.

#include "mbed.h"
 
// Read CO2 from K30
 
Serial pc(USBTX, USBRX);  // tx, rx
I2C i2c(p26 , p27); // sda, scl
DigitalOut led1(p17);
 
char co2Addr = 0xD0;      // I2C address shifted one bit (0x68 on datasheet)
char writeBuff[4] = {0x22, 0x00, 0x08, 0x2A}; // command string to read CO2
char readBuff[4];
int ack_w, ack_r;
short CO2; 
char sum;
 
int main() {
    pc.baud(9600);
    pc.printf("World, is that you?\n\r");
    led1 = 0;
    i2c.frequency(80000);  // reduce clockspeed
    
    while (1) {
        // K-30 command, from co2meters.com/Documentation/AppNotes/AN102-K30-Sensor-Arduino-I2C.pdf
        ack_w = i2c.write(co2Addr, writeBuff, 4, false);
 
         /*
        Wait for the sensor to process our command. The sensors's
        primary duties are to accurately measure CO2 values. Waiting 
        ensures the data is properly written to RAM
        */       
        wait_ms(50); // wait a bit longer..
        
        // Read 
        ack_r = i2c.read(co2Addr, readBuff, 4, false);

       // Combine high byte and low byte with this formula to get signed value
       //  high byte for value is 2nd byte in the packet, low byte for value is 3rd byte in the packet
       CO2 =  ((short)  readBuff[1]  <<  8) | ((short)  readBuff[2] ); 
                                                                                       
       sum = readBuff[0] + readBuff[1] + readBuff[2]; //Byte addition utilizes overflow
        
        if ( (sum == readBuff[3]) && (ack_w == 0) && (ack_r == 0) ) { // checksum is 4th byte in packet
            // Success!
            led1 = !led1;
            pc.printf("CO2 value = %d\n\r", CO2);
//            "b0 = 0x%02x,\tb1 = 0x%02x,\tb2 = 0x%02x,\tb3 = 0x%02x,\t
//            , readBuff[0], readBuff[1], readBuff[2], readBuff[3], 
        }
        else {
           // No joy
            pc.printf("CO2 value = ....\n\r");
      }
    wait(3);    
    }
}
posted by Wim Huiskamp 22 Jul 2017

Thanks, this unfortunately doesn't do any better. I added in a simple loop counter to see how (ir)regular the hangups are and they're usually occurring within the first 20 or so loops, and often much sooner, seemingly regardless of wait time between loops or I2C clock frequency. I also traded out my 9V battery for a stable bench-top supply to see if it was related to power but no dice. I have the 4k7 resistors pulling SDA and SCL up to 5 V. I appreciate your followup!

posted by Phil Bresnahan 22 Jul 2017

The datasheet and an appnote do state that the device needs a well regulated powersupply. You may even need to add a capacitor (47uF) on the sensorboard to deal with the current spikes when a measurement is made. Also note that depending on the type of sensor (you seem to have a K-30) the sensor CPU supply voltage is different. The K30 uses 3V3. This means the pull-up should also go to 3V3 ! See section 2.4 in the document referenced above. Make sure you have short wires, a good common ground and no electrical noise or at least proper shielding. Can you provide a wiring schematic to review. Can you measure or capture the I2C traffic on a scope or logic analyser?

posted by Wim Huiskamp 22 Jul 2017

The power supply is pretty high quality, so I think it's well regulated. I've tried pull-up voltages of 3.3 and 5 and it doesn't appear to change anything. I'm confident that my wiring matches the diagram on page 2 of this reference: http://www.co2meters.com/Documentation/AppNotes/AN102-K30-Sensor-Arduino-I2C.pdf. It's all breadboarded out so I'm sure the wires aren't are short as they could be, but they're only 10 or so cm, not super long. Not shielded though. I can get a logic analyzer from a coworker on Monday, I think. Thanks for continuing to help me.

posted by Phil Bresnahan 22 Jul 2017