Netduino home hardware projects downloads community

Jump to content


The Netduino forums have been replaced by new forums at community.wildernesslabs.co. This site has been preserved for archival purposes only and the ability to make new accounts or posts has been turned off.
Photo

Bosch BMP085 Digital Pressure and Temperature Sensor


  • Please log in to reply
34 replies to this topic

#1 phantomtypist

phantomtypist

    Advanced Member

  • Members
  • PipPipPip
  • 142 posts
  • LocationNew York, NY

Posted 27 October 2010 - 04:39 AM

This managed driver is for the Bosch BMP085 Digital Pressure and Temperature Sensor. This sensor allows you to retrieve the temperature and barometric pressure in both Pascals and Inches of Mercury (InHg). While not coded, you could hypothetically use the data from this sensor alone to calculate your altitude.

This code uses my I2CBus managed driver. You will need the I2CBus.cs file attached in that posting.

I completed this a few weeks ago and I've been testing it to make sure it works.

FYI there is a timer in this driver that takes the readings / measurements every 30 seconds. You can change that as you please. Furthermore, you can remove the timer and make the TakeMeasurements method public so that you can take readings / measurements only when you want or need to. Last, but not least, if you intend to use this sensor and code please read the datasheet. It will definitely help you understand what is going on in the code.

Here is an example of using the code:

    BMP085 pressureSensor = new BMP085(0x77, BMP085.DeviceMode.UltraLowPower);
    Debug.Print("BMP085 Pascal: " + pressureSensor.Pascal);
    Debug.Print("BMP085 InchesMercury: " + pressureSensor.InchesMercury.ToString("F2"));
    Debug.Print("BMP085 Temp*C: " + pressureSensor.Celsius.ToString("F2"));

Here is the code for the driver:

    /// <summary>
    /// Bosch BMP085 digital pressure and temperature sensor.
    /// </summary>
    public class BMP085
    {
        private I2CDevice.Configuration _slaveConfig; // BMP085 I2CDevice configuration
        private const byte ClockRateKHz = 40; // BMP085 clock rate

        // Oversampling for measurements.  Please see the datasheet for this sensor for more information.
        private byte _oversamplingSetting;

        // These wait times correspond to the oversampling settings.  
        // Please see the datasheet for this sensor for more information.
        private readonly byte[] _pressureWaitTime = new byte[] {5, 8, 14, 26};

        private const int TransactionTimeout = 1000; //ms
        private Timer _sensorTimer;
        public enum DeviceMode
        {
            UltraLowPower = 0,
            Standard = 1,
            HighResolution = 2,
            UltraHighResolution = 3
        }

        // Calibration data backing stores
        private short _ac1;
        private short _ac2;
        private short _ac3;
        private ushort _ac4;
        private ushort _ac5;
        private ushort _ac6;
        private short _b1;
        private short _b2;
        private short _mb;
        private short _mc;
        private short _md;

        public BMP085(byte address, DeviceMode deviceMode)
        {
            Address = address;
            _slaveConfig = new I2CDevice.Configuration(address, ClockRateKHz);
            _oversamplingSetting = (byte) deviceMode;

            // Get calibration data that will be used for future measurement taking.
            GetCalibrationData();

            // Take initial measurements.
            TakeMeasurements();

            // Take new measurements every 30 seconds.
            _sensorTimer = new Timer(TakeMeasurements, null, 200, 30000);
        }

        /// <summary>
        /// Calculates the compensated pressure and temperature.
        /// </summary>
        private void TakeMeasurements()
        {
            TakeMeasurements(null);
        }

        /// <summary>
        /// Calculates the compensated pressure and temperature.
        /// </summary>
        /// <param name="state"></param>
        private void TakeMeasurements(object state)
        {
            long x1, x2, x3, b3, b4, b5, b6, b7, p;

            long ut = ReadUncompensatedTemperature();

            long up = ReadUncompensatedPressure();

            // calculate the compensated temperature
            x1 = (ut - _ac6) * _ac5 >> 15;
            x2 = (_mc << 11) / (x1 + _md);
            b5 = x1 + x2;
            _celsius = (float)((b5 + 8) >> 4) / 10;

            // calculate the compensated pressure
            b6 = b5 - 4000;
            x1 = (_b2 * (b6 * b6 >> 12)) >> 11;
            x2 = _ac2 * b6 >> 11;
            x3 = x1 + x2;
            switch (_oversamplingSetting)
            {
                case 0:
                    b3 = ((_ac1 * 4 + x3) + 2) >> 2;
                    break;
                case 1:
                    b3 = ((_ac1 * 4 + x3) + 2) >> 1;
                    break;
                case 2:
                    b3 = ((_ac1 * 4 + x3) + 2);
                    break;
                case 3:
                    b3 = ((_ac1 * 4 + x3) + 2) << 1;
                    break;
                default:
                    throw new Exception("Oversampling setting must be 0-3");
            }
            x1 = _ac3 * b6 >> 13;
            x2 = (_b1 * (b6 * b6 >> 12)) >> 16;
            x3 = ((x1 + x2) + 2) >> 2;
            b4 = (_ac4 * (x3 + 32768)) >> 15;
            b7 = (up - b3) * (50000 >> _oversamplingSetting);
            p = (b7 < 0x80000000 ? (b7 * 2) / b4 : (b7 / b4) * 2);
            x1 = (p >> 8) * (p >> 8);
            x1 = (x1 * 3038) >> 16;
            x2 = (-7357 * p) >> 16;
            _pascal = (int) (p + ((x1 + x2 + 3791) >> 4));
        }

        private long ReadUncompensatedTemperature()
        {
            // write register address
            I2CBus.GetInstance().Write(_slaveConfig, new byte[2] { 0xF4, 0x2E }, TransactionTimeout);

            // Required as per datasheet.
            Thread.Sleep(5);

            // write register address
            I2CBus.GetInstance().Write(_slaveConfig, new byte[] { 0xF6 }, TransactionTimeout);

            // get MSB and LSB result
            byte[] inputData = new byte[2];
            I2CBus.GetInstance().Read(_slaveConfig, inputData, TransactionTimeout);

            return ((inputData[0] << 8) | inputData[1]);
        }

        private long ReadUncompensatedPressure()
        {
            // write register address
            I2CBus.GetInstance().Write(_slaveConfig, new byte[2] { 0xF4, (byte) (0x34+(_oversamplingSetting<<6)) }, TransactionTimeout);

            // insert pressure waittime using oversampling setting as index.
            Thread.Sleep(_pressureWaitTime[_oversamplingSetting]);

            // get MSB and LSB result
            byte[] inputData = new byte[3];
            I2CBus.GetInstance().ReadRegister(_slaveConfig, 0xF6, inputData, TransactionTimeout);

            return ((inputData[0] << 16) | (inputData[1] << 8) | (inputData[2])) >> (8 - _oversamplingSetting);
        }

        /// <summary>
        /// Retrieves the factory calibration data stored in the sensor.
        /// </summary>
        private void GetCalibrationData()
        {
            _ac1 = ReadShort(0xAA);
            _ac2 = ReadShort(0xAC);
            _ac3 = ReadShort(0xAE);
            _ac4 = (ushort)ReadShort(0xB0);
            _ac5 = (ushort)ReadShort(0xB2);
            _ac6 = (ushort)ReadShort(0xB4);
            _b1 = ReadShort(0xB6);
            _b2 = ReadShort(0xB8);
            _mb = ReadShort(0xBA);
            _mc = ReadShort(0xBC);
            _md = ReadShort(0xBE);
        }

        private short ReadShort(byte registerAddress)
        {
            // write register address
            I2CBus.GetInstance().Write(_slaveConfig, new byte[] { registerAddress }, TransactionTimeout);

            // get MSB and LSB result
            byte[] inputData = new byte[2];
            I2CBus.GetInstance().Read(_slaveConfig, inputData, TransactionTimeout);

            return (short)((inputData[0] << 8) | inputData[1]);
        }

        private byte _address;
        public byte Address
        {
            get { return _address; }
            private set { _address = value; }
        }

        private int _pascal;
        public int Pascal
        {
            get { return _pascal; }
        }

        public float InchesMercury
        {
            get
            {
                return (float) (_pascal / 3386.389);
            }
        }

        private float _celsius;
        public float Celsius
        {
            get { return _celsius; }
        }

        public void Dispose()
        {
            _sensorTimer.Dispose();
        }

    }

Attached Files



#2 Chris Walker

Chris Walker

    Secret Labs Staff

  • Moderators
  • 7767 posts
  • LocationNew York, NY

Posted 27 October 2010 - 04:58 AM

It would be cool to submit this data to a web service from the Netduino Plus--and then pull it down from a phone app. Great job.

#3 speedbird

speedbird

    New Member

  • Members
  • Pip
  • 2 posts
  • LocationGermany

Posted 16 November 2010 - 07:54 AM

Hi, I use the Sparkfun BMP085 Breakout. This sensor supports a voltage supply between 1.8 and 3.6VDC. Can i connect the I2C Bus direct to te NETDUNO Ports A4, A5 ? Greetings from germany Thomas

#4 phantomtypist

phantomtypist

    Advanced Member

  • Members
  • PipPipPip
  • 142 posts
  • LocationNew York, NY

Posted 16 November 2010 - 02:58 PM

This sensor supports a voltage supply between 1.8 and 3.6VDC.
Can i connect the I2C Bus direct to te NETDUNO Ports A4, A5 ?


Thomas, for the BMP085 sensor, please connect as follows:

  • The BMP085 ground to any ground pin on the Netduino
  • The BMP085 VCC to the 3.3V pin on the Netduino or any other 3.3V source
  • The BMP085 SDA pin to analog pin 4 on the Netduino
  • The BMP085 SCL pin to analog pin 5 on the Netduino


#5 CW2

CW2

    Advanced Member

  • Members
  • PipPipPip
  • 1592 posts
  • LocationCzech Republic

Posted 16 November 2010 - 03:12 PM

  • The BMP085 SDA pin to analog pin 4 on the Netduino
  • The BMP085 SCL pin to analog pin 5 on the Netduino

Also, don't forget pull-up resistors for I2C signal lines: connect both data and clock to 3.3V via ~2 kΩ resistors. The value is not critical, typically 10 kΩ is used for 100 kHz clock, 2 kΩ for higher clock rates, 4.7 kΩ as a reasonable compromise.

#6 phantomtypist

phantomtypist

    Advanced Member

  • Members
  • PipPipPip
  • 142 posts
  • LocationNew York, NY

Posted 16 November 2010 - 03:59 PM

Also, don't forget pull-up resistors for I2C signal lines: connect both data and clock to 3.3V via ~2 kΩ resistors. The value is not critical, typically 10 kΩ is used for 100 kHz clock, 2 kΩ for higher clock rates, 4.7 kΩ as a reasonable compromise.


If you are using the BMP085 breakout board from SparkFun (and Thomas is) there is no need for pull-up resistors as they are already on the breakout board. Just connect the four pins like I described.

#7 CW2

CW2

    Advanced Member

  • Members
  • PipPipPip
  • 1592 posts
  • LocationCzech Republic

Posted 18 November 2010 - 07:32 AM

If you are using the BMP085 breakout board from SparkFun (and Thomas is) there is no need for pull-up resistors as they are already on the breakout board.

Yes, you are right, SparkFun breakout board already has the pull-up resistors. Sorry for the confusion.

#8 Shaw

Shaw

    Member

  • Members
  • PipPip
  • 14 posts

Posted 20 November 2010 - 05:51 AM

Hi there, Great Driver. I have made a few little changes to your code and added some events to send updates back to my main app. Is there any reason why you have the initial TakeMeasurements() call when you also have the timer doing the same thing approx 200ms later? I found in my edited version of the class if let that first one run my events weren't wired up yet - but if I comment it out it works fine - and you only have to wait 200ms for the result anyway. My guess would be that because you're using it in a non-event driven manner that you need to force it to initialise the values?

#9 phantomtypist

phantomtypist

    Advanced Member

  • Members
  • PipPipPip
  • 142 posts
  • LocationNew York, NY

Posted 20 November 2010 - 06:00 PM

Hi there,

Great Driver. I have made a few little changes to your code and added some events to send updates back to my main app. Is there any reason why you have the initial TakeMeasurements() call when you also have the timer doing the same thing approx 200ms later?

I found in my edited version of the class if let that first one run my events weren't wired up yet - but if I comment it out it works fine - and you only have to wait 200ms for the result anyway.


If you want your driver to talk to your application you can implement an event in the driver and have your application subscribe to it.

I called the TakeMeasurements so that the data is there right after the class is initialized and your application will be able to get data right away.

I would normally only post the basic stuff in a driver (to keep the driver as small as possible) and let people customize it to fit their needs.

#10 phantomtypist

phantomtypist

    Advanced Member

  • Members
  • PipPipPip
  • 142 posts
  • LocationNew York, NY

Posted 20 November 2010 - 08:21 PM

My guess would be that because you're using it in a non-event driven manner that you need to force it to initialise the values?


Sorry, I totally overlooked that part of your question.

You are actually right! I am not using the driver in a manner that it causes an event to fire and have the application subscribe to that event.

I have a smart home project and the server is polling different sensors at different times. The server will make a request to the sensors and they will in return answer back with the data requested.

There is a lot of complicated logic happening on the server and it only gets data when it's needed. This is why I opted for a pull versus push approach.

But like I said in the previous reply, if it makes sense for you to push the data from the sensor to your application there is no problem in doing so.

PS - If you find any bugs let me know and I'll update the posting.

#11 mluckham

mluckham

    New Member

  • Members
  • Pip
  • 6 posts

Posted 01 December 2010 - 10:04 PM

I also have the BPM085 from Sparkfun. I'm finding that the pressure readings do not correspond very closely to local weather reports, even if I apply an altitude compensation to adjust the pressure relative to sea level. BPM085 pressure: 98400 BPM085 temperature: 212 Sea level equivalent for 206 metres altitude: 98873 Weather Underground report: 100500

#12 Shaw

Shaw

    Member

  • Members
  • PipPip
  • 14 posts

Posted 02 December 2010 - 12:04 AM

Hmmm that's a bit odd. I've noticed minor variations in my readings but not that much. Could it be a faulty chip?

#13 phantomtypist

phantomtypist

    Advanced Member

  • Members
  • PipPipPip
  • 142 posts
  • LocationNew York, NY

Posted 02 December 2010 - 12:30 AM

Hmmm that's a bit odd. I've noticed minor variations in my readings but not that much. Could it be a faulty chip?


If everything is hooked up correctly and you compensated for altitude, then the sensor might not have been calibrated correctly at the factory. As Shaw suggested, the sensor could be defective. Contact SparkFun tech support.

P.S. I personally received a defective BMP085 from them. They were more than happy to replace it for me.

#14 Tecchie

Tecchie

    Member

  • Members
  • PipPip
  • 23 posts
  • LocationGermany

Posted 02 December 2010 - 06:17 PM

BPM085 pressure: 98400
Sea level equivalent for 206 metres altitude: 98873
Weather Underground report: 100500


It seems that your compensation is not correct. As a rough estimation one may assume that the pressure decreases by 1hPa per 8 meters.

206 / 8 = 25
984 + 25 = 1009hPa

#15 mluckham

mluckham

    New Member

  • Members
  • Pip
  • 6 posts

Posted 03 December 2010 - 02:06 AM

It seems that your compensation is not correct. As a rough estimation one may assume that the pressure decreases by 1hPa per 8 meters.

206 / 8 = 25
984 + 25 = 1009hPa


Thanks Tecchie, I am using these calculations:

altimeter_setting = (float)101325*pow(((288-0.0065*known_altitude)/288),5.256);

...

long pressureASL = (101325 + pressure) - altimeter_setting;
Serial.print(" PressureASLcorrected (Pa): ");

#16 mluckham

mluckham

    New Member

  • Members
  • Pip
  • 6 posts

Posted 03 December 2010 - 02:07 AM

Hmmm that's a bit odd. I've noticed minor variations in my readings but not that much. Could it be a faulty chip?


Are you located near sea level?

#17 Shaw

Shaw

    Member

  • Members
  • PipPip
  • 14 posts

Posted 03 December 2010 - 03:38 AM

Yeah pretty close to sea level - maybe 35m ASL I think. I'll rerun the code and check how much variation there is between my pressure and the nearest weather station. I guess I could drive my netduino out to the airport :)

#18 Tecchie

Tecchie

    Member

  • Members
  • PipPip
  • 23 posts
  • LocationGermany

Posted 03 December 2010 - 11:20 AM

  altimeter_setting = (float)101325*pow(((288-0.0065*known_altitude)/288),5.256);
  long pressureASL = (101325 + pressure) - altimeter_setting;


The formula is correct:

altimeter_setting = 101325 * pow( 0.99535, 5.256 ) = 98873
pressureASL = (101325 + 98400) - 98873 = 100852

My guess is that your pow function does not calculate a correct value. Check if

pow(0.99535,5.256) = 0.9758


#19 mluckham

mluckham

    New Member

  • Members
  • Pip
  • 6 posts

Posted 03 December 2010 - 10:33 PM

The formula is correct:

altimeter_setting = 101325 * pow( 0.99535, 5.256 ) = 98873
pressureASL = (101325 + 98400) - 98873 = 100852

My guess is that your pow function does not calculate a correct value. Check if

pow(0.99535,5.256) = 0.9758


Thank you.

Yes, it is correct

Serial.print(" Pow: ");
Serial.print( pow(0.99535,5.256) * 10000, DEC);

produces

9758.0019531250


Today's readings are closer:

Temperature ©: 220
Sensor Pressure (Pa): 99363
Altimeter Setting (Pa): 98873
PressureASLcorrected (Pa): 101815

Wunderground reports 1021 hPa for my location ... so it's much closer today, out only by 3 hPa

Perhaps the higher pressure is a factor.

#20 mluckham

mluckham

    New Member

  • Members
  • Pip
  • 6 posts

Posted 05 December 2010 - 10:04 PM

Today I obtained values from an aircraft altimeter ... set to 0 feet it reads 29.34 (993.566 hPa); set to 700 feet it reads 30.05 (1017.61 hPa). The latter agrees with Weather Underground (1017 hPa and rising). The BMP-085 sensor output (after the equations in the datasheet) is 988.10, and corrected to sea level 1012.75. So it seems like the sensor output is 5.466 hPa too low, and as a result the sea level correction is 4.86 too low also. Re-checking my program, I manually substitute the pressure reading from the 0 feet altimeter 993.56 and the sea level corrected output is 1018.08 - which is much closer to the 700 feet altimeter reading, the error is only 0.45 hPa. Re-checking the program calculations from the datasheet, using the datasheet calibration values and input pressure, the output pressure is calculated correctly. So I am forced to conclude my sensor is reading too low -about 5.4 hPa.




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users

home    hardware    projects    downloads    community    where to buy    contact Copyright © 2016 Wilderness Labs Inc.  |  Legal   |   CC BY-SA
This webpage is licensed under a Creative Commons Attribution-ShareAlike License.