Gen 3 Device Support for Repeated Start I2C Requests?

Can gen 3 particle products (specifically Boron) support I2C repeated start?

I’m working with the Melexis MLX90614 5V infrared temperature sensor and a Boron LTE for a contactless temperature station. However it appears that for proper operation the sensor requires I2C repeated-start communication.

It does not appear that I2C repeated-start works natively, but I’m wondering: Can I2C repeated start functionality be added using a library?
If not, could I2C be extended via future updates to the particle Device OS?

For example, I was able to get the MLX90614 example sketch working properly on an arduino uno board by using 10KΩ pull up resistors on the SCL and SDA lines using the Adafruit_MLX90614 library.

However when using the same circuit connected to a Boron device along with examples from any of the following MLX90614 libraries:

  1. Adafruit_MLX90614 - This library demo just returns the max reading values:
    “Ambient = 1037.55C Object = 1037.55C
    Ambient = 1899.59F Object = 1899.59F”

  2. SparkfunMLX90614 - This library’s therm.begin() call always fails, returning a 0.

To me it appears that the I2C repeated start condition is the cause. All of the posts I’ve seen on the particle community forums which relate to the MLX90614 and their related particle cloud libraries were using the photon devices but I cannot get this working on a Boron or Argon device.

If anyone has experience using I2C repeated start sensors on gen 3 devices, any help with that process or any examples would be greatly appreciated. :slightly_smiling_face:

References:
Datasheet for the MLX90614
Arduino Uno Example that worked

Can you put the sensor in 400khz I2C mode?

I had to do that for a battery management chip that required repeated start under the SmBus standard.

Thanks for the suggestion.

I couldn’t find any information on the datasheet related to I2C clock speed. I did enable the 400Khz mode, but saw no change in operation.

Included this line before mlx.begin() which then calls Wire.begin()

// set the I2C clock speed to 400Khz
Wire.setSpeed(CLOCK_SPEED_400KHZ);

Adafruit_MLX90614 mlxtest.ino

/***************************************************
  This is a library example for the MLX90614 Temp Sensor

  Designed specifically to work with the MLX90614 sensors in the
  adafruit shop
  ----> https://www.adafruit.com/products/1748
  ----> https://www.adafruit.com/products/1749

  These sensors use I2C to communicate, 2 pins are required to
  interface
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  BSD license, all text above must be included in any redistribution
 ****************************************************/

#include <Adafruit_MLX90614.h>
#include <Wire.h>

Adafruit_MLX90614 mlx = Adafruit_MLX90614();

void setup() {
  Serial.begin(9600);

  Serial.println("Adafruit MLX90614 test");
  Serial.println("Set clock speed");
  Wire.setSpeed(CLOCK_SPEED_400KHZ);

  mlx.begin();
}

void loop() {
  Serial.print("Ambient = "); Serial.print(mlx.readAmbientTempC());
  Serial.print("*C\tObject = "); Serial.print(mlx.readObjectTempC()); Serial.println("*C");
  Serial.print("Ambient = "); Serial.print(mlx.readAmbientTempF());
  Serial.print("*F\tObject = "); Serial.print(mlx.readObjectTempF()); Serial.println("*F");

  Serial.println("Start Delay");
  delay(10000);
}

Sample output:

Adafruit MLX90614 test
Set clock speed
Ambient = 1037.55*C     Object = 1037.55*C
Ambient = 1899.59*F     Object = 1899.59*F
Start Delay
Ambient = 1037.55*C     Object = 1037.55*C
Ambient = 1899.59*F     Object = 1899.59*F
Start Delay
Ambient = 1037.55*C     Object = 1037.55*C
Ambient = 1899.59*F     Object = 1899.59*F
Start Delay

When attempting to use the SparkfunMLX90614, the overall result is similar. Obviously, it’s a different library, but therm.read() returns false. Are you aware of any tools available to debug the I2C communication at a lower level?

Looking deeper into the library, these are the two functions doing most of the communication work:

// Adafruit_MLX90614
float Adafruit_MLX90614::readTemp(uint8_t reg) {
  float temp;

  temp = read16(reg);
  temp *= .02;
  temp  -= 273.15;
  return temp;
}

/*********************************************************************/

uint16_t Adafruit_MLX90614::read16(uint8_t a) {
  uint16_t ret;

  Wire.beginTransmission(_addr); // start transmission to device
  Wire.write(a); // sends register address to read from
  Wire.endTransmission(false); // end transmission

  Wire.requestFrom(_addr, (uint8_t)3);// send data n-bytes read
  ret = Wire.read(); // receive DATA
  ret |= Wire.read() << 8; // receive DATA

  uint8_t pec = Wire.read();

  return ret;
}

For now, I will create the project with this library locally so I can insert debug statements in the library, specifically trying to see the data that is pass to and from the Wire statements. Any other suggestions would be appreciated.

I didn’t enable 400Hz I2C on the Argon but only on the battery chip.

The lack of repeat start appears to be a bug in the device-os:

By the documentation, calling Wire.endTransmission(false) at the end of a write/read should avoid an I2C STOP, and allow you to perform a repeat start by using Wire.requestFrom(…) or Wire.beginTransmission(...) afterwards. Instead it always issues an I2C STOP.

You can work around it by commenting out the following line in this file, then recompiling and flashing the deviceOS.

Basically, the stop parameter is always overridden inside uint8_t HAL_I2C_End_Transmission(HAL_I2C_Interface i2c, uint8_t stop, void* reserved). Since m_i2c_map[i2c].transfer_config.address (which appears to be the I2C address of the device), is typically not 0xFF, and by default HAL_I2C_TRANSMISSION_FLAG_STOP is set as a flag, stop will be non-zero (true).

This fixed my issues with an MMA8652 accelerometer, hopefully it helps you out as well.

 if (m_i2c_map[i2c].transfer_config.address != 0xff) {
        stop = m_i2c_map[i2c].transfer_config.flags & HAL_I2C_TRANSMISSION_FLAG_STOP;
    }

to

/*
 if (m_i2c_map[i2c].transfer_config.address != 0xff) {
        stop = m_i2c_map[i2c].transfer_config.flags & HAL_I2C_TRANSMISSION_FLAG_STOP;
    }
*/
1 Like

I’m not entirely sure why commenting out that line helps, but that condition (address != 0xff), only comes into play when using new API with WireTransmission objects (https://github.com/particle-iot/device-os/blob/develop/wiring/src/spark_wiring_i2c.cpp#L105).

In other cases, when using Wire.beginTrasmission(address) the default transfer config is used (https://github.com/particle-iot/device-os/blob/develop/hal/src/nRF52840/i2c_hal.cpp#L140) and stop argument is not overriden.

Ah! Then I suspect my “work-around” isn’t in fact needed, I was wondering where that config was coming from… in my defense it was late at night and I was desperate to make it work :slight_smile:

I am using WireTransmission, but I didn’t grasp that it overrode my endTransmission(false); when going through the code, my apologies.

I checked, and if I don’t use WireTransmission, endTranmission(false) works as expected, and if I do use WireTransmission and use .stop(false), repeat start works as well.

Thanks for the reply!

Original Code (trimmed for brevity)

Wire.beginTransmission(WireTransmission(address).timeout(100ms));
Wire.write(…);
Wire.endTransmission(false);
Wire.requestFrom(WireTransmission(address).quantity(len).timeout(100ms).stop(true));

Updated Code based on avtolstoy’s comment (trimmed for brevity)

Wire.beginTransmission(WireTransmission(address).timeout(100ms).stop(false)); //
Wire.write(…);
Wire.endTransmission(false); // <- could be true, false, or endTransmission(), doesn’t matter anymore
Wire.requestFrom(WireTransmission(address).quantity(len).timeout(100ms).stop(true));