Go Down

Topic: Using 2 BMP085 on I2C (Read 2927 times) previous topic - next topic

Thames

I am trying to connect two bmp085 pressure sensors on a i2c bus. I've attached the XCLR lines of each to digital out pins so that I can silence one sensor by setting its XCLR to low, while the other sensor's XCLR is high, in order to alternate reading each sensor.   

The problem is that when I do this my sensors give readings of temperature and pressure that are way too high.  From prodding at this on my own for a fair amount of time I've found out this:
Either sensor works fine when used alone and the XCLR is left floating,
If both sensors are hooked up but no switching is done between them so that one is always silent and the other is always reading, the temperature is correct but the pressure is too high.
If both sensors are hooked up and readings switch between the two, both temperature and pressure are too high.

My code came from the BMP085 setup help on Sparkfun, modified to do switching.
Code: [Select]
#include <Wire.h>

#define BMP085_ADDRESS 0x77  // I2C address of BMP085

const unsigned char OSS = 0;  // Oversampling Setting
const int left=10;            //When high, turns on the left sensor
const int right=11;           //When high, turns on the right sensor
int count=0;


// Calibration values
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;

// b5 is calculated in bmp085GetTemperature(...), this variable is also used in bmp085GetPressure(...)
// so ...Temperature(...) must be called before ...Pressure(...).
long b5;

short temperature;
long pressure;

void setup()
{
  pinMode(left,OUTPUT);
  pinMode(right,OUTPUT);
  digitalWrite(left,HIGH);
  digitalWrite(right,HIGH);
  Serial.begin(9600);
  Wire.begin();
  bmp085Calibration();
}

void loop()
{
  count++;              //Alternate between 1 and 2
  if(count==3){
    count=1;
  }
 
  if(count==1){
  digitalWrite(left,HIGH);          //turn on left sensor
  digitalWrite(right,LOW);          //turn off right sensor
  Serial.print("Left:");
}
  else{
  Serial.print("Right:");
  digitalWrite(left,LOW);           //turn off left sensor
  digitalWrite(right,HIGH);         //turn on right sensor
  }

  delay(10);
 
  temperature = bmp085GetTemperature(bmp085ReadUT());
    //delay(100);
  pressure = bmp085GetPressure(bmp085ReadUP());
    //delay(100);
  Serial.print("Temperature: ");
  Serial.print(temperature/10.0, 1);
  Serial.println(" deg C");
  Serial.print("Pressure: ");
  Serial.print(pressure, DEC);
  Serial.println(" Pa");
  Serial.println();
  delay(1000);
}

// Stores all of the bmp085's calibration values into global variables
// Calibration values are required to calculate temp and pressure
// This function should be called at the beginning of the program
void bmp085Calibration()
{
  ac1 = bmp085ReadInt(0xAA);
  ac2 = bmp085ReadInt(0xAC);
  ac3 = bmp085ReadInt(0xAE);
  ac4 = bmp085ReadInt(0xB0);
  ac5 = bmp085ReadInt(0xB2);
  ac6 = bmp085ReadInt(0xB4);
  b1 = bmp085ReadInt(0xB6);
  b2 = bmp085ReadInt(0xB8);
  mb = bmp085ReadInt(0xBA);
  mc = bmp085ReadInt(0xBC);
  md = bmp085ReadInt(0xBE);
}

// Calculate temperature given ut.
// Value returned will be in units of 0.1 deg C
short bmp085GetTemperature(unsigned int ut)
{
  long x1, x2;
 
  x1 = (((long)ut - (long)ac6)*(long)ac5) >> 15;
  x2 = ((long)mc << 11)/(x1 + md);
  b5 = x1 + x2;

  return ((b5 + 8)>>4); 
}

// Calculate pressure given up
// calibration values must be known
// b5 is also required so bmp085GetTemperature(...) must be called first.
// Value returned will be pressure in units of Pa.
long bmp085GetPressure(unsigned long up)
{
  long x1, x2, x3, b3, b6, p;
  unsigned long b4, b7;
 
  b6 = b5 - 4000;
  // Calculate B3
  x1 = (b2 * (b6 * b6)>>12)>>11;
  x2 = (ac2 * b6)>>11;
  x3 = x1 + x2;
  b3 = (((((long)ac1)*4 + x3)<<OSS) + 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>>OSS));
  if (b7 < 0x80000000)
    p = (b7<<1)/b4;
  else
    p = (b7/b4)<<1;
   
  x1 = (p>>8) * (p>>8);
  x1 = (x1 * 3038)>>16;
  x2 = (-7357 * p)>>16;
  p += (x1 + x2 + 3791)>>4;
 
  return p;
}

// Read 1 byte from the BMP085 at 'address'
char bmp085Read(unsigned char address)
{
  unsigned char data;
 
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();
 
  Wire.requestFrom(BMP085_ADDRESS, 1);
  while(!Wire.available())
    ;
   
  return Wire.read();
}

// Read 2 bytes from the BMP085
// First byte will be from 'address'
// Second byte will be from 'address'+1
int bmp085ReadInt(unsigned char address)
{
  unsigned char msb, lsb;
 
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();
 
  Wire.requestFrom(BMP085_ADDRESS, 2);
  while(Wire.available()<2){
  Serial.print(Wire.available());
  delay(100);}
  msb = Wire.read();
  lsb = Wire.read();
 
  return (int) msb<<8 | lsb;
}

// Read the uncompensated temperature value
unsigned int bmp085ReadUT()
{
  unsigned int ut;
 
  // Write 0x2E into Register 0xF4
  // This requests a temperature reading
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x2E);
  Wire.endTransmission();
 
  // Wait at least 4.5ms
  delay(5);
 
  // Read two bytes from registers 0xF6 and 0xF7
  ut = bmp085ReadInt(0xF6);
  return ut;
}

// Read the uncompensated pressure value
unsigned long bmp085ReadUP()
{
  unsigned char msb, lsb, xlsb;
  unsigned long up = 0;
 
  // Write 0x34+(OSS<<6) into register 0xF4
  // Request a pressure reading w/ oversampling setting
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x34 + (OSS<<6));
  Wire.endTransmission();
 
  // Wait for conversion, delay time dependent on OSS
  delay(2 + (3<<OSS));
 
  // Read register 0xF6 (MSB), 0xF7 (LSB), and 0xF8 (XLSB)
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF6);
  Wire.endTransmission();
  Wire.requestFrom(BMP085_ADDRESS, 3);
 
  // Wait for data to become available
  while(Wire.available() < 3)
    ;
  msb = Wire.read();
  lsb = Wire.read();
  xlsb = Wire.read();
 
  up = (((unsigned long) msb << 16) | ((unsigned long) lsb << 8) | (unsigned long) xlsb) >> (8-OSS);
 
  return up;
}



Keep in mind, I'm pretty new to Arduino.  Does anyone know what might be my problem?

Krodal

Did you read about someone who managed to do this?

The XCLR reset the chip and registers, but perhaps the chip will still react to it's i2c address.
The functions don't check for the error of Wire.endTransmission, so you will never know if an error occurred.

Thames

The data sheet for the BMP085 specifically mentions the ability to use two sensors on the same I2C bus using XCLR.  I've also found a few other mentions of this method on this site, but none with my specific problem.

Here is the link to the datasheet, the section about using 2 sensors is on pg 17.
http://dlnmh9ip6v2uc.cloudfront.net/datasheets/Sensors/Pressure/BST-BMP085-DS000-06.pdf

Krodal

#3
Jul 03, 2012, 08:50 pm Last Edit: Jul 03, 2012, 09:06 pm by Krodal Reason: 1

The data sheet for the BMP085 specifically mentions the ability to use two sensors on the same I2C bus using XCLR

Thank you. I didn't know that.

Can you add to test the return value of the Wire.endTransmission in your code.

In setup() it seems that both sensors are enabled if bmp085Calibration() is called. But only one can be calibrated.
So you have to calibrate one, and after that the other.
You have calibration values, but for which one ? Should each sensor have its own calibration values ?

You use the sparkfun code : http://www.sparkfun.com/tutorials/253
But that's very simple code for just one sensor.

By using a class, you could declare a class for each sensor. A driver with a class could be this one: http://code.google.com/p/bmp085driver/
You still have to switch the XCLR signals between the two classes.
I have checked that code, and I really like it.
Normally it would something like this:
Code: [Select]

BMP085 dps = BMP085();

void setup(void)
{
  Wire.begin();
  dps.init();   
  ...
}

void loop(void)
{
  dps.getTemperature(&Temperature);
  ...
}


And to make it for two sensors:
Code: [Select]

#define SELECT_RIGHT digitalWrite(left,LOW); digitalWrite(right,HIGH)
#define SELECT_LEFT digitalWrite(right,LOW); digitalWrite(left,HIGH)

BMP085 dps_right = BMP085();
BMP085 dps_left = BMP085();

void setup(void)
{
  ...
  Wire.begin();
  SELECT_RIGHT;
  dps_right.init();
  SELECT_LEFT;
  dps_left.init();   
  ...
}

void loop(void)
{
  ...
  SELECT_RIGHT;
  dps_right.getTemperature(&Temperature);
  SELECT_LEFT;
  dps_left.getTemperature(&Temperature);
  ...
}



Thames

From looking at this library it seems that if you turn off the right sensor in order to calibrate the left one, the calibration values of the left sensor overwrite the right sensor's values.  So instead of being able to call dps.init() once, you would have to call it immediately before taking a reading from each sensor every single time.  While that would work, it would also slow down the code considerably, and greatly reduce the number of readings I can get per second.

Krodal

I'm not sure what you mean, but all calibration values are stored in the class (see BMP085.h, the private variables). If you create two classes, they will never overwrite each other. You can use any of the two classes at any time.

Thames

Thanks for your help Krodal, but unfortunately I had the same problem with the new code that I had with the old.  Fortunately, I figured out what was wrong.

In case anyone else has this problem, I think I've found the solution.  Using the calibration values from one sensor to calculate the values for both sensors was one of my problems, so I modified the Sparkfun code to cycle through the sensors and store each variable in an array.
Code: [Select]
#include <Wire.h>

#define BMP085_ADDRESS 0x77  // I2C address of BMP085


int count=0;

const unsigned char OSS = 0;  // Oversampling Setting

// Calibration values
const int numBMP085=3;      //Number of BMP085 sensors being used, used to make array sizes
int BMPpin[numBMP085];

int ac1[numBMP085];
int ac2[numBMP085];
int ac3[numBMP085];
unsigned int ac4[numBMP085];
unsigned int ac5[numBMP085];
unsigned int ac6[numBMP085];
int b1[numBMP085];
int b2[numBMP085];
int mb[numBMP085];
int mc[numBMP085];
int md[numBMP085];

// b5 is calculated in bmp085GetTemperature(...), this variable is also used in bmp085GetPressure(...)
// so ...Temperature(...) must be called before ...Pressure(...).
long b5;

short temperature;
long pressure;

void setup()
{
  Serial.begin(9600);
  Wire.begin();
    delay(1000);
   
for(int i=0;i<numBMP085;i++){

   BMPpin[i]=12-i;                    //Set a pin number for each sensor, first is 13, 2nd is 12, etc
   pinMode(BMPpin[i],OUTPUT);         //Use BMPpin[0] to HIGH to turn on sensor 1, attached to pin 13
   //Serial.println(BMPpin[i]);
}                                     
   
  for(int i=0;i<numBMP085;i++){
     
    Serial.print("Calibrate sensor #");
    Serial.println(i);
   
    useSensor(i);
   

    bmp085Calibration(i);                //Calibrate the sensor for pin i
  }   

    delay(500);



 
}

void loop()
{
 
if(count==numBMP085){
  count=0;
}

  int sensor=count;                      //The sensor being used will be determined by loop count,
                                         //reset to 0 at numBMP085
  useSensor(sensor);
  Serial.print("Sensor #");
  Serial.print(sensor);
  Serial.print(": ");
 
 
  delay(5);
  count++;
 
  temperature = bmp085GetTemperature(bmp085ReadUT(),sensor);
  pressure = bmp085GetPressure(bmp085ReadUP(),sensor);
  Serial.print("Temperature: ");
  Serial.print(temperature/10.,1);
  Serial.print(" deg C");
  Serial.print("\tPressure: ");
  Serial.print(pressure, DEC);
  Serial.println(" Pa");
//  Serial.println();
// delay(1000);
}

// Stores all of the bmp085's calibration values into global variables
// Calibration values are required to calculate temp and pressure
// This function should be called at the beginning of the program
void bmp085Calibration(int sensorNum)
{
  ac1[sensorNum] = bmp085ReadInt(0xAA);
  ac2[sensorNum] = bmp085ReadInt(0xAC);
  ac3[sensorNum] = bmp085ReadInt(0xAE);
  ac4[sensorNum] = bmp085ReadInt(0xB0);
  ac5[sensorNum] = bmp085ReadInt(0xB2);
  ac6[sensorNum] = bmp085ReadInt(0xB4);
  b1[sensorNum] = bmp085ReadInt(0xB6);
  b2[sensorNum] = bmp085ReadInt(0xB8);
  mb[sensorNum] = bmp085ReadInt(0xBA);
  mc[sensorNum] = bmp085ReadInt(0xBC);
  md[sensorNum] = bmp085ReadInt(0xBE);
/*Serial.print("Sensor number ");
  Serial.println(sensorNum);
  Serial.println(ac1[sensorNum]);
  Serial.println(ac2[sensorNum]);
  Serial.println(ac3[sensorNum]);
  Serial.println(ac4[sensorNum]);
  Serial.println(ac5[sensorNum]);
  Serial.println(ac6[sensorNum]);
  Serial.println(b1[sensorNum]);
  Serial.println(b2[sensorNum]);
  Serial.println(mb[sensorNum]);
  Serial.println(mc[sensorNum]);
  Serial.println(md[sensorNum]);*/
}

// Calculate temperature given ut.
// Value returned will be in units of 0.1 deg C
short bmp085GetTemperature(unsigned int ut, int sensor)
{
  long x1, x2;
 
  x1 = (((long)ut - (long)ac6[sensor])*(long)ac5[sensor]) >> 15;
  x2 = ((long)mc[sensor] << 11)/(x1 + md[sensor]);
  b5 = x1 + x2;

  return ((b5 + 8)>>4); 
}

// Calculate pressure given up
// calibration values must be known
// b5 is also required so bmp085GetTemperature(...) must be called first.
// Value returned will be pressure in units of Pa.
long bmp085GetPressure(unsigned long up,int sensor)
{
  long x1, x2, x3, b3, b6, p;
  unsigned long b4, b7;
 
  b6 = b5 - 4000;
  // Calculate B3
  x1 = (b2[sensor] * (b6 * b6)>>12)>>11;
  x2 = (ac2[sensor] * b6)>>11;
  x3 = x1 + x2;
  b3 = (((((long)ac1[sensor])*4 + x3)<<OSS) + 2)>>2;
 
  // Calculate B4
  x1 = (ac3[sensor] * b6)>>13;
  x2 = (b1[sensor] * ((b6 * b6)>>12))>>16;
  x3 = ((x1 + x2) + 2)>>2;
  b4 = (ac4[sensor] * (unsigned long)(x3 + 32768))>>15;
 
  b7 = ((unsigned long)(up - b3) * (50000>>OSS));
  if (b7 < 0x80000000)
    p = (b7<<1)/b4;
  else
    p = (b7/b4)<<1;
   
  x1 = (p>>8) * (p>>8);
  x1 = (x1 * 3038)>>16;
  x2 = (-7357 * p)>>16;
  p += (x1 + x2 + 3791)>>4;
 
  return p;
}

// Read 1 byte from the BMP085 at 'address'
char bmp085Read(unsigned char address)
{
  unsigned char data;
 
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();
 
  Wire.requestFrom(BMP085_ADDRESS, 1);
  while(!Wire.available())
    ;
   
  return Wire.read();
}

// Read 2 bytes from the BMP085
// First byte will be from 'address'
// Second byte will be from 'address'+1
int bmp085ReadInt(unsigned char address)
{
  unsigned char msb, lsb;
 
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();
 
  Wire.requestFrom(BMP085_ADDRESS, 2);
  while(Wire.available()<2){
  Serial.print(Wire.available()); 
  delay(100); }
;
  msb = Wire.read();
  lsb = Wire.read();
 
  return (int) msb<<8 | lsb;
}

// Read the uncompensated temperature value
unsigned int bmp085ReadUT()
{
  unsigned int ut;
 
  // Write 0x2E into Register 0xF4
  // This requests a temperature reading
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x2E);
  Wire.endTransmission();
 
  // Wait at least 4.5ms
  delay(5);
 
  // Read two bytes from registers 0xF6 and 0xF7
  ut = bmp085ReadInt(0xF6);
  return ut;
}

// Read the uncompensated pressure value
unsigned long bmp085ReadUP()
{
  unsigned char msb, lsb, xlsb;
  unsigned long up = 0;
 
  // Write 0x34+(OSS<<6) into register 0xF4
  // Request a pressure reading w/ oversampling setting
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x34 + (OSS<<6));
  Wire.endTransmission();
 
  // Wait for conversion, delay time dependent on OSS
  delay(2 + (3<<OSS));
 
  // Read register 0xF6 (MSB), 0xF7 (LSB), and 0xF8 (XLSB)
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF6);
  Wire.endTransmission();
  Wire.requestFrom(BMP085_ADDRESS, 3);
 
  // Wait for data to become available
  while(Wire.available() < 3)
    ;
  msb = Wire.read();
  lsb = Wire.read();
  xlsb = Wire.read();
 
  up = (((unsigned long) msb << 16) | ((unsigned long) lsb << 8) | (unsigned long) xlsb) >> (8-OSS);
 
  return up;
}

void useSensor(int sensorNum)
{
 

for(int i=0;i<numBMP085;i++){            //Set all pins to low, turn off all sensors
      digitalWrite(BMPpin[i],LOW);
    }
   
digitalWrite(BMPpin[sensorNum],HIGH);    //Set the pin to be calibrated to high
  delay(1);                              //<-- DON'T REMOVE THIS
                                         //I don't know why, but without this the pressures are too high

}


The other issue I had was that the pressures and temperatures for both sensors would read way too high when I tried switching back and forth between the two sensors, because the XCLR pin wasn't reading as ground even when it should.  The pins for SDA and SCL are set to 5V, and the BMP085 says to have all pins at 3.3V.  Dropping these pins down to 3.3V with this logic level converter http://www.sparkfun.com/products/8745 fixed the problem, although you can also turn off the internal pull up resistors to achieve the same effect.  I also used a voltage divider to make the voltage at the XCLR pin also be 3.3V.

Go Up