Arduino & Barometric Pressure Sensor BMP085

December 5, 2009

in Sensors

In lack of any new projects (which are currently all in an very intermediate state) Interactive Matter presents yet another ‘how to connect a cool I2C sensor to Arduino’ post.

This time it is all about pressure. The BMP085 pressure sensor combines a absolute barometric pressure sensor (aka barometer) with an temperature sensor. It is build by Bosch Sensortec and intended to help GPS navigation units to detect their height above sea level. It combines the advantage of being quite cheap (~5$) and precise.

Arduino & Barometric Pressure Sensor BMP085

But of course there are myriads of other applications as well, like using it in a digital weather station, detecting the force of slammed doors or …

The information about this sensor is very sparse and Bosch is not very keen to give any information to makers. But fortunately Jeelabs offers it as an extension module for their Jeenode and published some very helpful code for it (which helped a lot – check out they are doing really amazing stuff).

Hardware

Arduino & Barometric Pressure Sensor BMP085

Hooking up the BMP085 to the Arduino works just like any other I2C part: Connect VCC to VCC and GND to GND, SCL goes to analogue pin 5, SDA to analogue pin4. Adding some pull up resistors (1K to 20K, most often something like 4.7K) between SDA, SCL and VCC finishes the setup (this was included in my breakout board).

The BMP08 accepts 1.8 to 3.6 Volts – so no chance to connect it directly to 5 Volts. The BMP085 has an additional EOC (end of conversion) pin indicating the successful data capture. This was connected to analogue pin 2 – but not used in the software implementation.

Software

Fortunately the code by Jeenode contained all the functionality, you need, taken directly from the datasheet. The only thing I added was the ability to use all oversampling modes (the BMP085 offers 4 oversampling mode, each on taking longer than the other and using more energy, but delivering more precise results).

First we need the ability to read 16 bit values from the BMP085 registers. Nearly all registers of the BMP085 are 16 bit wide:

int read_int_register(unsigned char r)
{
  unsigned char msb, lsb;
  Wire.beginTransmission(I2C_ADDRESS);
  Wire.send(r);  // register to read
  Wire.endTransmission();

  Wire.requestFrom(I2C_ADDRESS, 2); // read a byte
  while(!Wire.available()) {
    // waiting
  }
  msb = Wire.receive();
  while(!Wire.available()) {
    // waiting
  }
  lsb = Wire.receive();
  return (((int)msb<<8) | ((int)lsb));
}

Second you need a function to write a 8 bit register:

char read_register(unsigned char r)
{
  unsigned char v;
  Wire.beginTransmission(I2C_ADDRESS);
  Wire.send(r);  // register to read
  Wire.endTransmission();

  Wire.requestFrom(I2C_ADDRESS, 1); // read a byte
  while(!Wire.available()) {
    // waiting
  }
  v = Wire.receive();
  return v;
}

Now we can define some global variables to read the Eeprom calibration data:

//just taken from the BMP085 datasheet
int ac1;
int ac2;
int ac3;
unsigned int ac4;
unsigned int ac5;
unsigned int ac6;
int b1;
int b2;
int mb;
int mc;
int md;
void  bmp085_get_cal_data() {
  Serial.println("Reading Calibration Data");
  ac1 = read_int_register(0xAA);
  Serial.print("AC1: ");
  Serial.println(ac1,DEC);
  ac2 = read_int_register(0xAC);
  Serial.print("AC2: ");
  Serial.println(ac2,DEC);
  ac3 = read_int_register(0xAE);
  Serial.print("AC3: ");
  Serial.println(ac3,DEC);
  ac4 = read_int_register(0xB0);
  Serial.print("AC4: ");
  Serial.println(ac4,DEC);
  ac5 = read_int_register(0xB2);
  Serial.print("AC5: ");
  Serial.println(ac5,DEC);
  ac6 = read_int_register(0xB4);
  Serial.print("AC6: ");
  Serial.println(ac6,DEC);
  b1 = read_int_register(0xB6);
  Serial.print("B1: ");
  Serial.println(b1,DEC);
  b2 = read_int_register(0xB8);
  Serial.print("B2: ");
  Serial.println(b1,DEC);
  mb = read_int_register(0xBA);
  Serial.print("MB: ");
  Serial.println(mb,DEC);
  mc = read_int_register(0xBC);
  Serial.print("MC: ");
  Serial.println(mc,DEC);
  md = read_int_register(0xBE);
  Serial.print("MD: ");
  Serial.println(md,DEC);
}

The Eeprom readout can be done more efficiently by reading all the values with just one register write. But it is more comprehensible like this and in the initialization phase we have good amount of time.
The raw temperature (ut) and pressure (up) data can be readout as 16 bit and 24 bit value:

unsigned int bmp085_read_ut() {
  write_register(0xf4,0x2e);
  delay(5); //longer than 4.5 ms
  return read_int_register(0xf6);
}
long bmp085_read_up() {
  write_register(0xf4,0x34+(oversampling_setting<<6));
  delay(pressure_waittime[oversampling_setting]);

  unsigned char msb, lsb, xlsb;
  Wire.beginTransmission(I2C_ADDRESS);
  Wire.send(0xf6);  // register to read
  Wire.endTransmission();

  Wire.requestFrom(I2C_ADDRESS, 3); // read a byte
  while(!Wire.available()) {
    // waiting
  }
  msb = Wire.receive();
  while(!Wire.available()) {
    // waiting
  }
  lsb |= Wire.receive();
  while(!Wire.available()) {
    // waiting
  }
  xlsb |= Wire.receive();
  return (((long)msb<<16) | ((long)lsb<<8) | ((long)xlsb)) >>(8-oversampling_setting);
}

The conversion between the raw data and the real temperature data in 0.1 °C an Pascal is taken from the datasheet (combined with the corrections of Jeenodes):

void bmp085_read_temperature_and_pressure(int* temperature, long* pressure) {
  int  ut= bmp085_read_ut();
  long up = bmp085_read_up();
  long x1, x2, x3, b3, b5, b6, p;
  unsigned long b4, b7;

  //calculate the temperature
  x1 = ((long)ut - ac6) * ac5 >> 15;
  x2 = ((long) mc << 11) / (x1 + md);
  b5 = x1 + x2;
  *temperature = (b5 + 8) >> 4;

  //calculate the pressure
  b6 = b5 - 4000;
  x1 = (b2 * (b6 * b6 >> 12)) >> 11;
  x2 = ac2 * b6 >> 11;
  x3 = x1 + x2;
  b3 = (((int32_t) ac1 * 4 + x3)<> 2;
  x1 = ac3 * b6 >> 13;
  x2 = (b1 * (b6 * b6 >> 12)) >> 16;
  x3 = ((x1 + x2) + 2) >> 2;
  b4 = (ac4 * (uint32_t) (x3 + 32768)) >> 15;
  b7 = ((uint32_t) up - b3) * (50000 >> oversampling_setting);
  p = b7 < 0x80000000 ? (b7 * 2) / b4 : (b7 / b4) * 2;

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

}

Unfortunately temperature and pressure had to be computed at the same time (the temperature value is used for the pressure calculation). int* means a pointer to an integer value, which is written like *temperature = (b5 + 8 ) >> 4;. the function is called like

  int  temperature = 0;
  long pressure = 0;

  bmp085_read_temperature_and_pressure(&temperature,&pressure);

It is very simple and gives very good results.
If you want to try it yourself get the BMP085 Arduino Sketch. If there is any interest in the breakout board (assembled or unassembled) just drop me a note.

And big thanks to Jeelabs. Their code helped me a lot they real awesome stuff!

{ 38 comments… read them below or add one }

Leave a Comment

{ 3 trackbacks }