Raspberry Pi and the BMP085

General project help for Adafruit customers

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
User avatar
static
 
Posts: 188
Joined: Thu Dec 23, 2010 6:21 pm

Raspberry Pi and the BMP085

Post by static »

Originally I started looking at this issue here:
http://forums.adafruit.com/viewtopic.php?f=8&t=37850

I'm getting bad data from my BMP085 after updating the Adafruit_I2C library to the most recent version. I've been using a couple of sensors on the same bus: TMP102 and TSL2561. The TSL2561 is a work in progress, so I can't tell if it is having issues. The TMP102 seems to be working fine (temp is stable and jibes with what I'm expecting).

The BMP085 is giving bizarre, unstable readings. My latest test string generated starting at 77 degrees Celsius, and it is decreasing with each read. After 30 seconds I'm at -13.6. The TMP102 thermal sensor is a foot away and reading a consistent 24.1875 degrees Celsius.

I did make one modification to the Adafruit_BMP085.py file so that the bus number is passed through the BMP085 initialization to the I2C initialization.

User avatar
static
 
Posts: 188
Joined: Thu Dec 23, 2010 6:21 pm

Re: Raspberry Pi and the BMP085

Post by static »

Some relevant code:
Adafruit_BMP085, slightly modified

Code: Select all

#!/usr/bin/python

import time
from Adafruit_I2C import Adafruit_I2C

# ===========================================================================
# BMP085 Class
# ===========================================================================

class BMP085 :
  i2c = None

  # Operating Modes
  __BMP085_ULTRALOWPOWER     = 0
  __BMP085_STANDARD          = 1
  __BMP085_HIGHRES           = 2
  __BMP085_ULTRAHIGHRES      = 3

  # BMP085 Registers
  __BMP085_CAL_AC1           = 0xAA  # R   Calibration data (16 bits)
  __BMP085_CAL_AC2           = 0xAC  # R   Calibration data (16 bits)
  __BMP085_CAL_AC3           = 0xAE  # R   Calibration data (16 bits)
  __BMP085_CAL_AC4           = 0xB0  # R   Calibration data (16 bits)
  __BMP085_CAL_AC5           = 0xB2  # R   Calibration data (16 bits)
  __BMP085_CAL_AC6           = 0xB4  # R   Calibration data (16 bits)
  __BMP085_CAL_B1            = 0xB6  # R   Calibration data (16 bits)
  __BMP085_CAL_B2            = 0xB8  # R   Calibration data (16 bits)
  __BMP085_CAL_MB            = 0xBA  # R   Calibration data (16 bits)
  __BMP085_CAL_MC            = 0xBC  # R   Calibration data (16 bits)
  __BMP085_CAL_MD            = 0xBE  # R   Calibration data (16 bits)
  __BMP085_CONTROL           = 0xF4
  __BMP085_TEMPDATA          = 0xF6
  __BMP085_PRESSUREDATA      = 0xF6
  __BMP085_READTEMPCMD       = 0x2E
  __BMP085_READPRESSURECMD   = 0x34

  # Private Fields
  _cal_AC1 = 0
  _cal_AC2 = 0
  _cal_AC3 = 0
  _cal_AC4 = 0
  _cal_AC5 = 0
  _cal_AC6 = 0
  _cal_B1 = 0
  _cal_B2 = 0
  _cal_MB = 0
  _cal_MC = 0
  _cal_MD = 0

  # Constructor
  def __init__(self, address=0x77, busnum = -1, mode=1, debug=False):
    self.i2c = Adafruit_I2C(address, busnum)

    self.address = address
    self.debug = debug
    # Make sure the specified mode is in the appropriate range
    if ((mode < 0) | (mode > 3)):
      if (self.debug):
        print "Invalid Mode: Using STANDARD by default"
      self.mode = self.__BMP085_STANDARD
    else:
      self.mode = mode
    # Read the calibration data
    self.readCalibrationData()

  def readCalibrationData(self):
    "Reads the calibration data from the IC"
    self._cal_AC1 = self.i2c.readS16(self.__BMP085_CAL_AC1)   # INT16
    self._cal_AC2 = self.i2c.readS16(self.__BMP085_CAL_AC2)   # INT16
    self._cal_AC3 = self.i2c.readS16(self.__BMP085_CAL_AC3)   # INT16
    self._cal_AC4 = self.i2c.readU16(self.__BMP085_CAL_AC4)   # UINT16
    self._cal_AC5 = self.i2c.readU16(self.__BMP085_CAL_AC5)   # UINT16
    self._cal_AC6 = self.i2c.readU16(self.__BMP085_CAL_AC6)   # UINT16
    self._cal_B1 = self.i2c.readS16(self.__BMP085_CAL_B1)     # INT16
    self._cal_B2 = self.i2c.readS16(self.__BMP085_CAL_B2)     # INT16
    self._cal_MB = self.i2c.readS16(self.__BMP085_CAL_MB)     # INT16
    self._cal_MC = self.i2c.readS16(self.__BMP085_CAL_MC)     # INT16
    self._cal_MD = self.i2c.readS16(self.__BMP085_CAL_MD)     # INT16
    if (self.debug):
      self.showCalibrationData()

  def showCalibrationData(self):
      "Displays the calibration values for debugging purposes"
      print "DBG: AC1 = %6d" % (self._cal_AC1)
      print "DBG: AC2 = %6d" % (self._cal_AC2)
      print "DBG: AC3 = %6d" % (self._cal_AC3)
      print "DBG: AC4 = %6d" % (self._cal_AC4)
      print "DBG: AC5 = %6d" % (self._cal_AC5)
      print "DBG: AC6 = %6d" % (self._cal_AC6)
      print "DBG: B1  = %6d" % (self._cal_B1)
      print "DBG: B2  = %6d" % (self._cal_B2)
      print "DBG: MB  = %6d" % (self._cal_MB)
      print "DBG: MC  = %6d" % (self._cal_MC)
      print "DBG: MD  = %6d" % (self._cal_MD)

  def readRawTemp(self):
    "Reads the raw (uncompensated) temperature from the sensor"
    self.i2c.write8(self.__BMP085_CONTROL, self.__BMP085_READTEMPCMD)
    time.sleep(0.005)  # Wait 5ms
    raw = self.i2c.readU16(self.__BMP085_TEMPDATA)
    if (self.debug):
      print "DBG: Raw Temp: 0x%04X (%d)" % (raw & 0xFFFF, raw)
    return raw

  def readRawPressure(self):
    "Reads the raw (uncompensated) pressure level from the sensor"
    self.i2c.write8(self.__BMP085_CONTROL, self.__BMP085_READPRESSURECMD + (self.mode << 6))
    if (self.mode == self.__BMP085_ULTRALOWPOWER):
      time.sleep(0.005)
    elif (self.mode == self.__BMP085_HIGHRES):
      time.sleep(0.014)
    elif (self.mode == self.__BMP085_ULTRAHIGHRES):
      time.sleep(0.026)
    else:
      time.sleep(0.008)
    msb = self.i2c.readU8(self.__BMP085_PRESSUREDATA)
    lsb = self.i2c.readU8(self.__BMP085_PRESSUREDATA+1)
    xlsb = self.i2c.readU8(self.__BMP085_PRESSUREDATA+2)
    raw = ((msb << 16) + (lsb << 8) + xlsb) >> (8 - self.mode)
    if (self.debug):
      print "DBG: Raw Pressure: 0x%04X (%d)" % (raw & 0xFFFF, raw)
    return raw

  def readTemperature(self):
    "Gets the compensated temperature in degrees celcius"
    UT = 0
    X1 = 0
    X2 = 0
    B5 = 0
    temp = 0.0

    # Read raw temp before aligning it with the calibration values
    UT = self.readRawTemp()
    X1 = ((UT - self._cal_AC6) * self._cal_AC5) >> 15
    X2 = (self._cal_MC << 11) / (X1 + self._cal_MD)
    B5 = X1 + X2
    temp = ((B5 + 8) >> 4) / 10.0
    if (self.debug):
      print "DBG: Calibrated temperature = %f C" % temp
    return temp

  def readPressure(self):
    "Gets the compensated pressure in pascal"
    UT = 0
    UP = 0
    B3 = 0
    B5 = 0
    B6 = 0
    X1 = 0
    X2 = 0
    X3 = 0
    p = 0
    B4 = 0
    B7 = 0

    UT = self.readRawTemp()
    UP = self.readRawPressure()

    # You can use the datasheet values to test the conversion results
    # dsValues = True
    dsValues = False

    if (dsValues):
      UT = 27898
      UP = 23843
      self._cal_AC6 = 23153
      self._cal_AC5 = 32757
      self._cal_MC = -8711
      self._cal_MD = 2868
      self._cal_B1 = 6190
      self._cal_B2 = 4
      self._cal_AC3 = -14383
      self._cal_AC2 = -72
      self._cal_AC1 = 408
      self._cal_AC4 = 32741
      self.mode = self.__BMP085_ULTRALOWPOWER
      if (self.debug):
        self.showCalibrationData()

    # True Temperature Calculations
    X1 = ((UT - self._cal_AC6) * self._cal_AC5) >> 15
    X2 = (self._cal_MC << 11) / (X1 + self._cal_MD)
    B5 = X1 + X2
    if (self.debug):
      print "DBG: X1 = %d" % (X1)
      print "DBG: X2 = %d" % (X2)
      print "DBG: B5 = %d" % (B5)
      print "DBG: True Temperature = %.2f C" % (((B5 + 8) >> 4) / 10.0)

    # Pressure Calculations
    B6 = B5 - 4000
    X1 = (self._cal_B2 * (B6 * B6) >> 12) >> 11
    X2 = (self._cal_AC2 * B6) >> 11
    X3 = X1 + X2
    B3 = (((self._cal_AC1 * 4 + X3) << self.mode) + 2) / 4
    if (self.debug):
      print "DBG: B6 = %d" % (B6)
      print "DBG: X1 = %d" % (X1)
      print "DBG: X2 = %d" % (X2)
      print "DBG: B3 = %d" % (B3)

    X1 = (self._cal_AC3 * B6) >> 13
    X2 = (self._cal_B1 * ((B6 * B6) >> 12)) >> 16
    X3 = ((X1 + X2) + 2) >> 2
    B4 = (self._cal_AC4 * (X3 + 32768)) >> 15
    B7 = (UP - B3) * (50000 >> self.mode)
    if (self.debug):
      print "DBG: X1 = %d" % (X1)
      print "DBG: X2 = %d" % (X2)
      print "DBG: B4 = %d" % (B4)
      print "DBG: B7 = %d" % (B7)

    if (B7 < 0x80000000):
      p = (B7 * 2) / B4
    else:
      p = (B7 / B4) * 2

    X1 = (p >> 8) * (p >> 8)
    X1 = (X1 * 3038) >> 16
    X2 = (-7375 * p) >> 16
    if (self.debug):
      print "DBG: p  = %d" % (p)
      print "DBG: X1 = %d" % (X1)
      print "DBG: X2 = %d" % (X2)

    p = p + ((X1 + X2 + 3791) >> 4)
    if (self.debug):
      print "DBG: Pressure = %d Pa" % (p)

    return p

  def readAltitude(self, seaLevelPressure=101325):
    "Calculates the altitude in meters"
    altitude = 0.0
    pressure = float(self.readPressure())
    altitude = 44330.0 * (1.0 - pow(pressure / seaLevelPressure, 0.1903))
    if (self.debug):
      print "DBG: Altitude = %d" % (altitude)
    return altitude

    return 0

Here is the code that I'm using to test the I2C sensors:

Code: Select all

#!/usr/bin/python

#from Logger import *
from Adafruit_BMP085 import BMP085
from Ty_TMP102 import TMP102
from Ty_TSL2561 import TSL2561
import time

def test():
    bmp = BMP085(address = 0x77, busnum = 1, debug=True)
    tmp = TMP102(address = 0x48, busnum = 1)
    #tsl = TSL2561(address = 0x39, busnum = 1)
    print(bmp.readTemperature())
          
    print(tmp.readTemperature())
    #print(tsl.readLux())

x = 1
y = 0
while x == 1:
    y = y + 1
    test()
    time.sleep(5)


User avatar
static
 
Posts: 188
Joined: Thu Dec 23, 2010 6:21 pm

Re: Raspberry Pi and the BMP085

Post by static »

Looking at the data sheet, it looks like the uncompensated temperature needs to be bit shifted here:

Code: Select all

def readRawTemp(self):
    "Reads the raw (uncompensated) temperature from the sensor"
    self.i2c.write8(self.__BMP085_CONTROL, self.__BMP085_READTEMPCMD)
    time.sleep(0.005)  # Wait 5ms
    raw = self.i2c.readU16(self.__BMP085_TEMPDATA)
    if (self.debug):
      print "DBG: Raw Temp: 0x%04X (%d)" % (raw & 0xFFFF, raw)
    return raw
Before "raw" is returned, shouldn't the bit shift happen?
With the code kept like this from the GitHub, the temperature value is all over the place.
If you add the line:

Code: Select all

raw = self.i2c.reverseByteOrder(raw)
The value is very low, but it is consistently very low. I can't explain why it is very low (I checked the calculations. Everything else seems to be in line with the data sheet).
Further, when I heat the sensor on the board, the value goes up in a predictable fashion.

I'm going to keep plugging away at it. I just need to find where the calculation is screwy.

User avatar
static
 
Posts: 188
Joined: Thu Dec 23, 2010 6:21 pm

Re: Raspberry Pi and the BMP085

Post by static »

I submitted the same questions to the github repository. Let's see what happens.

I spent the morning trying different things, updating all the software, and generally trying to get back to square one.

Am I the only one having this problem? The bit-shift issue seems like it should be causing obvious problems.

I can't figure out how to look at old versions of the code. I can't even find my old SD cards... grrrr.

User avatar
static
 
Posts: 188
Joined: Thu Dec 23, 2010 6:21 pm

Re: Raspberry Pi and the BMP085

Post by static »

Big huge mess.
I just reverted to the old Adafruit_I2C.py file (found it!). It seems like this fixes the BMP085 readings, BUT I noticed that the calibration data is being read incorrectly with the newer version of the Adafruit_I2C.py file. I gotta dig away at that.

Also, I really don't understand how the Adafruit_BMP085.py file translated the BMP085 calculations from the datasheet. It really looks like it shouldn't work.

User avatar
static
 
Posts: 188
Joined: Thu Dec 23, 2010 6:21 pm

Re: Raspberry Pi and the BMP085

Post by static »

OK, it looks like the issue was with something going on in the Adafruit_I2C code. I pulled what works and got rid of what didn't (this is probably ugly, and I have to believe that this violates all kinds of best practices).
Adafruit_I2c.py Static's Revision

Code: Select all

#!/usr/bin/python

import smbus

# ===========================================================================
# Adafruit_I2C Base Class
# ===========================================================================

class Adafruit_I2C :

  @staticmethod
  def getPiRevision():
    "Gets the version number of the Raspberry Pi board"
    # Courtesy quick2wire-python-api
    # https://github.com/quick2wire/quick2wire-python-api
    try:
      with open('/proc/cpuinfo','r') as f:
        for line in f:
          if line.startswith('Revision'):
            return 1 if line.rstrip()[-1] in ['1','2'] else 2
    except:
      return 0

  @staticmethod
  def getPiI2CBusNumber():
    # Gets the I2C bus number /dev/i2c#
    return 1 if Adafruit_I2C.getPiRevision() > 1 else 0
 
  def __init__(self, address, busnum=-1, debug=False):
    self.address = address
    # By default, the correct I2C bus is auto-detected using /proc/cpuinfo
    # Alternatively, you can hard-code the bus version below:
    # self.bus = smbus.SMBus(0); # Force I2C0 (early 256MB Pi's)
    # self.bus = smbus.SMBus(1); # Force I2C1 (512MB Pi's)
    self.bus = smbus.SMBus(
      busnum if busnum >= 0 else Adafruit_I2C.getPiI2CBusNumber())
    self.debug = debug

#  def __init__(self, address, bus=smbus.SMBus(1), debug=False):
#    self.address = address
#    self.bus = bus
#    self.debug = debug

  def reverseByteOrder(self, data):
    "Reverses the byte order of an int (16-bit) or long (32-bit) value"
    # Courtesy Vishal Sapre
    dstr = hex(data)[2:].replace('L','')
    byteCount = len(dstr[::2])
    val = 0
    for i, n in enumerate(range(byteCount)):
      d = data & 0xFF
      val |= (d << (8 * (byteCount - i - 1)))
      data >>= 8
    return val

  def write8(self, reg, value):
    "Writes an 8-bit value to the specified register/address"
    try:
      self.bus.write_byte_data(self.address, reg, value)
      if (self.debug):
        print("I2C: Wrote 0x%02X to register 0x%02X" % (value, reg))
    except IOError, err:
      print "Error accessing 0x%02X: Check your I2C address" % self.address
      return -1

  def writeList(self, reg, list):
    "Writes an array of bytes using I2C format"
    try:
      self.bus.write_i2c_block_data(self.address, reg, list)
    except IOError, err:
      print "Error accessing 0x%02X: Check your I2C address" % self.address
      return -1

  def readList(self, reg, length):
    "Read a list of bytes from the I2C device"
    try:
      results = self.bus.read_i2c_block_data(self.address, reg, length)
      if self.debug:
        print ("I2C: Device 0x%02X returned the following from reg 0x%02X" %
         (self.address, reg))
        print results
      return results
    except IOError, err:
      return self.errMsg()

  def readU8(self, reg):
    "Read an unsigned byte from the I2C device"
    try:
      result = self.bus.read_byte_data(self.address, reg)
      if (self.debug):
        print "I2C: Device 0x%02X returned 0x%02X from reg 0x%02X" % (self.address, result & 0xFF, reg)
      return result
    except IOError, err:
      print "Error accessing 0x%02X: Check your I2C address" % self.address
      return -1

  def readS8(self, reg):
    "Reads a signed byte from the I2C device"
    try:
      result = self.bus.read_byte_data(self.address, reg)
      if (self.debug):
        print "I2C: Device 0x%02X returned 0x%02X from reg 0x%02X" % (self.address, result & 0xFF, reg)
      if (result > 127):
        return result - 256
      else:
        return result
    except IOError, err:
      print "Error accessing 0x%02X: Check your I2C address" % self.address
      return -1

  def readU16(self, reg):
    "Reads an unsigned 16-bit value from the I2C device"
    try:
      hibyte = self.bus.read_byte_data(self.address, reg)
      result = (hibyte << 8) + self.bus.read_byte_data(self.address, reg+1)
      if (self.debug):
        print "I2C: Device 0x%02X returned 0x%04X from reg 0x%02X" % (self.address, result & 0xFFFF, reg)
      return result
    except IOError, err:
      print "Error accessing 0x%02X: Check your I2C address" % self.address
      return -1

  def readS16(self, reg):
    "Reads a signed 16-bit value from the I2C device"
    try:
      hibyte = self.bus.read_byte_data(self.address, reg)
      if (hibyte > 127):
        hibyte -= 256
      result = (hibyte << 8) + self.bus.read_byte_data(self.address, reg+1)
      if (self.debug):
        print "I2C: Device 0x%02X returned 0x%04X from reg 0x%02X" % (self.address, result & 0xFFFF, reg)
      return result
    except IOError, err:
      print "Error accessing 0x%02X: Check your I2C address" % self.address
      return -1

I can't explain why the other code wasn't working. I'm still looking at that.
I'm getting pretty consistent sensor readings from the BMP085 and the TMP102 sensor on the bus (less than a degree of variance, rise and fall seem to be in sync).

I'm going to modify the code a little bit further, now that it works, to allow for bus specification from the inception of the sensor.

User avatar
static
 
Posts: 188
Joined: Thu Dec 23, 2010 6:21 pm

Re: Raspberry Pi and the BMP085

Post by static »

I made some changes to the BMP085 code. Despite what I said before, the calculations appear to be right on. No idea how they got the answers they did, but it works (and I couldn't make it work better).

First, I set it up so that the bus number can be assigned when the BMP085 is called. The function will take the bus number and pass it to the Adafruit_I2C code (or allow the default -1, which causes the Adafruit_I2C code to determine which bus is the default bus for that version of the Raspberry Pi). The specified bus is also reported if the Debug mode is enabled.

Second, I set it up so that if the Debug mode is enabled, it passes the Debug flag to the Adafruit_I2C code. Anyone wondering what is going on with their sensors is also going to wonder what is going on with their bus (well.... I would).

I went in to a little more depth on the blog, here:
http://medicforlife.blogspot.com

Code: Select all

#!/usr/bin/python

import time
from Adafruit_I2C import Adafruit_I2C

# ===========================================================================
# BMP085 Class
# ===========================================================================

class BMP085 :
  i2c = None

  # Operating Modes
  __BMP085_ULTRALOWPOWER     = 0
  __BMP085_STANDARD          = 1
  __BMP085_HIGHRES           = 2
  __BMP085_ULTRAHIGHRES      = 3

  # BMP085 Registers
  __BMP085_CAL_AC1           = 0xAA  # R   Calibration data (16 bits)
  __BMP085_CAL_AC2           = 0xAC  # R   Calibration data (16 bits)
  __BMP085_CAL_AC3           = 0xAE  # R   Calibration data (16 bits)
  __BMP085_CAL_AC4           = 0xB0  # R   Calibration data (16 bits)
  __BMP085_CAL_AC5           = 0xB2  # R   Calibration data (16 bits)
  __BMP085_CAL_AC6           = 0xB4  # R   Calibration data (16 bits)
  __BMP085_CAL_B1            = 0xB6  # R   Calibration data (16 bits)
  __BMP085_CAL_B2            = 0xB8  # R   Calibration data (16 bits)
  __BMP085_CAL_MB            = 0xBA  # R   Calibration data (16 bits)
  __BMP085_CAL_MC            = 0xBC  # R   Calibration data (16 bits)
  __BMP085_CAL_MD            = 0xBE  # R   Calibration data (16 bits)
  __BMP085_CONTROL           = 0xF4
  __BMP085_TEMPDATA          = 0xF6
  __BMP085_PRESSUREDATA      = 0xF6
  __BMP085_READTEMPCMD       = 0x2E
  __BMP085_READPRESSURECMD   = 0x34

  # Private Fields
  _cal_AC1 = 0
  _cal_AC2 = 0
  _cal_AC3 = 0
  _cal_AC4 = 0
  _cal_AC5 = 0
  _cal_AC6 = 0
  _cal_B1 = 0
  _cal_B2 = 0
  _cal_MB = 0
  _cal_MC = 0
  _cal_MD = 0

  # Constructor
  def __init__(self, address=0x77, busnum = -1, mode=0, debug=False):
    self.i2c = Adafruit_I2C(address, busnum, debug)

    self.address = address
    self.debug = debug
    self.bus = busnum
    # Make sure the specified mode is in the appropriate range
    if ((mode < 0) | (mode > 3)):
      if (self.debug):
        print "Invalid Mode: Using STANDARD by default"
      self.mode = self.__BMP085_STANDARD
    else:
      self.mode = mode
    # Read the calibration data
    self.readCalibrationData()

  def readCalibrationData(self):
    "Reads the calibration data from the IC"
    self._cal_AC1 = self.i2c.readS16(self.__BMP085_CAL_AC1)   # INT16
    self._cal_AC2 = self.i2c.readS16(self.__BMP085_CAL_AC2)   # INT16
    self._cal_AC3 = self.i2c.readS16(self.__BMP085_CAL_AC3)   # INT16
    self._cal_AC4 = self.i2c.readU16(self.__BMP085_CAL_AC4)   # UINT16
    self._cal_AC5 = self.i2c.readU16(self.__BMP085_CAL_AC5)   # UINT16
    self._cal_AC6 = self.i2c.readU16(self.__BMP085_CAL_AC6)   # UINT16
    self._cal_B1 = self.i2c.readS16(self.__BMP085_CAL_B1)     # INT16
    self._cal_B2 = self.i2c.readS16(self.__BMP085_CAL_B2)     # INT16
    self._cal_MB = self.i2c.readS16(self.__BMP085_CAL_MB)     # INT16
    self._cal_MC = self.i2c.readS16(self.__BMP085_CAL_MC)     # INT16
    self._cal_MD = self.i2c.readS16(self.__BMP085_CAL_MD)     # INT16
    if (self.debug):
      self.showCalibrationData()

  def showCalibrationData(self):
      "Displays the calibration values for debugging purposes"
      print "Address = ", hex(self.address)
      print "DBG: AC1 = %6d" % (self._cal_AC1)
      print "DBG: AC2 = %6d" % (self._cal_AC2)
      print "DBG: AC3 = %6d" % (self._cal_AC3)
      print "DBG: AC4 = %6d" % (self._cal_AC4)
      print "DBG: AC5 = %6d" % (self._cal_AC5)
      print "DBG: AC6 = %6d" % (self._cal_AC6)
      print "DBG: B1  = %6d" % (self._cal_B1)
      print "DBG: B2  = %6d" % (self._cal_B2)
      print "DBG: MB  = %6d" % (self._cal_MB)
      print "DBG: MC  = %6d" % (self._cal_MC)
      print "DBG: MD  = %6d" % (self._cal_MD)
      print "DBG: Bus  = %6d" % (self.bus)

  def readRawTemp(self):
    "Reads the raw (uncompensated) temperature from the sensor"
    self.i2c.write8(self.__BMP085_CONTROL, self.__BMP085_READTEMPCMD)
    time.sleep(0.005)  # Wait 5ms
    raw = self.i2c.readU16(self.__BMP085_TEMPDATA)
    if (self.debug):
      print "DBG: Raw Temp: 0x%04X (%d)" % (raw & 0xFFFF, raw)
    return raw

  def readRawPressure(self):
    "Reads the raw (uncompensated) pressure level from the sensor"
    self.i2c.write8(self.__BMP085_CONTROL, self.__BMP085_READPRESSURECMD + (self.mode << 6))
    if (self.mode == self.__BMP085_ULTRALOWPOWER):
      time.sleep(0.005)
    elif (self.mode == self.__BMP085_HIGHRES):
      time.sleep(0.014)
    elif (self.mode == self.__BMP085_ULTRAHIGHRES):
      time.sleep(0.026)
    else:
      time.sleep(0.008)
    msb = self.i2c.readU8(self.__BMP085_PRESSUREDATA)
    lsb = self.i2c.readU8(self.__BMP085_PRESSUREDATA+1)
    xlsb = self.i2c.readU8(self.__BMP085_PRESSUREDATA+2)
    raw = ((msb << 16) + (lsb << 8) + xlsb) >> (8 - self.mode)
    if (self.debug):
      print "DBG: Raw Pressure: 0x%04X (%d)" % (raw & 0xFFFF, raw)
    return raw

  def readTemperature(self):
    "Gets the compensated temperature in degrees celcius"
    UT = 0
    X1 = 0
    X2 = 0
    B5 = 0
    temp = 0.0

    # Read raw temp before aligning it with the calibration values
    UT = self.readRawTemp()
    X1 = ((UT - self._cal_AC6) * self._cal_AC5) >> 15
    X2 = (self._cal_MC << 11) / (X1 + self._cal_MD)
    B5 = X1 + X2
    temp = ((B5 + 8) >> 4) / 10.0
    if (self.debug):
      print "DBG: Calibrated temperature = %f C" % temp
    return temp

  def readPressure(self):
    "Gets the compensated pressure in pascal"
    UT = 0
    UP = 0
    B3 = 0
    B5 = 0
    B6 = 0
    X1 = 0
    X2 = 0
    X3 = 0
    p = 0
    B4 = 0
    B7 = 0

    UT = self.readRawTemp()
    UP = self.readRawPressure()

    # You can use the datasheet values to test the conversion results
    # dsValues = True
    dsValues = False

    if (dsValues):
      UT = 27898
      UP = 23843
      self._cal_AC6 = 23153
      self._cal_AC5 = 32757
      self._cal_MC = -8711
      self._cal_MD = 2868
      self._cal_B1 = 6190
      self._cal_B2 = 4
      self._cal_AC3 = -14383
      self._cal_AC2 = -72
      self._cal_AC1 = 408
      self._cal_AC4 = 32741
      self.mode = self.__BMP085_ULTRALOWPOWER
      if (self.debug):
        self.showCalibrationData()

    # True Temperature Calculations
    X1 = ((UT - self._cal_AC6) * self._cal_AC5) >> 15
    X2 = (self._cal_MC << 11) / (X1 + self._cal_MD)
    B5 = X1 + X2
    if (self.debug):
      print "DBG: X1 = %d" % (X1)
      print "DBG: X2 = %d" % (X2)
      print "DBG: B5 = %d" % (B5)
      print "DBG: True Temperature = %.2f C" % (((B5 + 8) >> 4) / 10.0)

    # Pressure Calculations
    B6 = B5 - 4000
    X1 = (self._cal_B2 * (B6 * B6) >> 12) >> 11
    X2 = (self._cal_AC2 * B6) >> 11
    X3 = X1 + X2
    B3 = (((self._cal_AC1 * 4 + X3) << self.mode) + 2) / 4
    if (self.debug):
      print "DBG: B6 = %d" % (B6)
      print "DBG: X1 = %d" % (X1)
      print "DBG: X2 = %d" % (X2)
      print "DBG: B3 = %d" % (B3)

    X1 = (self._cal_AC3 * B6) >> 13
    X2 = (self._cal_B1 * ((B6 * B6) >> 12)) >> 16
    X3 = ((X1 + X2) + 2) >> 2
    B4 = (self._cal_AC4 * (X3 + 32768)) >> 15
    B7 = (UP - B3) * (50000 >> self.mode)
    if (self.debug):
      print "DBG: X1 = %d" % (X1)
      print "DBG: X2 = %d" % (X2)
      print "DBG: B4 = %d" % (B4)
      print "DBG: B7 = %d" % (B7)

    if (B7 < 0x80000000):
      p = (B7 * 2) / B4
    else:
      p = (B7 / B4) * 2

    X1 = (p >> 8) * (p >> 8)
    X1 = (X1 * 3038) >> 16
    X2 = (-7375 * p) >> 16
    if (self.debug):
      print "DBG: p  = %d" % (p)
      print "DBG: X1 = %d" % (X1)
      print "DBG: X2 = %d" % (X2)

    p = p + ((X1 + X2 + 3791) >> 4)
    if (self.debug):
      print "DBG: Pressure = %d Pa" % (p)

    return p

  def readAltitude(self, seaLevelPressure=101325):
    "Calculates the altitude in meters"
    altitude = 0.0
    pressure = float(self.readPressure())
    altitude = 44330.0 * (1.0 - pow(pressure / seaLevelPressure, 0.1903))
    if (self.debug):
      print "DBG: Altitude = %d" % (altitude)
    return altitude

    return 0

Last edited by static on Thu Apr 04, 2013 5:25 am, edited 2 times in total.

csalty
 
Posts: 27
Joined: Sat Feb 23, 2013 8:32 pm

Re: Raspberry Pi and the BMP085

Post by csalty »

Hey Static. Nice work with the BMP085 sensor. I've noticed that my temperature readings are about 2-3 *C higher than normal. My pressure readings are pretty stable. I see a little bounce here and there. I'll have to try your mod-code and see if the data gets better.

Cheers!

User avatar
static
 
Posts: 188
Joined: Thu Dec 23, 2010 6:21 pm

Re: Raspberry Pi and the BMP085

Post by static »

My temperature readings were completely nuts before hand. Negative 70 jumping to positive 200 Celsius.
The temp readings were within a degree of the TMP102 sensor that I had running on the bus. Frequently the readings were even closer than that.

If you try the modified code, will you let me know how it goes? I only had errors with the BMP085 temperature. I don't know if I examined the pressure readings. I know the TMP102 was working fine. I think the TSL2561's were working, but occasionally gave inaccurate readings.

snoopytoo
 
Posts: 4
Joined: Thu Apr 18, 2013 5:33 am

Re: Raspberry Pi and the BMP085

Post by snoopytoo »

Hi guys,

First post here, time to stop being a lurker... I THINK I FOUND THE SOLUTION!!! :-)

Static - I wanted to understand what was going on and at first glance I found it hard to understand what you did to improve your results, so I started from scratch determined to understand the whole problem.

I'm a complete python novice so it took me nearly a day of reading the code and googling to figure out what was going on, but here is what I think the problem with the Adafruit BMP085 code is and the solution I came up with:

First things first, I found a few typos and missing lines in the Adafruit_BMP085.py code.
1) On line 220:
X2 = (-7375 *p) >> 16
according to the Bosch datasheet that should read
X2 = (-7357 * p) >> 16
2) In the debugging parts of the code (skip this bit if you don't care about the debugging code) there were several lines missing:
- Under the section starting with [if (dsValues): ...] there is no line there to set MB to the datasheet example value
- Under the section starting with [#Pressure Calculations] in the [if (self.debug):] part there is a line missing to print the X3 value on two occasions (line 200 and approx line 210)
- Between line 218:
X1 = (p >> 8) * (p >> 8)
and line 219:
X1 = (X1 * 3038) >> 16
it would also be helpful to add another one of these:
if (self.debug):
print "DBG: X1 = %d % (X1)
before the value changes again in line 219.

Next, Static - the reason why on line 137 [temp = ((B5 + 8) >> 4) / 10.0] the value is divided by 10 is simply because otherwise the value will be saved in tenths of a degree celsius (see page 12 of the datasheet, halfway down the example calculations on the right hand side, next to the box with the value for T in it.

Finally the BIG doozy with the BMP085.py code:

You can't use self.i2c.readS16 or self.i2c.readU16 to get the correct 2 byte values out of the BMP085 registers! The reason appears to be that it conjoins the two bytes incorrectly and results in garbage values. I understand why with the signed read, but the unsigned read (for AC4, AC5 and AC6) is still a bit of a mystery to me.

So when you do a self.i2c.readS16 to get the full two bytes for each calibration parameter, you're treating the whole two byte value (across two separate registers in the BMP085 - eg 0xAA and 0xAB for AC1) as one single signed integer. Consequently when the 2's complement conversion happens on the binary values, you're flipping bits in the second register (in the AC1 example register 0xAB) that shouldn't be flipped and adding one in the wrong place!

I had to study up and use google to relearn/find out about the whole signed/unsigned binary representation and conversion process, so in case anyone else is in the same boat I was, here's how it works:

A one byte integer value can represent decimal numbers between 127 and -128. Zero to positive 127 are represented by a straightforward binary representation. Whereas -1 to -128 are represented by taking the binary representation of the positive equivalent integer and inverting every bit, then at the end adding one to the result (look up "Two's Complement" on wikipedia for a better explanation). A quick example, decimal 4 is binary 00000100. In the Two's complement system decimal -4 is binary 11111011 plus 1, ie 11111100.

So if you read the BMP085 calibration values (all 16 bits) in one go, then converting back from the Two's complement system to get a signed decimal result, you're subtracting 1 from the Least Significant Bit of the Least Significant Byte (for AC1, that would be the right hand bit of register 0xAB) and flipping all the bits of the Least Significant Byte when it appears this is NOT how the BMP085 is set up to function. If you instead do a Two's Complement conversion on the Most Significant Byte (for AC1 that's 0xAA), then bit shift that byte over to the right by 8 bits to make room for the unmolested (unconverted and unsigned) Least Significant Byte register value (for AC1 that's 0xAB), suddenly everything starts working!

So the lines of code I replaced were all the ones in the section starting with [def readCalibrationData(self):] (line 66 onwards). Where you see:

self._cal_AC1 = self.i2c.readS16(self.__BMP085_CAL_AC1)

replace it with:

self._cal_AC1 = (self.i2c.readS8(0xAA) << 8) + self.i2c.readU8(0xAB)

Then do the same for all the other calibration parameters (with their correct names and associated register addresses from the datasheet - table on page 11). You could go and fix all the references up so that you can call each register in the same way the original code did - ie 0xAA is referenced as self.__BMP085_CAL_AC1, but then you'd have to change those definitions at the start of the code to have two labels for each parameter (eg AC1_MSB and AC1_LSB). I didn't bother.

I found that you also have to read AC4, AC5 and AC6 as two separate unsigned bytes and conjoin them afterwards as well, not sure why that is.

Oh, one last thing - when reading the raw temperature (line 100) you also need to read the two bytes of raw TEMPDATA in two lots of one byte in the same way as for the calibration values.

Replace line 100:
raw = self.i2c.readU16(self.__BMP085_TEMPDATA)

with:

raw = (self.i2c.readS8(0xF6) << 8) + self.i2c.readU8(0xF7)

Once again you could fix the self.__BMP085_TEMPDATA reference to be able to reference the MSB and LSB separately, but I didn't bother and just directly used the register addresses from the data sheet instead (0xF6) and (0xF7).

Hope this saves a bunch of effort and headaches for a bunch of other people! Any more knowledgeable python experts than me want to check that this makes sense and add anything that I missed?

Cheers,
S.

snoopytoo
 
Posts: 4
Joined: Thu Apr 18, 2013 5:33 am

Re: Raspberry Pi and the BMP085

Post by snoopytoo »

Whoops, in the paragraph of my post above:

"So if you read the BMP085 calibration values (all 16 bits) in one go, then converting back from the Two's complement system to get a signed decimal result, you're subtracting 1 from the Least Significant Bit of the Least Significant Byte (for AC1, that would be the right hand bit of register 0xAB) and flipping all the bits of the Least Significant Byte when it appears this is NOT how the BMP085 is set up to function. If you instead do a Two's Complement conversion on the Most Significant Byte (for AC1 that's 0xAA), then bit shift that byte over to the right by 8 bits to make room for the unmolested (unconverted and unsigned) Least Significant Byte register value (for AC1 that's 0xAB), suddenly everything starts working!"

In this sentence:
"If you instead do a Two's Complement conversion on the Most Significant Byte (for AC1 that's 0xAA), then bit shift that byte over to the right by 8 bits to make room for the unmolested (unconverted and unsigned) Least Significant Byte..."

I should have said "bit shift that byte over to the LEFT by 8 bits!

And apologies for the silly emoticon thing popping up throughout the post - wherever you see 8) that should read 8 followed by )... Guess that's why there's those boxes for inserting quoted code into that I haven't bothered to figure out how to use... :-)

User avatar
static
 
Posts: 188
Joined: Thu Dec 23, 2010 6:21 pm

Re: Raspberry Pi and the BMP085

Post by static »

I'm going to try to play with this today, depending on when I get a break. So far, it looks like good work followed by great explanations. I've been trying to understand the bit shift process, but I was having to just accept some of it.

I'll post up when I am able to test things out.

Thanks!

adafruit
 
Posts: 12151
Joined: Thu Apr 06, 2006 4:21 pm

Re: Raspberry Pi and the BMP085

Post by adafruit »

We're not sure exactly what happened, but we're going to try to fix this code ASAP, thanks!

snoopytoo
 
Posts: 4
Joined: Thu Apr 18, 2013 5:33 am

Re: Raspberry Pi and the BMP085

Post by snoopytoo »

Thanks Adafruit! One more thing that caused me confusion is that the datasheet you host for this sensor is an old version with mistakes in it. The version you have hosted is Rev 1.0, I've managed to find Rev 1.3 on the interweb and that clears up a couple of mistakes in the algorithm and calculation example on page 12.

I'VE HAD A REVELATION!!!! It's simpler (and MUCH more logical!!) than what I thought before:

The problem lies in the read_word_data call - all that's wrong is that it's supplying the two bytes of the result in the WRONG ORDER!!!!

Consequently, the way I fudged a solution and the way that Static did (different code modified - but same result) gives ALMOST right answers, but NOT exactly right answers!

Let me explain:

Let's start with this two byte integer:
0111 1000 1010 1000 = 0x78A8
Let's reverse the LSB and MSB which gives us:
1010 1000 0111 1000 = 0xA878

If you do a read_byte_data(xxx) << 8 + read_byte_data(xxx+1) you'll get the first integer and if you do a read_word_data(xxx) you'll get the second integer!!!

It just so happens that when doing the signed two's complement conversion on the MSB of a two byte integer it gives an ALMOST right result, but not ACTUALLY right result! Here's the example I used to test my hypothesis:
1000 1000 1000 1000 (= 34952 in decimal)
now 34952 - 65536 = 30584
putting back in the negative sign gives: -30584

BUT, if we convert ONLY the MSB like this (the way I described in my previous post and in fact the way the old Adafruit_I2C.py library appears to have done from what Static posted):
1000 1000 (=136 in decimal)
now 136 - 256 = 120
convert 120 back to binary representation gives: 0111 1000 (new MSB)
now join the MSB and the LSB back together:
0111 1000 1000 1000 (=30856 in decimal)
putting back the negative sign gives: -30856

-30584 ~= -30856 BUT NOT EQUAL!!!!

QED!!! :-)

Static - THIS is where the unused "reverseByteOrder" code you mentioned probably becomes useful!

User avatar
ktownsend
 
Posts: 1447
Joined: Thu Nov 05, 2009 2:18 am

Re: Raspberry Pi and the BMP085

Post by ktownsend »

Indeed, the latest I2C update has some issues. Working with signed and unsigned types from raw byte data is troublesome in Python, and a change was pushed to read 'word' values, when it's easier to handle this internally as two separate byte reads, and then shift accordingly. There are also situations where the byte order is reversed, and readin separate bytes is the easiest way to handle this.

If you try out the latest code I hope it will address these issues ... I'm getting sensible values that match the local weather readings to +/- 1hPa.

Locked
Please be positive and constructive with your questions and comments.

Return to “General Project help”