Raspberry Pi BME280 Environmental Sensor Project

bme280 raspberry pi 4

If you want to build a compact weather station with the Raspberry Pi or simply want to experiment with different sensors, then this tutorial will be perfect for you! In this article, you will familiarise yourself with using the BME280 sensor, a multi-purpose temperature, humidity and barometric pressure sensor, with the Raspberry Pi. Let’s begin!

Table of Contents

Project Overview

Bosch designed the BME280 chipset which is a newer generation in this line of environmental sensors compared to its brother, the BME180. With high accuracy and a fairly low cost, the BME280 allows for a greater measurement range than the BME180 in terms of temperature (-40°C – 85°C ± 1.0°C), humidity (0% – 100% ± 1%) and pressure (300Pa – 1100 hPa ± 1 hPa). Another additional feature of the BME280 is that although it technically doesn’t have the hardware nor the capabilities to directly measure altitude, it can still be calculated based on pressure readings. Regarding the interfacing of the sensor, it is fully capable of using both i2c and SPI to communicate with an Arduino or Raspberry Pi. In this tutorial, the i2c bus will be used and note that this sensor can support two i2c addresses, 0x76 and 0x77, meaning that two sensors can be utilized on the same bus. In regards to the code, a unique feature of this project is that we are not going to import a pre-made BME280 library/package to interface this sensor with the Pi. Hence, the code will consist of multiple blocks that will be responsible for reading and transforming the data to a readable form for us to view.

What You Need

For this project, the components required are quite minimal and aside from the BME280 itself, you should be able to find all these components in your electronics workshop:

  • Raspberry Pi (any model should work)
  • Male-Female Jumper Wires (4)
  • BME280 Environmental Sensor

Wiring

You will immediately notice that most BME280 sensor variants consist of two separate rows of header pins and the purpose of this is for its two communication interfaces: i2c and SPI (Serial Peripheral Interface). A typical sensor compatible with SPI will often have a 6-pin configuration (VCC, GND, Serial Clock (SCK), Serial Data Out (SDO), Serial Data In (SDI) and Chip Select (CS)), whereas i2c devices will usually have a 4-pin configuration (VCC, GND, Serial Clock (SCL) and Serial Data (SDA)). If you are brand new to the Raspberry Pi, connecting wires to the Pi may be a bit daunting at first due to none of the 40 pins having any labels to indicate what pin it may be. This means that it is recommended that you always follow a pinout guide to identify pins and double-check your wiring before powering up the Pi to avoid any shorts. Other than that, the wiring for this project is simple as aside from the main sensor, not a lot of extra components are needed. A circuit diagram of the BME280 Raspberry Pi wiring is also featured below.

bme280 raspberry pi wiring

  1. VCC/VIN to Raspberry Pi’s 3.3V (Pin 01)
  2. GND to Raspberry Pi’s GND (Pin 09)
  3. Serial Clock (SCL) to GPIO03 (Pin 05)
  4. Serial Data (SDL) to GPIO02 (Pin 03)
  5. You’re now prepared to run the code and start your project!

Project Code

Before you start uploading the code, you should run the following command in the Raspberry Pi’s Terminal to identify the i2c address of your BME280 sensor: 

				
					i2cdetect -y  1
				
			

From there, go to line 7 of the project code and if needed, change the last two digits of the address to match up with what you have received above. Another important point is that in order for your sensor to communicate with your Pi, you must enable i2c interfacing via the Raspberry Pi’s configuration settings. If i2c interfacing is not enabled, the Pi will not be able to recognize the sensor and therefore, your code will not work. BME280 Raspberry Pi python is as follows:

bme280 raspberry pi c code

				
					import smbus
import time
from ctypes import c_short
from ctypes import c_byte
from ctypes import c_ubyte
DEVICE = 0x77  #This address may have to change depending on your BME280 sensor.
bus = smbus.SMBus(1) # Version 2 Pi, Pi 2 & Pi 3 uses bus 1. Rev 1 Pi uses bus 0.
def getShort(data, index):
  # return two bytes from data as a signed 16-bit value
  return c_short((data[index+1] << 8) + data[index]).value
def getUShort(data, index):
  # return two bytes from data as an unsigned 16-bit value
  return (data[index+1] << 8) + data[index]
def getChar(data,index):
  # return one byte from data as a signed char
  result = data[index]
  if result > 127:
    result -= 256
  return result
def getUChar(data,index):
  # return one byte from data as an unsigned char
  result =  data[index] & 0xFF
  return result
def readBME280ID(addr=DEVICE):
  # Chip ID Register Address
  REG_ID     = 0xD0
  (chip_id, chip_version) = bus.read_i2c_block_data(addr, REG_ID, 2)
  return (chip_id, chip_version)
def readBME280All(addr=DEVICE):
  # Register Addresses
  REG_DATA = 0xF7
  REG_CONTROL = 0xF4
  REG_CONFIG  = 0xF5
  REG_CONTROL_HUM = 0xF2
  REG_HUM_MSB = 0xFD
  REG_HUM_LSB = 0xFE
  # Oversample setting - page 27
  OVERSAMPLE_TEMP = 2
  OVERSAMPLE_PRES = 2
  MODE = 1
  # Oversample setting for humidity register - page 26
  OVERSAMPLE_HUM = 2
  bus.write_byte_data(addr, REG_CONTROL_HUM, OVERSAMPLE_HUM)
  control = OVERSAMPLE_TEMP<<5 | OVERSAMPLE_PRES<<2 | MODE
  bus.write_byte_data(addr, REG_CONTROL, control)
  # Read blocks of calibration data from EEPROM
  # See Page 22 data sheet
  cal1 = bus.read_i2c_block_data(addr, 0x88, 24)
  cal2 = bus.read_i2c_block_data(addr, 0xA1, 1)
  cal3 = bus.read_i2c_block_data(addr, 0xE1, 7)
  # Convert byte data to word values
  dig_T1 = getUShort(cal1, 0)
  dig_T2 = getShort(cal1, 2)
  dig_T3 = getShort(cal1, 4)
  dig_P1 = getUShort(cal1, 6)
  dig_P2 = getShort(cal1, 8)
  dig_P3 = getShort(cal1, 10)
  dig_P4 = getShort(cal1, 12)
  dig_P5 = getShort(cal1, 14)
  dig_P6 = getShort(cal1, 16)
  dig_P7 = getShort(cal1, 18)
  dig_P8 = getShort(cal1, 20)
  dig_P9 = getShort(cal1, 22)
  dig_H1 = getUChar(cal2, 0)
  dig_H2 = getShort(cal3, 0)
  dig_H3 = getUChar(cal3, 2)
  dig_H4 = getChar(cal3, 3)
  dig_H4 = (dig_H4 << 24) >> 20
  dig_H4 = dig_H4 | (getChar(cal3, 4) & 0x0F)
  dig_H5 = getChar(cal3, 5)
  dig_H5 = (dig_H5 << 24) >> 20
  dig_H5 = dig_H5 | (getUChar(cal3, 4) >> 4 & 0x0F)
  dig_H6 = getChar(cal3, 6)
  # Wait in ms (Datasheet Appendix B: Measurement time and current calculation)
  wait_time = 1.25 + (2.3 * OVERSAMPLE_TEMP) + ((2.3 * OVERSAMPLE_PRES) + 0.575) + ((2.3 * OVERSAMPLE_HUM)+0.575)
  time.sleep(wait_time/1000)  # Wait the required time  
  # Read temperature/pressure/humidity
  data = bus.read_i2c_block_data(addr, REG_DATA, 8)
  pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
  temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
  hum_raw = (data[6] << 8) | data[7]
  #Refine temperature
  var1 = ((((temp_raw>>3)-(dig_T1<<1)))*(dig_T2)) >> 11
  var2 = (((((temp_raw>>4) - (dig_T1)) * ((temp_raw>>4) - (dig_T1))) >> 12) * (dig_T3)) >> 14
  t_fine = var1+var2
  temperature = float(((t_fine * 5) + 128) >> 8);
  # Refine pressure and adjust for temperature
  var1 = t_fine / 2.0 - 64000.0
  var2 = var1 * var1 * dig_P6 / 32768.0
  var2 = var2 + var1 * dig_P5 * 2.0
  var2 = var2 / 4.0 + dig_P4 * 65536.0
  var1 = (dig_P3 * var1 * var1 / 524288.0 + dig_P2 * var1) / 524288.0
  var1 = (1.0 + var1 / 32768.0) * dig_P1
  if var1 == 0:
    pressure=0
  else:
    pressure = 1048576.0 - pres_raw
    pressure = ((pressure - var2 / 4096.0) * 6250.0) / var1
    var1 = dig_P9 * pressure * pressure / 2147483648.0
    var2 = pressure * dig_P8 / 32768.0
    pressure = pressure + (var1 + var2 + dig_P7) / 16.0
  # Refine humidity
  humidity = t_fine - 76800.0
  humidity = (hum_raw - (dig_H4 * 64.0 + dig_H5 / 16384.0 * humidity)) * (dig_H2 / 65536.0 * (1.0 + dig_H6 / 67108864.0 * humidity * (1.0 + dig_H3 / 67108864.0 * humidity)))
  humidity = humidity * (1.0 - dig_H1 * humidity / 524288.0)
  if humidity > 100:
    humidity = 100
  elif humidity < 0:
    humidity = 0
  return temperature/100.0,pressure/100.0,humidity
def main():
  (chip_id, chip_version) = readBME280ID()
  print "Chip ID     :", chip_id
  print "Version     :", chip_version
  temperature,pressure,humidity = readBME280All()
  print "Temperature : ", temperature, "C"
  print "Pressure : ", pressure, "hPa"
  print "Humidity : ", humidity, "%"
if __name__=="__main__":
   main()
				
			

Code Explanation

A large portion of this project code is simply reading and transforming raw data that is coming from the BME280 from bytes of data to legible environmental values. Hence, in this explanation, not every single line of the code will be covered but the main, important blocks will be explained.

In the first block of the code, several libraries and packages are defined including the smbus library which basically allows for i2c communication with the sensor, the time library for delay/timing purposes and the ctypes library which is responsible for reading the raw data and running all the mathematics behind analyzing it. Then, the device address is declared and we set up the i2c interface using the smbus.SMbus function.

In the next four blocks of code, the raw data is received and processed with essential Python functions that can read from bytes. Then, the readBME280ID function is first used to get the ID of the BME280 device and the readBME280All function is subsequently used to establish oversampling settings, ensure proper calibration of the sensor and read the raw data into word values. In addition, calculations are later performed to refine the environmental data values and of course, towards the end of the code, print those values to the monitor (as seen by the main() block).

Ending

From this article, I hope that you are now confident in using the BME280 environmental sensor with the Raspberry Pi as quite frankly, the i2c interface is so common with lots of sensors. Plus, this project code offers a unique side to how one can extract and transform raw data without using a pre-made library. However, with all the open-source resources available online, I undoubtedly would recommend utilizing packages, modules and libraries for your projects simply because it can save you a lot of time on the software development aspect. Plus, libraries may offer additional features that you may have had trouble building yourself or features that you did not realize were available. Regardless, with the i2c interface allowing for many devices to be connected on the same bus, you could use this project as a stepping stone to work with even more modules and sensors!

More content you may be interested in

raspberry pi autostart
How to Auto Start Programs on Raspberry Pi

Automating program startup on the Raspberry Pi can be achieved through various methods. Editing the “/etc/rc.local” file or using desktop applications, while simpler, may not

Scroll to Top