Sunday, 24 November 2013

Interfacing a BMP085 Digital Pressure sensor to the Raspberry Pi

I recently bought a sensor with a BMP085 Digital Pressure sensor on it so I thought I'd write a post on how to read the data from the Raspberry Pi in Python over I2C.

Python code


Below is simple test code to initialise the sensor and then continuously loop around reading the temperature and air pressure.
#!/usr/bin/python
import smbus
import time

bus = smbus.SMBus(0) # or bus = smbus.SMBus(1) if you have a revision 2 board 
address = 0x77

def read_byte(adr):
    return bus.read_byte_data(address, adr)

def read_word(adr):
    high = bus.read_byte_data(address, adr)
    low = bus.read_byte_data(address, adr+1)
    val = (high << 8) + low
    return val

def read_word_2c(adr):
    val = read_word(adr)
    if (val >= 0x8000):
        return -((0xffff - val) + 1)
    else:
        return val

def write_byte(adr, value):
    bus.write_byte_data(address, adr, value)

def twos_compliment(val):
    if (val >= 0x8000):
        return -((0xffff - val) + 1)
    else:
        return val

def get_word(array, index, twos):
    val = (array[index] << 8) + array[index+1]
    if twos:
        return twos_compliment(val)
    else:
        return val

def calculate():
    # This code is a direct translation from the datasheet
    # and should be optimised for real world use
    
    #Calculate temperature
    x1 = ((temp_raw - ac6) * ac5) / 32768
    x2 = (mc * 2048) / (x1 + md)
    b5 = x1 + x2
    t = (b5 + 8) / 16
    
    # Now calculate the pressure
    b6 = b5 - 4000 
    x1 = (b2 * (b6 * b6 >> 12)) >> 11
    x2 = ac2 * b6 >> 11
    x3 = x1 + x2
    b3 = (((ac1 * 4 + x3) << oss) + 2) >> 2 
    
    x1 = (ac3 * b6) >> 13 
    x2 = (b1 * (b6 * b6 >> 12)) >> 16 
    x3 = ((x1 + x2) + 2) >> 2 
    b4 = ac4 * (x3 + 32768) >> 15 
    b7 = (pressure_raw - b3) * (50000 >> oss)
    if (b7 < 0x80000000):
        p = (b7 * 2) /b4
    else:
        p = (b7 / b4) *2
    x1 = (p >> 8) * (p >> 8)
    x1 = (x1 * 3038) >> 16
    x2 = (-7357 * p) >> 16
    p = p + ((x1 + x2 + 3791) >> 4)
    return(t,p)

calibration = bus.read_i2c_block_data(address, 0xAA, 22)

oss = 3              # Ultra high resolution
temp_wait_period = 0.004
pressure_wait_period = 0.0255 # Conversion time

# The sensor has a block of factory set calibration values we need to read
# these are then used in a length calculation to get the temperature and pressure
ac1 = get_word(calibration, 0, True)
ac2 = get_word(calibration, 2, True)
ac3 = get_word(calibration, 4, True)
ac4 = get_word(calibration, 6, False)
ac5 = get_word(calibration, 8, False)
ac6 = get_word(calibration, 10, False)
b1 =  get_word(calibration, 12, True)
b2 =  get_word(calibration, 14, True)
mb =  get_word(calibration, 16, True)
mc =  get_word(calibration, 18, True)
md =  get_word(calibration, 20, True)


while True:
    # Read raw temperature
    write_byte(0xF4, 0x2E)          # Tell the sensor to take a temperature reading
    time.sleep(temp_wait_period)    # Wait for the conversion to take place
    temp_raw = read_word_2c(0xF6)

    write_byte(0xF4, 0x34 + (oss << 6)) # Tell the sensor to take a pressure reading
    time.sleep(pressure_wait_period)    # Wait for the conversion to take place
    pressure_raw = ((read_byte(0xF6) << 16) \
                     + (read_byte(0xF7) << 8) \
                     + (read_byte(0xF8)) ) >> (8-oss)


    temperature, pressure = calculate()
    print time.time(), temperature / 10., pressure / 100.
    time.sleep(1)
To get a reading out of the sensor you first have to read the factory set calibration block (lines 080-090).  This is different for each device and is used in the lengthy calculations for both temperature and pressure.  The function calculate() is just a direct translation of the code presented in the datasheet, I don't understand what it's doing but it gives us the required values.

Testing the sensor and the code


To test everything was working OK I saved the above code to a file called read-pressure.py, ran it and re-directed the output to a file
sudo ./read-pressure.py > pressure-test.dat
I then slowly walked up and down the stairs in my house to get some data.  Then plotted the data with the following gnuplot program

  set terminal wxt persist size 800,800 background '#000000' 
  set style line 99 linecolor rgb "#ffffff" linetype 0 linewidth 2
  set key top right textcolor linestyle 99 
  set grid linestyle 99
  set border linestyle 99

  set yrange  [16.4:17.2]
  set y2range [1003.5:1005]
  set y2tics

  plot filename using 1:2 axes x1y1 title "True temp" w l ,\
       filename using 1:3 axes x1y2 title "True pressure" w l, \
       filename using 1:3 axes x1y2 title "Smoothed" smooth bezier

Here is the command to generate the plot below
gnuplot -e "filename='pressure-test.dat'" gnuplot-pressure.plg
You can see the pressure dropping as I went up the stairs and then back down again.  You can see the temperature went up slightly too which I think was just heat from my hand slowly raising it.

Sensor response as I walked up and down the stairs
To calculate altitude (height above ground) I used the first (p0) and the lowest (p) readings from the output and plugged them into the following formula, again this is taken from the datasheet.


This gave me a height of 2.86m, I was surprised to get a significant reading by just walking up and down the stairs so when I finally add it to a quad-copter I should get good results.

1 comment:

  1. Hello. Do you convert the script in python 3 ?
    With python3 we have many error like this unsupported operand type(s) for >>: 'float' and 'int'

    ReplyDelete

Temperature logging with a DS18B20 and a Raspberry Pi

I wanted to do some temperature logging so I hooked up a DS18B20 temperature sensor to a Raspberry Pi. About the DS18B20 Dallas DS18B...