今回は新しい部品を使っての電子工作!ラズベリーパイ(ラズパイ)で温度・湿度・気圧を一気に取得できる、とても便利なモジュールキットの使い方をご紹介します。
今回はじめて、はんだ付けにも挑戦しました!上の写真にあるとおり、この部品はピンヘッダが別に付属されているため、ブレッドボードに差し込んで使うためにははんだ付けが必要です。熱さと戦いつつ恐る恐る仕上げたはんだ付け、どうぞ温かい目で見てあげてください(笑)。
目次
AE-BME280

図1 秋月電子通商より
BME280使用 温湿度・気圧センサモジュールキット
ボッシュ社のBME280を搭載したセンサモジュールで、温度、湿度、気圧の3つの環境情報を同時に測定できます。マイコンとの通信方式は、I2CまたはSPIを選択することができます。
今回使用する電子部品はこちらです。16×10ミリという超小型サイズ!
メインの仕事をしてくれる「BME280」は中央にあるシルバーの部分になります。値を取得するために、表面にとても小さな穴が開いているので、これを塞いでしまわないように注意しましょう。
購入時は、AE-BME280本体とピンヘッダは接続されていない状態でした。ラズベリーパイ(ラズパイ)と組み合わせて使用するには、図1の写真のような形に仕上げた方が使いやすいので、はんだを使って組立作業が必要になります。ピンヘッダは10本のものが同梱されていたので、AE-BME280に合わせて6本目のところで折ってから使います。日本語のマニュアル付き。
通信方式は、I2CとSPIから選択できますが、SPIは以前扱ったので、今回はI2Cに挑戦したいと思います。
I2C – Wikipedia
I2C(アイ・スクエア・シー、アイ・ツー・シー)はフィリップス社で開発されたシリアルバスである。低速な周辺機器をマザーボードへ接続したり、組み込みシステム、携帯電話などで使われている。
Inter-Integrated Circuit の略で、I-squared-C(アイ・スクエアド・シー)が正式な読みとされている。ただし、一般的な文字コード環境のプレーンテキスト上では上付き文字が使えないため、I2CあるいはIICと表記されることも多く、日本国内ではこれをもって「アイ・ツー・シー」と発声されたりカタカナ表記されることがある。

図2 データシートより
図2 データシートより
I2CとSPIでは配線も異なるので、データシートをよく確認しておきましょう。
I2Cを使うには、部品側にも準備が必要と記載されています。上部の「J3」の部分にはんだをのせて「はんだジャンパ」と呼ばれる設定をします。
はんだ付け!

図3
ついに登場!はんだごて!(使い込まれたものを借りてきました!)
義務教育の時代に少し使ったことがあるようなないような……ほぼ初体験のはんだ付け作業、スタートです。下準備として、はんだごてをよくあたためておきましょう。
はんだ – Wikipedia
はんだ(半田、盤陀、英語: solder)とは、はんだ付けに利用される鉛とスズを主成分とした合金である。金属同士を接合したり、電子回路で、電子部品をプリント基板に固定するために使われる。材質にも依るが、4 – 10 K程度で超伝導状態へと転移する。
「はんだ」という名称は仮名書きされることが一般的で、カタカナ書きされることもあるが外来語ではない。
今回使用したこちらのはんだです。とてもやわらかく、ふにゃふにゃと逃げやすいので、使う分だけを引き出して、短めに持つのがポイントです。
失敗したときに備えて、はんだ吸取線も用意しておくと安心です。その名の通り、付けすぎてしまったはんだを吸い取ってくれる、初心者の強い味方です。

図4
まずは、表面のはんだジャンパです。一番左の「J3」と書かれた部分に、はんだを流し込みます。となりのピンとの間隔が狭いので、繋がってしまわないように注意しましょう。

図5
次はピンヘッダの接続です。こちらは裏側からはんだ付けしました。間隔が狭くて、むずかしい!両端ははんだがダマになってしまっている悪い例です……。
ピンにこて先を当てて、少しあたためてから、はんだを軽く押し当てるようにするのがコツです。こて先が熱くなりすぎると、はんだが焦げてダマになってしまうので、落ち着いてゆっくり作業したいときは、はんだごてのコンセントを抜いておくことをおすすめします。

図6
完成!ピンヘッダが付いて、ブレッドボードに縦に差し込めるようになりました。
はんだ付けの最中、ピンヘッダにこて先が当たってしまって、黒い部分が少し溶けてしまいましたが、値の取得には問題ありませんでした。
きちんとはんだ付けされているかどうか、実際に値を取得して確かめてみましょう!
配線
データシートを参考に、ラズベリーパイ(ラズパイ)とAE-BME280を配線していきましょう。I2Cを利用するので、図7のようになります。

図7 データシートより
ラズベリーパイ(ラズパイ)では、「SDA」は3番ピンのGPIO2、「SCL」は5番ピンのGPIO3です。VDDは1番ピンなので、ちょうど3つ並ぶような形になります。シンプルで覚えやすい配線ですね。

図8
今回は、AE-BME280の5番ピン(SDO)をGNDに接続しましたが、VDDに接続する方法もあります(後述しますが、その際はデータ取得時のアドレスが変わるので注意しましょう)。
ラズベリーパイ(ラズパイ)の設定
ラズベリーパイ(ラズパイ)は、初期状態ではI2Cが無効になっています。やり方は、第35回「WebIOPiでIoT!(6)プログラミング応用編~アナログ入力編」でSPIを有効化したときと同じです。メニューから[Preferences]-[Raspberry Pi Configuration]と選択し、設定画面を開きます。

図9
「Interfaces」タブをクリックし、「I2C」を「Enable」に設定します。

図10
設定を有効にするため、再起動をするかのメッセージが表示されるので、Yesボタンをクリックしましょう。再起動後、I2C通信が有効になります。
(OS:Raspbian Jessie/2015年11月21日リリース版にて確認済み)
ここで、I2Cを使うために必要なパッケージをインストールしておきましょう。コマンドラインから操作するには「i2c-tools」パッケージ、Pythonから操作するには「python-smbus」パッケージが必要になります。
インストールコマンド
sudo apt-get install i2c-tools
sudo apt-get python-smbus
i2cdetectコマンドを使用すると、I2Cで接続されているすべてのデバイスを検出できます。
sudo i2cdetect -y 1

図11
76と表示されました。16進数の76なので、正確には0x76ですね。
この数字については、付属の説明書にも書かれています。
データシート
※3: I2C のアドレス選択は、基板の 5 番ピン (SDO) を GND に接続すると [0x76]( デフォルト )、VDD に接続すると [0x77] になります。
今回はSDOをGNDに接続しているので0x76が表示されましたが、VDDの場合は0x77となります。
また、i2cdetectコマンドの最後の引数は、ラズベリーパイ(ラズパイ)の基板のリビジョンによって異なるようです。リビジョン1(2012年10月14日以前の発送分のRaspberry Pi Model Bまで)の場合は0を、リビジョン2のものは1を指定します。今回はRaspberry Pi2 Model Bを使用していたので、引数は1でうまく動作しました。

図12
試しに実行してみたところ、こんなエラーが発生。エラーメッセージにしたがってdevディレクトリを見てみると、エラーのとおり「i2c-0」というファイルは存在せず、代わりに「i2c-1」がありました。中身を見ることはできませんが、計測値はこのファイルに格納されているようですね。
sudo i2cdump -y 1 0x76

図13
上図は、i2cdumpコマンドを使って、レジスタ値を出力してみたところです。いろいろな数値が取得できているようですが……どれが何の値なのか、このままでは分かりません!
変換や計算を自力で行うのは少々ハードルが高そうなので、SWITCH SCIENCEさんの公開リポジトリで提供されているPythonのソースコードをお借りしました。
右上のDownload ZIPからダウンロードし、Python27フォルダ内の「bme_280_sample.py」ファイルを、ラズベリーパイ(ラズパイ)内の適当な場所に置きましょう。また、このプログラムを動かすには、先程インストールしておいた「python-smbus」パッケージのインストールが必須です。
python /home/pi/bme280_sample.py
準備ができたら、プログラム実行!温度・気圧・湿度の順に、3行に渡って結果が出力されれば成功です!

図14
注意点は、smbusパッケージの実行に管理者権限が必要であること。いつものようにPHPから操作しようと試みたのですが、エラーが発生してしまって断念……。bme280_sample.pyのプログラムソースの中身は複雑な計算がたくさん……ですが、出力部分の変更だけならPython初心者のわたしでもなんとかなりそう!ということで、カスタマイズしたソースが下記です。
/home/pi/bme280_custom.py
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | #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 |
ソースを大幅にいじる勇気は無かったので、メインの処理は微調整で対応し、別のPythonから呼び出すような形にしてみました。ソース内の「print」を「return」に変更して、CSVの書式になるようにカンマ区切りで値を返すように調整しました。
/home/pi/bme280.py
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 | #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() |
Pythonファイルをもう1つ用意して、先程調整した「bme280_custom.py」の「readData」関数を呼び出します。そして、取得した値をCSVファイルに保存しています。日付をファイル名として、1日毎のデータを残せるような仕様にしてみました。
このプログラムをcronに登録して、定時実行させるようにすれば、準備完了です!
sudo crontab -e
0-59/10 * * * * /home/pi/bme280.py
今回は、10分毎に動作するように設定しました。
あとは温度センサDS18B20で温度計を作ったときと同じように、表示用のPHPファイルを用意して、データを閲覧しやすいように一手間加えます。
/var/www/html/bme280.php
01 02 03 04 05 06 07 08 09 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 | <?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" > 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> |

図15
今回は簡単に、表形式で閲覧できるようなPHPファイルを作ってみました(PHPを使用する際は「php5」パッケージをインストールしておきましょう)。ブラウザから「 http://localhost/bme280.php 」にアクセスすると、CSVファイルの中身が閲覧できるようになります。10分間隔でも意外と変化がありますね!

図16
ちなみに最新のRaspbianには「LibreOffice」というオフィススイートがプリインストールされているので、データを閲覧するだけなら「LibreOffice Calc」を利用するのも便利です(CSVファイルをダブルクリックするだけでOK!)。
まとめ
新しい部品に新しい通信方式、そしてはじめてのはんだ付け……今回は新しい事づくしでお送りしました。
1つの部品で複数の計測値を取得できると、ブレッドボードもプログラムもシンプルに仕上がるので、とても使い勝手が良いですね。温度センサはこれまでに何度も扱ってきましたが、それよりもこんなに小さな部品で3種類の計測ができるというのが一番の驚きでした!
コツを掴むとなかなか楽しい、はんだ付け!最初は部品の小ささとはんだごての熱さに戸惑いましたが、はんだがうまく流れてきれいに仕上がった瞬間はなんだか感動的でした。自分の手で部品を作っていくのはとても面白い作業ですね!
次回は……
うわさのRaspberry Pi Zeroが登場!
A+よりさらに小さい!そして薄い!本当に動くのか不安になるくらいの小ささですが、果たして無事に起動するのでしょうか!?