Some birds and other animals are thought to be sensitive to changes in atmospheric pressure.
This year I plan to monitor & record pressure, in addition to activity in and around my bird nesting boxes.
The BMP180 is a very small, low cost sensor which can be used to determine temperature and atmospheric pressure. ...looks like a great place to start!
I don't know if changes in air pressure affect bird behaviour such as feeding rate, but as I record the number of times the entrance detector is triggered when parents are feeding their chicks, I may be able to look for patterns.
The sensor
The Bosch BMP180 is very small, measuring approx 12mm x 10mm. It is also pretty cheap, I paid less than £3 from a supplier in China via Amazon.
It is very easy to connect to a Raspberry Pi via an I2C interface, using just 2 wires, plus 2 more for power.
BMP180 Pi GPIO
Vin pin 1 3V3
SDA pin 3 serial data
SCL pin 5 serial clock
GND pin 6 0V
For testing, I've just soldered on 4 jumper leads with female fittings to suit the Pi GPIO connector.
Preparing the Pi
There are a couple of things we need to do to the Pi before we can use the I2C interface.
From a terminal window run:-
sudo raspi-config
Enable the i2c interface via raspi-config > advanced Options
After a restart, open /etc/modules as root and copy the following 2 lines;
i2c-bcm2708
i2c-dev
Initially we can test the Pi-sensor comms using some i2c-tools:-
sudo apt-get install i2c-tools
For all Pi variants except the original model B try this:-
sudo i2cdetect 1
(for the old model B, use "0" in place of"1")
...this should give you the chip address: 77hex
Grab a copy of the memory map and save to a file:-
sudo i2cdump -y 1 0x77 > bmp180_mem.txt
This file may be useful later on if you need to check your code by comparing captured values with values in this file.
Using Gambas
We need to install wiringPi and Gambas as described in this earlier post.
Add the following declarations to the Gambas project:-
Library "/usr/local/lib/libwiringPi"
Public Extern wiringPiSetup() As Integer
'i2c stuff
Public Extern wiringPiI2CSetup(ChipID As Integer) As Integer
Public Extern wiringPiI2CRead(intFd As Integer) As Integer
Public Extern wiringPiI2CReadReg8(intHandle As Integer, intMem As Integer) As Integer
Public Extern wiringPiI2CReadReg16(intHandle As Integer, intMem As Integer) As Integer
Public Extern wiringPiI2CWriteReg8(intHandle As Integer, intMem As Integer, intData As Integer) As Integer
Public Extern wiringPiI2CWriteReg16(intHandle As Integer, intMem As Integer, intData As Integer) As Integer
The BMP180 datasheet details the memory locations we need to access, how to access them and the calculations required. There are a number of short and horrible variable names used in the documentation which I've adopted and adapted in an attempt to simplify diagnostics. However, variables like "X1" are not a great choice for Gambas, as the auto-complete feature in the IDE requires at least 3 characters before it kicks-in.
I also had a bit of bother with some variable types, so it is important to use the "short" type in some cases.
'BMP180 id is 0x77 = 119 decimal
Const CHIP_ADDR As Integer = 119 '0x77
Const CHIP_ID_ADDR As Integer = 208 '0xD0
Const AC1_ADDR As Integer = 170 '0xAA
Const AC2_ADDR As Integer = 172 '0xAC
Const AC3_ADDR As Integer = 174 '0xAE
Const AC4_ADDR As Integer = 176 '0xB0
Const AC5_ADDR As Integer = 178 '0xB2
Const AC6_ADDR As Integer = 180 '0xB4
Const B1_ADDR As Integer = 182 '0xB6
Const B2_ADDR As Integer = 184 '0xB8
Const MB_ADDR As Integer = 186 '0xBA
Const MC_ADDR As Integer = 188 '0xBC
Const MD_ADDR As Integer = 190 '0xBE
Const CTRL_TEMP As Integer = 46 '0x2E
Const CTRL_PRESSURE As Integer = 52 '0x34
Const CTRL_MEASURE As Integer = 244 '0xF4
Const ADC_OUT_MSB As Integer = 246 '0xF6
Const ADC_OUT_LSB As Integer = 247 '0xF7
Const START_OF_CONVERT As Integer = 32 'bit5 = 1
Const OVER_SAMPLING As Integer = 196 'bits 6 & 7 = 1
Public intDeviceHandle As Integer
'BMP180 calibration data
Public iAC1 As Short
Public iAC2 As Short
Public iAC3 As Short
Public iAC4 As Integer
Public iAC5 As Integer
Public iAC6 As Integer
Public iB1 As Short
Public iB2 As Short
Public iMB As Short
Public iMC As Short
Public iMD As Short
Public uncompTemperature As Long
Public uncompPressure As Long
Public lX1 As Long
Public lX2 As Long
Public lB5 As Long
Public lngTemp As Long
Public lB6 As Long
Public lX3 As Long
Public lB3 As Long
Public lB4 As Long
Public lB7 As Long
Public lPress As Long
Public lPressPascal As Long
Public fPressureMbar As Float
Public intLocalAltitude As Integer
The sensor contains calibration data which is required to calculate accurate temperature and pressure values.
Public Function GetCalibrationData() As Boolean
Dim msb As Integer
Dim lsb As Integer
msb = wiringPiI2CReadReg8(intDeviceHandle, AC1_ADDR)
lsb = wiringPiI2CReadReg8(intDeviceHandle, AC1_ADDR + 1)
iAC1 = Shl(msb, 8) + lsb
msb = wiringPiI2CReadReg8(intDeviceHandle, AC2_ADDR)
lsb = wiringPiI2CReadReg8(intDeviceHandle, AC2_ADDR + 1)
iAC2 = Shl(msb, 8) + lsb
msb = wiringPiI2CReadReg8(intDeviceHandle, AC3_ADDR)
lsb = wiringPiI2CReadReg8(intDeviceHandle, AC3_ADDR + 1)
iAC3 = Shl(msb, 8) + lsb
msb = wiringPiI2CReadReg8(intDeviceHandle, AC4_ADDR)
lsb = wiringPiI2CReadReg8(intDeviceHandle, AC4_ADDR + 1)
iAC4 = Shl(msb, 8) + lsb
msb = wiringPiI2CReadReg8(intDeviceHandle, AC5_ADDR)
lsb = wiringPiI2CReadReg8(intDeviceHandle, AC5_ADDR + 1)
iAC5 = Shl(msb, 8) + lsb
msb = wiringPiI2CReadReg8(intDeviceHandle, AC6_ADDR)
lsb = wiringPiI2CReadReg8(intDeviceHandle, AC6_ADDR + 1)
iAC6 = Shl(msb, 8) + lsb
msb = wiringPiI2CReadReg8(intDeviceHandle, B1_ADDR)
lsb = wiringPiI2CReadReg8(intDeviceHandle, B1_ADDR + 1)
iB1 = Shl(msb, 8) + lsb
msb = wiringPiI2CReadReg8(intDeviceHandle, B2_ADDR)
lsb = wiringPiI2CReadReg8(intDeviceHandle, B2_ADDR + 1)
iB2 = Shl(msb, 8) + lsb
msb = wiringPiI2CReadReg8(intDeviceHandle, MB_ADDR)
lsb = wiringPiI2CReadReg8(intDeviceHandle, MB_ADDR + 1)
iMB = (Shl(msb, 8) + lsb) '* -1
msb = wiringPiI2CReadReg8(intDeviceHandle, MC_ADDR)
lsb = wiringPiI2CReadReg8(intDeviceHandle, MC_ADDR + 1)
iMC = Shl(msb, 8) + lsb
msb = wiringPiI2CReadReg8(intDeviceHandle, MD_ADDR)
lsb = wiringPiI2CReadReg8(intDeviceHandle, MD_ADDR + 1)
iMD = Shl(msb, 8) + lsb
Return True
The temperature is calculated in the following fashion.
Public Function ReadTemperature() As Long
Dim msb As Short
Dim lsb As Short
wiringPiI2CWriteReg8(intDeviceHandle, CTRL_MEASURE, CTRL_TEMP)
Wait 0.005
msb = wiringPiI2CReadReg8(intDeviceHandle, ADC_OUT_MSB)
lsb = wiringPiI2CReadReg8(intDeviceHandle, ADC_OUT_LSB)
uncompTemperature = Shl(msb, 8) + lsb
lX1 = (uncompTemperature - iAC6) * iAC5 / 2 ^ 15
lX2 = (iMC * 2 ^ 11) / (lX1 + iMD)
lB5 = lX1 + lX2
lngTemp = (lb5 + 8) / 2 ^ 4
Return lngTemp
The returned temperature value just needs to be divided by 10 to give degrees celsius.
If you thought that was bad, wait til you see the pressure calcs!
Public Function ReadPressure() As Long
Dim msb As Integer
Dim lsb As Integer
Dim xlsb As Integer
Dim oss As Short
oss = 3
wiringPiI2CWriteReg8(intDeviceHandle, CTRL_MEASURE, CTRL_PRESSURE + Shl(oss, 6))
Wait 0.05
msb = wiringPiI2CReadReg8(intDeviceHandle, ADC_OUT_MSB)
lsb = wiringPiI2CReadReg8(intDeviceHandle, ADC_OUT_LSB)
xlsb = wiringPiI2CReadReg8(intDeviceHandle, ADC_OUT_LSB + 1)
uncompPressure = Shr(Shl(msb, 16) + Shl(lsb, 8) + xlsb, 8 - oss)
lB6 = lB5 - 4000
lX1 = (iB2 * (lB6 * lB6 / 2 ^ 12)) / 2 ^ 11
lX2 = iAC2 * lB6 / 2 ^ 11
lX3 = lX1 + lX2
lB3 = (Shl(iAC1 * 4 + lX3, oss) + 2) / 4
lX1 = iAC3 * lB6 / 2 ^ 13
lX2 = (iB1 * (lB6 * lB6 / 2 ^ 12)) / 2 ^ 16
lX3 = ((lX1 + lX2) + 2) / 2 ^ 2
lB4 = iAC4 * (lX3 + 32768) / 2 ^ 15
lB7 = (uncompPressure - lB3) * Shr(50000, oss)
If lB7 < &H80000000 Then
lPress = (lB7 * 2) / lB4
Else
lPress = (lB7 / lB4) * 2
Endif
lX1 = (lPress / 2 ^ 8) * (lPress / 2 ^ 8)
lX1 = (lX1 * 3038) / 2 ^ 16
lX2 = (-7357 * lPress) / 2 ^ 16
lPressPascal = lPress + (lX1 + lX2 + 3791) / 2 ^ 4
The pressure reading needs to be adjusted to give the equivalent at sea level.
Public Function SeaLevelPressure() As Float
Dim intAltitude As Integer
Dim fTemp As Float
intAltitude = sboxAltitude.Value
If intAltitude < 0 Or intAltitude > 10000 Then
intAltitude = 0
Endif
ftemp = intAltitude / 44330
ftemp = 1 - ftemp
ftemp = ftemp ^ 5.225
' Me.Text = ftemp
Return 1 / ftemp
And now display temperature and atmospheric pressure:-
Public Sub tmrTemp_Timer()
Dim fSeaLevelCorrect As Float
fSeaLevelCorrect = SeaLevelPressure()
lblTemp.Text = CStr(ReadTemperature() / 10) & " 'C"
fPressureMbar = Round(ReadPressure() / 100 * fSeaLevelCorrect, -2)
lblPressure.Text = Format(fPressureMbar, "####.##") & " mbar"
End
The code snippets above were how the software looked just after I cracked the final problem, and started getting the right results on my test application. So there is plenty of scope to reduce and tidy the code. But I think it should provide the basics to allow anyone to get started.
My test app used for developing this code |
In my case, I will be taking the relevant code to produce an app which records barometric pressure at regular intervals during the nesting season. This could be integrated into the BirdBox system. However, as this is a battery operated system, which is only turned on by activity, I would not get a continuous set of data.
So I'll probably fit the sensor to the "always-on" Raspberry Pi time server (this resets the bird box systems real time clock each time either of the two boxes are triggered and power up).
No comments:
Post a Comment