In this tutorial, we’re going to use a multifunctional module that enables the Raspberry Pi to read temperature, humidity, and barometric pressure simultaneously.
Soldering is required in this project! As shown in the above picture, this part comes with a separate pin header so we need to solder them onto a breadboard.
Figure 1: AE-BME280 / © Akizuki Denshi
Temperature/Humidity/Pressure sensor module kit using the BME280 (in Japanese)
With a sensor module containing Bosch Sensortec BMP280, you can measure temperature, humidity, and barometric pressure simultaneously. It can communicate with microcontrollers using either I2C or SPI transmission methods.
We’ll be using this ultra compact AE-BME280 pressure sensor (Dimension: 16x10mm) in this tutorial. I purchased it from a Japanese parts supplier Akizuki Denshi but you can also use Adafruit BME280 which has the same chip. Just note that the pin layout is slightly different on the Adafruit so please make sure you refer to their datasheet for wiring.
The majority of the work is done by the BME280, a tiny, silver chip in the center of the component. It has a very small opening on the surface that is used to read values so you have to be careful not to cover this up.
When I bought it, the AE-BME280 board and the pin headers came disassembled. The easiest way to connect this to the Raspberry Pi is to put it together as shown in Figure 1. This requires some soldering in order to assemble. The pin header I purchased had 10 pins but only 6 pins were required to connect to AE-BME280, so it was trimmed after the 6th pin.
Here’s the Japanese manual for the AE-BME280.
You can choose either I2C or SPI communication. Since I’ve done SPI before, I’m going to try I2C this time.
I2C – Wikipedia
I2C (Inter-Integrated Circuit), is a serial bus invented by Philips Semiconductor (now NXP Semiconductors). I2C stands for I-square-C. Due to the character limitations in plain text environments, it is referred to as I2C or IIC. It is typically used for attaching low-speed peripheral ICs to things like motherboards, embedded systems, and mobile phones.
Figure 2: Pin numbers and functions – AE-BME280 datasheet
Wiring I2C or SPI is different so we have to pay close attention to the pins. Please refer to AE-BME280 datasheet (Japanese) OR Adafruit BME280 Datasheet (English).
As indicated in Figure 2, we need to solder J3 when using I2C. We have to set it by filling with solder. Note, this only applies if you’re using the AE-BME280 chip. The Adafruit chip doesn’t require this jumper to be set.
Figure 3: Soldering Equipment
Now, it’s time to solder! First, let’s heat up the soldering iron.
Solder – Wikipedia
Solder is an alloy composed mainly of lead and tin that is used with soldering irons. It is used for things like joining metal components and mounting electrical components to circuit boards with electrical circuits. Depending on its composition, it becomes a superconductor at 4-10 degrees.
This is the solder that I used in this project. It’s very soft and pliable and can leak easily so it’s important to only squeeze out little by little. It’s useful to have a desoldering wick ready in case you mess up. If you apply too much, it will wick up the extra solder so be careful!
Figure 4: Soldering J3
First, we’ll solder the J3 jumper indicated on Figure 4. J3 is close to its neighboring pins so be careful not to connect them. Fill in very carefully!
Figure 5: Soldering pin header
The next step is connecting up the pin header. I soldered this from the backside. The gaps between the pins are very small so it was quite difficult to fill them in. Figure 5 is an example of what happens when the solder gets lumpy on both ends.
The trick is to put the soldering iron tip to the pin, warm it up a little, and then lightly apply the solder. If the tip gets too hot, the solder can burn and form lumps. So, I recommend unplugging the soldering iron from the power outlet and cool it down before applying again. There is no need to rush here. Be careful not to connect the pins. Solder slowly!
Figure 6: AE-BME280 connected to breadboard
Done! The pin header is attached and it’s sitting vertically on the breadboard.
When soldering, I accidentally touched the pin header tip and burned it a little bit but thankfully it didn’t affect reading values at all.
Now, let’s wire the Raspberry Pi to the AE-BME280. It should look something like Figure 7 since we’re using I2C.
Figure 7: I2C Connection Method – AE-BME280 datasheet
On the Raspberry Pi, “SDA” connects to GPIO2 (pin 3) and “SCL” connects to GPIO3 (pin 5). VDD connects to pin 1 so these line up in the first 3 GPIO pins (refer to Figure 8). It’s easy to remember when wiring them.
Figure 8: Wiring diagram
I connected pin 5 (SDO) on the AE-BME280 to GND but you can also connect it to VDD. (Please note that the data collection address changes if you do it this way).
Initially, the Raspberry Pi has I2C disabled. The way to enable it is the same as enabling SPI described in previous tutorial: Raspberry Pi WebIOPi IOT, Programming Analog Input. From the menu, select [Preferences]-[Raspberry Pi Configuration], and open the Settings screen.
Click the [Interfaces] tab and set [I2C] to [Enable].
(OS: Confirmed on the 11/21/2015 release version of Raspbian Jessie)
Next, let’s install the packages necessary to use I2C. We need [i2c-tools] to use it from the command line and [python-smbus] to use it in Python.
Install commands
sudo apt-get install i2c-tools
sudo apt-get python-smbus
If you run the command “i2cdetect”, it will detect all devices connected via I2C.
sudo i2cdetect -y 1
76 is an hexadecimal number; it is represented as 0x76.
Note: When selecting the I2C address, it defaults to [0x76] if the pin 5 on the circuit board (SDO) is connected to GND and [0x77] if connected to VDD.
I connected SDO to GND so 0x76 is displayed, but it would show 0x77 if connected to VDD.
Also, it appears that the last parameter in the i2Cdetect command varies based on the version of Raspberry Pi you are using. Revision 1 (Raspberry Pi Model B shipped prior to 10/14/2012) uses 0 but Revision 2 specifies using 1. I’m using a Raspberry Pi 2 Model B so a parameter value of 1 worked.
When I tried running it, this error occurred:
Following the error, I searched in the dev directory and saw that there was a file named [i2c-1] rather than [i2c-0]. I wasn’t able to open the file but I’m assuming it contains the recorded measurements.
sudo i2cdump -y 1 0x76
The above picture shows what happens when I use the i2Cdump command to output the register values. It looks like all sorts of values were read, but I wasn’t able to tell which values came from where and for what just from looking at this.
It was hard to do conversions/calculations on these values, so I borrowed some Python source code from Switch Science’s repository:
Click on the “Download ZIP” link on the upper right and put the “bme_280_sample.py” file from the Python27 folder in the appropriate location on your Raspberry Pi. Note: this program requires the “python-smbus” package in order to run.
python /home/pi/bme280_sample.py
Once preparations are complete, run the program! Once you run it successfully, you will see temperature, pressure, and humidity shown as outputs in three lines.
Note: you have to have root access to run the smbus package. As always, I tried running it with PHP and got an error so I gave up. There are a lot of complex calculations in the bme_280_sample.py source code, but I managed to modify the output part even with minimal experience in Python. The portion of code that I customized is as follows:
/home/pi/bme280_custom.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
#coding: utf-8 import smbus import time bus_number = 1 i2c_address = 0x76 bus = smbus.SMBus(bus_number) digT = [] digP = [] digH = [] t_fine = 0.0 def writeReg(reg_address, data): bus.write_byte_data(i2c_address,reg_address,data) def get_calib_param(): calib = [] for i in range (0x88,0x88+24): calib.append(bus.read_byte_data(i2c_address,i)) calib.append(bus.read_byte_data(i2c_address,0xA1)) for i in range (0xE1,0xE1+7): calib.append(bus.read_byte_data(i2c_address,i)) digT.append((calib[1] << 8) | calib[0]) digT.append((calib[3] << 8) | calib[2]) digT.append((calib[5] << 8) | calib[4]) digP.append((calib[7] << 8) | calib[6]) digP.append((calib[9] << 8) | calib[8]) digP.append((calib[11]<< 8) | calib[10]) digP.append((calib[13]<< 8) | calib[12]) digP.append((calib[15]<< 8) | calib[14]) digP.append((calib[17]<< 8) | calib[16]) digP.append((calib[19]<< 8) | calib[18]) digP.append((calib[21]<< 8) | calib[20]) digP.append((calib[23]<< 8) | calib[22]) digH.append( calib[24] ) digH.append((calib[26]<< 8) | calib[25]) digH.append( calib[27] ) digH.append((calib[28]<< 4) | (0x0F & calib[29])) digH.append((calib[30]<< 4) | ((calib[29] >> 4) & 0x0F)) digH.append( calib[31] ) for i in range(1,2): if digT[i] & 0x8000: digT[i] = (-digT[i] ^ 0xFFFF) + 1 for i in range(1,8): if digP[i] & 0x8000: digP[i] = (-digP[i] ^ 0xFFFF) + 1 for i in range(0,6): if digH[i] & 0x8000: digH[i] = (-digH[i] ^ 0xFFFF) + 1 def readData(): data = [] for i in range (0xF7, 0xF7+8): data.append(bus.read_byte_data(i2c_address,i)) 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] #compensate_T(temp_raw) #compensate_P(pres_raw) #compensate_H(hum_raw) t = compensate_T(temp_raw) p = compensate_P(pres_raw) h = compensate_H(hum_raw) return p + "," + t + "," + h def compensate_P(adc_P): global t_fine pressure = 0.0 v1 = (t_fine / 2.0) - 64000.0 v2 = (((v1 / 4.0) * (v1 / 4.0)) / 2048) * digP[5] v2 = v2 + ((v1 * digP[4]) * 2.0) v2 = (v2 / 4.0) + (digP[3] * 65536.0) v1 = (((digP[2] * (((v1 / 4.0) * (v1 / 4.0)) / 8192)) / 8) + ((digP[1] * v1) / 2.0)) / 262144 v1 = ((32768 + v1) * digP[0]) / 32768 if v1 == 0: return 0 pressure = ((1048576 - adc_P) - (v2 / 4096)) * 3125 if pressure < 0x80000000: pressure = (pressure * 2.0) / v1 else: pressure = (pressure / v1) * 2 v1 = (digP[8] * (((pressure / 8.0) * (pressure / 8.0)) / 8192.0)) / 4096 v2 = ((pressure / 4.0) * digP[7]) / 8192.0 pressure = pressure + ((v1 + v2 + digP[6]) / 16.0) #print "pressure : %7.2f hPa" % (pressure/100) return "%7.2f" % (pressure/100) def compensate_T(adc_T): global t_fine v1 = (adc_T / 16384.0 - digT[0] / 1024.0) * digT[1] v2 = (adc_T / 131072.0 - digT[0] / 8192.0) * (adc_T / 131072.0 - digT[0] / 8192.0) * digT[2] t_fine = v1 + v2 temperature = t_fine / 5120.0 #print "temp : %-6.2f ℃" % (temperature) return "%.2f" % (temperature) def compensate_H(adc_H): global t_fine var_h = t_fine - 76800.0 if var_h != 0: var_h = (adc_H - (digH[3] * 64.0 + digH[4]/16384.0 * var_h)) * (digH[1] / 65536.0 * (1.0 + digH[5] / 67108864.0 * var_h * (1.0 + digH[2] / 67108864.0 * var_h))) else: return 0 var_h = var_h * (1.0 - digH[0] * var_h / 524288.0) if var_h > 100.0: var_h = 100.0 elif var_h < 0.0: var_h = 0.0 #print "hum : %6.2f %" % (var_h) return "%.2f" % (var_h) def setup(): osrs_t = 1 #Temperature oversampling x 1 osrs_p = 1 #Pressure oversampling x 1 osrs_h = 1 #Humidity oversampling x 1 mode = 3 #Normal mode t_sb = 5 #Tstandby 1000ms filter = 0 #Filter off spi3w_en = 0 #3-wire SPI Disable ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode config_reg = (t_sb << 5) | (filter << 2) | spi3w_en ctrl_hum_reg = osrs_h writeReg(0xF2,ctrl_hum_reg) writeReg(0xF4,ctrl_meas_reg) writeReg(0xF5,config_reg) setup() get_calib_param() if __name__ == '__main__': try: readData() except KeyboardInterrupt: pass |
I didn’t modify the codes too much. I just made some minor adjustments to the main “processing” section. I changed “print” to “return” and made edits so it would return values with commas in CSV format.
/home/pi/bme280.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#coding: utf-8 import bme280_custom import datetime import os dir_path = '/home/pi/bme280-data' now = datetime.datetime.now() filename = now.strftime('%Y%m%d') label = now.strftime('%H:%M') csv = bme280_custom.readData() if not os.path.exists('/home/pi/bme280-data'): os.makedirs('/home/pi/bme280-data') f = open('/home/pi/bme280-data/'+filename+'.csv','a') f.write("'"+label+"',"+csv+"\n") f.close() |
I created another py that calls readData() function from the previously modified “bme280_custom.py.” This saves the read values in CSV file. I set it to save the date as the file name and to record a full day’s worth of data in it.
I registered this program with cron and set it up so it runs on a regular schedule. It is now complete!
sudo crontab -e
0-59/10 * * * * /home/pi/bme280.py
Note: I set it to run every 10 minutes.
Next, I’ll create a PHP file to display the data so it can be easily read, similar to what I did when I made a thermometer using a DS18B20 temperature sensor.
/var/www/html/bme280.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
<?php $today = date("Ymd"); $csv_dir = '/home/pi/bme280-data/'; $csv_file = $today.'.csv'; $grapgh = ''; if (($handle = fopen($csv_dir.$csv_file, "r")) !== false) { while (($line = fgets($handle)) !== false) { $grapgh .= '['.rtrim($line).'],'.PHP_EOL; } fclose($handle); }else{ echo 'no data'; } ?> <html> <head> <script type="text/javascript" src="https://www.google.com/jsapi"></script> <script type="text/javascript"> google.load("visualization", "1", {packages:["table"]}); google.setOnLoadCallback(drawTable); function drawTable() { var data = new google.visualization.DataTable(); data.addColumn('string', 'Time'); data.addColumn('number', 'Pressure'); data.addColumn('number', 'Temperature'); data.addColumn('number', 'Humidity'); data.addRows([ <?php echo $grapgh; ?> ]); var table = new google.visualization.Table(document.getElementById('table_div')); table.draw(data, {showRowNumber: true}); } </script> </head> <body> <div id="table_div"></div> </body> </html> |
I created a simple PHP file that displays the data in a table (you have to install the “php5” package to use PHP).
That way, I can view the contents of the CSV file by navigating to http://localhost/bme280.php from the browser. There’s a big pressure change even at 10-minute intervals!
The newest version of Raspbian comes with an office suite called “LibreOffice” pre-installed. If you simply want to look at the data, you can use “LibreOffice Calc” (Just double-click on the CSV file) and you will see something like this:
Summary
Today, we created a simple module using AE-BME280 sensor to measure multiple values (pressure, humidity, and temperature). It’s quite amazing to see this small sensor measuring up to three different parameters. It’s tiny, yet very powerful!
This project also allowed me to improve my soldering skills. The pins on AE-BME280 were very small and they were so closely located, and I was worried I might accidently connect them all. I was so happy to see the finished product with pins nicely soldered. Projects requiring soldering can be difficult but very rewarding at the same time!