inaccurate BMP085

Hi to all.

writing form Italy, I'd like to know if anyone had problems interfacing the BMP085 pressure sensor (concerning accuracy).
I bought a couple of pieces thinking to draw a new project involving them, but... I wasn't able to obtain acceptable results :~
I downloaded a sketch from the net, after have tested it I developed one by myself, using the same approach suggested by Bosch (1-read the internal Eprom calibrating data, 2-read pressure and temperature from I2C, 3- apply the correction alghoritms) but... the obtained results are always the same.
I get accurate temperature readings, almost "acceptable" altitude readings but very innacurate pressure: ...some 50 mB (5000 Pa) less than expected :astonished:
Any suggestion? any experience in calibration informations tweaking?
It will be an eadhache trying to understand the meaning of each data and the possible further corrections to be applied :fearful:

thanks!
Roberto
ps. tried the BMP085 on an Arduino Uno and a couple of Mega2560s (R3 all of them).

It makes no sense that the calculated altitude can be 'almost acceptable' and the pressure be off by 50 mB. That's the difference between 110.8 meters of altitude (1000 mB) and 540.1 meters of altitude (950 mB). I don't expect 400 meters to be "almost acceptable" in altitude error.

Perhaps you are losing precision in your calculations. Please show your calculations. Perhaps you should display any intermediate results.

Are you using a breakout board? Which one?

My BMP085 is very accurate.
I use the Adafruit code.

The adafruit code uses BMP085_ULTRAHIGHRES as default, so I used that. I get even more accuracy by averaging 10 samples.

Comparing my values with other weater sites showed a difference. I had to correct my values for the altitude (a few meters above sea level), and now they are very precise.

I've been running a barometer with BMP085 and the readings are exactly the same as the actual reports from local airport. The issue is you must know your altitude (or, better, the altitude of your barometer) over the sea level with +/- 1 meter precision (as the meteo pressure is always adjusted to the sea level, BMP085 gives you an absolute pressure).

BTW, I'm using adafruit library. Your pressure "p0" (p0 - standard pressure at sea level) reading shall be (THIS FUNCTION IS NOT PART OF ADAFRUIT'S LIBRARY, pls do add it into your library):

// PITO 2/2012, p0 - standard pressure at sea level calculation
float Adafruit_BMP085::read_p0(float myAltitude, float ABSpressure) {
  float p0;
  p0 = ABSpressure / pow((1.0 - ( myAltitude / 44330.0 )), 5.255);  
  return p0;
}

Hi to all, thank you very much for your immediate answers!

here is the C code "reorganized" and commented by me starting from the common C example found on Internet by Jim Lindblom - SparkFun Electronics
I've spent some hours yesterday studying it:

#include <Wire.h>

#define BMP085 0x77         // **********->  I2C address of the sensor

const unsigned char OSS=2;  // 4 modes available: from 0 (lowest precision and power) 
                            // to 3 (highest precision, slowest operation and highest power) 

// calibration values, required for the calculation of the correction after have got the internal
// custom calibration coefficients stored in PROM
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;
long b5; 


float Corrected_Temp;
float Corrected_Press;
float Calculated_Alt;



void setup()
{
  Serial.begin(115200);
  Wire.begin();
  bmp085_Read_Calibration_Data(); // it is possible to send all the read data on the serial port 
}

void loop()
{
  unsigned long read_pressure;
  
  Corrected_Temp = bmp085_Temp_Correction(bmp085_Read_Temp());
// some parameters, calculated during the temperature correction, are needed by the pressure correction, 
// moreover, the temperature itself (in tenths of degrees) is needed for the same reason,
// so the temperature correction must be called BEFORE the pressure correction and it has to be divided
// by 10 AFTER that task
// the pressure is returned in Pascal, so it has to be divided by 100 in order to obtain millibars.
  
  read_pressure = bmp085_Read_Press();
  Serial.print("read Pressure : ");
  Serial.println(read_pressure) ;
  Serial.println();
  Corrected_Press = bmp085_Press_Correction(read_pressure);  // the obained data are fundamentally different depending upon the usage
                                                             // of the 4 OSS modes, it happens due the fact that the data contains OSS itself
                                                             // of course it is correctly managed in the correction pressure function (**1) 
  
  Calculated_Alt = (float)44330 * (1 - pow(((float) Corrected_Press/101325), 0.190295)); // 101325 pressure at sea level in Pascal

  Serial.print("Temperature in Celsius: ");
  Serial.println(Corrected_Temp/10, 2) ;
  Serial.print("Pressure in milliBar: ");
  Serial.println(Corrected_Press/100, 2);
  Serial.print("Altitude in meters: ");
  Serial.println(Calculated_Alt, 2);
  Serial.println();
  
  delay(3000);
}



//*************       TEMPERATURE CORRECTION
// the returned (short) is in thents of celsius degrees
short bmp085_Temp_Correction(unsigned int ReadTemp)
{
  short CorrectedTemp=0;
  long x1, x2;
  
  x1=(((long)ReadTemp-(long)ac6)*(long)ac5) >> 15;
  x2=((long)mc<<11)/(x1 + md);
  b5=x1 + x2;
  CorrectedTemp=((b5 + 8)>>4);
  return CorrectedTemp;
}
// it seems to work properly, the returned data is somewhat precise and reliable 


//************      PRESSURE CORRECTION
//
// the returned (long) is in Pascal
long bmp085_Press_Correction(unsigned long up)
{
  long x1, x2, x3, b3, b6, p;
  unsigned long b4, b7;
  
  b6=b5-4000;
  x1=(b2*(b6*b6)>>12)>>11;
  x2=(ac2*b6)>>11;
  x3=x1+x2;
  b3=(((ac1*4 + x3)<<OSS)+2)>>2;  // (**1) 
  x1=(ac3*b6)>>13;
  x2=(b1*((b6*b6)>>12))>>16;
  x3=((x1+x2)+2)>>2;
  b4=(ac4*(x3 + 32768))>>15;
    b7=(up-b3)*(50000>>OSS);      // (**1) 
  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;
}
 





/***************************************************************************************
**  ******************************     I2C     *****************************************
**                   I2C protocol, send and receive functions
**/



// 1 byte read at "address" 
char bmp085Read(unsigned char address)
{
  Wire.beginTransmission(BMP085);
  Wire.write(address);
  Wire.endTransmission();
  Wire.requestFrom(BMP085,1);
  while(!Wire.available());
  
  return Wire.read();
}



// 2 bytes read starting at "address"
int bmp085_Read_Data(unsigned char address)
{
  unsigned char msb, lsb;
  
  Wire.beginTransmission(BMP085);
  Wire.write(address);
  Wire.endTransmission();
  Wire.requestFrom(BMP085, 2);
  while(Wire.available()<2);
  msb = Wire.read();
  lsb = Wire.read();
  
  return (int) msb<<8|lsb;
}



//*************   READING OF THE INTERNAL CALIBRATION DATA
// this function has to be instantiated just once, all the read data are then required by the
// cyclic temp and pressure correction
void bmp085_Read_Calibration_Data()
{
  // serial port is already opened when this function is called, so it is possible to write on it
  ac1=bmp085_Read_Data(0xAA);
  ac2=bmp085_Read_Data(0xAC);
  ac3=bmp085_Read_Data(0xAE);
  ac4=bmp085_Read_Data(0xB0);
  ac5=bmp085_Read_Data(0xB2);
  ac6=bmp085_Read_Data(0xB4);
  b1 =bmp085_Read_Data(0xB6);
  b2 =bmp085_Read_Data(0xB8);
  mb =bmp085_Read_Data(0xBA);
  mc =bmp085_Read_Data(0xBC);
  md =bmp085_Read_Data(0xBE);
  Serial.print("ac1: ");
  Serial.println(ac1, DEC) ;
  Serial.print("ac2: ");
  Serial.println(ac2, DEC) ;
  Serial.print("ac3: ");
  Serial.println(ac3, DEC) ;
  Serial.print("ac4: ");
  Serial.println(ac4, DEC) ;
  Serial.print("ac5: ");
  Serial.println(ac5, DEC) ;
  Serial.print("ac6: ");
  Serial.println(ac6, DEC) ;
  Serial.print("b1: ");
  Serial.println(b1, DEC) ;
  Serial.print("b2: ");
  Serial.println(b2, DEC) ;
  Serial.print("mb: ");
  Serial.println(mb, DEC) ;  
  Serial.print("mc: ");
  Serial.println(mc, DEC) ;
  Serial.print("md: ");
  Serial.println(md, DEC) ;
}



// Temperature read (raw, to be corrected) 
unsigned int bmp085_Read_Temp()
{
  unsigned int ut;
  Wire.beginTransmission(BMP085);
  Wire.write(0xF4);  // command->READ
  Wire.write(0x2E);  // specify->TEMPERATURE
  Wire.endTransmission();
  
  delay(10);  // at least 5 ms...  better to wait 10 ms.
  // the delay time is independent by the chosen OSS (oversampling mode) as the temperature is read ALWAYS in one sample mode
  
  return bmp085_Read_Data(0xF6); // Read two bytes (UNSIGNED INT) starting at 0xF6
}



// pressure read (raw, to be corrected) 
unsigned long bmp085_Read_Press()
{
  unsigned char msb,lsb,xlsb;
  unsigned long up = 0;
  
  Wire.beginTransmission(BMP085);
  Wire.write(0xF4);            // command->READ
  Wire.write(0x34 +(OSS<<6));  // specify ->PRESSURE ("pressure" + the selected oversampling mode (3) = 4 samples, high precision)
  Wire.endTransmission();
  
  delay(2+(3<<OSS));           // delay time dependent on OSS, according with the datasheet
  
  Wire.beginTransmission(BMP085);  
  Wire.write(0xF6);               // *********************************************>>>   ...WHY ?!?!?!?  
  Wire.endTransmission();       
  
  Wire.requestFrom(BMP085, 3);  // Read register 0xF6 (MSB), 0xF7 (LSB), and 0xF8 (XLSB)
  while(Wire.available() < 3);  // Wait for data to become available (bmp085 has to do the required number of samples)
  msb = Wire.read();
  lsb = Wire.read();
  xlsb = Wire.read();
  
  return (((unsigned long) msb << 16) | ((unsigned long) lsb << 8) | (unsigned long) xlsb) >> (8-OSS);  // read 3 bytes (UNSIGNED LONG)

the first thing that appears is that the "compensated pressure" given by the sensor is NOT corrected any further
considering the REAL altitude that is normally known by all of us (my altitude is some 445 meters above the sea level, for example)

of course this approach is more precise and accurate:

p0 = ABSpressure / pow((1.0 - ( myAltitude / 44330.0 )), 5.255);

in any case I tryed several times to switch between all the 4 OSS modes: the final given results are almost identical in all the modes.
The sensor itself seems to be very accurate and reliable.

in any case the problem seems to rely on the calculation of the pressure.
in fact that reading is given BEFORE to calculate the altitude... it seems that
the "calculated" altitude corrects it... it should be the reason why the pressure is clearly wrong
but the given altitude calculated is almost perfect.

I did not try and study the C++ library you're suggesting. I'll do it immediately after have sent this post.

these are my readings:

and these are the formulas provided by Bosch and clearly written on their original C library (http://ros.org/rosdoclite/electric/api/bmp085/html/files.html) :

...according to the meteo sites it should be some 1017 millibar @ 30.7 C° right now.

If you put ie. 966.2 ABSpressure into the equation I gave you, together with your 445m altitude over see level, you get 1018hPa pressure.
So what is your issue then?
PS: Btw, GPS altitude might not be the same as the over the see level altitude..

p0 = 966.2 / pow((1.0 - ( 445 / 44330.0 )), 5.255) = 1018.8 [hPa];

Again, BMP085 pressure from adafruit gives you absolute pressure, that is NOT the barometric pressure the meteo forecast shows. You have to recalculate it by the above equation into the p0.

:astonished: :~ :disappointed_relieved:

this is the reading using the ADAFRUIT sketch...

Very probably my sensor is not giving correct results. AFAIK it should ave printed on top "Bosch" but...there is nothing...
in any case... WHY the calculated altitude is almost perfect?!? :blush:

ok Pito... you're right. I did not read your first post understanding immediately, before.
I tought that the formula was already applied in the Adafruit program but... of course I did not set the appropriate altitude!

...perfect! thank you all! ...of course the first C program was correct too!

Adafruit sketch does not calculate barometric pressure "p0", I do not see in the adafruit library the function for the standard barometric pressure at the see level calculation.

The function I gave you is NOT in the Adafruit's library.. Again, read carefully what I wrote above..

You MUST put your "Pressure = 96504" (that is the ABSpressure in my formula) and your altitude into the formula I gave you, and you MUST calculate p0. THAT IS THE METEO FORECAST PRESSURE.

PS: and what is your altitude actually?? 421.77m or 445m ??

If you DO NOT know your real altitude, then slowly change YOUR ALTITUDE IN MY FORMULA, until you get exactly the p0 pressure as you may see now on the web pages of your LOCAL meteo station (not from yesterday or tomorrow, but NOW)..
Have fun :slight_smile:

I noticed you saying 'millibar'.

The atmospheric pressure or baromic value is the absolute pressure. Normally measured at sea level. Mostly measured in Pa (Pascal).
I have the BMP085 value on a webpage, and I use hPa (hecto Pascal).

Millibar or bar is mostly used for relative pressure, the pressure with regard to the air around us. Like the pressure in a car tire. Even the pressure in a diving cylinder is actually a relative pressure.

Luckely a 'hPa' is the same as 'mbar', but I like to keep them seperate.

thank you very much for all the explanations.
I was just looking to obtain correct calculations, in millibar or hPa. I used millibar just because the forecast/meteo site use millibar, but it is just Pa/100, nothing really complicated.

I obtained my altitude looking on MapTool 2, unfortunately my GPS does not give this information.

I'm tweaking the prog a little bit right now, the sensor is giving incredibly precise and reliable results... at least confrontating them with the info obtained on the meteo site.

I'm putting togheter some sensors, creating a "standardized" protocol that will work sending a data stream on the serial port or on another subsystem (that I created) equipped by a 5 inches LCD driven by SSD1963 (and a card reader). All the system is working using a wireless net (...and I2C in some cases not only for the sensors).
Of course I 'm writing all the programs by myself and all the system is developed on specific hardware, entirely drawn by me, equipped by an Atmel AVR processor (slower than the 2560 but working at 3.3V and with a lower consumption). In order to do so I'm programming the CPU by a programmer, without to install a bootloader.
I'm using Arduino because it allows the immediate creation of hardware and firmware prototypes, in this case I'm using it just in order to test sensors and to create the appropriate protocol.
After the first approach made by Arduino I always rewrite everything using AVRStudio (or Proteus/winAVR it depends upon the processor model), they allow the complete simulation of the CPU and the realtime debug of the firmware too.

I moved from AVR coding to Arduino, and not planning to go back.
So you have to rewrite all the libraries ?
You can upload a sketch with a programming, avoiding the bootloader.

When you want the baromic pressure like every 5 minutes, you might consider using the average of a few samples. It gave me even more accuracy.

...yes... more or less I've to rewrite the most part of libraries. For this kind of sensors for sure, but this is the easiest part of the development.
the I2C approach is really easy, other developments are much more complicated.
Yes... I was thinking to use some initial taken samples in order to reach the point in which the system is able to calculate an exponential smooth { Kt-1 + (1-K)St-1 } ... the immediate forecast (some minutes) is really reliable.
Moreover: if you think at those samples like a function of time, it is possible to calculate a sort of pseudo-derivative that, in real world, works very similar to a real function derivative, notwithstanding the given data are not bound by any function each other (... at least not by a deterministic function, AFAIK).
And if you calculate that... calculating something that happears like a real function derivative of the second order is at least easy like the first step.
The statistic formula for calculate that is f' = Expmooth(1°...1n)- last data retrieved.
and, of course, the f' calculated - last data (again) gives something very similar to the second order derivative, considering functions.
...so you may calculate how fast the changment is taking place and... if that velocity of changment is increasing (acceleration), decreasing, if it reaches zero or if the pressure is starting to decrease, how much it will decrease in the same interval, if it is building up momentum or not and so on... without to think in terms of neural network, in order to provide forecasts.
It is possible to reach the point in which you consider those data like a smoothed continuous function too but, of course, in this case it is needed to solve a second order differential.

Concerning the arduino world, I'm developing some real custom subsytems. Arduino is really nice whenever you need to immediately try or tweak sensors, switches, mechanisms and firmware.
After that you have to develop everything by yourself, starting from the electronic circuit drawing, ending with the needed firmware, of course.

Erdin and Pito...

there are two small "problems".

I'm writing down a complete personalized and different library, right now, (thank you all again! :slight_smile: ) but...
the Adafruit job is not optimal, instantiating the RawPressure function three times instead of instantiate it only once for each measure taken, and the SparkFun has a small bug on the compensation function that gives wrong final pressure information (of some 4 mB).

The Adafuit library instantiates the function readRawPressure each time ReadPressure is called, and it means that it is called 3 times instead of one, the first for the pressure calculation, the second and the third for the altitude calculation. it is not needed at all, just think that each of them take 8 samples... moreover, the pressure taken for the three measures are not the same (of course). The difference is minimal, but... it is not correct, talking in terms of optimization, in any case.

To solve that problem it is just needed to instantiate the object just once, the prototypes of all of them (in the .h file) have to be changed, and their calls too:

the appropriate section of the .h file has to be changed in:


class Adafruit_BMP085 {
 public:
  Adafruit_BMP085();
  boolean begin(uint8_t mode = BMP085_ULTRAHIGHRES);  // by default go highres
  float readTemperature(uint16_t);
  int32_t readPressure(float, uint32_t);
  float readAltitude(uint32_t, float sealevelPressure = 101325); // std atmosphere
  uint16_t readRawTemperature(void);
  uint32_t readRawPressure(void);

...CUT

the "main" has to be changed in:


Adafruit_BMP085 bmp;
  
  
int MyAltitude = 445;  
float Temperature =0;
uint32_t Rpressure=0;
uint16_t RTemperature=0;
int32_t CPressure=0;
float p0=0;
  
void setup() {
  Serial.begin(115200);
  if (!bmp.begin()) {
    Serial.println("Could not find a valid BMP085 sensor, check wiring!");
    while (1) {}
  }
}
  
void loop() {
    Serial.println("C++ version, Adafruit");
    Serial.print("Temperature = ");
    RTemperature = bmp.readRawTemperature();    
    Serial.print(bmp.readTemperature(RTemperature));
    Serial.println(" *C");
    Rpressure=bmp.readRawPressure();
    CPressure = bmp.readPressure(RTemperature, Rpressure); 
    p0 = CPressure/pow((1.0 - ( MyAltitude/44330.0 )), 5.255);   
    Serial.print("Pressure = ");
    Serial.print(p0/100);

//    Serial.print(bmp.readPressure());
    Serial.println(" mB");
    
    // Calculate altitude assuming 'standard' barometric
    // pressure of 1013.25 millibar = 101325 Pascal
    Serial.print("Altitude = ");
    Serial.print(bmp.readAltitude(CPressure));
    Serial.println(" meters");

  // you can get a more precise measurement of altitude
  // if you know the current sea level pressure which will
  // vary with weather and such. If it is 1015 millibars
  // that is equal to 101500 Pascals.
    Serial.print("Real altitude = ");
    Serial.print(bmp.readAltitude(CPressure,101500));
    Serial.println(" meters");

...CUT

and of course the header of each function has to be changed in the .cpp file, deleting the further
declaration of each variable passed to the function, in order to avoid the usual "declaration of 'XXXXXX' shadows a parameter" error.


// readPressure  
int32_t Adafruit_BMP085::readPressure(float UT, uint32_t UP) {
	
  int32_t  B3, B5, B6, X1, X2, X3, p;
  uint32_t B4, B7;
  
//  UT = readRawTemperature();
//  UP = readRawPressure();

...CUT
****************************************************************************
// readTemperature     
float Adafruit_BMP085::readTemperature(uint16_t UT) {
	
  int32_t X1, X2, B5;     // following ds convention
  float temp;

...CUT
****************************************************************************

float Adafruit_BMP085::readAltitude(uint32_t RPressure, float sealevelPressure) {
	
  float altitude;

  altitude = 44330 * (1.0 - pow(RPressure /sealevelPressure,0.1903));

  return altitude;

}
****************************************************************************

I noticed that, and I already changed it for myself to make just one call XD

I think that the Adafruit libraries are better maintained and are better than most of the Sparkfun examples. So you confirm that once more.

When you paste a sketch in the text, you can paste it between [code] ... [/code] tags.
You might modify your previous posts, it just looks better.

thank u very much for your suggestion.

great forum populated by good members! :slight_smile:

Good info above. Will read later. Ran into this thread looking for code and reasoning why my temp was so far off. 118 C in the room here. :slight_smile: Realized looking at the code the constants read from the device with SPE demo code wasn't negative for some of them.

On a Due (32 bit ARM) the signed and unsigned 16bit is a short. It appears to be a int under the AVR. I had to change the types for the constants read in from unsigned int/int to unsigned short/short. Otherwise the constants that should be negative won’t be negative. The temp and pressure looked reasonable after this.