Need help with HTU21D Humidity Sensor (for RPi outreach)


17 posts
by Davespice » Thu May 08, 2014 1:06 pm
Hey folks, I can't talk too much about what this is for but I could really use some help here.
I'm almost to the point of...
Image
...so any help would be gratefully received.

This is the product in question.
https://www.sparkfun.com/products/12064 (has a link to the datasheet)

I've got a working C program for it here which uses GordonDrogon's wiringPi library;
Code: Select all
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#include "wiringPi.h"
#include "wiringPiI2C.h"

#define   ADDR   0x40

// cTemp:
//   Convert sensor reading into temperature.
//   See page 14 of the datasheet

double cTemp (int sensorTemp)
{
  double tSensorTemp = sensorTemp / 65536.0 ;
  return -46.85 + (175.72 * tSensorTemp) ;
}

// cHumid:
//   Convert sensor reading into humidity.
//   See page 15 of the datasheet

double cHumid (int sensorHumid)
{
  double tSensorHumid = sensorHumid / 65536.0 ;
  return -6.0 + (125.0 * tSensorHumid) ;
}

int main (void)
{
  int fd ;
  unsigned int x ;
  unsigned char buf [4] ;
  unsigned int temp, humid ;

  wiringPiSetup () ;

  if ((fd = wiringPiI2CSetup (ADDR)) < 0)
  {
    fprintf (stderr, "Unable to open I2C device: %s\n", strerror (errno)) ;
    exit (-1) ;
  }

// Temperature

  wiringPiI2CWrite (fd, 0xF3) ;
  delay (100) ;
  x = read (fd, buf, 3) ;

  if (x != 3)
    printf ("%d: %02X %02X %02X\n", x, buf[0], buf[1], buf[2]) ;

  temp = (buf [0] << 8 | buf [1]) & 0xFFFC ;

  printf ("%4d %5.1fC\n", temp, cTemp (temp)) ;

// Yoomidity

  wiringPiI2CWrite (fd, 0xF5) ;
  delay (100) ;
  x = read (fd, buf, 3) ;

  if (x != 3)
    printf ("%d: %02X %02X %02X\n", x, buf[0], buf[1], buf[2]) ;

  humid = (buf [0] << 8 | buf [1]) & 0xFFFC ;

  printf ("%4d %5.2f %%rh\n", humid, cHumid (humid)) ;

  return 0 ;
}

However I'm trying to write the same program in Python but am running into problems. I need it in the python language for consistency across the rest of the project since the code will be used for educational resources.
This is what I have so far;
Code: Select all
#!/usr/bin/python
import time
import smbus

msleep = lambda x: time.sleep(x/1000.0)

ADDR = 0x40

i2c = smbus.SMBus(1)

CMD_READ_TEMP_HOLD = 0xe3
CMD_READ_HUM_HOLD = 0xe5
CMD_READ_TEMP_NOHOLD = 0xf3
CMD_READ_HUM_NOHOLD = 0xf5
CMD_WRITE_USER_REG = 0xe6
CMD_READ_USER_REG = 0xe7
CMD_SOFT_RESET= 0xfe

i2c.write_byte(ADDR, CMD_SOFT_RESET)
msleep(100)

i2c.write_byte(ADDR, CMD_READ_TEMP_NOHOLD)
msleep(100)

data = i2c.read_i2c_block_data(ADDR, CMD_READ_TEMP_NOHOLD, 3)
msleep(100)

No matter what I try the read_i2c_block_data line throws IOError: [Errno 5] Input/output error

I am aware of some existing libraries for this sensor, but they all exhibit this same issue for me. These include;
https://github.com/jwineinger/quick2wir ... /htu21d.py
https://github.com/randymxj/Adafruit-Ra ... _HTU21D.py

Can anyone see what is going wrong with the python code?
User avatar
Raspberry Pi Foundation Employee & Forum Moderator
Raspberry Pi Foundation Employee & Forum Moderator
Posts: 1586
Joined: Fri Oct 14, 2011 8:06 pm
Location: London, United Kingdom
by simplesi » Thu May 08, 2014 1:12 pm
JFI - I find that once I get an Python I2C error 5 - I have to power cycle before re-running any program as the PI seems to "remember" the errored state and you can end up applying a fix but not know that it would have worked had you just power cycled

Pain in the neck but I can fix I2C bugs quicker nowadays that way

Simon
Seeking help with Scratch and I/O stuff for Primary age children
http://cymplecy.wordpress.com/ @cymplecy on twitter
Posts: 2327
Joined: Fri Feb 24, 2012 6:19 pm
Location: Euxton, Lancashire, UK
by Davespice » Thu May 08, 2014 1:18 pm
Okay cool, I don't think its that though because I can run the c binary directly after getting the error 5 and that works okay.
User avatar
Raspberry Pi Foundation Employee & Forum Moderator
Raspberry Pi Foundation Employee & Forum Moderator
Posts: 1586
Joined: Fri Oct 14, 2011 8:06 pm
Location: London, United Kingdom
by simplesi » Thu May 08, 2014 1:29 pm
I think its am SMBUS python module bug - its certainly pops up in my ScratchGPIO developement and it took a while before I relasied what was going on so now I correct what I think gives the error - re-save - reboot and then retry :(

Slow but its faster than not doing it I find :)

Simon
Seeking help with Scratch and I/O stuff for Primary age children
http://cymplecy.wordpress.com/ @cymplecy on twitter
Posts: 2327
Joined: Fri Feb 24, 2012 6:19 pm
Location: Euxton, Lancashire, UK
by joan » Thu May 08, 2014 2:07 pm
You could bypass the smbus module and just use the raw device.

I have some code which demonstrates the principle.

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

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

   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(sample[s][0], sample[s][1], sample[s][2]))

   end1 = time.time()

   print(end1-start1)

   dev.close()

i2c.py
User avatar
Posts: 12653
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by Davespice » Thu May 08, 2014 2:16 pm
Hi simplesi, I did try your suggestion but to no avail. Joan, I am going to try this approach, thanks for that I think it might work.
User avatar
Raspberry Pi Foundation Employee & Forum Moderator
Raspberry Pi Foundation Employee & Forum Moderator
Posts: 1586
Joined: Fri Oct 14, 2011 8:06 pm
Location: London, United Kingdom
by Davespice » Thu May 08, 2014 3:36 pm
Go it working! Woohoo, thanks to Joan.
Code: Select all
#!/usr/bin/python
import struct, array, time, io, fcntl

I2C_SLAVE=0x0703
HTU21D_ADDR = 0x40
CMD_READ_TEMP_HOLD = "\xE3"
CMD_READ_HUM_HOLD = "\xE5"
CMD_READ_TEMP_NOHOLD = "\xF3"
CMD_READ_HUM_NOHOLD = "\xF5"
CMD_WRITE_USER_REG = "\xE6"
CMD_READ_USER_REG = "\xE7"
CMD_SOFT_RESET= "\xFE"

class i2c(object):
   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()

class HTU21D(object):
   def __init__(self):
      self.dev = i2c(HTU21D_ADDR, 1) #HTU21D 0x40, bus 1
      self.dev.write(CMD_SOFT_RESET) #soft reset
      time.sleep(.1)

   def ctemp(self, sensorTemp):
      tSensorTemp = sensorTemp / 65536.0
      return -46.85 + (175.72 * tSensorTemp)

   def chumid(self, sensorHumid):
      tSensorHumid = sensorHumid / 65536.0
      return -6.0 + (125.0 * tSensorHumid)

   def crc8check(self, value):
      # Ported from Sparkfun Arduino HTU21D Library: https://github.com/sparkfun/HTU21D_Breakout
      remainder = ( ( value[0] << 8 ) + value[1] ) << 8
      remainder |= value[2]
      
      # POLYNOMIAL = 0x0131 = x^8 + x^5 + x^4 + 1
      # divsor = 0x988000 is the 0x0131 polynomial shifted to farthest left of three bytes
      divsor = 0x988000
      
      for i in range(0, 16):
         if( remainder & 1 << (23 - i) ):
            remainder ^= divsor
         divsor = divsor >> 1
      
      if remainder == 0:
         return True
      else:
         return False
   
   def read_tmperature(self):
      self.dev.write(CMD_READ_TEMP_NOHOLD) #measure temp
      time.sleep(.1)

      data = self.dev.read(3)
      buf = array.array('B', data)

      if self.crc8check(buf):
         temp = (buf[0] << 8 | buf [1]) & 0xFFFC
         return self.ctemp(temp)
      else:
         return -255
         
   def read_humidity(self):
      self.dev.write(CMD_READ_HUM_NOHOLD) #measure humidity
      time.sleep(.1)

      data = self.dev.read(3)
      buf = array.array('B', data)
      
      if self.crc8check(buf):
         humid = (buf[0] << 8 | buf [1]) & 0xFFFC
         return self.chumid(humid)
      else:
         return -255
         
if __name__ == "__main__":
   obj = HTU21D()
   print "Temp:", obj.read_tmperature(), "C"
   print "Humid:", obj.read_humidity(), "% rH"

Image
User avatar
Raspberry Pi Foundation Employee & Forum Moderator
Raspberry Pi Foundation Employee & Forum Moderator
Posts: 1586
Joined: Fri Oct 14, 2011 8:06 pm
Location: London, United Kingdom
by simplesi » Thu May 08, 2014 6:26 pm
Does this mean the SMBus module is broken then?

Because a lot of Pi-oneers are going to want to use it so its got to be worth getting it fixed rather than just bypassing it I'd thought

Simon
Seeking help with Scratch and I/O stuff for Primary age children
http://cymplecy.wordpress.com/ @cymplecy on twitter
Posts: 2327
Joined: Fri Feb 24, 2012 6:19 pm
Location: Euxton, Lancashire, UK
by joan » Thu May 08, 2014 6:52 pm
simplesi wrote:Does this mean the SMBus module is broken then?

Because a lot of Pi-oneers are going to want to use it so its got to be worth getting it fixed rather than just bypassing it I'd thought

Simon

Perhaps the problem is that smbus != I2C.

I don't think Read I2C Block Data is part of the smbus standard. I'm not sure there is an agreed definition of what it is meant to do.
User avatar
Posts: 12653
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by simplesi » Thu May 08, 2014 8:55 pm
Aah I see :)

I'm not (usually anyway - I did used to be able to write 6502 machine code when I was a lad) a low-level programmer so I don't know these things :)

Maybe what's needed is an I2C Python module then.

Sounds like a good thing for Foundation to fund?

Simon
Seeking help with Scratch and I/O stuff for Primary age children
http://cymplecy.wordpress.com/ @cymplecy on twitter
Posts: 2327
Joined: Fri Feb 24, 2012 6:19 pm
Location: Euxton, Lancashire, UK
by joan » Thu May 08, 2014 9:06 pm
I really don't know how much of a problem this is in practice. Perhaps another combination of smbus commands could work with the HTU21D sensor.

Just googled to find this http://www.ti.com/lit/an/sloa132/sloa132.pdf which gives a short overview.
User avatar
Posts: 12653
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by simplesi » Thu May 08, 2014 9:38 pm
really don't know how much of a problem this is in practice


Well - Dave can't get it to work in practice :)

Simon
Seeking help with Scratch and I/O stuff for Primary age children
http://cymplecy.wordpress.com/ @cymplecy on twitter
Posts: 2327
Joined: Fri Feb 24, 2012 6:19 pm
Location: Euxton, Lancashire, UK
by joan » Thu May 08, 2014 9:44 pm
simplesi wrote:
really don't know how much of a problem this is in practice


Well - Dave can't get it to work in practice :)

Simon

If he was working to a deadline he may not have had the luxury of exploring alternative solutions.
User avatar
Posts: 12653
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by richlane » Tue Dec 30, 2014 5:22 pm
Thank you for this post, this gave me a huge boost getting my own humidity and temperature project working. I have included my own code below which takes the original code and adds a loop which logs to a CSV file on a network share every 5 minutes. The output file is flushed every time it is written and the format works well with excel using a scatter chart.

Code: Select all
#!/usr/bin/python
import sys, struct, array, time, io, os, fcntl, datetime

I2C_SLAVE=0x0703
HTU21D_ADDR = 0x40
CMD_READ_TEMP_HOLD = "\xE3"
CMD_READ_HUM_HOLD = "\xE5"
CMD_READ_TEMP_NOHOLD = "\xF3"
CMD_READ_HUM_NOHOLD = "\xF5"
CMD_WRITE_USER_REG = "\xE6"
CMD_READ_USER_REG = "\xE7"
CMD_SOFT_RESET= "\xFE"

class i2c(object):
   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()

class HTU21D(object):
   def __init__(self):
      self.dev = i2c(HTU21D_ADDR, 1) #HTU21D 0x40, bus 1
      self.dev.write(CMD_SOFT_RESET) #soft reset
      time.sleep(.1)

   def ctemp(self, sensorTemp):
      tSensorTemp = sensorTemp / 65536.0
      return -46.85 + (175.72 * tSensorTemp)

   def chumid(self, sensorHumid):
      tSensorHumid = sensorHumid / 65536.0
      return -6.0 + (125.0 * tSensorHumid)

   def crc8check(self, value):
      # Ported from Sparkfun Arduino HTU21D Library: https://github.com/sparkfun/HTU21D_Breakout
      remainder = ( ( value[0] << 8 ) + value[1] ) << 8
      remainder |= value[2]
     
      # POLYNOMIAL = 0x0131 = x^8 + x^5 + x^4 + 1
      # divsor = 0x988000 is the 0x0131 polynomial shifted to farthest left of three bytes
      divsor = 0x988000
     
      for i in range(0, 16):
         if( remainder & 1 << (23 - i) ):
            remainder ^= divsor
         divsor = divsor >> 1
     
      if remainder == 0:
         return True
      else:
         return False
   
   def read_tmperature(self):
      self.dev.write(CMD_READ_TEMP_NOHOLD) #measure temp
      time.sleep(.1)

      data = self.dev.read(3)
      buf = array.array('B', data)

      if self.crc8check(buf):
         temp = (buf[0] << 8 | buf [1]) & 0xFFFC
         return self.ctemp(temp)
      else:
         return -255
         
   def read_humidity(self):
      self.dev.write(CMD_READ_HUM_NOHOLD) #measure humidity
      time.sleep(.1)

      data = self.dev.read(3)
      buf = array.array('B', data)
     
      if self.crc8check(buf):
         humid = (buf[0] << 8 | buf [1]) & 0xFFFC
         return self.chumid(humid)
      else:
         return -255

if __name__ == "__main__":
   # Open logging file
   if len(sys.argv) != 2:
      print 'Usage: python ', sys.argv[0], '<logfile.csv>'
   filename = '/mnt/share/htu21d_logs/' + sys.argv[1]
   try:
      file = open(filename, 'a')
   except IOError:
      print "Could not open:", filename
      exit(1)
   print "Opened in append mode:", filename
   # Create humdity sensor object
   obj = HTU21D()
   base_date = datetime.datetime(1970,1,1)
   now = datetime.datetime.now()
   target = int((now-base_date).total_seconds())
   # Round up to next 5 minute boundary
   target = ((target + 299) / 300) * 300
   while True:
      # Get current datetime
      now = datetime.datetime.now()
      now_seconds = int((now-base_date).total_seconds())
      if now_seconds >= target:
         # Read temp and humidity and log to file
         temp = obj.read_tmperature()
         humid = obj.read_humidity()
         out_string = "%d-%02d-%02d %02d:%02d,%.1f,%.1f" % (now.year, now.month, now.day, now.hour, now.minute, temp, humid)
         print out_string
         print >>file, out_string
         file.flush()
         os.fsync(file)
         target += 300 # Advance target by 5 minutes
      time.sleep(30)
Attachments
htu21d.PNG
Screenshot of humidity and temperature plots in excel.
htu21d.PNG (18.17 KiB) Viewed 7882 times
Posts: 9
Joined: Thu Mar 13, 2014 9:37 pm
by pumatrax » Thu Dec 24, 2015 6:16 am
Hey Dave,

Nice work here, it works with an ebay senor I just picked up. By any chance would you happen to know the correct math to output the temperature in Fahrenheit?

Thanks!

class HTU21D(object):
def __init__(self):
self.dev = i2c(HTU21D_ADDR, 1) #HTU21D 0x40, bus 1
self.dev.write(CMD_SOFT_RESET) #soft reset
time.sleep(.1)

def ctemp(self, sensorTemp):
tSensorTemp = sensorTemp / 65536.0
return -46.85 + (175.72 * tSensorTemp)

def chumid(self, sensorHumid):
tSensorHumid = sensorHumid / 65536.0
return -6.0 + (125.0 * tSensorHumid)
Posts: 4
Joined: Thu Dec 24, 2015 6:09 am
by Alexandrekx » Mon Jul 04, 2016 10:22 am
It´s necessary to download the library of the sensor or i just need to put the code that you posted?
If the library is necessary pls post the link, ty.
Posts: 1
Joined: Mon Jul 04, 2016 10:00 am
by edwardsnick » Thu Oct 13, 2016 1:53 am
WOW, what is going on here? I've been miserably trying to use the SMBUS library to interface a CO2 sensor which was never going to work then resorted to installing the PIGPIO library and having to slow the clock speed right down as it wouldn't pause to a delayed clock cycle - a terrible mess although I got it to work.
Is this 'io' and 'fcntl' a software implementation? Sorry I'm learning, if this seems like a basic question but I haven't found any other example like it and SMBus often doesn't work for I2C devices. I nearly abandoned my project and started again using a Arduino to interface the sensors as I was getting so frustrated with the SMBus protocol.


Davespice wrote:Go it working! Woohoo, thanks to Joan.
Code: Select all
#!/usr/bin/python
import struct, array, time, io, fcntl

I2C_SLAVE=0x0703
HTU21D_ADDR = 0x40
CMD_READ_TEMP_HOLD = "\xE3"
CMD_READ_HUM_HOLD = "\xE5"
CMD_READ_TEMP_NOHOLD = "\xF3"
CMD_READ_HUM_NOHOLD = "\xF5"
CMD_WRITE_USER_REG = "\xE6"
CMD_READ_USER_REG = "\xE7"
CMD_SOFT_RESET= "\xFE"

class i2c(object):
   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()

class HTU21D(object):
   def __init__(self):
      self.dev = i2c(HTU21D_ADDR, 1) #HTU21D 0x40, bus 1
      self.dev.write(CMD_SOFT_RESET) #soft reset
      time.sleep(.1)

   def ctemp(self, sensorTemp):
      tSensorTemp = sensorTemp / 65536.0
      return -46.85 + (175.72 * tSensorTemp)

   def chumid(self, sensorHumid):
      tSensorHumid = sensorHumid / 65536.0
      return -6.0 + (125.0 * tSensorHumid)

   def crc8check(self, value):
      # Ported from Sparkfun Arduino HTU21D Library: https://github.com/sparkfun/HTU21D_Breakout
      remainder = ( ( value[0] << 8 ) + value[1] ) << 8
      remainder |= value[2]
      
      # POLYNOMIAL = 0x0131 = x^8 + x^5 + x^4 + 1
      # divsor = 0x988000 is the 0x0131 polynomial shifted to farthest left of three bytes
      divsor = 0x988000
      
      for i in range(0, 16):
         if( remainder & 1 << (23 - i) ):
            remainder ^= divsor
         divsor = divsor >> 1
      
      if remainder == 0:
         return True
      else:
         return False
   
   def read_tmperature(self):
      self.dev.write(CMD_READ_TEMP_NOHOLD) #measure temp
      time.sleep(.1)

      data = self.dev.read(3)
      buf = array.array('B', data)

      if self.crc8check(buf):
         temp = (buf[0] << 8 | buf [1]) & 0xFFFC
         return self.ctemp(temp)
      else:
         return -255
         
   def read_humidity(self):
      self.dev.write(CMD_READ_HUM_NOHOLD) #measure humidity
      time.sleep(.1)

      data = self.dev.read(3)
      buf = array.array('B', data)
      
      if self.crc8check(buf):
         humid = (buf[0] << 8 | buf [1]) & 0xFFFC
         return self.chumid(humid)
      else:
         return -255
         
if __name__ == "__main__":
   obj = HTU21D()
   print "Temp:", obj.read_tmperature(), "C"
   print "Humid:", obj.read_humidity(), "% rH"

Image
Posts: 8
Joined: Sat Sep 19, 2015 12:15 am