ノード機のプログラミング
配線
I2C (SDA, SCL)と、電源 (3.3V, GND)を接続します。また、I2Cモードでの動作のために CSを3.3Vへ、アドレスの固定のために SDOをGNDへ接続します。
コマンドラインによるテスト
準備の項でインストールした i2c-toolsを使って、正しく接続されているかテストします。
$ 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: -- -- -- -- -- -- -- -- -- -- -- -- 5c -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --
上記の通りアドレス5cが表示されれば、正しく接続できています。
Rubyプログラムによるテスト
プログラムは、段階をおってテストしながら作成します。
まずは、デバイスが正しく制御できるかを確認します。準備の項でgemを使ってインストールしたI2Cクラスを使います。
factory : i2c = I2C.create(NODE) read : i2c.read(ADRS, n_of_read, registor) write : i2c.write(ADRS, registor, data)
データシートによると、このデバイスは、WHO_AM_Iレジスタ (0x0f)を読み込むと 0xbdが返される仕様なので、これを確認してみます。
デバイステスト用プログラム
- device_test.rb
require "i2c" ADRS = 0x5c # LPS25H NODE = "/dev/i2c-1" # I2C device node i2c = I2C.create(NODE) res = i2c.read(ADRS, 1, 0x0f) printf "RESULT: 0x%02x\n", res.getbyte(0)
実行結果
$ ruby device_test.rb RESULT: 0xbd
0xbdが表示されれば成功です。
参考までに、DSOで観測した結果を載せておきます。
これを見ると、RaspberyPiのI2Cは、このセンサーがデータシートで要求しているI2Cシーケンス(下表 Table13)と少し異なりますが、さいわい意図した動作はするようです。
次に、気温と気圧を取得することに挑戦します。データシートから最も簡単に使えそうな、1回/秒で自動で計測するモードを試します。これには、CTRL_REG1(0x20)を設定します。併せてアクティブモードにして動作を開始させます。
i2c.write(ADRS, 0x20, 0x90)
測定値読み込みは、PRESS_POUT(0x28,0x29,0x2a)レジスタ、TEMP_OUT(0x2b,0x2c)を読み込みます。いずれのレジスタも8bit値なので、組み合わせて換算します。また、このICはレジスタ指定時に最上位ビットを立てることで、連続したレジスタを一度に読み込むことができるので、それを利用します。
buf = i2c.read(ADRS, 5, 0xa8) # Read 5 bytes from 0x28 register with cont mode.
あとは換算ですが、バイト列を整数値に直すには、定型的な関数 (to_int16, to_uint24) があるのでそれをコピーして使います。
換算式は、データシートに書いてあるので、それをコードにするだけです。
p_cnt = to_uint24(buf.getbyte(2), buf.getbyte(1), buf.getbyte(0)) p p_cnt.to_f / 4096 t_cnt = to_int16(buf.getbyte(4), buf.getbyte(3)) p 42.5 + t_cnt.to_f / 480
できたプログラム全体です。
- device_test2.rb
require "i2c" ADRS = 0x5c # LPS25H NODE = "/dev/i2c-1" # I2C device node def to_int16( b1, b2 ) return (b1 << 8 | b2) - ((b1 & 0x80) << 9) end def to_uint24( b1, b2, b3 ) return (b1 << 16) | (b2 << 8) | b3 end i2c = I2C.create(NODE) res = i2c.read(ADRS, 1, 0x0f) printf "RESULT: 0x%02x\n", res.getbyte(0) i2c.write(ADRS, 0x20, 0x90) sleep 1 buf = i2c.read(ADRS, 5, 0xa8) # Read 5 bytes from 0x28 register with cont mode. p_cnt = to_uint24(buf.getbyte(2), buf.getbyte(1), buf.getbyte(0)) p p_cnt.to_f / 4096 t_cnt = to_int16(buf.getbyte(4), buf.getbyte(3)) p 42.5 + t_cnt.to_f / 480
実行結果
$ ruby device_test2.rb RESULT: 0xbd 1016.0849609375 19.333333333333332
こちらも参考までに、DSOで観測した結果を載せておきます。
プログラミング
では、この結果をふまえて、AloneのWorkerモジュールを使って、定期的に動作する常駐プログラムを作ってみます。
ステップ1
プログラムファイル設置場所は、/home/pi/alone/bin とし、worker.rb のファイル名で作ります。
雛形は、ドキュメントの AlWorker 基本機能 ページからコピーします。
cd alone mkdir bin cd bin nano worker.rb # 任意のエディターで worker.rb を編集
この雛形へ、先ほど作ったテストプログラムから必要部分をコピーします。この際、
- デバイス初期化は、イニシャライザ initialize2()メソッドで行う。
- デバイスからの読み込みと数値換算部分は get_data()メソッドにまとめ、Hashを戻り値とする。
まずはこの2つを行なってみます。
- worker.rb
require "al_worker" require "i2c" ADRS = 0x5c # LPS25H NODE = "/dev/i2c-1" # I2C device node def to_int16( b1, b2 ) return (b1 << 8 | b2) - ((b1 & 0x80) << 9) end def to_uint24( b1, b2, b3 ) return (b1 << 16) | (b2 << 8) | b3 end class Worker1 < AlWorker def initialize2() @i2c = I2C.create(NODE) @i2c.write(ADRS, 0x20, 0x90) end def get_data() ret = {} buf = @i2c.read(ADRS, 5, 0xa8) p_cnt = to_uint24(buf.getbyte(2), buf.getbyte(1), buf.getbyte(0)) ret[:pressure] = p_cnt.to_f / 4096 t_cnt = to_int16(buf.getbyte(4), buf.getbyte(3)) ret[:temperature] = 42.5 + t_cnt.to_f / 480 return ret end end worker1 = Worker1.new() #worker1.daemon() worker1.run(:nostop) sleep 1 p worker1.get_data()
実行結果
$ ruby -I../lib worker.rb {:pressure=>1023.2646484375, :temperature=>20.5375}
テストのやりやすさのために、雛形ではdaemon()メソッドで常駐していたのを一旦やめて、run(:nostop) で、通常のコマンドラインプログラムのように動かします。そして、get_data()メソッドを呼んで、戻り値が正しく取得できているかを確認します。
ステップ2
正しく動作がすることが確認できたら、タイマーで定期的に動作するようにします。最終的には取得したデータをサーバ機へ送信するのですが、サーバ機側のプログラムはまだ作っていませんので、ここでは送信のモックだけ用意しておきます。モックでは、データをログファイルへ記録します。
- worker.rb
#!/usr/bin/env ruby require "al_worker" require "al_worker_timer" # 追加 require "i2c" ADRS = 0x5c # LPS25H NODE = "/dev/i2c-1" # I2C device node def to_int16( b1, b2 ) return (b1 << 8 | b2) - ((b1 & 0x80) << 9) end def to_uint24( b1, b2, b3 ) return (b1 << 16) | (b2 << 8) | b3 end class Worker1 < AlWorker def initialize2() @i2c = I2C.create(NODE) @i2c.write(ADRS, 0x20, 0x90) @timer = Timer.periodic( 60, 1 ) # 追加 @timer.run() { d = get_data() send_data( d ) } end def get_data() ret = {} buf = @i2c.read(ADRS, 5, 0xa8) p_cnt = to_uint24(buf.getbyte(2), buf.getbyte(1), buf.getbyte(0)) ret[:pressure] = p_cnt.to_f / 4096 t_cnt = to_int16(buf.getbyte(4), buf.getbyte(3)) ret[:temperature] = 42.5 + t_cnt.to_f / 480 return ret end def send_data( data ) # 追加 AlWorker.log( data ) end end worker1 = Worker1.new() worker1.daemon() # 雛形どおりに戻す
実行
ruby -I../lib worker.rb
実行しても、コマンドラインに戻るだけで、今度は何も表示されないはずです。しかしプログラムは裏で動いています。psコマンド等で確認してみてください。
ログファイルは、/tmp/al_worker.log です。tailコマンド等で確認できます。
ps x tail -F /tmp/al_worker.log
正しく動作ができていれば、以下の通りログファイルに1分に1回のペースで気圧と気温が記録されます。
I, [2021-11-04T14:01:19.699439 #700] INFO -- al_worker: start I, [2021-11-04T14:01:20.703444 #700] INFO -- : {:pressure=>1018.7236328125, :temperature=>20.49375} I, [2021-11-04T14:02:20.703303 #700] INFO -- : {:pressure=>1018.772216796875, :temperature=>20.44375}
ここまでできたら、サーバ機のデータ受信部にうつりましょう。
デバッグ時の注意
一度プログラムが常駐すると、2回目以降はそれを検知して起動ができません。
ruby -I../lib worker.rb ruby -I../lib worker.rb ERROR: Still work.
この場合は、常駐しているプロセスを終了させてから、再度起動します。
kill `cat /tmp/al_worker.pid` ruby -I../lib worker.rb