CO2 readings via I2C not working

Hi guys, I hooked up a senseair sunrise co2 sensor to my arduino. Connecting the sensor only to the arduino works fine, however, when I connect an sd/rtc sensor shield to the arduino and use the same pins, the readings would fail (Overview | Adafruit Data Logger Shield | Adafruit Learning System is the shield I use).
I am pretty sure there is some interference in I2C, but I am too smooth brained to detect it. I can provice the code if needed.

Cheers!
Edit: here is my code:

#include <Wire.h>
#include "RTClib.h"
#include <SPI.h>
#include <SD.h>

#define WIRE_WORKAROUND   (0)
#define DISABLE_ABC       (0) /* It could be necessary to disable ABC if sensor will be tested with CO2 concentrations below 400ppm. */

/*sunrise configuration */
const int       SUNRISE_EN              = 8;/* Define serial EN pin */
const uint8_t   SUNRISE_ADDR            = 0x68;/* Sunrise communication address, both for Modbus and I2C */
const int       ATTEMPTS                 = 5;/* Amount of wakeup attempts before time-out */
const int       EEPROM_UPDATE_DELAY_MS   = 25;/* It takes 25ms to write one EE register */

/* Register Addresses */
const uint8_t ERROR_STATUS             = 0x01;
const uint8_t MEASUREMENT_MODE         = 0x95;
const uint8_t METER_CONTROL            = 0xA5;
const uint8_t START_MEASUREMENT        = 0xC3;
const uint8_t ABC_TIME                 = 0xC4;

/* Measurement modes */
const uint16_t CONTINUOUS               = 0x0000;
const uint16_t SINGLE                   = 0x0001;

/* Delays in milliseconds*/
const int STABILIZATION_MS              = 35;
const int WAIT_FOR_PIN_MS               = 2000;

/* Reading period, in milliseconds. Default is 4 seconds */
int give_sensor_info = 0;
int show_error = 0;
int readPeriodMs = 1000;

/* Array for storing sensor state data */
uint8_t state[24];

/* logger shield configuration */
const byte chipSelect = 10;
RTC_DS1307 rtc;
File myFile;
char dateBuffer[12];
char sensBuffer[10];

void setup() {
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);

  pinMode(SUNRISE_EN, OUTPUT);
  digitalWrite(SUNRISE_EN, HIGH);
  delay(STABILIZATION_MS);  /* Wait for sensor start-up and stabilization */
  reInitI2C();              /* Initialize I2C and use default pins defined for the board */
  Serial.println("Initialization complete\n");

  if (give_sensor_info == 1) {
    /* Read the sensor's configs */
    Serial.println("Sensor Measurement Configurations:");
    read_sensor_config(SUNRISE_ADDR);
    Serial.println();

#if (DISABLE_ABC ==1)
    setABC(SUNRISE_ADDR, false);
#endif

    change_measurement_mode(SUNRISE_ADDR);  /* Change measurement mode if continuous */
    Serial.println("Saving Sensor State"); /* Initial measurement */
    save_state(SUNRISE_ADDR);
  }

  if (! rtc.begin()) {
    Serial.println("No RTC");
    Serial.flush();
    abort();
  }
  if (! rtc.isrunning()) {
    Serial.println("RTC was off. Setting new time.");
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }
  //rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));

  digitalWrite(SUNRISE_EN, LOW);
  delay(readPeriodMs);
}

void loop() {
  static int pin_value = HIGH;
  static unsigned long last_abc_stamp = 0;

  DateTime time = rtc.now();

  //Full Timestamp
  Serial.println(String("DateTime::TIMESTAMP_FULL:\t") + time.timestamp(DateTime::TIMESTAMP_FULL));

  /* Read measurements */
  read_sensor_measurements(SUNRISE_ADDR);
  delay(readPeriodMs);

  /* Indicate working state */
  digitalWrite(LED_BUILTIN, pin_value);
  pin_value  = ((pin_value == HIGH) ? LOW : HIGH);
}

/*
   ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
   Functions used to start the sensor and communicate with it.
   ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

*/

int WireRequestFrom(uint8_t dev_addr, uint8_t bytes_numbers, uint8_t offset_to_read, bool stop) {
  /* Workaround regarding BAD implementations of Wire.endTransmission(false) function */
  int error;
#if (WIRE_WORKAROUND == 1)
  error = Wire.requestFrom((uint8_t)dev_addr, (uint8_t)bytes_numbers /* how many bytes */, (uint32_t)offset_to_read /* from address*/, (uint8_t)1/* Address size - 1 byte*/, stop /* STOP*/);
#else
  Wire.beginTransmission(dev_addr);
  Wire.write(offset_to_read); //starting register address, from which read data
  Wire.endTransmission(false);
  error = Wire.requestFrom((uint8_t)dev_addr, (uint8_t)bytes_numbers /* how many bytes */, (uint8_t)stop /* STOP*/);
#endif
  return error;
}



void  reInitI2C() {
  /* Initialize I2C bus and pins */
  /* Initialize I2C and use default pins defined for the board */
  Wire.begin();
  /* Setup I2C clock to 100kHz */
  Wire.setClock(100000);
}

void save_state(uint8_t target) {
  /**
    @brief  Reads the sensors current state data.

    @param  target:      The sensor's communication address
    @note   If host device has no state data, it is very important
            that host do not write '0' to address 0xC2 - 0xDB the
            first time it starts a measurement.
    @retval None
  */
  /* Function variables */
  int error;

  int numBytes = 24;

  /* Drive EN pin HIGH */
  digitalWrite(SUNRISE_EN, HIGH);

  /* Wait for sensor start-up and stabilization */
  delay(STABILIZATION_MS);


  /* Wakeup */
  if (!(_wakeup(target))) {
    Serial.print("Failed to wake up sensor.");
    /* FATAL ERROR */
    digitalWrite(SUNRISE_EN, LOW);
    while (true);
  }

  /* Request state data */
  error = WireRequestFrom(target, numBytes /* how many bytes */, ABC_TIME /* from address*/, true /* STOP*/);
  if (error != numBytes ) {
    Serial.print("Failed to read measurements command. Error code: ");
    Serial.println(error);
    digitalWrite(SUNRISE_EN, LOW);
    /* FATAL ERROR */
    while (true);
  }

  /* Read and save state data */
  for (int n = 0 ; n < numBytes ; n++) {
    state[n] = Wire.read();
  }

  /* Drive EN pin LOW */
  digitalWrite(SUNRISE_EN, LOW);

  Serial.println("Saved Sensor State Successfully\n");
}

bool _wakeup(uint8_t target) {
  /**
    @brief  Wakes up the sensor by initializing a write operation
            with no data.

    @param  target:      The sensor's communication address
    @note   This example shows a simple way to wake up the sensor.
    @retval true if successful, false if failed
  */
  int attemps = ATTEMPTS;
  int error;

  do {
    uint8_t byte_0;
    /* */
    Wire.beginTransmission(target);
    error = Wire.endTransmission(true);
  } while (((error != 0 /*success */) && (error != 2 /*Received NACK on transmit of address*/) && (error != 1 /* BUG in STM32 library*/)) && (--attemps > 0));
  /* STM32 driver can stack under some conditions */
  if (error == 4) {
    /* Reinitialize I2C*/
    reInitI2C();
    return false;
  }
  return (attemps > 0);
}

void read_sensor_config(uint8_t target) {
  /* Function variables */
  int error;
  int numBytes = 7;

  /* Wakeup */
  if (!(_wakeup(target))) {
    Serial.print("Failed to wake up sensor.");
    return;
  }

  /* Request values */
  error = WireRequestFrom(target, numBytes /* how many bytes */, MEASUREMENT_MODE /* from address*/, true /* STOP*/);
  if (error != numBytes ) {
    Serial.print("Failed to write to target. Error code : ");
    Serial.println(error);
    return;
  }

  /* Read values */
  /* Measurement mode */
  uint8_t measMode = Wire.read();

  /* Measurement period */
  uint8_t byteHi = Wire.read();
  uint8_t byteLo = Wire.read();
  uint16_t measPeriod = ((int16_t)(int8_t) byteHi << 8) | (uint16_t)byteLo;

  /* Number of samples */
  byteHi = Wire.read();
  byteLo = Wire.read();
  uint16_t numSamples = ((int16_t)(int8_t) byteHi << 8) | (uint16_t)byteLo;

  /* ABCPeriod */
  byteHi = Wire.read();
  byteLo = Wire.read();
  uint16_t abcPeriod = ((int16_t)(int8_t) byteHi << 8) | (uint16_t)byteLo;

  /* Most propable that the sensor will not go into sleep mode, but to be insure...*/
  /* Wakeup */
  if (!(_wakeup(target))) {
    Serial.print("Failed to wake up sensor.");
    return;
  }

  /* Request values */
  error = WireRequestFrom(target, 1, METER_CONTROL /* from address*/, true /* STOP*/);
  if (error != 1 ) {
    Serial.print("Failed to write to target. Error code : ");
    Serial.println(error);
    return;
  }

  uint8_t  meterControl = Wire.read();

  Serial.print("Measurement Mode: ");
  Serial.println(measMode);
  readPeriodMs = measPeriod * 1000;

  Serial.print("Measurement Period, sec: ");
  Serial.println(measPeriod);

  Serial.print("Number of Samples: ");
  Serial.println(numSamples);

  if ((0U == abcPeriod) ||  (0xFFFFU == abcPeriod) || (meterControl & 0x02U)) {
    Serial.println("ABCPeriod: disabled");
  } else {
    Serial.print("ABCPeriod, hours: ");
    Serial.println(abcPeriod);
  }

  Serial.print("MeterControl: ");
  Serial.println(meterControl, HEX);

#if (DISABLE_ABC ==0)
  /* If we do not implicity disable ABC try to enable it in case if someone forget to do that...*/
  if ((meterControl & 0x02U) != 0) {
    setABC(target, true);
  }
#endif
}

void change_measurement_mode(uint8_t target) {
  /**
    @brief  Changes the sensor's current measurement mode, if it's
            currently in single mode.

    @param  target:      The sensor's communication address
    @note   This example shows a simple way to change the sensor's
            measurement mode. The sensor has to be manually restarted after the
            changes.
    @retval None
  */
  /* Function variables */
  int error;
  int numBytes = 1;

  /* Wakeup */
  if (!(_wakeup(target))) {
    Serial.print("Failed to wake up sensor.");
    /* FATAL ERROR */
    while (true);
  }

  /* Read Value */
  error = WireRequestFrom(target, numBytes /* how many bytes */, MEASUREMENT_MODE /* from address*/, true /* STOP*/);
  if (error != numBytes ) {
    Serial.print("Failed to read measurement mode. Error code: ");
    Serial.println(error);
    /* FATAL ERROR */
    while (true);
  }

  /* Change mode if continuous */
  if (Wire.read() != SINGLE) {
    Serial.println("Changing Measurement Mode to Single...");
    /* Wakeup */
    if (!_wakeup(target)) {
      Serial.print("Failed to wake up sensor.");
      /* FATAL ERROR */
      while (true);
    }
    Wire.beginTransmission(target);
    Wire.write(MEASUREMENT_MODE);
    Wire.write(SINGLE);
    error = Wire.endTransmission(true);
    delay(EEPROM_UPDATE_DELAY_MS);

    if (error != 0) {
      Serial.print("Failed to send request. Error code: ");
      Serial.println(error);
      /* FATAL ERROR */
      while (true);
    }
    Serial.println("Sensor restart is required to apply changes");
    /* Turn-off sensor */
    digitalWrite(SUNRISE_EN, LOW);
    /* Wait for sensor restart */
    delay(STABILIZATION_MS);
    /* Turn-on sensor */
    digitalWrite(SUNRISE_EN, HIGH);
  }
}

void setABC(uint8_t target, bool enable) {
  /* Wakeup */
  if (!(_wakeup(target))) {
    Serial.print("Failed to wake up sensor.");
    return;
  }

  /* Request values */
  int error = WireRequestFrom(target, 1, METER_CONTROL /* from address*/, true /* STOP*/);
  if (error != 1 ) {
    Serial.print("Failed to write to target. Error code : ");
    Serial.println(error);
    return;
  }

  uint8_t  meterControl = Wire.read();

  if (enable) {
    Serial.println("Enabling ABC...");
    meterControl &= (uint8_t)~0x02U;
  } else {
    Serial.println("Disabling ABC...");
    meterControl |= 0x02U;
  }

  /* Wakeup */
  if (!(_wakeup(target))) {
    Serial.print("Failed to wake up sensor.");
    return;
  }

  Wire.beginTransmission(target);
  Wire.write(METER_CONTROL);
  Wire.write(meterControl);
  error = Wire.endTransmission(true);
  delay(EEPROM_UPDATE_DELAY_MS);

  if (error != 0) {
    Serial.print("Failed to send request. Error code: ");
    Serial.println(error);
    /* FATAL ERROR */
    while (true);
  }
}

void read_sensor_measurements(uint8_t target) {
  /**
    @brief  Reads and prints the sensor's current CO2 value and
            error status.

    @param  target:      The sensor's communication address
    @note   This example shows a simple way to read the sensor's
            CO2 measurement and error status.
    @retval None
  */
  /* Function variables */
  int error;

  int numRegCmd = 25;
  int numRegRead = 7;
  int numRegState = 24;

  uint8_t cmdArray[25];

  cmdArray[0] = 0x01;

  for (int n = 1 ; n < numRegCmd ; n++) {
    cmdArray[n] = state[n - 1];
  }

  /* Drive EN pin HIGH */
  digitalWrite(SUNRISE_EN, HIGH);

  /* Wait for sensor start-up and stabilization */
  delay(STABILIZATION_MS);

  /* Wakeup */
  if (!_wakeup(target)) {
    Serial.print("Failed to wake up sensor.");
    return;
  }

  /* Write measurement command and sensor state to 0xC3 */
  Wire.beginTransmission(target);
  Wire.write(START_MEASUREMENT);
  for (int reg_n = 0; reg_n < numRegCmd; reg_n++) {
    Wire.write(cmdArray[reg_n]);
  }
  error = Wire.endTransmission(true);
  if (error != 0) {
    Serial.print("Failed to send measurement command. Error code: ");
    Serial.println(error);
    digitalWrite(SUNRISE_EN, LOW);
    return;
  }

  /* Wait until ready pin goes low */
  delay(WAIT_FOR_PIN_MS);

  /* Wakeup */
  if (!(_wakeup(target))) {
    Serial.print("Failed to wake up sensor.");
    digitalWrite(SUNRISE_EN, LOW);
    return;
  }

  /* Request values */
  error = WireRequestFrom(target, numRegRead /* how many bytes */, ERROR_STATUS /* from address*/, true /* STOP*/);
  if (error != numRegRead ) {
    Serial.print("Failed to read values. Error code: ");
    Serial.println(error);
    digitalWrite(SUNRISE_EN, LOW);
    return;
  }

  /* Read values */
  /* Error status */
  uint8_t eStatus = Wire.read();

  /* Reserved */
  uint8_t byteHi = Wire.read();
  uint8_t byteLo = Wire.read();

  byteHi = Wire.read();
  byteLo = Wire.read();

  /* CO2 value */
  byteHi = Wire.read();
  byteLo = Wire.read();
  uint16_t co2Val = ((int16_t)(int8_t) byteHi << 8) | (uint16_t)byteLo;

  /* Wakeup */
  if (!_wakeup(target)) {
    Serial.print("Failed to wake up sensor.");
    digitalWrite(SUNRISE_EN, LOW);
    return;
  }

  /* Read sensor state data from 0xC4-0xDB and save it for next measurement */
  error = WireRequestFrom(target, numRegState /* how many bytes */, ABC_TIME /* from address*/, true /* STOP*/);
  if (error != numRegState) {
    Serial.print("Failed to read measurements command. Error code: ");
    Serial.println(error);
    digitalWrite(SUNRISE_EN, LOW);
    return;
  }

  for (int n = 0 ; n < numRegState ; n++) {
    state[n] = Wire.read();
  }

  /* Drive EN pin LOW */
  digitalWrite(SUNRISE_EN, LOW);

  /* Print values */
  Serial.print("CO2: ");
  Serial.print(co2Val);
  Serial.println(" ppm");
  if (show_error == 1) {
    Serial.print("Error Status: 0x");
    Serial.println(eStatus, HEX);
    Serial.println();
  }
}


Edit 2: The output of the sensor config reading gives zeros only, the co2 values are wrong. When removing the logger shield, sensor config is properly read and co2 values are correct,.

After hooking up the co2 thingy and the shield and running the I2C scanner what were the results of the I2C scan?

Which co2 thingy are you using?

I didnt run the SD card yet, neither the RTC. I will add this part later. The I2C scanner shows only

14:10:23.688 -> I2C Scanner
14:10:23.688 -> Scanning...
14:10:23.688 -> I2C device found at address 0x68  !
14:10:23.734 -> done

Posting your code would be a good idea.

I did now. Thanks for the help

There is an address collision:
SUNRISE_ADDR = 0x68
PCF8523 (on your logger shield)=0x68

1 Like

Thank you for the help. Can you explain how you found the adress of the sd shield?

If this is the correct datasheet

https://www.google.com/url?sa=t&source=web&rct=j&url=https://duomo.co.uk/wp-content/uploads/I2C-on-Senseair-Sunrise-TDE5531.pdf&ved=2ahUKEwjal4rUh5T5AhWCd8AKHatZCLcQFnoECAkQAQ&usg=AOvVaw39JPixpc2IiUOtoKd6b0M4

... then the i2c address can be changed and saved in the sensor's EEPROM. This only needs to be done once, with the RTC shield disconnected. After that, the shield can be connected again because the sensor's address will no longer clash with the RTC.

I knew the adress of the co2 sensor, but couldnt find the information about the shield. I will need someone to programm that part, thats too difficult for me :smiley:

I checked photo on adafruit site, unfortunately they didn't provide any details about this shield.

Thank you so much. Tbh I expected the sd card to be the problem and didnt look for the rtc

@PaulRB Your link is this link: https://duomo.co.uk/wp-content/uploads/I2C-on-Senseair-Sunrise-TDE5531.pdf

@de_botaniker1 What does this mean: "Workaround regarding BAD implementations of Wire.endTransmission(false) function". Is there something that I don't know, please tell me.

The code is provided by the manufacturer, I don't know what the workaround is being used for. Do you know how to write the code to change the sensor's address? I struggle a lot. This is totally not one of my skills

I think you better start a new topic for that.
Give all the information, datasheet, link to manufacturer code, and so on.

A few days ago I wrote: "I was already shivering and sweating, because that is often hard to do"

That was done, aarg has flagged it as duplicate. Let the mods sort it out?

Hey folks, this is my second post today, because I don't understand how to change the I2C address of my sensor. I have the documentation, but it's not my key competence. I would appreciate if some could help me to figure that out.
Link for documentation: https://duomo.co.uk/wp-content/uploads/I2C-on-Senseair-Sunrise-TDE5531.pdf
(page 30 + 31).
I think if one knows how to write i2c code, this should (?) be easy.
thank you already!

duplicate post, find additional info here:
https://forum.arduino.cc/t/co2-readings-via-i2c-not-working/1015891

@de_botaniker1
Do you have an arduino library for accessing the sensor?

https://github.com/Niclas73/Sunrise-master
Look in examples

@ZX80 Thank you so much, I wasn't aware there was a library.

@b707 I didn't, but now I have!