BMP085 Pressure Sensor

Introduction

The BMP085 from Bosch Sensortech is an excellent high-resolution sensor, for measuring absolute atmospheric pressure. You can use it for measuring barometric pressure as part of a weather station, or as an altimeter. It's fast enough to handle rocketry in lower resolution modes, but tops out at 300 mb which is roughly 30k ft - if you want to go higher, use a different sensor (I've heard that it still outputs data above 300 mb, outside the factory calibration range, this could get user calibrated).

There is a lot of noise in the atmosphere, so don't expect a smooth output from any pressure sensor unless it's in a sealed, insulated chamber. For the same reason, discussions about sensors measuring a few centimeters of altitude change are meaningless. If you're comparing the output to a local weather station, pressure changes won't quite match because of local effects. Also there will be an offset, because professional weather stations are calibrated for sea level no matter where they are located.

Most pressure sensors are g-sensitive. If you are using one using in a high-g environment, such as model rocketry, fast model airplane or UAV, the sensor element should be mounted 90 deg to the g-axis, or use two in opposing orientations and average them. I've been meaning to try calibrating out the g-effect using an accelerometer.

What's Good

  • Moderately high resolution in "ultra high" mode, perfectly adequate for most applications (but not quite up to Bosch's marketing)
  • Fast enough for most applications (up to 130 or 222 Hz in two lower resolution modes)
  • Low cost, compared to precision barometric pressure sensors
  • Flexible, has several modes of resolution and update rates
  • Fast response, no hysteresis (which I've seen on gel stabilized pressure sensors, probably used to reduce vibration effects)
  • All digital, uses I2C interface
  • Also provides temperature
  • Easy to wire up
  • Very small size, low power
  • The manufacturer provides working code for the calibration routines, although it's a bit ugly
  • okini3939 has posted a published library - I haven't tested it, and used my own code here. Note that my post is a tutorial and product review, and I don't intend to create my own library

Not so Good

  • A complex calibration routine must be run for each sample (however it uses integer math and the < 1 uS execution time is trivial on mbed)
  • Doesn't have a continuous mode of operation, so you need to run a ticker on the mbed to get samples at fixed rates
  • Doesn't have an hose port, so if you need one you'll have to make an adapter
  • No option for changing I2C bus address, however the data sheet has a trick for using two on the same I2C bus
  • Should have options for even more oversampling than given by the "ultra high" resolution mode

Hardware

Breakout boards are available from SparkFun (provided photo below) and DIY Drones, and other distributors. Bosch has their own breakout board (available from Future Electronics) but it's twice as big and expensive as the others. The sensor uses a very small surface mount package, so you will need some experience to mount it on your own board.

/media/uploads/tkreyche/09694-01_i_ma.jpg

Wiring

See the BMP085 data sheet for pinouts - the connections are pretty simple.

  • Power - wire up 3.3v and ground to the BMP085 from the mbed. The sensor has separate analog and digital 3.3v pins but you can tie them together.
  • Ideally, the two power pins should be supplied power and filtered separately to prevent digital noise feeding into the analog circuitry.
  • I2C - wire up SCL and SDA, and make sure to use pull up resistors if the breakout board you are using doesn't have them.
  • EOC - add a wire for end of conversion notification, from the BMP085 to an InterruptIn pin on the mbed. You could also poll EOC, or skip this step entirely and use a time delay, but both methods are inefficient.
  • You can ignore the master clear input and leave it floating; the device resets on power up. I didn't use it in numerous tests and didn't notice any issues. However you may want to use this with a DigitalOut pin and force a reset on occasion.

Test Setup

All tests below are run in "ultra high" resolution mode to get the best oversampling in hardware, since I'm primarily interested in high-res applications. Also I don't have a test setup for a fast change in altitude, to see how well that works. I might add a "standard mode" resolution noise test later. I didn't test for temperature stability, although that can be done by hitting the sensor with radiant heat and seeing how much drift is induced.

Comparison with Professional Sensor

This chart compares results from the BMP085 to a professional barometric sensor at the University of Washington, Dep't of Atmospheric Sciences, about a mile from my house. The results compare extremely well - the BMP085 is great for this application! The results won't match perfectly because of the distance between the locations, how the sensors are housed, etc. The chart shows passage of a deep low (Point A), followed by a rapid rise in pressure to a weak high (Point B) and then another weaker trough (Point C). The traces are offset slightly to improve readability. The samples from both sources are averaged and updated once per minute.

/media/uploads/tkreyche/uw40.png

Noise Comparison

The chart below left shows a noise level comparison between the BMP085 and a Freescale analog sensor (MPXAZ6115A) hooked to a ultra-high resolution oversampling ADC (AD7190) displaying 17 bits of data. The sensors are just sitting on my bench, not in a chamber, and running simultaneously. The external ADC, which is much higher tech solution, obviously gives somewhat better results but the BMP085 holds up remarkably well.

The x-axis shows number of samples, and with sensors running at 10 SPS. The y-axis is in millibars * 10. The atmosphere at sea level will typically be in the 1005 to 1015 mb range - this trace was taken during a low pressure passage. The two traces are offset to make the chart more readable. The second chart, below right, is a standard deviation taken every 5 samples from the same data as the first chart. It shows the difference more dramatically, with the BMP085 having much greater deviation.

/media/uploads/tkreyche/std_noise.png /media/uploads/tkreyche/std_dev2.png

Resolution Comparison

The 17-bit AD7190 solution provides far better resolution than the BMP085, as it should, given the much better hardware. In this test, I walked up and down two short flights of stairs (twice for each sensor) - the total height of the stairs is about 10 feet. The BMP085 is running in its best mode (ultra high resolution) at its fastest speed (40 Hz), with a 41-tap moving average filter. The AD7190 is running at 20 Hz with a 21-tap filter, so the response time is roughly similar. The AD7190 is doing more oversampling at 20 Hz, which is a big advantage since it further reduces noise. And it will oversample even further down to 1.5 SPS, or you can run it up to 4800 SPS. The x-axis shows number of samples, and with sensors running at about 10 SPS.

You will not get clean 17-bits of resolution out of the BMP085 unless you are averaging a very large number of samples, and this will slow down response time considerably. Many applications don't need the both the extra resolution and response time anyway - one or the other will do. If you're running a weather station you don't need sub-second response time, so you can get very good resolution by running the sensor at 40 Hz and averaging all the samples over a period of a minute or two - this is the setup above, against the professional sensor.

However the test does show that even a very good all-digital sensor is not quite competitive with a solution made of top grade discrete components. No doubt digital sensors will continue to improve - I look forward to the next generation BMP085, especially since this one's been available for a couple years.

AD7190 with Freescale analog sensor - followed by BMP085

/media/uploads/tkreyche/7190.png /media/uploads/tkreyche/085.png

Code Explanation

The code example has a ticker that triggers a pressure conversion every 50 ms (20 Hz) on the BMP085. You can slow this down or can speed it up, but note that the BMP085 is running in the ultra high resolution (highest) oversampling mode to get the best resolution. It can't sample any faster than about 40 Hz unless you lower the oversampling settng.

After the conversion is complete, the BMP085 signals end of conversion which is serviced by an interrupt. This is much more efficient that polling or using a timer, but those solutions may be ok for some apps.

The BMP085 requires a temperature reading for proper pressure compensation. The code below takes a temperature sample at the same rate as the pressure, but this may be unnecessary. For example, in a weather station the temperature won't fluctuate as fast the pressure. I commented out a few lines of code that reduce the interval for temperature sampling, you can uncomment them for better efficiency and change the interval using the tCount variable. The temperature routine also has a fixed wait of 4 ms for the sampling to complete; this could improved - wasting 4 ms isn't great, but it's not a huge penalty.

The pressure data is smoothed slightly with a moving average filter.

I borrowed code posted on the manufacturer's web site for the calibration routines. This is a basic version and doesn't handle all the operating modes.

#include "mbed.h"

#define EE 22
#define BMP085ADDR 0xEF

long b5;
int calcPress(int upp);
int calcTemp(int ut);
short ac1;
short ac2;
short ac3;
unsigned short ac4;
unsigned short ac5;
unsigned short ac6;
short b1;
short b2;
short mb;
short mc;
short md;

#define COEFZ 21
static int k[COEFZ];
static int movAvgIntZ (int input);

I2C i2c(p28, p27);        // sda, scl
InterruptIn dr(p26);

uint32_t drFlag;
void cvt();
void drSub();

Serial pc(USBTX, USBRX); // tx, rx

int main() {

    // let hardware settle
    wait(1);

    /////////////////////////////////////////////////
    // set up timer to trigger pressure conversion
    /////////////////////////////////////////////////
    Ticker convert;
    convert.attach_us(&cvt, 50000); // 50 ms, 20 Hz

    /////////////////////////////////////////////////
    // set up data ready interrupts
    /////////////////////////////////////////////////
    // set up interrupts
    __disable_irq();
    // ADC data ready
    drFlag = 0;
    dr.mode(PullDown);
    dr.rise(&drSub);

    /////////////////////////////////////////////////
    // set up i2c
    /////////////////////////////////////////////////
    char addr = BMP085ADDR; // define the I2C Address
    char rReg[3] = {0,0,0};
    char wReg[2] = {0,0};
    char cmd = 0x00;

    /////////////////////////////////////////////////
    // get EEPROM calibration parameters
    /////////////////////////////////////////////////

    char data[EE];
    cmd = 0xAA;

    for (int i = 0; i < EE; i++) {
        i2c.write(addr, &cmd, 1);
        i2c.read(addr,rReg,1);
        data[i] =  rReg[0];
        cmd += 1;
        wait_ms(10);
    }

    // parameters AC1-AC6
    ac1 =  (data[0] <<8) | data[1];
    ac2 =  (data[2] <<8) | data[3];
    ac3 =  (data[4] <<8) | data[5];
    ac4 =  (data[6] <<8) | data[7];
    ac5 =  (data[8] <<8) | data[9];
    ac6 = (data[10] <<8) | data[11];
    // parameters B1,B2
    b1 =  (data[12] <<8) | data[13];
    b2 =  (data[14] <<8) | data[15];
    // parameters MB,MC,MD
    mb =  (data[16] <<8) | data[17];
    mc =  (data[18] <<8) | data[19];
    md =  (data[20] <<8) | data[21];


    //int tCounter = 200;
    //int pCounter = 0;
    int temp = 0;

    // ready to start sampling loop
    __enable_irq();

    /////////////////////////////////////////////////
    // main
    /////////////////////////////////////////////////

    while (1) {

        if (drFlag == 1) {

            /////////////////////////////////////////////////
            // uncompensated pressure
            /////////////////////////////////////////////////
            cmd = 0xF6;
            i2c.write(addr, &cmd, 1);
            i2c.read(addr,rReg,3);
            int up = ((rReg[0] << 16) | (rReg[1] << 8) | rReg[2]) >> 5;

            /////////////////////////////////////////////////
            // temperature
            // only do this every 10 sec or so
            /////////////////////////////////////////////////

            //if (tCounter == 200) {
                wReg[0] = 0xF4;
                wReg[1] = 0x2E;
                i2c.write(addr, wReg, 2);
                wait_ms(4);

                // uncompensated temperature
                cmd = 0xF6;
                i2c.write(addr, &cmd, 1);
                i2c.read(addr,rReg,2);
                int ut = (rReg[0] << 8) | rReg[1];

                // compensated temperature
                temp = calcTemp(ut);

                //tCounter = 0;
            //}
            /////////////////////////////////////////////////
            // compensated pressure
            /////////////////////////////////////////////////

            int press = calcPress(up);
            int pressZ = movAvgIntZ(press);
            //if (pCounter == 20) {
                pc.printf("%d\t%d\n",pressZ,temp);
                //pCounter = 0;
           // }

            /////////////////////////////////////////////////
            // data ready cleanup tasks
            /////////////////////////////////////////////////

            // reset data ready flag
            drFlag = 0;
            //tCounter++;
            //pCounter++;

        }
    }
}

////////////////////////////////////////////////////////////////////////////////////
// start pressure conversion
////////////////////////////////////////////////////////////////////////////////////

void cvt() {

    char w[2] = {0xF4, 0xF4};
    i2c.write(BMP085ADDR, w, 2);
}

////////////////////////////////////////////////////////////////////////////////////
// Handle data ready interrupt, just sets data ready flag
////////////////////////////////////////////////////////////////////////////////////

void drSub() {

    drFlag = 1;
}

/////////////////////////////////////////////////
// calculate compensated pressure
/////////////////////////////////////////////////

int calcPress(int upp) {

    long pressure,x1,x2,x3,b3,b6;
    unsigned long b4, b7;
    int oversampling_setting = 3;

    unsigned long up = (unsigned long)upp;

    b6 = b5 - 4000;
    // calculate B3
    x1 = (b6*b6) >> 12;
    x1 *= b2;
    x1 >>=11;

    x2 = (ac2*b6);
    x2 >>=11;

    x3 = x1 +x2;

    b3 = (((((long)ac1 )*4 + x3) <<oversampling_setting) + 2) >> 2;

    // calculate B4
    x1 = (ac3* b6) >> 13;
    x2 = (b1 * ((b6*b6) >> 12) ) >> 16;
    x3 = ((x1 + x2) + 2) >> 2;
    b4 = (ac4 * (unsigned long) (x3 + 32768)) >> 15;

    b7 = ((unsigned long)(up - b3) * (50000>>oversampling_setting));
    if (b7 < 0x80000000) {
        pressure = (b7 << 1) / b4;
    } else {
        pressure = (b7 / b4) << 1;
    }

    x1 = pressure >> 8;
    x1 *= x1;
    x1 = (x1 * 3038) >> 16;
    x2 = (pressure * -7357) >> 16;
    pressure += (x1 + x2 + 3791) >> 4;	// pressure in Pa

    return (pressure);
}

/////////////////////////////////////////////////
// calculate compensated temp from uncompensated
/////////////////////////////////////////////////
int calcTemp(int ut) {

    int temp;
    long x1,x2;

    x1 = (((long) ut - (long) ac6) * (long) ac5) >> 15;
    x2 = ((long) mc << 11) / (x1 + md);
    b5 = x1 + x2;
    temp = ((b5 + 8) >> 4);  // temperature in 0.1°C

    return (temp);
}

////////////////////////////////////////////////////////////////////////////////////
// int version of moving average filter
////////////////////////////////////////////////////////////////////////////////////

static int movAvgIntZ (int input) {

    int cum = 0;

    for (int i = 0; i < COEFZ; i++) {
        k[i] = k[i+1];
    }

    k[COEFZ - 1] = input;

    for (int i = 0; i < COEFZ; i++) {
        cum += k[i];
    }

    return ( cum / COEFZ ) ;
}


Report

3 comments on BMP085 Pressure Sensor:

14 Jul 2012

Do your firmware have bug? I load your ex to my complier and edit only printf like this /media/uploads/udomsak/program.jpg but the result are /media/uploads/udomsak/monitor.jpg no temperture data and pressure wrong! I connect SDA to mbed pin 28 and SCL to pin 27 EOC to pin 26 respectively I use hyperterminal to monitor the data from sensor using baudrate 9600 bps

but I try okini's Lib OK! show 1008.88hPa and 30.60C

thanks

16 Jul 2012

Dear Udomsak,

There are a lot of variables when connecting an external device, so it can take some work to track down the source of the problem. Since I only bought and interfaced one of these devices it's difficult for me to assess the nature of the problem.

If I was doing this troubleshooting, the first thing I would do is to see if the eeprom values are being returned correctly - the data sheet lists typical values, so it's easy to see if they are in the correct range. And this will confirm that the I2C connection is working correctly.

After that, I would print out the raw pressure and temperature values and see if they looked correct. This will narrow down whether the problem is in the conversion routine.

Of course the pull up resistors must be correct. There may be some timing issues on the I2C bus - it is helpful if you have an I2C bus analyzer but many people do not.

If the other library is working correctly of course you should use that - this project was intended to evaluate the sensor performance, not create a public library.

Thanks, Tom

16 Dec 2012

Thanks for your useful advices, Udomsak.

Please login to post comments.