Trouble using I2C to send more than 2 bytes of commands to a device

I’m working on communicating with a flow sensor using I2C. The specific device is the Sensirion SFM3200, and I’m using an Uno.

What is really strange about this issue is that I can communicate effectively with it if I’m just asking the device for flow data. I send the start measurement command, then I read the data out, it all looks great. But, I need to use multiple sensors on one I2C line so I am following instructions for how to change the I2C address of a device. To do this I need to send a write-to-EEPROM command, followed by 2 register address bytes, to move the device internal pointer to the write location to either read out the EEPROM data or write to it.

After I write my 3 bytes of data to the buffer (the command, then the two address bytes) and use Wire.endTransmission I get NACK 3, Nack on data sent. I cannot figure out why this is happening. If I only send the command the data is sent. If I only send the first or the second byte of the address, the data sends.

I know this is not a hardware problem because, again, I can communicate and read verifiably accurate flow data out of the device. I just can’t send the whole command I need to send.

The code I’m using:

#include <Wire.h>

// See data sheets for specific commands and default address value
const uint8_t commandReset[2] = { 0x20, 0x00 };
const uint8_t commandStartFlowMeasure[2] = { 0x10, 0x00 };
const uint8_t ADDRESS = 64;
// Calibration data for the specific device
const uint16_t FLOW_OFFSET = 32768;
const float FLOW_SCALE_FACTOR = 120.0;

void setup() {
  
  Wire.begin();
  Serial.begin(9600);
  delay(100);
  Serial.println("Initialize, read test data");

  // Soft reset the device
  Wire.beginTransmission(ADDRESS);
  Wire.write(commandReset,2);
  Serial.println(Wire.endTransmission(false));
  delay(20);

  Wire.beginTransmission(ADDRESS);
  Wire.write(commandStartFlowMeasure, 2);
  Serial.println(Wire.endTransmission(false));
  delay(20);

  // Test device by reading and printing 20 flow measurements
  uint8_t data[3];
  float flowData;

  for (int i = 0; i < 20; i++) {

    Wire.beginTransmission(ADDRESS);
    Wire.write(commandStartFlowMeasure, 2);
    Wire.endTransmission(false);
    delay(5);

    Wire.requestFrom(ADDRESS, 3); // 2 bytes of flow data, 1 crc byte
    delay(5);
    if (Wire.available() == 3) { // If transmission was interrupted or measurement was not ready, ignore results
      data[0] = Wire.read();
      data[1] = Wire.read();
      data[2] = Wire.read();
      flowData = (((data[0] << 8) + data[1]) - FLOW_OFFSET ) / FLOW_SCALE_FACTOR;
      Serial.println(flowData);
    }
  }
  
  delay(500);

  Serial.println("Attempt to send write EEPROM command");
  Wire.beginTransmission(64);
  Wire.write(0xFA);
  Wire.write(0x2C);
  Wire.write(0x20);
  Serial.println(Wire.endTransmission(false));
  
}

void loop() {
}

The output I get (the flow data should all be zero, maybe it’s got some noise? Still, I haven’t seen the command to access EEPROM work a single time out of dozens of tries so I’m skeptical that this is all due to noise…)

Initialize, read test data
2
0
0.00
0.00
0.00
546.10
0.00
546.00
0.00
0.00
0.00
546.10
0.03
0.03
0.00
546.10
546.10
546.10
0.03
546.10
0.00
0.00
Attempt to send write EEPROM command
3

Attached are the data sheets to the device I’ve been using.

LQ_AN_LiquidFlowSensors_ImplementationGuideToI2C_EN_D2.pdf (795 KB)

Sensirion_Mass_Flo_Meters_SFM3xxx_I2C_Functional_Description.pdf (359 KB)

Sensirion_Mass_Flow_Meters_Application_Note_SFM3xxx.pdf (119 KB)

Sensirion_Mass_Flow_Meters_SFM3200_Datasheet.pdf (324 KB)

Read this once more please: Arduino - WireEndTransmission.

To send a command, the I2C data needs a STOP-condition at the end. When you use 'false' as a parameter, there will be no STOP.
When you use Wire.endTransmission() without parameter, then it assumes a 'true' as parameter, that is the default.

The Wire.endTransmission() should only have a 'false' when it is used just before the Wire.requestFrom() and when the datasheet says that you must use a repeated start.

Try with all the 'false' as parameter removed, and check the datasheet if a repeated start is required.

Please be very careful when writing to the EEPROM, you can change basic settings and the sensor could not longer work.

The I2C address is often written as a hexadecimal number. The Arduino Wire library uses the 7-bit shifted address, so that is 0x40. In the datasheet they use the 8-bit unshifted address 80h or 0x80.

I think your sequence is okay:
64 : default I2C address.
0xFA, 0x2C, 0x20: select EEPROM address.

After that you have to read 3 bytes (one word plus crc).
Then use the table, and be sure you change the proper bits !
Then select the EEPROM address once more and write the data.

You can use a I2C Scanner sketch to check if it was successful.

tarnarmour:
But, I need to use multiple sensors on one I2C line so I am following instructions for how to change the I2C address of a device. To do this I need to send a write-to-EEPROM command, followed by 2 register address bytes, to move the device internal pointer to the write location to either read out the EEPROM data or write to it.

The default I2C address of the sensor is 1000000b (7-bit). Can you please refer me to the document that shows the ways of changing the I2C address as you have said above?

If you have multiple sensors, you may use I2C bus extender.