Page 1 of 1

Read 3 bytes on SMBus without register?

Posted: Tue Aug 19, 2014 11:27 pm
by dalexgray
I'm trying to use a HTU21D-F temperature and humidity sensor in a Pi project. I seem to be able to send commands to it, but I'm having trouble reading back anything. One triggers a reading by sending a single byte to the HTU21D-F e.g. bus.write_byte(0x40, 0xE5) and it sends back 3 bytes of information, MSB, LSB, and Checksum. The problem is, as far as I can tell, the HTU21D-F doesn't bother with a register. The only SMBus commands that read more than a single byte require specifying a register.
If I try three read_byte(0x40) commands I get an IOError.
I'm pretty new at I2C and the Raspberry Pi, so maybe there is something really fundamental that I'm missing.

Here is the communication sequence from the datasheet:
htu21df_communication.png
htu21df_communication.png (52.21 KiB) Viewed 7214 times
There is also a no hold communication sequence that requires continuous polling of the I2C bus if that might be easier.

I found a Raspberry Pi driver for this chip written in C, but I don't think it's possible to use it.

Re: Read 3 bytes on SMBus without register?

Posted: Wed Aug 20, 2014 12:02 am
by FLYFISH TECHNOLOGIES
Hi,
dalexgray wrote:One triggers a reading by sending a single byte to the HTU21D-F e.g. bus.write_byte(0x40, 0xE5) and it sends back 3 bytes of information
As I read the sequence from datasheet you attached, you need to send three bytes (0x80, 0xE5 and 0x81) and after a moment or two (upon completed measurement), you can read three bytes...


Best wishes, Ivan Zilic.

Re: Read 3 bytes on SMBus without register?

Posted: Wed Aug 20, 2014 2:15 am
by dalexgray
Is there a way to do that with SMBus or another function in Python?

As far as I know SMBus takes the 7 bit address, 0x40, and shifts it to 0x80 by adding the write bit "0" to the end. I then do a read command which takes the 7 bit address and shifts it to 0x81 by adding the read bit "1." What I can't figure out is how to read all three bytes that come back. Is there any way to listen to a specific address on the i2c bus?

Re: Read 3 bytes on SMBus without register?

Posted: Wed Aug 20, 2014 7:36 am
by joan
Presumably it's an I2C device rather than a SMB device. They have many similarities and many but not all devices will work with both buses.

The pigpio Python module will let you talk I2C to the chip via the read and write calls. For testing you could do the same on the command line using the pigs read and write commands.

There is also a way of getting raw I2C access from Python without using a library.

For example i2c.py

Code: Select all

#!/usr/bin/env python

import io
import fcntl

I2C_SLAVE=0x0703

class i2c:

   def __init__(self, device, bus):

      self.fr = io.open("/dev/i2c-"+str(bus), "rb", buffering=0)
      self.fw = io.open("/dev/i2c-"+str(bus), "wb", buffering=0)

      # set device address

      fcntl.ioctl(self.fr, I2C_SLAVE, device)
      fcntl.ioctl(self.fw, I2C_SLAVE, device)

   def write(self, bytes):
      self.fw.write(bytes)

   def read(self, bytes):
      return self.fr.read(bytes)

   def close(self):
      self.fw.close()
      self.fr.close()

if __name__ == "__main__":

   import time

   import struct

   import i2c

   import smbus

   dev = i2c.i2c(0x53, 0) # device 0x53, bus 0

   dev.write("\x2D\x00") # POWER_CTL reset
   dev.write("\x2D\x08") # POWER_CTL measure
   dev.write("\x31\x00") # DATA_FORMAT reset
   dev.write("\x31\x0B") # DATA_FORMAT full res +/- 16g

   num_samples=5000

   sample=[(0,0,0)]*num_samples

   start1 = time.time()

   for s in xrange(num_samples):

      dev.write("\x32")
      sample[s] = struct.unpack('3h', dev.read(6))
      # print("x={} y={} z={}".format(buf[0], buf[1], buf[2]))

   end1 = time.time()

   bus = smbus.SMBus(0)
   sensor_address = 0x53

   start2 = time.time()

   for s in xrange(num_samples):

      buf = bus.read_i2c_block_data(sensor_address, 0x32, 6)
      tup=(buf[0]|buf[1]<<8, buf[2]|buf[3]<<8, buf[4]|buf[5]<<8)
      sample[s] = tup

   end2 = time.time()

   print(end1-start1)

   print(end2-start2)

   """
         x = buf[1]<<8| buf[0];
         y = buf[3]<<8| buf[2];
         z = buf[5]<<8| buf[4];
         xa = (90.0 / 256.0) * (float) x;
         ya = (90.0 / 256.0) * (float) y;
         za = (90.0 / 256.0) * (float) z;
         printf("%4.0f %4.0f %4.0f\n", xa, ya, za);
   """

   """
   # PCF8591
   dev = i2c.i2c(0x48, 1) # device 0x48, bus 1

   for i in xrange(256):
      str = chr(0x44) + chr(i)
      dev.write(str)
      time.sleep(0.02)

   for i in xrange(256):
      print(i, ord(dev.read(1)))
      time.sleep(0.02)
   """

   dev.close()

Re: Read 3 bytes on SMBus without register?

Posted: Wed Aug 20, 2014 10:35 pm
by dalexgray
Thank you, Joan! That was just what I needed.
I installed pigpio and I was just able to communicate via command line with the sensor. I got all three bytes no sweat. I'm so excited!

I'll post my results here so anyone else using this sensor with Python can get a hand.

Re: Read 3 bytes on SMBus without register?

Posted: Thu Aug 21, 2014 11:14 pm
by dalexgray
Driver done! The trick was using i2c_read_device(handle, count) a function not available from SMBus.
Thanks for all the help.

HTU21DF.PY

Code: Select all

################################################################
# Raspberry Pi Driver for Adafruit HTU21D-F
# Go buy one at https://www.adafruit.com/products/1899
# written by D. Alex Gray dalexgray@mac.com
# Thanks to egutting at the adafruit.com forums
# Thanks to Joan on the raspberrypi.org forums
# This requires the pigpio library
# Get pigpio at http://abyz.co.uk/rpi/pigpio/index.html
# No warranty offered or implied.  God help you if you use this.
################################################################
import time
import pigpio
import math

pi = pigpio.pi()

# HTU21D-F Address
addr = 0x40

# i2c bus, if you have a Raspberry Pi Rev A, change this to 0
bus = 1

# HTU21D-F Commands
rdtemp = 0xE3
rdhumi = 0xE5
wtreg = 0xE6
rdreg = 0xE7
reset = 0xFE

def htu_reset():
	handle = pi.i2c_open(bus, addr) # open i2c bus
	pi.i2c_write_byte(handle, reset) # send reset command
	pi.i2c_close(handle) # close i2c bus
	time.sleep(0.2) # reset takes 15ms so let's give it some time

def read_temperature():
	handle = pi.i2c_open(bus, addr) # open i2c bus
	pi.i2c_write_byte(handle, rdtemp) # send read temp command
	time.sleep(0.055) # readings take up to 50ms, lets give it some time
	(count, byteArray) = pi.i2c_read_device(handle, 3) # vacuum up those bytes
	pi.i2c_close(handle) # close the i2c bus
	t1 = byteArray[0] # most significant byte msb
	t2 = byteArray[1] # least significant byte lsb
	temp_reading = (t1 * 256) + t2 # combine both bytes into one big integer
	temp_reading = math.fabs(temp_reading) # I'm an idiot and can't figure out any other way to make it a float 
	temperature = ((temp_reading / 65536) * 175.72 ) - 46.85 # formula from datasheet
	return temperature

def read_humidity():
	handle = pi.i2c_open(bus, addr) # open i2c bus
	pi.i2c_write_byte(handle, rdhumi) # send read humi command
	time.sleep(0.055) # readings take up to 50ms, lets give it some time
	(count, byteArray) = pi.i2c_read_device(handle, 3) # vacuum up those bytes
	pi.i2c_close(handle) # close the i2c bus
	h1 = byteArray[0] # most significant byte msb
	h2 = byteArray[1] # least significant byte lsb
	humi_reading = (h1 * 256) + h2 # combine both bytes into one big integer
	humi_reading = math.fabs(humi_reading) # I'm an idiot and can't figure out any other way to make it a float
	uncomp_humidity = ((humi_reading / 65536) * 125 ) - 6 # formula from datasheet
	# to get the compensated humidity we need to read the temperature
	temperature = read_temperature()
	humidity = ((25 - temperature) * -0.15) + uncomp_humidity
	return humidity