昉·星光系列课程8:昉·星光开发板气压与高度测量

课程8:昉·星光开发板气压传感器与高度测量

本次课程为昉·星光开发板IoT开发系列课程的第八讲,将带领同学们在昉·星光开发板上,使用气压传感器BM180。
本次课程,在昉·星光开发板V1和V2上,操作步骤相同。

一、学习目标

  • 学习昉·星光开发板使用气压传感器获取当前环境的温度与大气压,并进行高度测量

二、准备工作

在开始本次课程的实际操作之前,同学们需要做好一些准备工作,课程中涉及到的硬件如下:

  • 开发板:昉·星光开发板
  • 气压传感器: 气压传感器BMP180
  • 杜邦线:若干

三、气压传感器使用原理

本次课程中,使用的气压传感器模块具体如下:

该气压传感器的躯体型号为BMP180,用于测量周围空气的绝对压力,也就是大气压,其测量范围在的测量范围为300hPa至1100hPa,精度低至0.02hPa。其同时还可以测量温度,并通过转换公式能够得到高度值。
6.高度与气压

BMP180气压传感器通过I2C接口进行通信,可以很方便的连接到星光派开发板。本课程所使用的模块,可以使用5V供电,也可以使用3.3V供电,但两者不能同时供电。
但在实际使用中,受到测量环境的实际情况影响,BMP180实际测量到的气压值和温度值都较为准确,但计算出来的海拔高度并不完全准确,因为空气流动、阳光等,都可能带来大气压力和温度的变化,所以计算出来的高度可能相差0~100米。但在一个较为稳定的环境中,可通过其测量高度差,得到较为可靠的数据。例如,在小区内地面测得一个气压值,记为小区气压基础值,然后到不同楼层上,以此基础值进行测量得出的高度值,就相对较为准确。后续延时,将会采用这个方式测试楼层的高度。

四、气压传感器使用

首先,参考下图,将气压传感器模块,连接到昉·星光开发板:

实物连接如下:

注意:
应根据实际使用的传感器,确定供电电压。本次课程中使用的气压传感器模块使用5V供电电压。

将BMP180气压传感器模块连接到开发板以后,可以试用is2tools工具来查看连接是否正常。
查看I2C设备连接情况:

i2cdetect -y -r 0

从上图中,可以得知,BMP180气压传感器模块已经成功连接,其IIC通讯地址为0x77。

要获取BMP180的信息,开发板需要用过IIC接口进行通讯,可以通过第三方模块bmp180 来帮助我们完成这个工作。

git clone https://github.com/m-rtijn/bmp180.git

pip install smbus

下载第三方库bmp180后,需要对其中的 bmp180.py 文件做一些修改,才可以在星光派开发板上使用,具体如下:

"""
This program handles the communication over I2C between a Raspberry Pi and a
BMP180 Temperature/Pressure sensor.
Made by: MrTijn/Tijndagamer
Copyright 2015-2017
Released under the MIT license.
"""

import smbus
import math
from time import sleep

class bmp180:
    # Global variables
    address = None
    bus = smbus.SMBus(0) # 星光派开发板40Pin上的I2C使用i2c-0
    mode = 1 # TODO: Add a way to change the mode

    # BMP180 registers
    CONTROL_REG = 0xF4
    DATA_REG = 0xF6

    # Calibration data registers
    CAL_AC1_REG = 0xAA
    CAL_AC2_REG = 0xAC
    CAL_AC3_REG = 0xAE
    CAL_AC4_REG = 0xB0
    CAL_AC5_REG = 0xB2
    CAL_AC6_REG = 0xB4
    CAL_B1_REG = 0xB6
    CAL_B2_REG = 0xB8
    CAL_MB_REG = 0xBA
    CAL_MC_REG = 0xBC
    CAL_MD_REG = 0xBE

    # Calibration data variables
    calAC1 = 0
    calAC2 = 0
    calAC3 = 0
    calAC4 = 0
    calAC5 = 0
    calAC6 = 0
    calB1 = 0
    calB2 = 0
    calMB = 0
    calMC = 0
    calMD = 0


    def __init__(self, address):
        self.address = address

        # Get the calibration data from the BMP180
        self.read_calibration_data()

    # I2C methods

    def read_signed_16_bit(self, register):
        """Reads a signed 16-bit value.

        register -- the register to read from.
        Returns the read value.
        """
        msb = self.bus.read_byte_data(self.address, register)
        lsb = self.bus.read_byte_data(self.address, register + 1)

        if msb > 127:
            msb -= 256

        return (msb << 8) + lsb

    def read_unsigned_16_bit(self, register):
        """Reads an unsigned 16-bit value.

        Reads the given register and the following, and combines them as an
        unsigned 16-bit value.
        register -- the register to read from.
        Returns the read value.
        """
        msb = self.bus.read_byte_data(self.address, register)
        lsb = self.bus.read_byte_data(self.address, register + 1)

        return (msb << 8) + lsb

    # BMP180 interaction methods

    def read_calibration_data(self):
        """Reads and stores the raw calibration data."""
        self.calAC1 = self.read_signed_16_bit(self.CAL_AC1_REG)
        self.calAC2 = self.read_signed_16_bit(self.CAL_AC2_REG)
        self.calAC3 = self.read_signed_16_bit(self.CAL_AC3_REG)
        self.calAC4 = self.read_unsigned_16_bit(self.CAL_AC4_REG)
        self.calAC5 = self.read_unsigned_16_bit(self.CAL_AC5_REG)
        self.calAC6 = self.read_unsigned_16_bit(self.CAL_AC6_REG)
        self.calB1 = self.read_signed_16_bit(self.CAL_B1_REG)
        self.calB2 = self.read_signed_16_bit(self.CAL_B2_REG)
        self.calMB = self.read_signed_16_bit(self.CAL_MB_REG)
        self.calMC = self.read_signed_16_bit(self.CAL_MC_REG)
        self.calMD = self.read_signed_16_bit(self.CAL_MD_REG)

    def get_raw_temp(self):
        """Reads and returns the raw temperature data."""
        # Write 0x2E to CONTROL_REG to start the measurement
        self.bus.write_byte_data(self.address, self.CONTROL_REG, 0x2E)

        # Wait 4,5 ms
        sleep(0.0045)

        # Read the raw data from the DATA_REG, 0xF6
        raw_data = self.read_unsigned_16_bit(self.DATA_REG)

        # Return the raw data
        return raw_data

    def get_raw_pressure(self):
        """Reads and returns the raw pressure data."""
        # Write appropriate data to sensor to start the measurement
        self.bus.write_byte_data(self.address, self.CONTROL_REG, 0x34 + (self.mode << 6))

        # Sleep for 8 ms.
        # TODO: Way to use the correct wait time for the current mode
        sleep(0.008)

        MSB = self.bus.read_byte_data(self.address, self.DATA_REG)
        LSB = self.bus.read_byte_data(self.address, self.DATA_REG + 1)
        XLSB = self.bus.read_byte_data(self.address, self.DATA_REG + 2)

        raw_data = ((MSB << 16) + (LSB << 8) + XLSB) >> (8 - self.mode)

        return raw_data

    def get_temp(self):
        """Reads the raw temperature and calculates the actual temperature.

        The calculations used to get the actual temperature are from the BMP-180
        datasheet.
        Returns the actual temperature in degrees Celcius.
        """
        UT = self.get_raw_temp()

        X1 = 0
        X2 = 0
        B5 = 0
        actual_temp = 0.0

        X1 = ((UT - self.calAC6) * self.calAC5) / math.pow(2, 15)
        X2 = (self.calMC * math.pow(2, 11)) / (X1 + self.calMD)
        B5 = X1 + X2
        actual_temp = ((B5 + 8) / math.pow(2, 4)) / 10

        return actual_temp

    def get_pressure(self):
        """Reads and calculates the actual pressure.

        Returns the actual pressure in Pascal.
        """
        UP = self.get_raw_pressure()
        UT = self.get_raw_temp()
        B3 = 0
        B4 = 0
        B5 = 0
        B6 = 0
        B7 = 0
        X1 = 0
        X2 = 0
        X3 = 0
        pressure = 0

        # These calculations are from the BMP180 datasheet, page 15

        # Not sure if these calculations should be here, maybe they could be
        # removed?
        X1 = ((UT - self.calAC6) * self.calAC5) / math.pow(2, 15)
        X2 = (self.calMC * math.pow(2, 11)) / (X1 + self.calMD)
        B5 = X1 + X2

        # Todo: change math.pow cals to constants
        B6 = B5 - 4000
        X1 = (self.calB2 * (B6 * B6 / math.pow(2, 12))) / math.pow(2, 11)
        X2 = self.calAC2 * B6 / math.pow(2, 11)
        X3 = X1 + X2
        B3 = (((self.calAC1 * 4 + int(X3)) << self.mode) + 2) / 4
        X1 = self.calAC3 * B6 / math.pow(2, 13)
        X2 = (self.calB1 * (B6 * B6 / math.pow(2, 12))) / math.pow(2, 16)
        X3 = ((X1 + X2) + 2) / math.pow(2, 2)
        B4 = self.calAC4 * (X3 + 32768) / math.pow(2,15)
        B7 = (UP - B3) * (50000 >> self.mode)

        if B7 < 0x80000000:
            pressure = (B7 * 2) / B4
        else:
            pressure = (B7 / B4) * 2

        X1 = (pressure / math.pow(2, 8)) * (pressure / math.pow(2, 8))
        X1 = (X1 * 3038) / math.pow(2, 16)
        X2 = (-7357 * pressure) / math.pow(2, 16)
        pressure = pressure + (X1 + X2 + 3791) / math.pow(2, 4)

        return pressure

    def get_altitude(self, sea_level_pressure = 101325):
        """Calulates the altitude.

        This method calculates the altitude using the pressure.
        This method is not reliable when the sensor is inside.
        sea_level_pressure -- the pressure at the sea level closest to you in
        Pascal.
        Returns the altitude in meters.

        !!! This method probably does not work correctly. I've tried to test
        it but at the moment I have no way of verifying the data. !!!
        """
        altitude = 0.0
        pressure = float(self.get_pressure())

        # altitude = 44330.0 * (1.0 - math.pow(pressure / sea_level_pressure, 0.00019029495))
        altitude = 44330.0 * (1.0 - pow(pressure / sea_level_pressure, (1.0/5.255))) # 矫正计算结果
        altitude = round(altitude, 2)

        return altitude

if __name__ == "__main__":
    bmp = bmp180(0x77)
    print(bmp.get_temp())
    print(bmp.get_pressure())
    print(bmp.get_altitude())

然后,参考样例,再编写如下的程序读取所需的数据:

# -*- coding: utf-8 -*-
# file: ~/projects/pressure/bmp180/read_data_from_bm180.py
from bmp180 import bmp180
import time

bmp = bmp180(0x77)

while True:
    temp = bmp.get_temp()
    pressure = bmp.get_pressure() / 1000
    # altitude = bmp.get_altitude() # 当参数为空时,用于测量基准值
    altitude = bmp.get_altitude(102225.9) # 以基准值为基础,测量高度值
    print("当前温度: %0.1f ℃" % temp)
    print("当前气压: %0.4f kPa" % pressure)
    print("可能高度: %0.2f 米\n" % altitude)
    time.sleep(2)

编写完成后,运行 read_data_from_bm180.py,即可输出实际测量到的数据。

pyhon3 read_data_from_bm180.py

实际运行结果如下:

在实际使用中,需要先使用 bmp.get_altitude() 进行基准值的测量,然后以此基准值为基础,使用 bmp.get_altitude(基准值) 测量相对高度值。

在实际测量中,受环境影响,结果值会有一定程度的漂移,可以考虑连续读取多条的数据,然后使用一定的算法取得最终合理的测验结果。

五、总结

在本次课程中,我们学习了气压传感器的基本使用。
气压传感器应用非常广泛,在室内外导航、天气预报、室内环境监测、风扇功率控制等诸多场合都有应用。

六、课后作业

  • 了解气压传感器
  • 进行实际的气压值测量,并完成相对高度值的实际测试
  • 测量风扇在不同风力情况下的产生的气压值

七、参考资料

2 Likes