====== ノード機のプログラミング ======
===== 配線 =====
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