Raspberry Pi 3 & BMP180からJavaで気圧・温度を読み取る

はじめに

Raspberry Pi 3 を使って、気圧・温度センサー BMP180 から気圧および温度情報を読み取ります。また海面気圧を指定すると、測定した気圧をもとに測定地点の高度を算出します。プログラミング言語は Java を使用しています。

準備

このセンサーは I2C で通信を行うため、Raspberry Pi の I2C 通信機能を有効にしておきます(デフォルトでは無効になっています)。

Raspbian の GUI から有効にする場合は、タスクバーの「Menu」ボタンから「設定」~「Raspberry Pi の設定」で設定パネルを開き、「インターフェイス」タブを選んで「I2C」を有効にします。

コマンドラインから有効にする場合は、sudo raspi-config コマンドを入力し、「Interfacing Options」~「I2C」と選んで、最後に「Yes」を選択します。

設定した後は、念のためにリブートしておきます。

今回 Raspberry Pi と BMP180 は以下のように接続しました。

回路図
BMP180.png

実態配線図

BMP180_ブレッドボード.png

(参考)Raspberry Pi 3 のピン割り当て

Pin Numbering - Raspberry Pi 3 Model B

センサーの動作モード

このセンサーは、気圧を読み取る際に4つのモードのいずれかを指定して読み取ります。指定されるモードによって、サンプリング数(精度)と消費電力などが異なってきます。このモードは気圧の読み取り時のみに有効で、温度の読み取りに対しては無関係です。
BMP180_Datasheet.png

(参考)BMP180のデータシート

BMP180 Digital pressure sensor

プログラム

構成

プログラムは、以下の2つのファイルから構成されています。
BMP180Example.java
BMP180.java

センサーのアドレスはデフォルトの 77H を想定しています。センサーを接続した後以下のコマンドを入力し、77H 以外のところで認識されている場合は、BMP180.java の中で定義している定数 I2C_ADRESS にその値を設定してください。

pi@raspberrypi:~ $ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- 77
pi@raspberrypi:~ $

ソースコード

BMP180Example.java
import java.util.Date;
import java.util.concurrent.TimeUnit;

public class BMP180Example {

    public static void main( String... args ) throws Exception {

        BMP180 bmp180 = new BMP180();                       // use I2C bus 1, standard mode
    //  BMP180 bmp180 = new BMP180( BMP180.ULTRAHIGHRES );  // use I2C bus 1, ultra high resolution mode
    //  BMP180 bmp180 = new BMP180( BMP180.HIGHRES );       // use I2C bus 1, high resolution mode
    //  BMP180 bmp180 = new BMP180( BMP180.STANDARD );      // use I2C bus 1, standared mode
    //  BMP180 bmp180 = new BMP180( BMP180.ULTRALOWPOWER ); // use I2C bus 1, ultra low power mode

        double temperature, pressure;
        while ( true ) {
            System.out.println( "Last valid input: " + new Date() );

            temperature = bmp180.readTemperature();
            System.out.printf( "Temperature: %.2f C (%.1f F)\n",
                               temperature, BMP180.convertToFahrenheit( temperature ) );

            pressure = bmp180.readPressure();
            System.out.printf( "Pressure   : %.2f hPa\n", pressure );

    //      bmp180.setStandardSeaLevelPressure( pressure ); // specify sea lavel pressure in hPa
            System.out.printf("Altitude   : %.2f m\n\n", bmp180.readAltitude() );

            TimeUnit.SECONDS.sleep( 1 );
        }
    }
}
BMP180.java
import com.pi4j.io.i2c.I2CBus;
import com.pi4j.io.i2c.I2CDevice;
import com.pi4j.io.i2c.I2CFactory;
import com.pi4j.io.i2c.I2CFactory.UnsupportedBusNumberException;
import java.io.IOException;
import java.lang.Math;
import java.util.concurrent.TimeUnit;

public class BMP180 {

    // Hardware pressure sampling accuracy modes
    static final int ULTRALOWPOWER = 0;
    static final int STANDARD      = 1;
    static final int HIGHRES       = 2;
    static final int ULTRAHIGHRES  = 3;

    private int mode;

    // Registers
    final int CAL_AC1   = 0xAA;
    final int CTRL_MEAS = 0xF4;
    final int OUT_MSB   = 0xF6;

    // Commands
    final byte CMD_READTEMP     = 0x2E;
    final byte CMD_READPRESSURE = 0x34;

    private final int I2C_ADRESS = 0x77;
    private I2CDevice device;

    private int AC1;
    private int AC2;
    private int AC3;
    private int AC4;
    private int AC5;
    private int AC6;
    private int B1 ;
    private int B2 ;
    private int MB ;
    private int MC ;
    private int MD ;

    private double standardSeaLevelPressure = 1013.89; // avarage sea level pressure in Tokyo

    public BMP180( int i2cbus, int mode ) throws UnsupportedBusNumberException, IOException {
        // Create I2C bus
        I2CBus bus = I2CFactory.getInstance( i2cbus );

        // Get I2C device, BMP180 I2C address is 0x77(119)
        device = bus.getDevice( I2C_ADRESS );

        // Calibration Coefficients stored in EEPROM of the device
        // Read 22 bytes of data from address 0xAA(170)
        byte[] data = new byte[ 22 ];
        device.read( CAL_AC1, data, 0, data.length );

        // Convert the data
        AC1 = INT( data[ 0 ], data[ 1 ] );
        AC2 = INT( data[ 2 ], data[ 3 ] );
        AC3 = INT( data[ 4 ], data[ 5 ] );
        AC4 = UINT( data[ 6 ], data[ 7 ] );
        AC5 = UINT( data[ 8 ], data[ 9 ] );
        AC6 = UINT( data[ 10 ], data[ 11 ] );
        B1 = INT( data[ 12 ], data[ 13 ] );
        B2 = INT( data[ 14 ], data[ 15 ] );
        MB = INT( data[ 16 ], data[ 17 ] );
        MC = INT( data[ 18 ], data[ 19 ] );
        MD = INT( data[ 20 ], data[ 21 ] );

        this.mode = mode;
    }

    public BMP180( int mode ) throws UnsupportedBusNumberException, IOException {
        this( I2CBus.BUS_1, mode );
    }

    public BMP180() throws UnsupportedBusNumberException, IOException {
        this( I2CBus.BUS_1, BMP180.STANDARD );
    }

    int readTemperatureB5() throws IOException, InterruptedException {
        // Select measurement control register
        // Enable temperature measurement
        device.write( CTRL_MEAS, CMD_READTEMP );
        TimeUnit.MILLISECONDS.sleep( 5 );

        // Read 2 bytes of data from address 0xF6(246)
        // temp msb, temp lsb
        byte[] data = new byte[ 2 ];
        device.read( OUT_MSB, data, 0, data.length );

        // Convert the data
        int UT = UINT( data[ 0 ], data[ 1 ] );

        // Callibration for Temperature
        int X1 = ( ( UT - AC6 ) * AC5 ) >> 15;
        int X2 = ( MC << 11 ) / ( X1 + MD );
        int B5 = X1 + X2;

        return B5;
    }

    public double readTemperature() throws IOException, InterruptedException {
        return ( ( readTemperatureB5() + 8 ) >> 4 ) / 10.0;
    }

    public static double convertToFahrenheit( double c ) {
        return c * 1.8 + 32.0;
    }

    public double readPressure() throws IOException, InterruptedException {
        // Select measurement control register
        // Enable pressure measurement
        device.write( CTRL_MEAS, ( byte )( CMD_READPRESSURE + ( mode << 6 ) ) );
        switch ( mode ) {
            case ULTRALOWPOWER:
                TimeUnit.MILLISECONDS.sleep( 5 );
                break;
            case STANDARD:
                TimeUnit.MILLISECONDS.sleep( 8 );
                break;
            case HIGHRES:
                TimeUnit.MILLISECONDS.sleep( 14 );
                break;
            default:
                TimeUnit.MILLISECONDS.sleep( 26 ); // ULTRAHIGHRES mode
                break;
        }

        // Read 3 bytes of data from address 0xF6(246)
        // pres msb1, pres msb, pres lsb
        byte[] data = new byte[ 3 ];
        device.read( OUT_MSB, data, 0, data.length );

        int UP = UINT( data[ 0 ], data[ 1 ], data[ 2 ] ) >> ( 8 - mode );

        // Callibration for Pressure
        int B6 = readTemperatureB5() - 4000;
        int X1 = ( B2 * ( B6 * B6 ) >> 12) >> 11;
        int X2 = ( AC2 * B6 ) >> 11;
        int X3 = X1 + X2;
        int B3 = ( ( ( AC1 * 4 + X3 ) << mode ) + 2 ) / 4;

        X1 = ( AC3 * B6 ) >> 13;
        X2 = ( B1 * ( ( B6 * B6 ) >> 12 ) ) >> 16;
        X3 = ( ( X1 + X2 ) + 2 ) >> 2;
        int B4 = ( AC4 * ( X3 + 32768 ) ) >> 15;
        int B7 = ( UP - B3 ) * ( 50000 >> mode );

        int p = B7 < 0x80000000 ? ( B7 * 2 ) / B4 : ( B7 / B4 ) * 2;

        X1 = ( p >> 8 ) * ( p >> 8 );
        X1 = ( X1 * 3038 ) >> 16;
        X2 = ( -7357 * p ) >> 16;
        p = p + ( ( X1 + X2 + 3791 ) >> 4 );

        return p / 100.0;
    }

    int INT( byte msb, byte lsb ) {
        int hi = msb & 0xFF;
        return ( ( hi > 127 ? hi - 256 : hi ) << 8 ) + ( lsb & 0xFF );
    }

    int UINT( byte msb, byte lsb ) {
        return ( ( msb & 0xFF ) << 8 ) + ( lsb & 0xFF );
    }

    int UINT( byte msb, byte lsb, byte xlsb ) {
        return ( ( msb & 0xFF ) << 16 ) + UINT( lsb, xlsb );
    }

    public void setStandardSeaLevelPressure( double standardSeaLevelPressure ) {
        this.standardSeaLevelPressure = standardSeaLevelPressure;
    }

    public double readAltitude() throws IOException, InterruptedException {
        // Calculates the altitude in meters
        double pressure = readPressure();
        return 44330.0 * ( 1.0 - Math.pow( pressure / standardSeaLevelPressure, 0.1903 ) );
    }
}

コンパイルと実行

上の2つのファイルを適当なディレクトリに置いてコンパイルします。

pi@raspberrypi:~ $ pi4j -c BMP180Example.java
--------------------------------------------
Pi4J - Compiling: BMP180Example.java
--------------------------------------------
+ javac -classpath '.:classes:*:classes:/opt/pi4j/lib/*' -d . BMP180Example.java
pi@raspberrypi:~ $

コンパイルが終わったらルート権限で実行します。毎秒ごとに温度・気圧・高度のデータを読み出して表示します。

高度については、このセンサー自体に GPS のような高度を読み取るような機能があるわけではありません。海面気圧を指定すると、計測地との気圧差で高度が計算できる、というだけのものです。このプログラムでは海面気圧が指定されていない場合、東京の年間平均の海面気圧の値がデフォルトとして使用されます。気圧が高い日などは、海面下に沈んだりします。

プログラムを終了する場合は Ctrl+C を入力してください。

pi@raspberrypi:~ $ sudo pi4j BMP180Example
+ java -classpath '.:classes:*:classes:/opt/pi4j/lib/*' BMP180Example
Last valid input: Sat Mar 17 20:47:43 JST 2018
Temperature: 18.70 C (65.7 F)
Pressure   : 1020.65 hPa
Altitude   : -56.09 m

Last valid input: Sat Mar 17 20:47:44 JST 2018
Temperature: 18.60 C (65.5 F)
Pressure   : 1020.63 hPa
Altitude   : -56.26 m

Last valid input: Sat Mar 17 20:47:45 JST 2018
Temperature: 18.60 C (65.5 F)
Pressure   : 1020.61 hPa
Altitude   : -55.60 m

Last valid input: Sat Mar 17 20:47:46 JST 2018
Temperature: 18.60 C (65.5 F)
Pressure   : 1020.71 hPa
Altitude   : -56.26 m

Last valid input: Sat Mar 17 20:47:47 JST 2018
Temperature: 18.60 C (65.5 F)
Pressure   : 1020.63 hPa
Altitude   : -55.60 m

^Cpi@raspberrypi:~ $

プログラム利用時の注意点など

実際にプログラムを利用する場合必要になるのは、BMP180.java ファイルのみです。インスタンスの作成方法と、利用できるメソッドを以下に紹介します。

インスタンスの作成

メインとなるクラス BMP180 のコンストラクタのパラメータは、以下の3通りの指定方法があります。

BMP180 bmp180 = new BMP180();              // I2C チャネル1、Standard モードで動作

BMP180 bmp180 = new BMP180( BMP180.ULTRAHIGHRES );   // I2C チャネル1、Ultra High Resoluion モードで動作

BMP180 bmp180 = new BMP180( 2, BMP180.STANDARD );    // I2C チャネル2、Standard モードで動作

モードを指定する場合は、以下のいずれかを指定します。

BMP180.ULTRALOWPOWER   // Ultra Low Power モード

BMP180.STANDARD        // Standard モード

BMP180.HIGHRES         // High Resolution モード

BMP180.ULTRAHIGHRES    // Ultra High Resolution モード

メソッド

以下のメソッドが用意されています。

public double readTemperature() throws IOException, InterruptedException
センサーから温度を読み取り、その値を返します。単位は度(摂氏)です。

public static double convertToFahrenheit( double c )
パラメータに度(摂氏)の値を指定すると、度(華氏)に変換して返します。

public double readPressure() throws IOException, InterruptedException
センサーから気圧を読み取り、その値を返します。単位は hPa(ヘクトパスカル)です。

public void setStandardSeaLevelPressure( double standardSeaLevelPressure )
高度を測定する準備として、海面気圧を設定します。単位は hPa(ヘクトパスカル)で指定します。

public double readAltitude() throws IOException, InterruptedException
センサーから読み取った気圧とあらかじめ設定されている海面気圧をもとに、高度を返します。単位は m(メートル)です。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.