Raspberry Pi 2 Model B でI2C 気圧+温度センサーモジュール(BMP180)を使う

## 概要
Raspberry pi 2 Model B (Raspbian)での aitendo [M180-4P](http://www.aitendo.com/product/10760 "M180-4P")(BMP180使用 気圧センサモジュール) 設定覚書き

---
## 参考
参考にしました、ありがとうございます。

* [Bosch Datasheet←説明書](https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BMP180-DS000-121.pdf "Datasheet")
* [Bosch Driver](https://github.com/BoschSensortec/BMP180_driver "Driver")
* [adafruit Driver](https://learn.adafruit.com/using-the-bmp085-with-raspberry-pi/using-the-adafruit-bmp-python-library)
* [wiringPi](http://wiringpi.com)

---
## センサーモジュール接続
[ここ](http://pi.gadgetoid.com/pinout/pin1_3v3_power "Raspberry Pinout")のピンアウト表を参照しました。今回は、1,3,5,9ピンを使用し設定します。
5Vトレラントなので、5Vで接続したほうが安定するかもしれません。
3.3Vで使ってみて様子をみます。

![image](https://qiita-image-store.s3.amazonaws.com/0/74786/d8c141d8-bcd0-bf76-cdf3-41a6b2ac63c5.png)

## センサーモジュール設定情報
説明書を読んでみます。
このセンサーは、以下のようなレジスタ構成になっています。

![image](https://qiita-image-store.s3.amazonaws.com/0/74786/0d55e19e-7ec3-ef23-f36e-a56aafa6e194.png)

* MSB(out_msb), LSB(out_lsb), XLSB(out_xlsb)
測定データが入ります。
温度(UT)は、16ビットになります。MSBとLSBを使います。
気圧(UP)は、モードにより16ビット〜19ビットになります。MSB, LSB, XLSBを使います。

* ctrl_meas
![image](https://qiita-image-store.s3.amazonaws.com/0/74786/0c2527c5-265b-75f1-08d7-71a69e1eaa2d.png)

【oss(oversampling_setting)】:
2ビットで、温度測定時は、00。気圧測定時は、ここの設定でモードを決めます。
４種類のモードとソフトウェア的に精度を上げた(複数回計測しているだけですが)1計測毎内部サンプリング回数が32回のモードの５種類があります。
【sco】:
1ビットで、1の間は、計測中です。計測が完了すると0になります。
【measurement】:
5ビットで、温度測定は、"01110"。気圧測定は、"10100"です。

測定したいモードの設定値を0xF4のコントロールレジスタに(例えば、温度なら0x2E)書き込み、変換時間待ったのち、out_msb, out_lsb, out_xlsbから計測値を取得すればよいようです。

* soft(Soft reset)
0xB6を書き込むと電源リセットと同様のシーケンスでソフトリセットされます。

* id
0x55(固定値)です。接続確認などに使います。

* calib21-calib0(補正値)
各16ビットの11補正パラメータです。AC4, AC5, AC6がunsignedで、それ以外のパラメータは、signedです。
補正値は、気温、気圧を計算する際、以下の計算式で使用します。
結構、めんどくさい計算です。その分、正確な値が計測できるのかな。

【補正値 AC1, AC2, AC3, AC4, AC5, AC6, B1. B2, MB, MC, MD】
各補正値 = ((各補正値のMSB << 8) | 各補正値のLSB);

【気温 t】
UT = ((MSB << 8) | LSB);

X1 = ((UT - AC6) * AC5 >> 15);
X2 = ((MC << 11) / (X1 + MD));
B5 = (X1 + X2);
t = ((B5 + 8) >> 4);

t / 10.0 ℃

【気圧 p】
UP = ((MSB << 16) | (LSB << 8) | XLSB) >> (8 - oss)

B3の計算

B6 = (B5 - 4000);
X1 = ((B6 * B6) >> 12);
X1 = (X1 * B2);
X1 = (X1 >> 11);
X2 = (AC2 * B6);
X2 = (X2 >> 11);
X3 = (X1 + X2);
B3 = (AC1 * 4 + X3);
B3 = ((B3 << oss) + 2);
B3 = (B3 >> 2);

B4の計算

X1 = ((AC3 * B6) >> 13);
X2 = ((B6 * B6) >> 12);
X2 = ((B1 * X2) >> 16);
X3 = ((X1 + X2) + 2) >> 2;
B4 = (AC4 * (X3 + 32768)) >> 15;

pの計算

B7 = ((UP - B3) * (50000 >> oss));
if (B7 < 0x80000000) {
    p = (B7 << 1) / B4;
} else {
    p = (B7 / B4) << 1;
}
X1 = p >> 8;
X1 *= X1;
X1 = (X1 * 3038) >> 16;
X2 = (p * -7357) >> 16;
p += (X1 + X2 + 3791) >> 4;

p / 100.0 hPa

## センサーモジュール設定手順


#### I2C有効

```bash:I2C有効
$ sudo raspi-config
```

「8 Advanced Options」 → 「A7 SPI」 → 「I2C enable」

#### I2Cモジュール設定

```bash:I2Cモジュール読み込み設定
$ sudo nano /etc/modules
```

```
snd-bcm2835
i2c-dev
```

#### I2Cライブラリインストール

```bash:I2Cライブラリインストール
$ sudo sudo apt-get install libi2c-dev
```

#### gitインストール

```bash:gitインストール
$ sudo apt-get install git-core
```

#### アップデート

```bash:アップデート
$ sudo apt-get update
$ sudo apt-get upgrade
```

#### センサーモジュールアドレス確認
たぶん、0x77だと思います。

```bash:アドレス確認
$ 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
```

### センサーモジュール接続確認
データを取得できるか試すため、idを取得してみます。
0x55が返ってくれば正しく接続できています。

```bash:接続確認
$ sudo i2cget -y 1 0x77 0xD0 b
0x55
```

#### サンプルコード
このサンプルコードは、温度と気圧のソフトウェア精度モードを取得します。
BMP180は、扱いやすいセンサーのようです。
Pythonで[adafruitのドライバ](https://github.com/adafruit/Adafruit_Python_BMP)を利用している方が多いようでした。
センサーの使い方を勉強するため、説明書を読んで、メーカーのドライバとadafruitのドライバのソースコードを読んでみました。

BMP085と下位互換があるようですので、Raspbianに入っているbmp085のカーネルモジュールで簡単に動作するのかもしれません。試してないです。

まずは、シェルスクリプトで気温の値が取得できるか挑戦してみました。
実は、16bitのsignedの扱いでそうとう苦労しました。
あまり綺麗な方法でないと思っています。
何か良い計算方法があったら教えて下さい。

```bash:bmp180_test.sh
#!/bin/bash

AC5_MSB=$(sudo i2cget -y 1 0x77 0xB2 b)
AC5_LSB=$(sudo i2cget -y 1 0x77 0xB3 b)
AC5=$((AC5_MSB<<8|AC5_LSB))

AC6_MSB=$(sudo i2cget -y 1 0x77 0xB4 b)
AC6_LSB=$(sudo i2cget -y 1 0x77 0xB5 b)
AC6=$((AC6_MSB<<8|AC6_LSB))

MC_MSB=$(sudo i2cget -y 1 0x77 0xBC b)
MC_LSB=(sudo i2cget -y 1 0x77 0xBD b)
MC=$(printf "0x%x" $((MC_MSB<<8|MC_LSB)))
if [ $(printf "%d" $MC) -gt 32767 ];then
    MC=$((((MC^0xffff)+1)*-1))
else
    MC=$(printf "%d" $MC)
fi

MD_MSB=$(sudo i2cget -y 1 0x77 0xBE b)
MD_LSB=$(sudo i2cget -y 1 0x77 0xBF b)
MD=$((MD_MSB<<8|MD_LSB))

sudo i2cset -y 1 0x77 0xF4 0x2E b
sleep 1

UT_MSB=$(sudo i2cget -y 1 0x77 0xF6 b)
UT_LSB=$(sudo i2cget -y 1 0x77 0xF7 b)
UT=$((UT_MSB<<8|UT_LSB))

X1=$((((UT-AC6)*AC5)>>15))
X2=$(((MC<<11)/(X1+MD)))
B5=$((X1+X2))
TEMPERATURE=$(((B5+8)>>4))

printf "Temprature: %.2f C\n" $(echo "scale=2;$TEMPERATURE / 10.0" | bc)

exit 0
```

サンプルコードは、[WiringPi](https://git.drogon.net/?p=wiringPi;a=summary)と[Boschのドライバ](https://github.com/BoschSensortec/BMP180_driver)を利用しています。

気になるところや間違いなどあればコメントいただけると助かります。

```bash:ドライバダウンロード
$ cd
$ git clone git://git.drogon.net/wiringPi
$ cd wiringPi
$ ./build
$ cd ..
$ git clone https://github.com/BoschSensortec/BMP180_driver.git
$ cd BMP180_driver
```

```c:bmp180_sample.c
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <wiringPi.h>
#include <wiringPiI2C.h>
#include "bmp180.h"

s8 BMP180_I2C_bus_read(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt);
s8 BMP180_I2C_bus_write(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt);
void BMP180_delay_msek(u32 msek);

int main(void)
{
	struct bmp180_t bmp180;

	s32 com_rslt = E_BMP_COMM_RES;
	u16 v_uncomp_temp_u16 = BMP180_INIT_VALUE;
	u32 v_uncomp_press_u32 = BMP180_INIT_VALUE;

	bmp180.bus_write = BMP180_I2C_bus_write;
	bmp180.bus_read = BMP180_I2C_bus_read;
	bmp180.dev_addr = BMP180_I2C_ADDR;
	bmp180.delay_msec = BMP180_delay_msek;

	com_rslt = bmp180_init(&bmp180);
	if(com_rslt < 0) {
		printf("error: Initialization failed\n");
	}

	bmp180.sw_oversamp = BMP180_SW_OVERSAMP_U8X;
	bmp180.oversamp_setting = BMP180_OVERSAMP_SETTING_U8X;

	v_uncomp_temp_u16 = bmp180_get_uncomp_temperature();
	v_uncomp_press_u32 = bmp180_get_uncomp_pressure();

	printf("temp: %.2f C\n", bmp180_get_temperature(v_uncomp_temp_u16) / 10.0);
	printf("press: %.2f hPa\n", bmp180_get_pressure(v_uncomp_press_u32) / 100.0);

	return 0;
}

s8 BMP180_I2C_bus_read(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt)
{
	int fd;

	s32 iError = BMP180_INIT_VALUE;
	u8 stringpos = BMP180_INIT_VALUE;

	fd = wiringPiI2CSetup(dev_addr);

	for (stringpos = BMP180_INIT_VALUE; stringpos < cnt; stringpos++) {
		*(reg_data + stringpos) = wiringPiI2CReadReg8(fd, reg_addr + stringpos);
	}
	return (s8)iError;
}

s8 BMP180_I2C_bus_write(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt)
{
	int fd;

	s32 iError = BMP180_INIT_VALUE;
	u8 stringpos = BMP180_INIT_VALUE;

	fd = wiringPiI2CSetup(dev_addr);

	for (stringpos = BMP180_INIT_VALUE; stringpos < cnt; stringpos++) {
		iError = wiringPiI2CWriteReg8(fd, reg_addr + stringpos, *(reg_data + stringpos));
	}
	return (s8)iError;
}

void BMP180_delay_msek(u32 msek)
{
	usleep(msek * 1000);
}
```

```bash:実行
$ gcc -Wall -lwiringPi bmp180.c bmp180_sample.c -o bmp180_sample
$ sudo ./bmp180_sample
temp: 15.10 C
press: 1004.03 hPa
```

以上