I2C Daisychian PCF8574 & CCS811

Hello,

I am trying to daisychain the following: NodeMCU->PCF8574->CCS811

This works great:
NodeMCU -> PCF8574
NodeMCU -> CCS811

However having them both daisy chained to my NodeMCU does not work, the CCS811 will give an I2C error.
Is there something I need to do, to get this working?

Photo in the attachment.

Code:

#include <Wire.h>    // I2C library
#include "PCF8574.h"
#include "ccs811.h"  // CCS811 library

// Set i2c address
PCF8574 pcf8574(0x27, 5, 4);


CCS811 ccs811(D3); // nWAKE on D3
void setup()
{
  Serial.begin(115200);
  Wire.begin();
  for (int i = 0; i < 8; i++) {
    pcf8574.pinMode(i, OUTPUT);
  }
  pcf8574.begin();

  ccs811.set_i2cdelay(50); // Needed for ESP8266 because it doesn't handle I2C clock stretch correctly
  bool ok = ccs811.begin();
  if ( !ok ) Serial.println("setup: CCS811 begin FAILED");
  ok = ccs811.start(CCS811_MODE_1SEC);
  if ( !ok ) Serial.println("setup: CCS811 start FAILED");
}

void loop()
{
  getInteralAirQuality();
  pcf8574.digitalWrite(P0, HIGH);
  pcf8574.digitalWrite(P1, HIGH);
  pcf8574.digitalWrite(P2, HIGH);
  pcf8574.digitalWrite(P3, HIGH);
  pcf8574.digitalWrite(P4, HIGH);
  pcf8574.digitalWrite(P5, HIGH);
  pcf8574.digitalWrite(P6, HIGH);
  pcf8574.digitalWrite(P7, HIGH);
  delay(1000);
  pcf8574.digitalWrite(P0, LOW);
  pcf8574.digitalWrite(P1, LOW);
  pcf8574.digitalWrite(P2, LOW);
  pcf8574.digitalWrite(P3, LOW);
  pcf8574.digitalWrite(P4, LOW);
  pcf8574.digitalWrite(P5, LOW);
  pcf8574.digitalWrite(P6, LOW);
  pcf8574.digitalWrite(P7, LOW);
  delay(1000);
}



void getInteralAirQuality()
{
  uint16_t eco2, etvoc, errstat, raw;
  ccs811.read(&eco2, &etvoc, &errstat, &raw);
  delay(1000);


  Serial.print("INTERNAL AIRQUALITY: ");
  Serial.println(eco2);
}

What operating voltages are involved on both systems?
Most I2C devices are designed for either 3.3V or 5V, not both.

DrDiettrich:
What operating voltages are involved on both systems?
Most I2C devices are designed for either 3.3V or 5V, not both.

PCF8574: 2.5 - 6V DC
CCS811: 1.8 - 3.3V DC

Connected to 3.3V

And the NodeMCU also works with 3.3V?

Which pullup resistors?

Pullup resistors to 3.3 V only. Remove any on 5 V device.

Maybe also call:
pcf8574.begin() ;

Before the call to:
pcf8574.pinMode() ;

Instead of afterwards.

DrDiettrich:
And the NodeMCU also works with 3.3V?

Which pullup resistors?

Paul__B:
Pullup resistors to 3.3 V only. Remove any on 5 V device.

I do not use any external pullup resistors. The NodeMCU is connected through USB and works with 3.3V

The PCF8574 has pullup resistors on board (as far as I know off).

So: NodeMCU (3.3V) -> PCF8574(with pullup resistors) -> CCS811.

The intresting part it: It is working like this, after some code changes. I can read the CCS811 or the PCF8574. However, I can't get them to work together in one code. If you want I can post both codes.

6v6gt:
Maybe also call:
pcf8574.begin() ;

Before the call to:
pcf8574.pinMode() ;

Instead of afterwards.

I have tried this. It does not work.

I wonder: if both modules are connected, the code works if it only uses one of these? If so, this indicates incompatible libraries. Quite unlikely...

Which addresses does I2Csniffer find?

DrDiettrich:
I wonder: if both modules are connected, the code works if it only uses one of these? If so, this indicates incompatible libraries. Quite unlikely...

Which addresses does I2Csniffer find?

0xA5 = CCS811, 0x27 = PCF8574 but the I2C sniffer only finds 0xA5!

Code used for the sniffer:

  // --------------------------------------
// i2c_scanner
//
// Version 1
//    This program (or code that looks like it)
//    can be found in many places.
//    For example on the Arduino.cc forum.
//    The original author is not known.
// Version 2, Juni 2012, Using Arduino 1.0.1
//     Adapted to be as simple as possible by Arduino.cc user Krodal
// Version 3, Feb 26  2013
//    V3 by louarnold
// Version 4, March 3, 2013, Using Arduino 1.0.3
//    by Arduino.cc user Krodal.
//    Changes by louarnold removed.
//    Scanning addresses changed from 0...127 to 1...119,
//    according to the i2c scanner by Nick Gammon
//    http://www.gammon.com.au/forum/?id=10896
// Version 5, March 28, 2013
//    As version 4, but address scans now to 127.
//    A sensor seems to use address 120.
// 
//
// This sketch tests the standard 7-bit addresses
// Devices with higher bit address might not be seen properly.
//

#include <Wire.h>


void setup()
{
  Wire.begin();

  Serial.begin(9600);
  Serial.println("\nI2C Scanner");
}


void loop()
{
  byte error, address;
  int nDevices;

  Serial.println("Scanning...");

  nDevices = 0;
  for(address = 1; address < 127; address++ ) 
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address<16) 
        Serial.print("0");
      Serial.print(address,HEX);
      Serial.println("  !");

      nDevices++;
    }
    else if (error==4) 
    {
      Serial.print("Unknow error at address 0x");
      if (address<16) 
        Serial.print("0");
      Serial.println(address,HEX);
    }    
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");

  delay(5000);           // wait 5 seconds for next scan
}

The changed code I use for reading the CCS811 and blinking the IO pins of the PCF8574:

#include <Wire.h>    // I2C library
#include "PCF8574.h"
#include "SparkFunCCS811.h"

#define CCS811_ADDR 0x5A //Alternate I2C Address

CCS811 mySensor(CCS811_ADDR);

// Set i2c address
PCF8574 pcf8574(0x27, 5, 4);


void setup()
{
  Serial.begin(9600);
  Wire.begin();
  Serial.println("CCS811 Basic Example");
  CCS811Core::status returnCode = mySensor.begin();
  if (returnCode != CCS811Core::SENSOR_SUCCESS)
  {
    Serial.println(".begin() returned with an error.");
    while (1); //Hang if there was a problem.
  }
  pcf8574.begin();
  for (int i = 0; i < 8; i++) {
    pcf8574.pinMode(i, OUTPUT);
  }


}

void loop()
{
  //Check to see if data is ready with .dataAvailable()
  if (mySensor.dataAvailable())
  {
    //If so, have the sensor read and calculate the results.
    //Get them later
    mySensor.readAlgorithmResults();

    Serial.print("CO2[");
    //Returns calculated CO2 reading
    Serial.print(mySensor.getCO2());
    Serial.print("] tVOC[");
    //Returns calculated TVOC reading
    Serial.print(mySensor.getTVOC());
    Serial.print("] millis[");
    //Simply the time since program start
    Serial.print(millis());
    Serial.print("]");
    Serial.println();
  }
  //getInteralAirQuality();
  pcf8574.digitalWrite(P0, HIGH);
  pcf8574.digitalWrite(P1, HIGH);
  pcf8574.digitalWrite(P2, HIGH);
  pcf8574.digitalWrite(P3, HIGH);
  pcf8574.digitalWrite(P4, HIGH);
  pcf8574.digitalWrite(P5, HIGH);
  pcf8574.digitalWrite(P6, HIGH);
  pcf8574.digitalWrite(P7, HIGH);
  delay(1000);
  pcf8574.digitalWrite(P0, LOW);
  pcf8574.digitalWrite(P1, LOW);
  pcf8574.digitalWrite(P2, LOW);
  pcf8574.digitalWrite(P3, LOW);
  pcf8574.digitalWrite(P4, LOW);
  pcf8574.digitalWrite(P5, LOW);
  pcf8574.digitalWrite(P6, LOW);
  pcf8574.digitalWrite(P7, LOW);
  delay(1000);
}

Result: The PCF I/O pins blink but the CCS811 does not read, no errors given.

Code for reading the CCS811 only:

/******************************************************************************
  Read basic CO2 and TVOCs on alternate Wire ports

  Marshall Taylor @ SparkFun Electronics
  Nathan Seidle @ SparkFun Electronics

  April 4, 2017

  https://github.com/sparkfun/CCS811_Air_Quality_Breakout
  https://github.com/sparkfun/SparkFun_CCS811_Arduino_Library

  Read the TVOC and CO2 values from the SparkFun CSS811 breakout board

  This shows how to begin communication with the sensor on a different Wire port.
  Helpful if you have platform that is a slave to a larger system and need
  a dedicated Wire port or if you need to talk to many sensors at the same time.

  A new sensor requires at 48-burn in. Once burned in a sensor requires
  20 minutes of run in before readings are considered good.

  Hardware Connections (Breakoutboard to Arduino):
  3.3V to 3.3V pin
  GND to GND pin
  SDA to A4
  SCL to A5

  Resources:
  Uses Wire.h for i2c operation

  Development environment specifics:
  Arduino IDE 1.8.1

  This code is released under the [MIT License](http://opensource.org/licenses/MIT).

  Please review the LICENSE.md file included with this example. If you have any questions
  or concerns with licensing, please contact techsupport@sparkfun.com.

  Distributed as-is; no warranty is given.
******************************************************************************/
#include <Wire.h>

#include "SparkFunCCS811.h"

//#define CCS811_ADDR 0x5B //Default I2C Address
#define CCS811_ADDR 0x5A //Alternate I2C Address

CCS811 mySensor(CCS811_ADDR);

void setup()
{
  Serial.begin(9600);
  Serial.println("CCS811 Basic Example");

  Wire.begin(); //Compilation will fail here if your hardware doesn't support additional Wire ports

  //It is recommended to check return status on .begin(), but it is not
  //required.
  CCS811Core::status returnCode = mySensor.begin();
  if (returnCode != CCS811Core::SENSOR_SUCCESS)
  {
    Serial.println(".begin() returned with an error.");
    while (1); //Hang if there was a problem.
  }
}

void loop()
{
  //Check to see if data is ready with .dataAvailable()
  if (mySensor.dataAvailable())
  {
    //If so, have the sensor read and calculate the results.
    //Get them later
    mySensor.readAlgorithmResults();

    Serial.print("CO2[");
    //Returns calculated CO2 reading
    Serial.print(mySensor.getCO2());
    Serial.print("] tVOC[");
    //Returns calculated TVOC reading
    Serial.print(mySensor.getTVOC());
    Serial.print("] millis[");
    //Simply the time since program start
    Serial.print(millis());
    Serial.print("]");
    Serial.println();
  }

  delay(10); //Don't spam the I2C bus
}

Result: Works as expected, reading both eCO2 and VOC.

Code used for setting the I/O pins on the PCF:

#include <Wire.h>    // I2C library
#include "PCF8574.h"

// Set i2c address
PCF8574 pcf8574(0x27, 5, 4);


void setup()
{
  Serial.begin(115200);
  Wire.begin();
  pcf8574.begin();
  for (int i = 0; i < 8; i++) {
    pcf8574.pinMode(i, OUTPUT);
  }

}

void loop()
{
  //getInteralAirQuality();
  pcf8574.digitalWrite(P0, HIGH);
  pcf8574.digitalWrite(P1, HIGH);
  pcf8574.digitalWrite(P2, HIGH);
  pcf8574.digitalWrite(P3, HIGH);
  pcf8574.digitalWrite(P4, HIGH);
  pcf8574.digitalWrite(P5, HIGH);
  pcf8574.digitalWrite(P6, HIGH);
  pcf8574.digitalWrite(P7, HIGH);
  delay(1000);
  pcf8574.digitalWrite(P0, LOW);
  pcf8574.digitalWrite(P1, LOW);
  pcf8574.digitalWrite(P2, LOW);
  pcf8574.digitalWrite(P3, LOW);
  pcf8574.digitalWrite(P4, LOW);
  pcf8574.digitalWrite(P5, LOW);
  pcf8574.digitalWrite(P6, LOW);
  pcf8574.digitalWrite(P7, LOW);
  delay(1000);
}

Result: Working as expected, all pins go HIGH to LOW continuously.

I've used my extreme cool MS Paint skills to clarify the wiring (see attachment).

My conclusion is that or my code is wrong or the libraries are incompatible. But I am not sure how to solve this..

The CCS811 does not have a VCC terminal!?

How much current does the CCS811 draw? It may be too much for the controller voltage regulator, in detail when all the LEDs are on.

Terick:
The PCF8574 has pullup resistors on board (as far as I know off).

But it is a 5 V device as I understand it. To use with a 3.3 V system they must be removed. :astonished:

DrDiettrich:
The CCS811 does not have a VCC terminal!?

How much current does the CCS811 draw? It may be too much for the controller voltage regulator, in detail when all the LEDs are on.

I am sorry: on the VIN terminal. 46mW is the max an CCS881 can possible draw. So I assume this would not be the problem.

I disconnected all the LEDs to be sure, it still acts the same way.

Paul__B:
But it is a 5 V device as I understand it. To use with a 3.3 V system they must be removed. :astonished:

Where did you find that? The PCF8574 break-out board does have two 10k resistors on board. This should be sufficient for both 3.3V and 5V as far as I know. It is also not an 5V device, technical instruments states it can carry 2.5V up to 6V.

Also the internal pull-ups on the NodeMCU should become active when wire.begin() is called.

I am continuing my research and found that the CCS811 requires clock stretching. The NodeMCU is capable of doing this, as I can get the data out of the CSS811. I do wonder though, if this is why a combination on the I2C bus does not work? This is new territory for me!

See #2. If there is no 5V supply, the pullups only can go to the 3.3V supply, whatever is present.

Clock stretching in fact might confuse other bus devices. But if so which device should fail?
If this really is a problem, an I2C multiplexer can be used to separate multiple sub-buses.

Terick:
Where did you find that? The PCF8574 break-out board does have two 10k resistors on board. This should be sufficient for both 3.3V and 5V as far as I know. It is also not an 5V device, technical instruments states it can carry 2.5V up to 6V.

It is simple.

You must not pull up SCL and SDA on any 3.3 V device higher than 3.3 V. If the PCF8574 break-out board has 10k resistors pulling up to 5 V, than that is exceeding the specifications for any 3.3 V device on the same bus, so you should remove them in order not to assert excess voltage on the 3.5 V devices..

That said, whether a 10k resistor to 5 V is actually likely to harm any 3.3 V I/O pin or cause it to malfunction is probably unlikely as it could never source more than 500 µA. But the effect on an ESP8266 is somewhat uncertain.

In general, 5 V logic will usually work fine with 3.3 V logic levels, but some devices are more fussy than others, especially at higher speeds..

Paul__B:
You must not pull up SCL and SDA on any 3.3 V device higher than 3.3 V. If the PCF8574 break-out board has 10k resistors pulling up to 5 V,

... AND is powered by 5V! Pullup resistors should go to the supplied operating voltage, not to anything else.

The TO confirmed to power everything by 3.3V only.

DrDiettrich:
... AND is powered by 5V! Pullup resistors should go to the supplied operating voltage, not to anything else.

Sorry, that is rather confusing.

If you run 3.3 V and 5 V devices on a single I2C bus, the pull-ups must go to 3.3 V in order not to apply excess voltages to the 3.3 V devices. The 5 V devices will generally operate just fine on the 3.3 V levels unless the loading is critical and using high speed.

But yes, you can use "bidirectional logic-level converters" which will generally operate just fine unless perhaps the loading is critical and using high speed.

See #2, both devices can and actually do run on 3.3V. So why discuss mixed voltage usage here?

A flexible device, like the PCF8574, capable and designed for running on both 3.3V and 5V cannot have pullup resistors to a dedicated 5V supply. Instead it will use for pullups whatever actual Vcc is supplied.