====== ノード機のプログラミング ====== ===== 配線 ===== I2C (SDA, SCL)と、電源 (3.3V, GND)を接続します。また、I2Cモードでの動作のために CSを3.3Vへ、アドレスの固定のために SDOをGNDへ接続します。 {{:raspi_iot:schematic.png?nolink|}} ===== コマンドラインによるテスト ===== 準備の項でインストールした 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が返される仕様なので、これを確認してみます。   {{:raspi_iot:lps25h_reg_0f.png?direct&500|}} デバイステスト用プログラム 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で観測した結果を載せておきます。 {{:raspi_iot:dso_i2c_1.png?nolink|}} これを見ると、RaspberyPiのI2Cは、このセンサーがデータシートで要求しているI2Cシーケンス(下表 Table13)と少し異なりますが、さいわい意図した動作はするようです。   {{:raspi_iot:lsp25h_i2c.png?direct&500|}} 次に、気温と気圧を取得することに挑戦します。データシートから最も簡単に使えそうな、1回/秒で自動で計測するモードを試します。これには、CTRL_REG1(0x20)を設定します。併せてアクティブモードにして動作を開始させます。   {{:raspi_iot:lps25h_reg_20.png?direct&500|}}\\   {{:raspi_iot:lps25h_reg_20_t18.png?direct&500|}} 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 できたプログラム全体です。 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で観測した結果を載せておきます。 {{:raspi_iot:dso_i2c_2.png?nolink|}} ===== プログラミング ===== では、この結果をふまえて、AloneのWorkerモジュールを使って、定期的に動作する常駐プログラムを作ってみます。 ==== ステップ1 ==== プログラムファイル設置場所は、/home/pi/alone/bin とし、worker.rb のファイル名で作ります。\\ 雛形は、ドキュメントの [[alworker:基本機能|AlWorker 基本機能]] ページからコピーします。 cd alone mkdir bin cd bin nano worker.rb # 任意のエディターで worker.rb を編集 この雛形へ、先ほど作ったテストプログラムから必要部分をコピーします。この際、 * デバイス初期化は、イニシャライザ initialize2()メソッドで行う。 * デバイスからの読み込みと数値換算部分は get_data()メソッドにまとめ、Hashを戻り値とする。 まずはこの2つを行なってみます。 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 ==== 正しく動作がすることが確認できたら、タイマーで定期的に動作するようにします。最終的には取得したデータをサーバ機へ送信するのですが、サーバ機側のプログラムはまだ作っていませんので、ここでは送信のモックだけ用意しておきます。モックでは、データをログファイルへ記録します。 #!/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