I2C issue after hours

Hi everybody,

I have a very annoying issue with my spark core.

The context :

  • I use a spark core and the deep update has been done.
  • I use a lux sensor (Texas Instrument OPT3001).
  • There are 4.7K pull-up on SDA/SCL pins.
  • Sensor and Pull-up are connected to *3.3V.

I can access to all registers of the sensor but after a while (some minutes, hours, even day) i lost I2C communication with the sensor.

I have checked the voltage level and the waveform with an oscilloscope and the pin SDA and SCL doesn’t show any life :frowning:

After this, i flash a program who it toggle D0 and D1. It call setup_test() in setup function and it call test_pin() in loop function.

void setup_test() {
    pinMode(D0, OUTPUT);
    pinMode(D1, OUTPUT);
}

void test_pin() {
    digitalWrite(D0, LOW);
    digitalWrite(D1, HIGH);
    delay(1000);               // Wait for 1 second in off mode
    digitalWrite(D0, HIGH);
    digitalWrite(D1, LOW);
    delay(1000);               // Wait for 1 second in off mode
}

I can see the pins toggled on oscilloscope.

So, i reflash my program to get data from sensor and it work again but not for long.

I think i have checked everything but low level software.

I have some others sensors (BME280, HDC1001) that i want to tried but i can right now.

So, if anyone have any clue i will take it :wink:

Many thanks in advance if someone want to help me.

Hi @futaba

The lux sensor has a bunch of modes for i2c/SMBBus that you use–how are you setting up the chip? Is there a driver library? Using fast or high-speed mode? Using the interrupt pin too? I would look at all the register settings inside the lux sensor and make sure they make sense.

Another thing to check is how long your i2c connection wires are and how are they routed? Are you keeping them short and away from Spark’s RF antenna?

@futaba, you may also want to pull-up to 3.3V instead of *3.3V, which is typically lower than 3.3V in my experience.

On a recent topic, it was also found that the Core’s speed can affect I2C communications if it sends data too quickly to an I2C device. The library may work well with the slower Arduino but may need tweaking for the Core. :smile:

2 Likes

Hi @bko,

Thank you for your response.

The lux sensor is configured in standard (100KHz) I2C mode. I just used wire library and write some functions to start a conversion and get result or get device ID/Vendor ID. The lux sensor is still in default configuration (Automatic Full Range, 800ms Conversion Time, Power down). I do not use the interrupt pin.

So, all registers has default value and i just start a single conversion.

You can see on the right of the photo, the board that support the sensors. On the left, we have the spark core. The lengh of wire is less than 10cm.

I will tried to put a capture with oscilloscope with I2C working and when it doesn’t.

In this last state, the pin D0 = 0V and D1 = 3.3V. That why, i don’t think the sensor have any issue. I haven’t see any data or clock coming out of the Core.

@peekay123, thank you for your reply. I will try to use 3.3V for the pull-ups.
I guess, if the Core send data to quickly, there is an error but on the next try of sending data every thing will be ok ?
Like i said, i have configured I2C to 100KHz so, i don’t think it’s to high for this sensor. It is able to get data rate up to 2.6MHz.

Thank for the help to all.

I forget to mention that the function who get the lux result from sensor uses polling method on the conversion ready field bit. I use a timeout of 200 iterations to avoid blocking of the polling method. I put 2 ms of delay between each read sequence.

Thanks

@futaba, can you post your code?

Does 4.7k seem a bit low to anyone else? IIRC, I use 470k on I2C lines…

Nope - your 470K is way too high.

4.7K is pretty standard for 5V 100KHz, higher speeds or lower voltages see it dropping much lower. I think I have used 330 ohm on high speed 1.2V busses.

1 Like

This is my .ino file :

// This #include statement was automatically added by the Spark IDE.
#include "BOSCH.h"

// This #include statement was automatically added by the Spark IDE.
#include "HDC100x.h"

// This #include statement was automatically added by the Spark IDE.
#include "OPT3001.h"

// D0 = SDA
// D1 = SCL

//#define DEBUG_PIN

#define VENDOR_LUX      1
#define DEVICE_LUX      2
#define VENDOR_HDC      3
#define DEVICE_HDC      4
#define VENDOR_BOSCH    5

// Define the pins we're going to call pinMode on
int led = D7; // This one is the built-in tiny one to the right of the USB jack

// Declaration of variables
int  iRSSI = 0;
double d_Temperature = 0.0;
unsigned int ui_RH = 0;
double d_Pressure = 0.0;

double d_Lux = 0.0;

// Declartion of function
int func_iVerifyID(String ID);

void setup_test();
void test_pin();


void setup_test() {
    pinMode(D0, OUTPUT);
    pinMode(D1, OUTPUT);
}

void test_pin() {
    digitalWrite(D0, LOW);
    digitalWrite(D1, HIGH);
    delay(1000);               // Wait for 1 second in off mode
    digitalWrite(D0, HIGH);
    digitalWrite(D1, LOW);
    delay(1000);               // Wait for 1 second in off mode
}

// This function return 1 if the command to verify ID or Vendor of a component is correct
// It return -1 if is wrong and -2 if the command doesn't correspond to anything
int func_iVerifyID(String ID){
    int iTemp;
    unsigned int uiTemp;
    
    switch(ID.toInt())
    {
        case VENDOR_LUX :
        { 
            uiTemp = funcOPT_ui_get_VendorID();
            if(uiTemp == VENDOR_ID_LUX)
            {
                iTemp = 1;
            }
            else
            {
                iTemp = -1;
            }
            break;
        }
        
        case DEVICE_LUX :
        {
            uiTemp = funcOPT_ui_get_DeviceID();
            if(uiTemp == DEVICE_ID_LUX)
            {
                iTemp = 1;
            }
            else
            {
                iTemp = -1;
            }
            break;
        }
        case VENDOR_HDC :
        {
            uiTemp = funcHDC_ui_get_DeviceID();
            if(uiTemp == DEVICE_ID_HDC)
            {
                iTemp = 1;
            }
            else
            {
                iTemp = -1;
            }
            break;
        }
        case DEVICE_HDC :
        {
            uiTemp = funcHDC_ui_get_VendorID();
            if(uiTemp == VENDOR_ID_HDC)
            {
                iTemp = 1;
            }
            else
            {
                iTemp = -1;
            }
            break;
        }
        case VENDOR_BOSCH :
        {
            uiTemp = funcBOSCH_ui_get_DeviceID();
            if(uiTemp == CHIP_ID_BOSCH)
            {
                iTemp = 1;
            }
            else
            {
                iTemp = uiTemp;
            }
            break;
        }
        
        default:
        iTemp = -2;
    }
    
    return iTemp;
}

// This routine runs only once upon reset
void setup() {
  
  // Register a Spark variables here
  Spark.variable("temperature", &d_Temperature, DOUBLE);
  Spark.variable("RH", &ui_RH, INT);
  Spark.variable("Pressure", &d_Pressure, DOUBLE);
  Spark.variable("Lux", &d_Lux, DOUBLE);

  Spark.variable("RSSI", &iRSSI, INT);
  
  // Register a Spark Function here
  Spark.function("ID_OK",func_iVerifyID);
  
  // Initialize D7 pin as output
  // It's important you do this here, inside the setup() function rather than outside it or in the loop function.
  pinMode(led, OUTPUT);

  // Configure and initialize I2C Bus
  Wire.setSpeed(CLOCK_SPEED_100KHZ);
  Wire.begin();
  
  // Configure Lux sensor
  funcOPT_f_setup();
}

// This routine gets called repeatedly, like once every 5-15 milliseconds.
// Spark firmware interleaves background CPU activity associated with WiFi + Cloud activity with your code. 
// Make sure none of your code delays or blocks for too long (like more than 5 seconds), or weird things can happen.
void loop() {

  unsigned int uiBoucleFor;
  
  digitalWrite(led, HIGH);   // Turn ON the LED pins
  
  //Return the signal strengh of the wifi network
  //iRSSI = WiFi.RSSI();
  
  d_Lux = funcOPT_d_get_Lux();
  
  // Test if there is an communication error with sensor
  if(d_Lux == ERROR_CONV | d_Lux == ERROR_BUS)
  {
      if(!RGB.controlled())
      {
          RGB.control(true);
      }
      RGB.brightness(255);
      RGB.color(255,0,0);

      // Try to restart the I2C peripheral
      Wire.endTransmission();
      Wire.setSpeed(CLOCK_SPEED_100KHZ);
      Wire.begin();
  }
  else
  {
      if(RGB.controlled())
      {
          RGB.control(false);
      }
  }
  
  digitalWrite(led, LOW);    // Turn OFF the LED pins
  delay(1000);               // Wait for 1000mS = 1 second
}

This is my code for communicate with the sensor opt3001.c:

#include <spark_wiring_i2c.h>
#include "math.h"
#include "OPT3001.h"

void funcOPT_f_setup(void)
{
    unsigned int uiTemp = DEFAULT_CONFIG;
    
    unsigned char ucMSBConf;
    unsigned char ucLSBConf;
    
    // Split config var in two byte
    ucMSBConf = (unsigned char) (uiTemp >> 8);
    ucLSBConf = (unsigned char) (uiTemp);
    
    // Start a write sequence to OPT3001 in configuration register with last value
    Wire.beginTransmission(I2C_ADDRESS_OPT3001);
    Wire.write(ADDR_REG_CONFIG);
    Wire.write(ucMSBConf);
    Wire.write(ucLSBConf);
    Wire.endTransmission();
}

double funcOPT_d_get_Lux(void)
{
    unsigned char ucTimeout;
    unsigned char ucMSBConf;
    unsigned char ucLSBConf;
    
    unsigned int ui_mantissa;
    unsigned int ui_exponent;
    unsigned int ui_result;
    unsigned int uiTemp = DEFAULT_CONFIG;
    
    double dTemp;

    // Initialize a var with single conversion configuration 
    uiTemp |=  SINGLE_SHOT;
    
    // Split config var in two byte
    ucMSBConf = (unsigned char) (uiTemp >> 8);
    ucLSBConf = (unsigned char) (uiTemp);
    
    // Start a write sequence to OPT3001 in configuration register to start a measurement
    Wire.beginTransmission(I2C_ADDRESS_OPT3001);
    Wire.write(ADDR_REG_CONFIG);
    Wire.write(ucMSBConf);
    Wire.write(ucLSBConf);
    Wire.endTransmission();
    
    ucTimeout = 0;
    
    // Waits until the measurement is performed and there was no timeout 
    do
    {
        Wire.requestFrom(I2C_ADDRESS_OPT3001,2);
        
        if(Wire.available() != 0)
        {
            uiTemp = Wire.read();
            uiTemp = uiTemp << 8;
            uiTemp |= Wire.read();
            
            uiTemp = uiTemp && 0x0080;
            
            if(uiTemp == 0x0080)
            {
                uiTemp = BUSY;
            }
            else
            {
                uiTemp = READY; 
            }
        }
        else
        {
            uiTemp = ERROR_BUS;
            ucTimeout = 200;
        }
        
        delay(2);
        
        ucTimeout = ucTimeout + 1;
    }while(uiTemp == BUSY && ucTimeout < 50);
    
    // Test, if the measurement is sucessful
    if(uiTemp == READY)
    {
        Wire.beginTransmission(I2C_ADDRESS_OPT3001);
        Wire.write(ADDR_REG_RESULT);
        Wire.endTransmission();
        
        Wire.requestFrom(I2C_ADDRESS_OPT3001,2);
        
        if(Wire.available() != 0)
        {
            ui_result = Wire.read();
            ui_result = ui_result << 8;
            ui_result |= Wire.read();
            
            ui_mantissa = (ui_result & 0xF000) >> 12;
            ui_exponent = (ui_result & 0x0FFF);
            
            // Use the equation 3 i(See datasheet page 20) to get Lux 
            dTemp = 0.01 * pow(2,ui_mantissa) * ui_exponent;
            
            return dTemp;
        }
        else
        {
            // Retunr an error bus
            return (double)ERROR_BUS;
        }
    }
    else
    {
        // Return an error in case the conversion failed
        return (double)ERROR_CONV;
    }
}

unsigned int funcOPT_ui_get_VendorID(void)
{
    unsigned int uiTemp;
    
    // Start a write sequence to write the vendor ID address in access register
    Wire.beginTransmission(I2C_ADDRESS_OPT3001);
    Wire.write(ADDR_REG_VENDOR_ID);
    Wire.endTransmission();
    
    // Start a read sequence to get the Vendor ID
    Wire.requestFrom(I2C_ADDRESS_OPT3001,2);
    
    if(Wire.available() != 0)
    {
        uiTemp = Wire.read();
        uiTemp = uiTemp << 8;
        uiTemp |= Wire.read();
    }
    else
    {
        // Return an bus error
        uiTemp = ERROR_BUS;
    }
    
    // Return the Vendor ID
    return uiTemp;
}

unsigned int funcOPT_ui_get_DeviceID(void)
{
    unsigned int uiTemp;
    
    // Start a write sequence to write the Device ID address in access register
    Wire.beginTransmission(I2C_ADDRESS_OPT3001);
    Wire.write(ADDR_REG_DEVICE_ID);
    Wire.endTransmission();
    
    // Start a read sequence to get the Device ID
    Wire.requestFrom(I2C_ADDRESS_OPT3001,2);
    
    if(Wire.available() != 0)
    {
        uiTemp = Wire.read();
        uiTemp = uiTemp << 8;
        uiTemp |= Wire.read();
    }
    else
    {
        // Return an bus error
        uiTemp = ERROR_BUS;
    }
    
    // Return the Device ID
    return uiTemp;
}

The last one is the opt3001.h :

#ifndef _OPT3001_H_
#define _OPT3001_H_

#define ADDR_REG_RESULT     0X00
#define ADDR_REG_CONFIG     0X01
#define ADDR_REG_LOW_LIM    0X02
#define ADDR_REG_HIGH_LIM   0X03
#define ADDR_REG_VENDOR_ID  0X7E
#define ADDR_REG_DEVICE_ID  0X7F

#define DEFAULT_CONFIG      0xC810
#define SINGLE_SHOT         (0x01 << 9)

#define I2C_ADDRESS_OPT3001 0x44

#define ERROR_CONV          0xFFFFFC
#define BUSY                0xFD
#define READY               0xFE
#define ERROR_BUS           0xFF

#define VENDOR_ID_LUX       0x5449
#define DEVICE_ID_LUX       0x3001

void funcOPT_f_setup(void);
double funcOPT_d_get_Lux(void);
unsigned int funcOPT_ui_get_VendorID(void);
unsigned int funcOPT_ui_get_DeviceID(void);

#endif

I planned to change the setup function of the sensor to accept parameters and really configure the sensor, not use an static config like right now. But its not the source of the problem i think.

I found an errata on the STM32 processor ERRATA. There are bugs with I2C1. Did you know if the workarounds are implemented in low level or wire library ?

I agree with @AndyW for the value of pull-up. My sensors are 3.3V powered so it is even a little high but not really a problem.

Thanks to all of you.

I will never understand the witchcraft of analog electronics.

I read the errata sheet and I don’t see how those apply to Spark core–maybe one of the Spark guys could look too. Lots and lots of people including me have use i2c successfully. The only caveat I know about is the one @peekay123 mentioned of some devices (slow LCD displays) requiring slower transfers since Arduino is so much slower than the STM32 processor on Spark. That looks like calling delayMicroseconds(2); after every Wire function.

It would be interesting to know which device is pulling D0 low when it fails–can you remove power to the sensor with the scope on the line when it fails?

Your test code does not really mimic i2c since you are not doing the open-drain/open-collector type of output that the Wire library uses.

1 Like

@futaba, you may want to consider using the (easily ported) energia library for the OPT3001. It might provide a good starting point :smile:

All electronics is analog, if you look close enough :slight_smile:

3 Likes

Not that I think this is the problem, but here is some clarification on what the pull-up resistors should be. According to NXP (nee Phillips) UM10204 I2C Specification, the current loading should be 3 ma for standard (100 KHz) and fast (400/1000 KHz) modes and 20 ma for ultra-fast (unto 3.4 MHz. Here is a link to a very informative study on I2C pull-up resistor impact with an Arduino by Wayne Truchsess a few years back. I use 1.2K resistors for pull-ups on my stuff. Still a bit under 3 ma on a 3.3V system, but I normally have a number of devices on the bus, and most adafruit/sparkfun etc. breakouts have 10K pull-ups which bring it down close to the 3 ma range. Of course, if you have more capacitance than NXP specs for (10 - 400 pf) then the resistance needs to be lower. They give you formulas to calculate what it should be in UM10204 (assuming that you know what the capacitance is :smile:)

Some I2C devices can do clock stretching (hold CLK low) to buy time when they need to do something part way through an I2C transaction. There is no indication anywhere in the OPT3001 data sheet that this device makes use of the capability.

I do see one potential problem:

  • The default conversion time is 800 ms, which you have not changed.
  • You have a do loop of 50 iterations checking if conversion is complete with a 2 ms delay each iteration. That’s only 100 ms plus change, and you give up. So you should be always getting ERROR_CONV returned from funcOPT_d_get_Lux().

I suggest you just change the millisecond delay per iteration to 20 rather than 2. That way you will wait for up to one second before timing out.

In loop(), when you get an error, you try to restart the I2C Peripheral. Maybe that is not working correctly, and leaving the I2C bus in a bad state (i.e. D0 Low)??.

1 Like

Yes, and digital is a very specific subset. So specific that virtually no analog expertise is required nor can be gained though its application.

Full analog is voodoo and every time I think I have some portion understood I am quickly proven incorrect.

3 Likes

Hi every body,

Thanks a lot for your replies.

Sorry for my silence, i went through two hard days at work.

So, i will try to give an answer to everyone :

@bko : Thanks for the tips of delay after every Wire function.I know that my test code does not mimic i2c and i don’t try it. It just a test to be sure the pins aren’t broken.
I think i discovers that is the BOSCH Sensor that is the guilty.
On this oscillogram, we can see that the SDA pin is in a weird level and SCL still high.

I don’t know why right now, but when i remove power supply of it, the lux sensor respond again.

@peekay123 : Thanks a lot for the library. I will use it for sure. It is much better than mine.

@pra : Thank for your reply but i can confirm that this is not from pull-up. Yes, i know it is a little high and yes i’m aware of what is capacitance but for a standard speed, there is no problem not to take into consideration.
You can see it on this oscillogram, the rise of SCL is at 40% of the “1” on SDA (with 4.7K on SDA).

The I2C device really see a high logic level.

Despite of that, you are right for the ERROR_CONV returned. I have made modification. Thank for that.

I will make some endurance test with just lux sensor and i will keep you in touch guys.

Thanks a lot.

2 Likes

Hi every body,

So, after one month, every things are fine. My sensor and my spark give me the lux data every time.
I have notice also that i have many less disconnection with cloud.
What i do ? I have re-wired my breadboard to my spark.
Even if, i’m not 100% sure, i think there was a intermittent short circuit between VCC and VSS and maybe I2C line. That explain the disconnection with cloud and the lost of communication with sensor.
Thank for your help, guys.

A special thanks to @peekay123 for his link to library for OPT3001.

I have ported it to spark, so if anyone is interested, write to me.

1 Like