0

I am using the Sensirion SFM3300 flow sensor and can read the correct values with the Arduino with the following code (I2C):

#include <Wire.h>

void setup() {
  // put your setup code here, to run once:
  Wire.begin();
  Serial.begin(115200);
  Wire.beginTransmission(byte(0x40));
  Wire.write(byte(0x10));
  Wire.write(byte(0x00));
  Wire.endTransmission();
}

void loop() {
  // put your main code here, to run repeatedly:
  delay(100);
  Wire.requestFrom(0x40,2);
  uint16_t a = Wire.read();
  uint8_t  b = Wire.read();
  a = (a<<8) | b;
  float flow = ((float)a - 32768) / 120;
  Serial.println(flow);
}

But using the Raspberry Pi I have written the nearly the same code, hoping that it also will works. This is the code:

from smbus2 import SMBus
import time
import numpy as np

address=0x40
bus = SMBus(1)

def write(value):
    bus.write_byte(address,value)

write(0x10)
write(0x00)

while True:
    time.sleep(0.1)
    a = np.uint16(bus.read_byte(0x40))
    b = np.uint8(bus.read_byte(0x40))
    a = (a<<8) | b
    flow = (float(a)-32768)/120
    print(flow)

The code really looks the same, but I only get -273,06666666666 as a return value. Does somebody knows where are the differences between Raspberry Pi and Arduino I2C and can help me to get the right values on the Pi?

hcheung
  • 3,377
  • 3
  • 11
  • 23
  • What is the correct value expected? I would recommend to use `struct` unit instead of np requiring the ugly shift and or approach. This may be an endianness problem. – guidot Mar 18 '20 at 14:15
  • The expected value should be 0 (without any flow) and the sensor should react on the flow. But on the Raspbery I only get -273,0666666 with and without any float. How can I use struct in this case? Can you please help me. – Patryk Dzierżawski Mar 18 '20 at 14:18
  • Print the byte values first and see the result before calculations. Really 101 of `printf()` type of debugging :) Ah, yes, good recommendation in the answer. Have you read data sheet, by the way? – 0andriy Mar 18 '20 at 18:23

3 Answers3

1

You can use read_i2c_block_data(addr, offset, numOfBytes) method to get more than 1 byte of data from i2c. the return data is a list of bytes. So it is very easy to convert into an integer.

Edited based on datasheet and Arduino sketch

Here is the complete code for Python that should matched the Arduino example:

from SMBus2 import SMBus
import time

offset = 32768
scale = 120

addr = 0x40
cmd = [0x10, 0x00]

with SMBus(1) as bus:
    bus.write_i2c_block_data(addr, 0, cmd)
    time.sleep(0.1)
    block = bus.read_i2c_block_data(addr, 0, 3)
reading = block[0] * 256 + block[1]
crc = block[2]    # should do some crc check for error
flow = (reading - offset)/scale
print(flow)
hcheung
  • 3,377
  • 3
  • 11
  • 23
  • Thank you for this recommendation. Your code helps me, but I think, that the problem is on the I2C-Bus. With your code I also get a constant negative value of -273,.....With i2cset -y 1 0x40 0x10 and i2cset -y 1 0x40 0x00 I tried to start the measuring of the SFM3300 sensor. And with i2cget -y 1 0x40 I wanted to read out a byte, but I only get 0x00. – Patryk Dzierżawski Mar 19 '20 at 11:43
  • I think your formula wrong then. what you get is the flow value, it does not need to minus 32768 then divided by 120. The reason you get -273 is because the `value` = 0, so (0-32768)/120 = -273. I've edited my code. – hcheung Mar 19 '20 at 11:50
  • It is from the official datasheet: https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/5_Mass_Flow_Meters/Application_Notes/Sensirion_Mass_Flo_Meters_SFM3xxx_I2C_Functional_Description.pdf – Patryk Dzierżawski Mar 19 '20 at 12:15
  • Based on the datasheet and you said that the Arduino code works, I edit my answer to include the complete python code. Again, I can't test it as I don't have the sensor and setup, you will have to run and debug it. – hcheung Mar 20 '20 at 00:48
  • I don't know why it's not working on the Raspberry Pi. The flow is still -271,45 (with and without flow). The print of block[0] is always 0, of block[1] 194 and block[2] 37. – Patryk Dzierżawski Mar 20 '20 at 08:02
  • I think the mistake is in the write process. In my opinion the sensor never starts a measument. – Patryk Dzierżawski Mar 20 '20 at 09:12
0

I don't think your read process in python is correct. Reading from port 40 two times is different from reading two bytes from port 40.

I suggest to use read_byte_data(0x40, 0, 2) and process that with struct.unpack(">H").

guidot
  • 5,095
  • 2
  • 25
  • 37
0

I found a working solution. It would be nice if a I2C-expert could tell me why the following code is working instead of the python code above.

from fcntl import ioctl
from struct import unpack
from smbus import SMBus

address = 0x40

SMBus(1).write_byte_data(address,16,0)
i2c = open("/dev/i2c-1", "rb", buffering=0)
ioctl(i2c,0x0703,address)
i2c.read(3)

d0,d1,c = unpack('BBB', i2c.read(3))
d = d0 << 8 | d1
a = (float(d)-32768.)/120
print(a)
  • 1
    Ah, the `i2c-dev.c` on linux on i2cdev-read function has an [explanation](https://github.com/torvalds/linux/blob/master/drivers/i2c/i2c-dev.c#L112) on why it is needed. What `ioctl()` does is to sort of let the SDA float, and if a slave exists it will pull the SDA down. This has the same effect as running linux command `i2cdetect -y 1` for detecting i2c addr. In fact, once you run `i2cdetect -y 1` once on newer Raspbian (as compare to earlier versions), you will not need the `ioctl()` code any more and the normal SMBus code that I provide or you written will probably work. – hcheung Mar 21 '20 at 01:28