目次

ノード機のプログラミング

配線

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 を編集

この雛形へ、先ほど作ったテストプログラムから必要部分をコピーします。この際、

まずはこの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