ユーザ用ツール

サイト用ツール


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

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

配線

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[0].ord

実行結果

$ 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[2].ord, buf[1].ord, buf[0].ord)
p p_cnt.to_f / 4096
 
t_cnt = to_int16(buf[4].ord, buf[3].ord)
p 42.5 + t_cnt.to_f / 480

できたプログラム全体です。

device_test.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[0].ord
 
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[2].ord, buf[1].ord, buf[0].ord)
p p_cnt.to_f / 4096
 
t_cnt = to_int16(buf[4].ord, buf[3].ord)
p 42.5 + t_cnt.to_f / 480

実行結果

$ ruby device_test.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[2].ord, buf[1].ord, buf[0].ord)
    ret[:pressure] = p_cnt.to_f / 4096
 
    t_cnt = to_int16(buf[4].ord, buf[3].ord)
    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 )         # 追加
    @timer.run() {
      d = get_data()
      send_data( d )
    }
  end
 
  def get_data()
    ret = {}
    buf = @i2c.read(ADRS, 5, 0xa8)
    p_cnt = to_uint24(buf[2].ord, buf[1].ord, buf[0].ord)
    ret[:pressure] = p_cnt.to_f / 4096
 
    t_cnt = to_int16(buf[4].ord, buf[3].ord)
    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分に1回のペースで気圧と気温が記録されます。

ここまでできたら、サーバ機のデータ受信部にうつりましょう。

デバッグ時

最初の測定は起動から1分後ですので、少し待たなければなりません。デバッグ時に何度もトライする場合は待ち時間がもったいないので、イニシャライザの、Timer.periodic( 60 ) を60秒から数秒程度に短くして試すのが良いでしょう。

また、一度プログラムが常駐すると、2回目以降はそれを検知して起動ができません。

ruby -I../lib worker.rb
ruby -I../lib worker.rb
ERROR: Still work.

この場合は、常駐しているプロセスを終了させる必要があります。

kill `cat /tmp/al_worker.pid`
raspi_iot/ノード機のプログラミング.txt · 最終更新: 2019/03/30 11:30 by hirohito