bring up code for the Bosch barometer and thermometer BMP085. This code is forked from http://mbed.org/users/tkreyche/notebook/bmp085-pressure-sensor/ but with a lot more comments and links to references. The extraction of the altitude via 2 methods is also implemented. Overall temperature seems way off (but implementation is correct). Altitude is off too (but implementation is correct).

Dependencies:   mbed

main.cpp

Committer:
Rom
Date:
2012-12-28
Revision:
0:184eb17ff158

File content as of revision 0:184eb17ff158:

#include "mbed.h"

/* sample code for the barometer BMP085 as provided on the breakout board from sparkfun.
Pull up resistors for I2C are already provided (aka already on the board).
Link to sparkfun board: https://www.sparkfun.com/products/11282?

Datasheet of barometer: http://dlnmh9ip6v2uc.cloudfront.net/datasheets/Sensors/Pressure/BST-BMP085-DS000-06.pdf

Comments:
 - pressure values returned by the moving average seems believable (1090 Hecto Pascal on 12/18/2012)
 - Temperature seems to oscillate between 365 and 87 and seems totally wrong.
 - Altitude is off (obviously fluctates with weather) but roughly speaking 
   indicates -800m when altitude should be 0m

Example of output in the terminal:

Temperature: 87 (raw value (uncomp): 61184)
Pressure (Pa): 106626 (raw: 249856) Mov Avg: 62353
altitude is (m): -432.270721
wikipedia altitude (m): -432.061523

Temperature: 365 (raw value (uncomp): 62464)
Pressure (Pa): 109312 (raw: 244736) Mov Avg: 67558
altitude is (m): -644.688416
wikipedia altitude (m): -644.382446

Temperature: 365 (raw value (uncomp): 62464)
Pressure (Pa): 111417 (raw: 249856) Mov Avg: 72864
altitude is (m): -808.229309
wikipedia altitude (m): -807.841614
   
*/

#define EE 22 //The EEPROM has 176bits of calibration data (176/8 = 22 Bytes)
#define BMP085ADDR 0xEF // I2C address of the barometer

//Calibration variables
long b5;
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;

// Function to compute the human interpretable values
int calcPress(int upp, int oversampling_setting);
int calcTemp(int ut);
float calcAltitude(int pressure);
float calcWikipediaAltitude(int pressure);
 
//Moving average variables
#define COEFZ 21 // 21 bits used for computing the moving average
static int k[COEFZ]; // an array used for the moving average
static int movAvgIntZ (int input);

//address of commands
#define CMD_READ_VALUE 0xF6
#define CMD_READ_CALIBRATION 0xAA

#define OVERSAMPLING_ULTRA_LOW_POWER 0
#define OVERSAMPLING_STANDARD 1
#define OVERSAMPLING_HIGH_RESOLUTION 2
#define OVERSAMPLING_ULTRA_HIGH_RESOLUTION 3

const float ALTITUDE_EXPONENT = 1/5.255;
const float ALTITUDE_PRESSURE_REFERENCE = 101325.0; // In Pa
const float ALTITUDE_COEFF = 44330.0;

const float TEMPERATURE_LAPSE_RATE = 0.0065; // in K/m
const float SEA_LEVEL_STANDARD_TEMPERATURE = 288.15; // in Kelvins
const float GRAVITATIONAL_ACCELERATION = 9.81; // in m/(s*s)
const float MOLAR_MASS_AIR = 0.0289644; // in Kg/mol
const float R = 8.31447; // in J/(mol*K) universal gas constant
  
const float WIKIPEDIA_EXPONENT = GRAVITATIONAL_ACCELERATION * MOLAR_MASS_AIR/ (R * TEMPERATURE_LAPSE_RATE);

 
I2C i2c(p28, p27);        // sda, scl
InterruptIn dr(p26);      // EOC, conversion notification
                          // Note that XCLR is floating = not connected (power for analog portion?)

uint32_t drFlag;

void cvt();  // Send I2C command to start pressure conversion (?)
void drSub(); // set a flag to one. Pretty poorly written code IMO.

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 //Pointer to a void function
 
    /////////////////////////////////////////////////
    // 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
    int oversampling_setting = OVERSAMPLING_HIGH_RESOLUTION; // called oss in the documentation
    // read page 12 of doc. 3 is ultra high precision and it requires a lot of time for the device to do the conversion
    char rReg[3] = {0,0,0};  // read registers for I2C
    char wReg[2] = {0,0};  // write registers for I2C
    char cmd = 0x00; 
 
    /////////////////////////////////////////////////
    // get EEPROM calibration parameters
    /////////////////////////////////////////////////
 
    char data[EE];
    cmd = CMD_READ_CALIBRATION; // EEPROM calibration command
 
    for (int i = 0; i < EE; i++) { // read over the 22 registers of the EEPROM
        i2c.write(addr, &cmd, 1);
        i2c.read(addr, rReg, 1);  // rReg is array? of size 3. We only read the 1st register???
        data[i] = rReg[0];
        cmd += 1;
        wait_ms(10);
    }
 
    // parameters AC1-AC6
    //The calibration is partioned in 11 words of 16 bits, each of them representing a coefficient
    ac1 =  (data[0] <<8) | data[1]; // AC1(0xAA, 0xAB)... and so on
    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];
 
    // ready to start sampling loop
    __enable_irq();
    
    // END OF INITIALIZATION
 
    /////////////////////////////////////////////////
    // main
    /////////////////////////////////////////////////
    pc.printf("Starting barometer main()!!\n\r");
    while (1) {
        if (drFlag == 1) {
           // Documentation recommend the following chronology: 
           // read uncomp temp, read  uncomp pressure, compute true temp, 
           // compute true pressure
 
            /////////////////////////////////////////////////
            // uncomp temperature
            /////////////////////////////////////////////////
            wReg[0] = 0xF4;
            wReg[1] = 0x2E; 
            i2c.write(addr, wReg, 2); // write 0x2E in reg 0XF4
            wait_ms(4.5);
            cmd = CMD_READ_VALUE; // 0xF6
            i2c.write(addr, &cmd, 1); // set pointer on 0xF6 before reading it?
            i2c.read(addr, rReg, 2); // read 0xF6 (MSB) and 0xF7 (LSB)// rReg is 3 long though 
            int uncomp_temperature = (rReg[0] << 8) | rReg[1]; // UT = MSB << 8 + LSB
            
            /////////////////////////////////////////////////
            // uncomp pressure
            /////////////////////////////////////////////////
            
            //write 0x34 + (oss<<6) into reg 0xF4, wait (waiting time not specified in doc)
            int uncomp_pressure_cmd = 0x34 + (oversampling_setting<<6);
            wReg[0] = 0xF4;
            wReg[1] = uncomp_pressure_cmd;
            i2c.write(addr, wReg, 2);
            // See page 12 of documentation
            switch (oversampling_setting) {
              case OVERSAMPLING_ULTRA_LOW_POWER:
                wait_ms(4.5);
                break;
              case OVERSAMPLING_STANDARD:
                wait_ms(7.5);
                break;
              case OVERSAMPLING_HIGH_RESOLUTION:
                wait_ms(13.5);
                break;
              case OVERSAMPLING_ULTRA_HIGH_RESOLUTION:
                wait_ms(25.5);
                break;
            }
           
            cmd = CMD_READ_VALUE; // 0xF6
            i2c.write(addr, &cmd, 1);
            i2c.read(addr, rReg, 3); // read 0xF6 (MSB), 0xF7 (LSB), 0xF8 (XLSB)
            int uncomp_pressure = ((rReg[0] << 16) | (rReg[1] << 8) | rReg[2]) >> (8 - oversampling_setting);
            // UP = (MSB<<16 + LSB<<8 + XLSB)>>(8-oss)
            
            // true temperature
            int temperature = calcTemp(uncomp_temperature);
            pc.printf("Temperature: %d (raw value (uncomp): %d)\n\r", temperature, uncomp_temperature);
            // even though implementation seems right, 
            //returned value is wrong hostilating between 87 and 365 (i.e. 8.7C and 36.5C)
            
            // True pressure
            int pressure = calcPress(uncomp_pressure, oversampling_setting);
            int avg_pressure = movAvgIntZ(pressure);
            pc.printf("Pressure (Pa): %d (raw: %d) Mov Avg: %d\n\r", pressure, uncomp_pressure, avg_pressure);
 
            //Compute an estimate of altitude
            // see page 15 of manual
            float altitude = calcAltitude(pressure);
            pc.printf("altitude is (m): %f\n\r", altitude); // note that the implementation is correct but returns wrong absolute numbers

            float wiki_altitude = calcWikipediaAltitude(pressure);
            pc.printf("wikipedia altitude (m): %f\n\r", wiki_altitude); // results are VERY similar and therefore not 

            /////////////////////////////////////////////////
            // data ready cleanup tasks
            /////////////////////////////////////////////////
            drFlag = 0; // This should stop the while loop... but it does not!
            pc.printf("\n\r");
        }
    wait(3); // just to make reading in the terminal easier
    } //end while
}
 
////////////////////////////////////////////////////////////////////////////////////
// start pressure conversion
////////////////////////////////////////////////////////////////////////////////////
void cvt() { //This is only called once during the initialization phase!
    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, int oversampling_setting) {
   // Return the pressure in Pa (Pascals)
 
    long pressure, x1, x2, x3, b3, b6;
    unsigned long b4, b7;
    // read page 12 of doc. This is ultra high precision and it requires a lot of time for the device to do the conversion
 
    unsigned long up = (unsigned long)upp;
 
    b6 = b5 - 4000;
    // calculate B3
    x1 = (b6*b6) >> 12; // full formula(b2*(b6*b6)/pow(2,12))/pow(2,11)
    x1 *= b2;
    x1 >>= 11;
 
    x2 = (ac2*b6);
    x2 >>= 11;
 
    x3 = x1 + x2;
 
    b3 = (((((long)ac1 )*4 + x3) <<oversampling_setting) + 2) >> 2; // doc says /4!
 
    // 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; // pressure/pow(2,8) * pressure/pow(2, 8)
    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) {
    // Returns the temperature in °C
 
    int temp;
    long x1, x2;
 
    x1 = (((long) ut - (long) ac6) * (long) ac5) >> 15; // aka (ut-ac6) * ac5/pow(2,15)
    x2 = ((long) mc << 11) / (x1 + md); // aka mc * pow(2, 11) / (x1 + md)
    b5 = x1 + x2;
    temp = ((b5 + 8) >> 4); // (b5+8)/pow(2, 4)
    // temperature in 0.1°C (aka 1903 = 190.3°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]; // do we really need k as a global variable?
    }
    k[COEFZ - 1] = input;
    for (int i = 0; i < COEFZ; i++) {
        cum += k[i];
    }
    return (cum/COEFZ) ;
}

float calcAltitude(int pressure) {
  //Compute an estimate of altitude
  // see page 15 of manual
  float float_comp_pressure = (float) pressure;
  float altitude = float_comp_pressure/ALTITUDE_PRESSURE_REFERENCE;
  altitude = pow(altitude, ALTITUDE_EXPONENT);
  altitude = 1.0 - altitude;
  altitude *= ALTITUDE_COEFF;
  // formula is: altitude = 44330 * (1-pow((p/p0), 1/5.255))
  return altitude;
}

float calcWikipediaAltitude(int pressure) {
  // compute the altitude in meters
  // per: http://en.wikipedia.org/wiki/Atmospheric_pressure
  float float_comp_pressure = (float) pressure;

  float altitude = float_comp_pressure/ALTITUDE_PRESSURE_REFERENCE;
  float exponent = 1.0/WIKIPEDIA_EXPONENT;
  altitude = pow(altitude, exponent);
  altitude = 1.0 - altitude;
  altitude *= (SEA_LEVEL_STANDARD_TEMPERATURE/TEMPERATURE_LAPSE_RATE);
  
  return altitude;
}