Introduction: Reading the BMP180 Pressure Sensor With an Attiny85 and Add a DHT11 Too

About: I am a physician by trade. After a career in the pharmeceutical world I decided to take it a bit slower and do things I like. Other than my hobbies that involves grassroots medicine in S.E.&P Asia. I have buil…

I love the Attiny 85 series and like to explore all the things you can do with it. As it doesnt have too many pins, using I2C hardware on it is a good idea. I2C on the attiny can be a bit fiddly as it cannot compile the Wire library, but the TinyWireM library works fine.


The 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.

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.

Just a word of explanation on the device address 0x77. Page 20 of the datasheet mentions two device addresses: 0xEE for read and 0xEF for write.

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 TWIthese are combined with the 7 bit address to indicate a read or write action

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.beginTransmission(BMP180_ADDRESS);
  TinyWireM.send(address);
  TinyWireM.endTransmission();
  TinyWireM.requestFrom(BMP180_ADDRESS, 2);
  while(TinyWireM.available()<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
  TinyWireM.beginTransmission(BMP180_ADDRESS);
  TinyWireM.send(0xF4);
  TinyWireM.send(0x2E);
  TinyWireM.endTransmission();
  
  // Wait at least 4.5ms
  delay(5);
  
  // 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 X1=(UT-AC6)*AC5/2^15 X2=(MC * 2^11 /(X1+MD) B5=X1+X2 T=(B5+8)/2^4 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) delay(5);

else if (oversampling == BMP085_STANDARD) delay(8);

else if (oversampling == BMP085_HIGHRES) delay(14);

else delay(26);

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). OSS=0->5 OSS=1->10 OSS=2->15 OSS=3->25 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.beginTransmission(BMP180_ADDRESS);
  TinyWireM.send(0xF4);
  TinyWireM.send(0x34 + (OSS<<6));
  TinyWireM.endTransmission();
  
  // Wait for conversion, delay time dependent on OSS
  delay(5 + (5*OSS));
  
  // Read register 0xF6 (MSB), 0xF7 (LSB), and 0xF8 (XLSB)
  TinyWireM.beginTransmission(BMP180_ADDRESS);
  TinyWireM.send(0xF6);
  TinyWireM.endTransmission();
  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;
  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;
}


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.
Warning 1: There are quite some 'fishhooks' in the above program and sadly instructables (and other websites) have a tendency to sometimes see those as html code. I have checked thoroughly if the code is OK and I think it is. However, it is best to check the code that I will link to in my next step.

Warning 2: When I wanted to display the values found by the BMP180, I initially grabbed a two wire LCD interface that I had build with a 164 Shift Register as I had that available. 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> into #if defined(__AVR_ATtiny85__) || (__AVR_ATtiny2313__) #include "TinyWireM.h"

// include this if ATtiny85 or ATtiny2313

#else

#include <Wire.h> // original lib include #endif

Step 1: Now We Have the BMP180, Let's Add a DHT11

Once we have that of course we need to do something practical with that.
as we used only 2 pins both for the BMP180 and the LCD (after all it is I2C) there are plenty of pins left on the Attiny85.
So let's add a DHT11 on pin PB1.

normally I would publish the code here but there are just too many "fishhooks" in the code and that is too much of a gamble, so I have added it as a file.
The connections to the Attiny are as follows:
BMP180 and LCD:
SDA=physical pin 5
SCL=physical pin 7

DHT11 to PB1=physical pin 6
The picture shows a 3 legged DHT11 breakoutboard that can be used as is. If you happen to have a bare 4 legged DHT11 you need a 10k pull up between S and +

When u use the Bro Hogan library, make sure that when you compile the code, that the compiler picks is the right library as the Bro Hogan library has the same name as the satndard library

If you so wish you could leave out the display and add the BMP180 to my Mini weather station.