Reading the BMP180 pressure sensor with an Attiny85

IMG_20160210_232932The BMP180 pressure sensor is a relatively cheap and popular sensor to read atmospheric pressure. Additionally it can read temperature.
If you want to use this sensor on an arduino, Adafruit has a library (for the BMP085 and the BMP180) that wil read it for you. However, the new library also needs their general ‘Sensor library’ and those are memory guzzlers. Perhaps OK on an Arduino, but not on an attiny. They do have one for the Tiny85 as well. Sparkfun also has a library  for the Arduino.
Furthermore, the BMP180 is an I2C device and I2C is not a standard on the Attiny series.

So, if you want to read the BMP180 sensor on an attiny, you would need to do some work yourself.
Fortunately, the datasheet is very very clear. Page 15 tells us exactly what to do.
The sequence is as follows:
1-Read the chip specific calibration data
2-Read the uncorrected temparature value
3-Read the uncorrected pressure value
4-Calculate true temperature
5-calculate true pressure

It also shows you what should be in a loop and what not:
reading the calibration data only needs to be done once and therefore goes in the ‘Setup’ routine. The rest is in a loop and therefore goes in the ‘loop’ routine.

So, programming is a breeze if you follow the flow chart on page 15…. we only need to ‘translate’ that into language the I2C protocol understands.
We therefore start the program with defining some general parameters:
For the Attiny there is the TinyWireM library that implements an I2C protocol on the attiny, so we need to load that library.
We need the I2C address of the BMP180 (which is 0x77), and we need to declare a whole bunch of variables. Most of the variables used will contain the chip specific calibration data that we will be reading from the chip’s EEPROM, we will need some variables for the various calculations and we will need some variables to contain the output (temperature and pressure)
To keep it easy, I have chosen names for the variables as mentioned in the datasheet.

So, the first lines of a program will look like this:

//The connection for  Attiny & BMP180 are  SDA pin 5 ,SCL pin 7 for I2C 
#include  <TinyWireM.h>
#define BMP180_ADDRESS 0x77  // I2C address of BMP180   
// define calibration data for temperature:
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; 
//define variables for pressure and temperature calculation
long x1,x2;
//define variables for pressure calculation
long x3,b3,b6,p;
unsigned long b4,b7;
//define variables for temperature and pressure reading

short temperature;
long pressure;
const unsigned char OSS = 0;  // Oversampling Setting
/* blz 12 Datasheet
OSS=0 ultra Low Power Setting, 1 sample, 4.5 ms 3uA
OSS=1 Standard Power Setting, 2 samples, 7.5 ms 5uA
OSS=2 High Resolution,              4 samples, 13.5 ms 7uA
OSS=3 Ultra High Resolution,    2 samples, 25.5 ms 12uA

Then we have to define the ‘Setup’ routine. Frankly, the only thing we have to do there is read the calibration data. To keep it simple, i will just call a procedure ‘bmp180ReadInt(address)’, which we then can implement later.
Our Setup therefore will look like this:

void setup() {
  // First read calibration data from EEPROM
  ac1 = bmp180ReadInt(0xAA);
  ac2 = bmp180ReadInt(0xAC);
  ac3 = bmp180ReadInt(0xAE);
  ac4 = bmp180ReadInt(0xB0);
  ac5 = bmp180ReadInt(0xB2);
  ac6 = bmp180ReadInt(0xB4);
  b1 = bmp180ReadInt(0xB6);
  b2 = bmp180ReadInt(0xB8);
  mb = bmp180ReadInt(0xBA);
  mc = bmp180ReadInt(0xBC);
  md = bmp180ReadInt(0xBE);


Ofcourse I could have just called 1 procedure and call that ‘bmp180ReadCalibration’ but that procedure then would do the same as I now defined already in the setup

The ‘loop’ procedure is equally simple. It is basically
Read uncorrected temperature
Correct that uncorrected temperature
Read uncorrected pressure
Correct that uncorrected pressure
But as no one is interested in the uncorrected data, we make that procedure:
Correct(Read Uncorrected temperature)
Correct(Read Uncorrected pressure)
like this:

void loop() {
 // first, read uncompensated temperature
 //temperature = bmp180ReadUT();
 //and then calculate calibrated temperature
 temperature = bmp180CorrectTemperature(bmp180ReadUT());
 // then , read uncompensated pressure
 //pressure = bmp180ReadUP();
 //and then calculate calibrated pressure
 pressure = bmp180CorrectPressure(bmp180ReadUP());

So that is it. We now only have to define the procedures that we call.
We will start with ‘bmp180ReadInt(address)’
This procedure will use the TinyWireM library to read an integer from a given address. In getting data from an I2C device, the general rule is to first write to that device to tell it what to do and then to read at a specific address for the outcome. As we will be reading from the EEPROM there is no specific command we have to send, other than to notify the I2C port where we want to be (at the I2C address of the chip) and send the address we want to read and how many bytes we want to read. We then combine those two butes in an integer and return that.
Our precedure will thus look like this:

int bmp180ReadInt(unsigned char address)
  unsigned char msb, lsb;
  TinyWireM.requestFrom(BMP180_ADDRESS, 2);
  msb = TinyWireM.receive();
  lsb = TinyWireM.receive();
  return (int) msb<<8 | lsb;

The next procedure we need is to read the uncompensated temperature. To get that we have to first send the value of 0x2E to register 0xF4 and wait at least 4.5 msec. That is the time the chip needs to take 1 reading. After we waited we will read the uncompensated temperature from registers 0xF6 and 0xf7. That last read we do with the earlier defined ‘bmp180ReadInt’ procedure that reads 2 bytes and combines them into an integer.
The procedure thus will look like this:

unsigned int bmp180ReadUT()
  unsigned int ut;
  // Write 0x2E into Register 0xF4 and wait at least 4.5mS
  // This requests a temperature reading 
  // with results in 0xF6 and 0xF7
  // Wait at least 4.5ms
  // Then read two bytes from registers 0xF6 (MSB) and 0xF7 (LSB)
  // and combine as unsigned integer
  ut = bmp180ReadInt(0xF6);
  return ut;

Subsequently we have to calculate the corrected temperature from the uncorrected temperature.
The datasheet defines that as follows:
UT=uncompensated temperature
X2=(MC * 2^11 /(X1+MD)
in software that looks like this

double bmp180CorrectTemperature(unsigned int ut)
  x1 = (((long)ut - (long)ac6)*(long)ac5) >> 15;
  x2 = ((long)mc << 11)/(x1 + md);  
  b5 = x1 + x2; 
  return (((b5 + 8)>>4));  

Well the temperature is done, now we need to read the uncompensated pressure. For that we need to write the value 0x34 in the register 0xF4, but we also have to set the value vor the oversampling rate.
The oversampling rate determines the amount of samples the chip needs to make before giving a result.
Page 4 of the datasheet tells we have 4 choices:
OSS=0 ultra Low Power Setting, 1 sample, 4.5 ms 3uA
OSS=1 Standard Power Setting, 2 samples, 7.5 ms 5uA
OSS=2 High Resolution, 4 samples, 13.5 ms 7uA
OSS=3 Ultra High Resolution, 12 samples, 25.5 ms 12uA
For this program I have chosen the OSS to be 0
The OSS contains bits 6 and 7 in register 0xF4. Bit 0-4 determine the control of the measurement.
if we write the value 0x34 that is in binary: 00110100. Bits 0 to 4 are not so important for now, but bit 5 will also be set and thus start the conversion. It will stay high during the conversion and reset to LOW after the conversion. In order to set the bits 6 and or 7 we have to left shift 6 the value of OSS. Suppose we had wanted to set OSS as 3. in binary that is 0b11 if we left shift 6 that, it will be 11000000 (=192d or 0xC0), which will set bits 6 and 7. 0x34+0xC0=0xF4=0b11110100 which as we can see is the same as 0x34 plus bit 6 and 7 set.
As we are using ‘0’ for the OSS value, both bit 6 and 7 will not be set.
after we start the conversion we have to wait between 4.5 and 25.5 msecs (depending on OSS). As we have OSS=0 we will wait 5msec.
Subsequently we will read 3 bytes as the temperature is a ‘long’ (4 bytes) not an integer, we will however only need 3 bytes.
With regard to the delay, it would be nice if we will define it as a dependency of the OSS so you do not need to manually change it when you change the OSS. The Adafruit library solevs this with some IF statements:
if (oversampling == BMP085_ULTRALOWPOWER)
else if (oversampling == BMP085_STANDARD)
else if (oversampling == BMP085_HIGHRES)
However, I hoped to find a formula that will determine it. As it isn’t a strict linear function, the closest one gets is the formula: 5+(OSS*5).
Well, I guess that would be close enough
The procedure is as follows

// Read the uncompensated pressure value
unsigned long bmp180ReadUP()
  unsigned char msb, lsb, xlsb;
  unsigned long up = 0;
  // Write 0x34+(OSS<<6) into register 0xF4
  // Request a pressure reading w/ oversampling setting
  TinyWireM.send(0x34 + (OSS<<6));
  // Wait for conversion, delay time dependent on OSS
  delay(5 + (5*OSS));
  // Read register 0xF6 (MSB), 0xF7 (LSB), and 0xF8 (XLSB)
  TinyWireM.requestFrom(BMP180_ADDRESS, 3);
  // Wait for data to become available
  while(TinyWireM.available() < 3)
  msb = TinyWireM.receive();
  lsb = TinyWireM.receive();
  xlsb = TinyWireM.receive();
  up = (((unsigned long) msb << 16) | ((unsigned long) lsb << 8) | (unsigned long) xlsb) >> (8-OSS);
  return up;

Now that is done, we need to correct the uncompensated pressure. The result will be in Pascal

double bmp180CorrectPressure(unsigned long up)
  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;
  p = (b7/b4)<<1;   
  x1 = (p>>8) * (p>>8);
  x1 = (x1 * 3038)>>16;
  x2 = (-7357 * p)>>16;
  p += (x1 + x2 + 3791)>>4;
  return p;

With the above program one can decide for oneself what to do with the found data: either  send it to a display, or perhaps send it via an RF link to a base station.
As said, the output of the pressure reading is in Pascal (Pa). hectoPascals are a more convenient unit. Some other units it can be  calculated in are:
1 hPa = 100 Pa = 1 mbar = 0.001 bar
1 hPa = 0.75006168 Torr
1 hPa = 0.01450377 psi (pounds per square inch)
1 hPa = 0.02953337 inHg (inches of mercury)
1 hpa = 0.00098692 atm (standard atmospheres)

One last advice still:  When you use the BMP180, remember it needs 3.3 Volt. 5 Volt will kill it. Using it on the I2C from a 5 Volt microcontroller shouldnot cause a problem though. Various break outboards actually do have a  3.3 Voltage regulator on it.

When I wanted to display the values found by the BMP180, I  grabbed a two wire LCD interface that I ahd build with a 164 Shift Register. I subsequently tried to figure out for several hours why I wasnt getting any decent read out. In fact, the read out didnt change wether I connected the BMP180 or not. After many many trials I started to suspect my display interface and decided to  hook up an I2C LCD. That worked like a charm.
The LiquidCrystal_I2C from Francisco Malpertida doesn’t work on the Attiny85. I used the classic LiquidCrystal_I2C that is adapted by Bro Hogan to work on the Attiny85 as well.
He did that by changing the line:

#include <Wire.h>
#if defined(__AVR_ATtiny85__) || (__AVR_ATtiny2313__)
#include "TinyWireM.h"      // include this if ATtiny85 or ATtiny2313
#include <Wire.h>           // original lib include


18 thoughts on “Reading the BMP180 pressure sensor with an Attiny85

  1. Wait, you were able to read from the bmp180 and send text to the i2c display with a single attiny85?? I’ve been trying to do this but have always hit memory issues. I’ll have to try your method. I have some digisparks lying around. Because of their bootloader, they have less space. Do you think it’s possible to send data collected from the bmp180 to the i2c led display? Thanks

    1. yes that is possible but it is all a matter of space.
      As i currently have an attiny with a bmp and an i2c display, it must be possible.
      Normally I use the Malpertida library but i have used another library (Bro Hogan) as Malpertidas libray gives problems on the attiny, I seem to remember it was an intrinsiccall to the wire linrary that causes that

    2. Nick, I checked my setup again (that was laying in a corner somewhere) and it is indeed the LCD on I2C. and that on an atiny85 with bmp180, as I remember it wasnt the Malpartida LCD library but the BroHogan library.
      I did make some pics, null and
      I hope you can see the screen, it is a bit difficult to shoot an LCD screen, but the picture I added to the top of the instructable should be clear.
      The Arduino pro mini that you see has no other function than provide 5 Volt and at the low end of the small breadboard you see the attiny85.
      From the Output I see I also had a DHT11 hanging on pin 1 (PB1) of the Attiny

      I Think this is the code I used: BMP-test

  2. Man! I have this sensor and I’m trying to read it… can you tell me where is written in the datasheet that the device adress is 0x77!!?? I can see that it is 0xEE … Thanks!

    1. I2C addresses is a common source of confusion.
      A I2C device address may be specified as a 7 bit address, which is 7 bits that may distinguish one device from another.
      Or as a byte that include the R/W bit in the LSB position.

      The Bosch datasheet does not specify the 7 bit address, which is 0x77.
      Instead it specifies (page 20) the 8 bit Write address, which is 0xEE and the 8 bit Read address, which is 0xEF.
      Both are 0x77 R/W

      0x77 = 111 0111
      0xEE = 111 01110
      0xEF = 111 01111

      in the TinywireM library two values are defined
      #define USI_SEND 0 // indicates sending to TWI
      #define USI_RCVE 1 // indicates receiving from TWI
      these are combined with the 7 bit address to indicate a read or write action

      1. That’s the problem… I searched on the internet and most of the codes use 0x77 but datasheet shows 0xEE… let’s assume that this is a new revision of the datasheet and only new sensors from 201X have that address (0xEE)… Yesterday I tried reading the sensor with 0xEE and I can’t access the device, BUT I can read the EEPROM coefficients EXCEPT the las 3 MB, MC, MD …. and it’s super weird! I can send you the code, maybe you can give me a hand. THanks!

      2. I am not sure what you are trying to say. Are you describing the problem you HAD before my explanation or do you still have a problem. You can always send me yr code if you like

      3. ok… this is new for me.. I normally use STM sensor where the datasheets are crystal clear.. now:

        i2c_start(0x77 + 0x01) = search for the BPM180 on the bus? or how should it be?

      4. It all depends on how you define yr procedures or what library you use. If you use the tinWireM library just stick to 0x77 because the library will take care of adding the read or write bit.
        If you want to do it yourself 0x77+0x01 wont cut it because that is 0x78. What you need to do is to leftshift 0x77 with 1 and then OR it with the read or write bit, or since you know if you want to read or write, use 0xEE or 0xEF.
        But again, it depends on how you define your procedures or the library you use. If you use TinyWireM, just use 0x77 and dont bother with 0xEE and 0xFF.
        If you dont use any library, but just writing your code from scratch, use 0xEE for sending and 0xEF for receiving.wtig regard to the leftsshift and OR:
        0x77 = 0111 0111
        0x77 << 1= 1110 1110
        ORred with 0000 0001
        1110 1111 = 0xEF

        1110 1110 OR-ed with 0000 0000 =1110 1110 =0xEE

  3. I can’t access my BMP 180 sensor via this sketch. No SDA/SCL signals appear on Pin 5&7 of the ATTiny85.
    Not sure why happens. The ATTiny85 runs fine, my additional test LED flashes every second.
    I program the ATTiny85 via a Arduino Uno ISP

  4. I found the error. There was a missing ‘TinyWireM.begin()’ statement inside void setup(). Took me several hours to realise… my fault.. as I took the source code from the above snippets.
    Question: Do you have the complete source code somewhere to download ?

    1. good. I will see if I still have my code somewhere as i have not been doing much with the attiny lately.
      Thanks for the feedback, sorry it cost you time.

      Edit found what looked like the code in an old folder. On its way to the email address you left when commenting

  5. Edwin, thanks for the code you sent me by email. I couldn’t get it running at first – same result as with my code. You made almost the same error as I did: the ‘TinyWire.M.begin()’ statement was present inside ‘void setup()’, but at the wrong spot: It belongs to the very top, and not after the first read of calibration data. Because the ‘bmp180ReadInt(xx)’ routine uses ‘TinyWireM.___ statements. So you need to issue the ‘TinyWireM.begin()’ command at first.

    Now all is cool and running. Thanks for sharing your code. Really nifty 🙂

    So here the correct setup code for other users:

    void setup() {

    // We want this here and no where else 🙂

    // First read calibration data from EEPROM
    ac1 = bmp180ReadInt(0xAA);
    ac2 = bmp180ReadInt(0xAC);
    ac3 = bmp180ReadInt(0xAE);
    ac4 = bmp180ReadInt(0xB0);
    ac5 = bmp180ReadInt(0xB2);
    ac6 = bmp180ReadInt(0xB4);
    b1 = bmp180ReadInt(0xB6);
    b2 = bmp180ReadInt(0xB8);
    mb = bmp180ReadInt(0xBA);
    mc = bmp180ReadInt(0xBC);
    md = bmp180ReadInt(0xBE);


    Best regards

    1. absolutely right. As I had my code working, somehow this must have been result of trying to brush up code for publishing. Didnt know I had done that. Anyway, good it is working for you now.
      actually the reason why I wrote this code is because it was so tempting to do: the datasheet -contrary to many other datasheets- is really clear in explaining how to get the various factors and what registers to read and I wanted to keep the overhead as small as possible

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s